diff --git a/.github/workflows/jekyll-build.yml b/.github/workflows/jekyll-build.yml index 7e853a6..cef716a 100644 --- a/.github/workflows/jekyll-build.yml +++ b/.github/workflows/jekyll-build.yml @@ -25,6 +25,8 @@ jobs: ruby-version: '3.3' bundler: '2.7.2' bundler-cache: true + - 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 diff --git a/Gemfile b/Gemfile index 5e434ff..d422b7f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,11 @@ -#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 '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' +gem 'wdm', '>= 0.1.0' if Gem.win_platform? +gem 'webrick', '~> 1.8' 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/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/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' diff --git a/tools/test_previews.py b/tools/test_previews.py index ac8adef..e5fd956 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,8 +48,11 @@ 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') + 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