From aeebc03509c8386c9a65efa95c61fd52ea11f814 Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Sun, 11 Oct 2026 10:16:39 -0400 Subject: [PATCH 1/6] Use direct Pages-compatible dependencies without remote-theme archive chain --- .github/workflows/jekyll-build.yml | 11 ++++++++++- Gemfile | 22 +++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/jekyll-build.yml b/.github/workflows/jekyll-build.yml index 7e853a6..ff67715 100644 --- a/.github/workflows/jekyll-build.yml +++ b/.github/workflows/jekyll-build.yml @@ -24,7 +24,16 @@ jobs: with: ruby-version: '3.3' bundler: '2.7.2' - bundler-cache: true + bundler-cache: false + - name: Resolve minimal dependency lock (PR bootstrap) + run: | + bundle lock + bundle install + - name: Upload resolved lock + uses: actions/upload-artifact@v7 + with: + name: resolved-lock + path: Gemfile.lock - name: Build site run: bundle exec jekyll build - name: Validate generated previews and fallback metadata diff --git a/Gemfile b/Gemfile index 5e434ff..dd3c3dd 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,10 @@ -#after any changes to the Gemfile, execute bundle update! -source "https://rubygems.org" -gem "jekyll" -gem "jekyll-gist" -gem "jekyll-sitemap" -gem "jekyll-seo-tag" -# If you do not want to use GitHub Pages, comment out the line below, then run bundle update. -gem "github-pages", group: :jekyll_plugins +source 'https://rubygems.org' -# Delete the following lines if not on Windows: -# Performance-booster for watching directories on Windows -gem "wdm", ">= 0.1.0" if Gem.win_platform? - -gem "webrick", "~> 1.8" +# Match the legacy GitHub Pages renderer; layouts and theme assets are local. +# Avoid the blanket github-pages metagem and its unused remote-theme/ZIP chain. +gem 'jekyll', '= 3.10.0' +gem 'jekyll-gist', '= 1.5.0' +gem 'jekyll-sitemap', '= 1.4.0' +gem 'jekyll-seo-tag', '= 2.8.0' +gem 'wdm', '>= 0.1.0' if Gem.win_platform? +gem 'webrick', '~> 1.8' From 8134f721de1a47dbb0a59659c90c8ffecaff684e Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Sun, 11 Oct 2026 10:17:49 -0400 Subject: [PATCH 2/6] Retain GFM parser and guard local-theme dependency boundary --- .github/workflows/jekyll-build.yml | 2 ++ Gemfile | 1 + tools/check_build_contract.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 tools/check_build_contract.rb diff --git a/.github/workflows/jekyll-build.yml b/.github/workflows/jekyll-build.yml index ff67715..b4c6c28 100644 --- a/.github/workflows/jekyll-build.yml +++ b/.github/workflows/jekyll-build.yml @@ -36,6 +36,8 @@ jobs: path: Gemfile.lock - name: Build site run: bundle exec jekyll build + - name: Validate local-theme dependency boundary + run: bundle exec ruby tools/check_build_contract.rb - name: Validate generated previews and fallback metadata run: python3 tools/check_site_previews.py _site - name: Exercise serialization, invalid payloads, and alternate origins diff --git a/Gemfile b/Gemfile index dd3c3dd..d422b7f 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' # Match the legacy GitHub Pages renderer; layouts and theme assets are local. # Avoid the blanket github-pages metagem and its unused remote-theme/ZIP chain. gem 'jekyll', '= 3.10.0' +gem 'kramdown-parser-gfm', '= 1.1.0' gem 'jekyll-gist', '= 1.5.0' gem 'jekyll-sitemap', '= 1.4.0' gem 'jekyll-seo-tag', '= 2.8.0' diff --git a/tools/check_build_contract.rb b/tools/check_build_contract.rb new file mode 100644 index 0000000..85f27c2 --- /dev/null +++ b/tools/check_build_contract.rb @@ -0,0 +1,29 @@ +# The local theme has no ZIP extraction boundary. Keep the unused archive +# dependency out of the locked build and prevent remote-theme activation. +require 'bundler' +require 'yaml' + +def check_config(config) + raise 'Remote themes are outside this local-theme build contract' if config.key?('remote_theme') + plugins = Array(config['plugins']) + Array(config['gems']) + raise 'Remote-theme plugin must not be enabled' if plugins.include?('jekyll-remote-theme') +end + +check_config(YAML.safe_load(File.read('_config.yml'), aliases: true)) +names = Bundler.load.specs.map(&:name) +forbidden = names & %w[github-pages jekyll-remote-theme rubyzip] +raise "Unexpected archive build dependencies: #{forbidden.join(', ')}" unless forbidden.empty? + +# Negative controls: even an empty remote-theme setting or the legacy plugin +# alias must fail, before any renderer could fetch or extract an archive. +[{ 'remote_theme' => '' }, { 'remote_theme' => 'owner/repo' }, + { 'plugins' => ['jekyll-remote-theme'] }, { 'gems' => ['jekyll-remote-theme'] }].each do |config| + rejected = false + begin + check_config(config) + rescue RuntimeError + rejected = true + end + raise 'Remote-theme negative control was accepted' unless rejected +end +puts 'Local-theme dependency contract and negative controls passed' From ba926ac016b7eecd774aa15b60fc2fe52da12639 Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Sun, 11 Oct 2026 10:19:59 -0400 Subject: [PATCH 3/6] Lock direct dependencies and resolve fixture layouts from isolated source --- Gemfile.lock | 206 ++--------------------------------------- README.md | 19 ++++ tools/test_previews.py | 5 +- 3 files changed, 32 insertions(+), 198 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2009d5b..ab0e56c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,41 +1,15 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.2.3.1) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1, < 6) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.23.11) concurrent-ruby (1.3.7) - connection_pool (3.0.2) - dnsruby (1.61.5) - simpleidn (~> 0.1) - drb (2.2.3) + csv (3.3.6) em-websocket (0.5.2) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) - ethon (0.12.0) - ffi (>= 1.3.0) eventmachine (1.2.7) - execjs (2.7.0) faraday (2.14.3) faraday-net_http (>= 2.0, < 3.5) json @@ -44,67 +18,13 @@ GEM net-http (~> 0.5) ffi (1.14.2) forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (228) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.3) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.4.0) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) - jekyll-include-cache (= 0.2.1) - jekyll-mentions (= 1.6.0) - jekyll-optional-front-matter (= 0.3.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.16.0) - jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.8.0) - jekyll-sitemap (= 1.4.0) - jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.2.0) - jekyll-theme-cayman (= 0.2.0) - jekyll-theme-dinky (= 0.2.0) - jekyll-theme-hacker (= 0.2.0) - jekyll-theme-leap-day (= 0.2.0) - jekyll-theme-merlot (= 0.2.0) - jekyll-theme-midnight (= 0.2.0) - jekyll-theme-minimal (= 0.2.0) - jekyll-theme-modernist (= 0.2.0) - jekyll-theme-primer (= 0.6.0) - jekyll-theme-slate (= 0.2.0) - jekyll-theme-tactile (= 0.2.0) - jekyll-theme-time-machine (= 0.2.0) - jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.2) - kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.4) - mercenary (~> 0.3) - minima (= 2.5.1) - nokogiri (>= 1.13.6, < 2.0) - rouge (= 3.26.0) - terminal-table (~> 1.4) - github-pages-health-check (1.17.9) - addressable (~> 2.3) - dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) - typhoeus (~> 1.3) - html-pipeline (2.14.0) - activesupport (>= 2) - nokogiri (>= 1.4) http_parser.rb (0.6.0) i18n (1.14.8) concurrent-ruby (~> 1.0) - jekyll (3.9.3) + jekyll (3.10.0) addressable (~> 2.4) colorator (~> 1.0) + csv (~> 3.0) em-websocket (~> 0.5) i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) @@ -115,101 +35,17 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) - jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) - coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.4.0) - commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.4.0) - commonmarker (~> 0.23.7) - jekyll (~> 3.9.0) - jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 5.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) - jekyll (>= 3.7, < 5.0) + webrick (>= 1.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.13.0) - jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-mentions (1.6.0) - html-pipeline (~> 2.3) - jekyll (>= 3.7, < 5.0) - jekyll-optional-front-matter (0.3.2) - jekyll (>= 3.0, < 5.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.3.0) - jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.16.0) - jekyll (>= 3.3, < 5.0) - jekyll-relative-links (0.6.1) - jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.3) - addressable (~> 2.0) - jekyll (>= 3.5, < 5.0) - jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) - rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) jekyll-seo-tag (2.8.0) jekyll (>= 3.8, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) - jekyll-swiss (1.0.0) - jekyll-theme-architect (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.6.0) - jekyll (> 3.5, < 5.0) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.3) - jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) - html-pipeline (~> 2.2) - jekyll (>= 3.0, < 5.0) json (2.21.2) kramdown (2.3.2) rexml @@ -221,30 +57,19 @@ GEM rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) mercenary (0.3.6) - mini_portile2 (2.8.9) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.27.0) net-http (0.9.1) uri (>= 0.11.1) - nokogiri (1.19.4) - mini_portile2 (~> 2.8.2) - racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.7) - racc (1.8.1) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.4.2) rouge (3.26.0) - rubyzip (2.3.0) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) @@ -254,19 +79,6 @@ GEM sawyer (0.9.3) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - securerandom (0.4.1) - simpleidn (0.2.1) - unf (~> 0.1.4) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) uri (1.1.1) webrick (1.8.2) @@ -274,11 +86,11 @@ PLATFORMS ruby DEPENDENCIES - github-pages - jekyll - jekyll-gist - jekyll-seo-tag - jekyll-sitemap + jekyll (= 3.10.0) + jekyll-gist (= 1.5.0) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + kramdown-parser-gfm (= 1.1.0) webrick (~> 1.8) BUNDLED WITH diff --git a/README.md b/README.md index 12c81b0..f3ae938 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,25 @@ This is the website for Civ13, which you can find [here](https://github.com/Civ1 It is built using **Jekyll** and the **Hyde** theme [find it here](https://github.com/poole/hyde). +## Build dependency boundary + +The theme's layouts, includes, and assets live in this repository. The build +does not download remote themes or extract ZIP archives. `Gemfile` pins the +direct Jekyll renderer and plugins rather than installing the `github-pages` +metagem, whose unused remote-theme chain requires an affected rubyzip version. +Jekyll 3.10.0 matches the legacy Pages renderer; the GFM parser remains explicit. +Use Ruby 3.3 and Bundler 2.7.2, then `bundle install` with the committed lockfile. +Dependency changes should be resolved with Bundler and reviewed, not edited +into the lockfile by hand. + +Run `bundle exec ruby tools/check_build_contract.rb` alongside the build and +preview checks. It verifies that the resolved bundle contains no remote-theme +or ZIP extraction dependency and rejects both current and legacy configuration +keys that could activate remote-theme fetching, including negative controls. +This protects the repository-managed build. GitHub manages its own legacy Pages +dependencies separately; the unchanged site configuration supplies no remote +theme or archive input there. Hosting still publishes `master` through Pages. + ## Website previews Public content pages declare a concise `description` and a raster `image` with diff --git a/tools/test_previews.py b/tools/test_previews.py index ac8adef..948326d 100644 --- a/tools/test_previews.py +++ b/tools/test_previews.py @@ -1,6 +1,7 @@ """Integration checks against the locked Jekyll renderer and malformed payloads.""" import copy import json +import os import shutil import subprocess import tempfile @@ -47,7 +48,9 @@ def test_render(): config = Path(tmp) / 'alternate.yml' config.write_text('url: https://preview.example\nbaseurl: /civ13\n', encoding='utf-8') output = Path(tmp) / 'site' - subprocess.run(['bundle', 'exec', 'jekyll', 'build', '--source', str(source), '--destination', str(output), '--config', str(source / '_config.yml') + ',' + str(config)], cwd=repo, check=True) + # Jekyll 3.10 resolves layout paths relative to the working directory. + # Keep fixtures isolated while reusing the repository's locked bundle. + subprocess.run(['bundle', 'exec', 'jekyll', 'build', '--source', str(source), '--destination', str(output), '--config', str(source / '_config.yml') + ',' + str(config)], cwd=source, env=dict(os.environ, BUNDLE_GEMFILE=str(repo / 'Gemfile')), check=True) check_site(output, 'https://preview.example', '/civ13') html = (output / 'escaped.html').read_text(encoding='utf-8') errors, counts = check_html(html) From 1442e3db957cfb474317ca2b794a619a9b184870 Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Sun, 11 Oct 2026 10:20:17 -0400 Subject: [PATCH 4/6] Validate committed minimal lock with frozen Bundler cache --- .github/workflows/jekyll-build.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/jekyll-build.yml b/.github/workflows/jekyll-build.yml index b4c6c28..a486a02 100644 --- a/.github/workflows/jekyll-build.yml +++ b/.github/workflows/jekyll-build.yml @@ -24,16 +24,7 @@ jobs: with: ruby-version: '3.3' bundler: '2.7.2' - bundler-cache: false - - name: Resolve minimal dependency lock (PR bootstrap) - run: | - bundle lock - bundle install - - name: Upload resolved lock - uses: actions/upload-artifact@v7 - with: - name: resolved-lock - path: Gemfile.lock + bundler-cache: true - name: Build site run: bundle exec jekyll build - name: Validate local-theme dependency boundary From 11cca1b04bbfeeacdf426b00be773034aefb4e0b Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Sun, 11 Oct 2026 10:21:53 -0400 Subject: [PATCH 5/6] Check archive dependency boundary before site rendering --- .github/workflows/jekyll-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jekyll-build.yml b/.github/workflows/jekyll-build.yml index a486a02..cef716a 100644 --- a/.github/workflows/jekyll-build.yml +++ b/.github/workflows/jekyll-build.yml @@ -25,10 +25,10 @@ jobs: ruby-version: '3.3' bundler: '2.7.2' bundler-cache: true - - name: Build site - run: bundle exec jekyll build - name: Validate local-theme dependency boundary run: bundle exec ruby tools/check_build_contract.rb + - name: Build site + run: bundle exec jekyll build - name: Validate generated previews and fallback metadata run: python3 tools/check_site_previews.py _site - name: Exercise serialization, invalid payloads, and alternate origins From f14642dd70f83eb7dd841846f4553c4d20cd9001 Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Sun, 11 Oct 2026 10:23:09 -0400 Subject: [PATCH 6/6] Keep companion banner base-path aware without implicit Pages plugins --- civ14.md | 2 +- tools/test_previews.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/civ14.md b/civ14.md index ef3c421..da94955 100644 --- a/civ14.md +++ b/civ14.md @@ -13,7 +13,7 @@ image: Civilization 14 (Civ14 for short) is a port of Civilization 13 from BYOND to Space Station 14. -![banner](public/img/splash.png) +![banner]({{ '/public/img/splash.png' | relative_url }}) Civ14 is an immersive game, set across vast timelines, from the Stone Age to the Modern Era, players take on roles as leaders, builders, warriors, or everyday citizens, shaping the course of their chosen society. Whether forging empires through diplomacy and trade, waging war to expand territory, or simply surviving in a dynamic, ever-evolving world, Civ14 offers a sandbox experience defined by cooperation, conflict, and creativity. diff --git a/tools/test_previews.py b/tools/test_previews.py index 948326d..e5fd956 100644 --- a/tools/test_previews.py +++ b/tools/test_previews.py @@ -52,6 +52,7 @@ def test_render(): # Keep fixtures isolated while reusing the repository's locked bundle. subprocess.run(['bundle', 'exec', 'jekyll', 'build', '--source', str(source), '--destination', str(output), '--config', str(source / '_config.yml') + ',' + str(config)], cwd=source, env=dict(os.environ, BUNDLE_GEMFILE=str(repo / 'Gemfile')), check=True) check_site(output, 'https://preview.example', '/civ13') + assert 'src="/civ13/public/img/splash.png"' in (output / 'civ14.html').read_text(encoding='utf-8') html = (output / 'escaped.html').read_text(encoding='utf-8') errors, counts = check_html(html) assert not errors, errors