Commit 2272f4bf41

2272f4bf412ab26c57517e9c369f8859cd269bb1

parent: f12304e4b7

Unsigned ci/build: success ci/lint: success

cmc <hello@cleberg.net> · 2026-10-21 06:15 UTC

Version stylesheet URLs by content hash (!7)

`styles.css` and `syntax.css` are served with `max-age=31536000`, so CSS changes need a Cloudflare purge and still sit stale in browsers. `build.py` now rewrites every page's stylesheet links to `?v=<sha256[:10]>` of the built file after orgo runs, including pages carried over from the build cache. Fails the build if no links match.

Layout: unified · split

build.py +41
@@ -25,6 +25,7 @@ Author:
2525 Christian Cleberg <hello@cleberg.net>
2626"""
2727
28import hashlib
2829import os
2930import re
3031import subprocess
@@ -135,6 +136,45 @@ def rewrite_orgo_version(build_dir):
135136 print(f"Set the footer orgo version to {version} on {count} pages")
136137
137138
139# Stylesheet links, with or without a ?v= from an earlier build. Cloudflare and browsers
140# cache these for a year, so the URL has to change whenever the file does.
141STYLESHEETS = ("styles.css", "syntax.css")
142STYLESHEET_LINK = re.compile(
143 r'(<link rel="stylesheet" href="[^"]*?)(styles\.css|syntax\.css)(?:\?v=[0-9a-f]+)?(")'
144)
145
146
147def rewrite_stylesheet_versions(build_dir):
148 """Append ?v=<content hash> to every page's stylesheet links.
149
150 Runs over every page for the same reason as rewrite_orgo_version: a page carried
151 over from an earlier build still names the old hash. A build that matches no link
152 fails, because shipping unversioned links means the next CSS change sits behind
153 a year-long cache.
154 """
155 versions = {
156 name: hashlib.sha256((Path(build_dir) / name).read_bytes()).hexdigest()[:10]
157 for name in STYLESHEETS
158 }
159 count = 0
160 for html in Path(build_dir).rglob("*.html"):
161 text = html.read_text(encoding="utf-8")
162 new_text, n = STYLESHEET_LINK.subn(
163 lambda m: f"{m[1]}{m[2]}?v={versions[m[2]]}{m[3]}", text
164 )
165 if n and new_text != text:
166 html.write_text(new_text, encoding="utf-8")
167 count += n
168 if count == 0:
169 print(
170 "No stylesheet links found in the build — the <link> tags in "
171 "content/templates/base.html no longer match STYLESHEET_LINK",
172 file=sys.stderr,
173 )
174 sys.exit(1)
175 print(f"Versioned {count} stylesheet links: {versions}")
176
177
138178def run_orgo_build(build_dir):
139179 """
140180 Build the site with orgo.
@@ -226,6 +266,7 @@ def main():
226266 if os.environ.get("BUILD", "").casefold() == "true":
227267 run_orgo_build(build_dir)
228268 rewrite_orgo_version(build_dir)
269 rewrite_stylesheet_versions(build_dir)
229270 # The onion needs same-origin images; dev previews keep the absolute URLs.
230271 # Runs over every page, not just the re-rendered ones, so a page carried over
231272 # from an earlier build is rewritten too.