From 85f4f0b4e00a6953043fba60eea3ce946cf878b3 Mon Sep 17 00:00:00 2001
From: Dan Ballard <dan@mindstab.net>
Date: Sun, 17 Aug 2025 09:35:02 -0500
Subject: [PATCH] Add target config setting and front matter variables so one
 could create multiple outputs from a single site codebase with various target
 site included files

---
 docs/_docs/configuration/default.md     |  1 +
 docs/_docs/deployment.md                |  1 +
 docs/_docs/deployment/multiple-sites.md | 44 +++++++++++++++++++++++++
 lib/jekyll/collection.rb                |  9 +++--
 lib/jekyll/configuration.rb             |  1 +
 lib/jekyll/document.rb                  |  9 +++--
 lib/jekyll/drops/document_drop.rb       |  2 +-
 lib/jekyll/page.rb                      |  2 +-
 lib/jekyll/readers/post_reader.rb       |  3 ++
 lib/jekyll/site.rb                      |  6 ++--
 10 files changed, 67 insertions(+), 11 deletions(-)
 create mode 100644 docs/_docs/deployment/multiple-sites.md

diff --git a/docs/_docs/configuration/default.md b/docs/_docs/configuration/default.md
index e96c5071e..bda69e8e7 100644
--- a/docs/_docs/configuration/default.md
+++ b/docs/_docs/configuration/default.md
@@ -43,6 +43,7 @@ show_drafts         : null
 limit_posts         : 0
 future              : false
 unpublished         : false
+target              : "default"
 
 # Plugins
 whitelist           : []
diff --git a/docs/_docs/deployment.md b/docs/_docs/deployment.md
index 45722e4e9..9e69d909e 100644
--- a/docs/_docs/deployment.md
+++ b/docs/_docs/deployment.md
@@ -9,3 +9,4 @@ Sites built using Jekyll can be deployed in a large number of ways due to the st
 * [Manually]({{ '/docs/deployment/manual/' | relative_url }})
 * [Automated]({{ '/docs/deployment/automated/' | relative_url }})
 * [Third Party]({{ '/docs/deployment/third-party/' | relative_url }})
+* [Multiple Sites and Target]({{ '/docs/deployment/multiple-sites' | relative_url }})
diff --git a/docs/_docs/deployment/multiple-sites.md b/docs/_docs/deployment/multiple-sites.md
new file mode 100644
index 000000000..1b9947948
--- /dev/null
+++ b/docs/_docs/deployment/multiple-sites.md
@@ -0,0 +1,44 @@
+---
+title: Multiple Sites and Target
+permalink: /docs/deployment/multiple-sites/
+---
+
+The `target` setting in `_config.yml` and frontmatter in conjunction with config stacking allows multiple sites with varitation to be built from one codebase.
+
+## Use Case
+
+You may have multiple access paths to your site, and want to offer unique content on some of them, for examples you may have a open web version of your site at `https://foo.com` and you may also have an [onion service](https://community.torproject.org/onion-services/setup/) version of your site accesible via `http://foo000...abc.onion`. Alternately you may have a public facing version of the site, and an internal network or VPN accessible version that you want additional content to be included on.
+
+## Method
+
+By default the `target` config option is set to "default" in `_site_config.yml` and when comparing with frontmatter of content, if none is set, it inherit's the site's value. So by default all content will be published. To make content only appear on a specific version of a site, in frontmatter add `target: xVersion` where "xVersion" will be a `target` option set in a second `_site_x.yml`.
+
+You generate the normal site the same
+
+`bundle exec jekyll build`
+
+For the second site, you create a mini site specific config, like
+
+```yml
+url: https://altDomain.com
+destination: _site_xVersion
+target: xVersion
+
+```
+
+and build it with
+
+`bundle exec jekyll build --config "_config.yml,_config_xversion.yml"`
+
+Jekyll configs stack, and overrides from additional configs override earlier ones, so this will get all the default site config options and then just change `url`, `destination` and `target`
+
+
+Then any content with a frontmatter `target` of "xVersion" will be included in this build which will be output to the new `_site_xVersion`. 
+
+If there is any content you want on the default version of the site but not any alternative ones, just add `target: default` to it's frontmatter.
+
+Then configure your webserver  with a second site config with the alternative access path and pointing to `_site_xVersion` directory.
+
+### Potential Cons
+
+This does result in multiple builds, which is potentially the one negative of this method if you are very constrained for space or your jekyll site is very large.
diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb
index 1ce33bf23..3a20af79a 100644
--- a/lib/jekyll/collection.rb
+++ b/lib/jekyll/collection.rb
@@ -183,9 +183,12 @@ module Jekyll
     # Whether the collection's documents ought to be written as individual
     #   files in the output.
     #
-    # Returns true if the 'write' metadata is true, false otherwise.
+    # Returns true if the 'write' metadata is true and
+    #   if the collection's target matches the site's target,
+    #   false otherwise.
     def write?
-      !!metadata.fetch("output", false)
+      !!metadata.fetch("output", false) &&
+        (site.target == metadata.fetch("target", site.target))
     end
 
     # The URL template to render collection's documents at.
@@ -217,7 +220,7 @@ module Jekyll
     def read_document(full_path)
       doc = Document.new(full_path, :site => site, :collection => self)
       doc.read
-      docs << doc if site.unpublished || doc.published?
+      docs << doc if (site.unpublished || doc.published?) && (site.target == doc.data.fetch("target", site.target))
     end
 
     def sort_docs!
diff --git a/lib/jekyll/configuration.rb b/lib/jekyll/configuration.rb
index 9b2fbffc2..ad19b2e79 100644
--- a/lib/jekyll/configuration.rb
+++ b/lib/jekyll/configuration.rb
@@ -30,6 +30,7 @@ module Jekyll
       "limit_posts"         => 0,
       "future"              => false,
       "unpublished"         => false,
+      "target"              => "default",
 
       # Plugins
       "whitelist"           => [],
diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb
index 2e39024e7..4f03cec86 100644
--- a/lib/jekyll/document.rb
+++ b/lib/jekyll/document.rb
@@ -348,17 +348,20 @@ module Jekyll
     end
 
     # Determine whether this document should be written.
-    # Based on the Collection to which it belongs.
+    # Based on the Collection to which it belongs
+    # and site and document target.
     #
     # True if the document has a collection and if that collection's #write?
-    # method returns true, and if the site's Publisher will publish the document.
+    # method returns true, and if the site's Publisher will publish the document,
+    # and if the document's target matches the site target or is undefined.
     # False otherwise.
     #
     # rubocop:disable Naming/MemoizedInstanceVariableName
     def write?
       return @write_p if defined?(@write_p)
 
-      @write_p = collection&.write? && site.publisher.publish?(self)
+      @write_p = collection&.write? && site.publisher.publish?(self) &&
+        (site.target == data.fetch("target", site.target))
     end
     # rubocop:enable Naming/MemoizedInstanceVariableName
 
diff --git a/lib/jekyll/drops/document_drop.rb b/lib/jekyll/drops/document_drop.rb
index 0cff37410..b1bf3ed03 100644
--- a/lib/jekyll/drops/document_drop.rb
+++ b/lib/jekyll/drops/document_drop.rb
@@ -15,7 +15,7 @@ module Jekyll
       private delegate_method_as :data, :fallback_data
 
       delegate_methods :id, :output, :content, :to_s, :relative_path, :url, :date
-      data_delegators "title", "categories", "tags"
+      data_delegators "title", "categories", "tags", :target
 
       def collection
         @obj.collection.label
diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb
index 64821a82d..c94767cb6 100644
--- a/lib/jekyll/page.rb
+++ b/lib/jekyll/page.rb
@@ -180,7 +180,7 @@ module Jekyll
     end
 
     def write?
-      true
+      site.config["target"] == data.fetch("target", site.config["target"])
     end
 
     def excerpt_separator
diff --git a/lib/jekyll/readers/post_reader.rb b/lib/jekyll/readers/post_reader.rb
index 25c5f98fb..23a5260d6 100644
--- a/lib/jekyll/readers/post_reader.rb
+++ b/lib/jekyll/readers/post_reader.rb
@@ -69,6 +69,9 @@ module Jekyll
       elsif !doc.content.valid_encoding?
         Jekyll.logger.debug "Skipping:", "#{doc.relative_path} is not valid UTF-8"
         false
+      elsif site.target != doc.data.fetch("target", site.target)
+        Jekyll.logger.debug "Skipping:", "#{doc.relative_path} does not belong to this site target"
+        false
       else
         publishable?(doc)
       end
diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb
index adec4cc06..f531878ed 100644
--- a/lib/jekyll/site.rb
+++ b/lib/jekyll/site.rb
@@ -6,7 +6,7 @@ module Jekyll
                   :file_read_opts, :future, :gems, :generators, :highlighter,
                   :include, :inclusions, :keep_files, :layouts, :limit_posts,
                   :lsi, :pages, :permalink_style, :plugin_manager, :plugins,
-                  :reader, :safe, :show_drafts, :static_files, :theme, :time,
+                  :reader, :safe, :target, :show_drafts, :static_files, :theme, :time,
                   :unpublished
 
     attr_reader :cache_dir, :config, :dest, :filter_cache, :includes_load_paths,
@@ -47,7 +47,7 @@ module Jekyll
     def config=(config)
       @config = config.clone
 
-      %w(safe lsi highlighter baseurl exclude include future unpublished
+      %w(safe lsi highlighter baseurl exclude include future unpublished target
          show_drafts limit_posts keep_files).each do |opt|
         send(:"#{opt}=", config[opt])
       end
@@ -360,7 +360,7 @@ module Jekyll
     end
 
     def each_site_file
-      pages.each { |page| yield page }
+      pages.each { |page| yield (page) if page.write? }
       static_files.each { |file| yield(file) if file.write? }
       collections.each_value { |coll| coll.docs.each { |doc| yield(doc) if doc.write? } }
     end
-- 
2.34.1

