Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions .github/workflows/jekyll.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,41 @@ permissions:
contents: read

jobs:
update-asset-version:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Update asset version
run: python script/asset_version.py

- name: Commit asset version if changed
run: |
if git diff --quiet -- _data/assets.yml; then
echo "Asset version already up to date"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add _data/assets.yml
git commit -m "Update asset version hash for CSS/JS changes."
git push

build:
needs: update-asset-version
if: always() && (needs.update-asset-version.result == 'success' || needs.update-asset-version.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -30,8 +64,9 @@ jobs:
- name: Check WebP derivatives
run: python script/generate_webp.py --check

- name: Generate asset version
run: python script/asset_version.py
- name: Check asset version
if: github.event_name != 'pull_request'
run: python script/asset_version.py --check

- name: Setup Pages
uses: actions/configure-pages@v5
Expand Down
1 change: 0 additions & 1 deletion .gitignore

This file was deleted.

8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ CI runs `python script/generate_webp.py --check` to ensure WebP copies are prese

### CSS/JS cache busting

CI runs `python script/asset_version.py` before each Jekyll build. It hashes `css/style.css`, `css/github_markdown.css`, and `js/gallery.js`, then writes `_data/assets.yml`. Layout templates append `?v=` that hash to stylesheet and script URLs so browsers fetch new copies when those files change.
`_data/assets.yml` stores a content hash for `css/style.css`, `css/github_markdown.css`, and `js/gallery.js`. Layout templates append it as `?v=` on stylesheet and script URLs. The file must be in the repo because GitHub Pages runs its own Jekyll build.

For a local Jekyll preview, run the same script first:
On pull requests, CI runs `script/asset_version.py` and commits an updated `_data/assets.yml` to the PR branch when CSS or JS changed, so the hash is ready before you merge.

Pushes to `master` run `script/asset_version.py --check` instead (the merged PR should already include the updated file).

To update locally:

```bash
python script/asset_version.py
Expand Down
1 change: 1 addition & 0 deletions _data/assets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
assets_version: "8a29461d"
2 changes: 1 addition & 1 deletion _layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<link rel="alternate" type="application/rss+xml" title="{{ site.title | xml_escape }} RSS feed" href="{{ site_root }}/feed.xml" />
<link rel="canonical" href="{{ canonical_url | xml_escape }}" />

{% assign assets_version = site.data.assets.assets_version | default: 'dev' %}
{% assign assets_version = site.data.assets.assets_version | default: site.github.build_revision | default: 'dev' %}
<link rel="stylesheet" type="text/css" href="{{ site.baseurl }}/css/github_markdown.css?v={{ assets_version }}" media="screen" />
<link rel="stylesheet" type="text/css" media="screen" href="{{ site.baseurl }}/css/style.css?v={{ assets_version }}">
</head>
Expand Down
54 changes: 48 additions & 6 deletions script/asset_version.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/usr/bin/env python3
"""Write _data/assets.yml with a content hash for CSS/JS cache busting."""
"""Manage _data/assets.yml content hash for CSS/JS cache busting."""

from __future__ import annotations

import argparse
import hashlib
import re
import sys
from pathlib import Path

Expand All @@ -15,20 +17,60 @@
OUTPUT = Path("_data/assets.yml")


def main() -> int:
def compute_version() -> str:
hasher = hashlib.sha256()

for path in ASSET_FILES:
if not path.is_file():
print(f"Missing asset file: {path}", file=sys.stderr)
return 1
raise FileNotFoundError(path)
hasher.update(path.read_bytes())
hasher.update(b"\0")

version = hasher.hexdigest()[:8]
return hasher.hexdigest()[:8]


def read_version() -> str | None:
if not OUTPUT.is_file():
return None

match = re.search(r'^assets_version:\s*"([0-9a-f]+)"\s*$', OUTPUT.read_text(encoding="utf-8"), re.MULTILINE)
return match.group(1) if match else None


def write_version(version: str) -> None:
OUTPUT.parent.mkdir(exist_ok=True)
OUTPUT.write_text(f'assets_version: "{version}"\n', encoding="utf-8")
print(f"Wrote {OUTPUT} (assets_version: {version})")


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check",
action="store_true",
help="Fail if _data/assets.yml is missing or does not match current CSS/JS files",
)
args = parser.parse_args()

try:
expected = compute_version()
except FileNotFoundError as exc:
print(f"Missing asset file: {exc}", file=sys.stderr)
return 1

if args.check:
actual = read_version()
if actual != expected:
print(
f"_data/assets.yml is out of date (have {actual!r}, expected {expected!r}). "
"Run: python script/asset_version.py",
file=sys.stderr,
)
return 1
print(f"Asset version OK ({expected})")
return 0

write_version(expected)
print(f"Wrote {OUTPUT} (assets_version: {expected})")
return 0


Expand Down