atproto tools in zig zat.dev
sdk atproto zig

docs: cache-bust assets and center footer

+50 -5
+38
scripts/build-wisp-docs.mjs
··· 8 8 access, 9 9 } from "node:fs/promises"; 10 10 import path from "node:path"; 11 + import { execFile } from "node:child_process"; 12 + import { promisify } from "node:util"; 11 13 12 14 const repoRoot = path.resolve(new URL("..", import.meta.url).pathname); 13 15 const docsDir = path.join(repoRoot, "docs"); 14 16 const siteSrcDir = path.join(repoRoot, "site"); 15 17 const outDir = path.join(repoRoot, "site-out"); 16 18 const outDocsDir = path.join(outDir, "docs"); 19 + 20 + const execFileAsync = promisify(execFile); 17 21 18 22 async function exists(filePath) { 19 23 try { ··· 53 57 return fallback.replace(/\.md$/i, ""); 54 58 } 55 59 60 + async function getBuildId() { 61 + try { 62 + const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { 63 + cwd: repoRoot, 64 + }); 65 + const full = String(stdout || "").trim(); 66 + if (full) return full.slice(0, 12); 67 + } catch { 68 + // ignore 69 + } 70 + return String(Date.now()); 71 + } 72 + 56 73 async function main() { 57 74 await rm(outDir, { recursive: true, force: true }); 58 75 await mkdir(outDir, { recursive: true }); 59 76 60 77 // Copy static site shell 61 78 await cp(siteSrcDir, outDir, { recursive: true }); 79 + 80 + // Cache-bust immutable assets on Wisp by appending a per-commit query string. 81 + const buildId = await getBuildId(); 82 + const outIndex = path.join(outDir, "index.html"); 83 + if (await exists(outIndex)) { 84 + let html = await readFile(outIndex, "utf8"); 85 + html = html.replaceAll('href="./style.css"', `href="./style.css?v=${buildId}"`); 86 + html = html.replaceAll( 87 + 'src="./vendor/marked.min.js"', 88 + `src="./vendor/marked.min.js?v=${buildId}"`, 89 + ); 90 + html = html.replaceAll( 91 + 'src="./app.js"', 92 + `src="./app.js?v=${buildId}"`, 93 + ); 94 + html = html.replaceAll( 95 + 'href="./favicon.svg"', 96 + `href="./favicon.svg?v=${buildId}"`, 97 + ); 98 + await writeFile(outIndex, html, "utf8"); 99 + } 62 100 63 101 // Copy docs 64 102 await mkdir(outDocsDir, { recursive: true });
+10 -2
site/app.js
··· 1 1 const navEl = document.getElementById("nav"); 2 2 const contentEl = document.getElementById("content"); 3 3 4 + const buildId = new URL(import.meta.url).searchParams.get("v") || ""; 5 + 6 + function withBuild(url) { 7 + if (!buildId) return url; 8 + const sep = url.includes("?") ? "&" : "?"; 9 + return `${url}${sep}v=${encodeURIComponent(buildId)}`; 10 + } 11 + 4 12 function escapeHtml(text) { 5 13 return text 6 14 .replaceAll("&", "&") ··· 30 38 } 31 39 32 40 async function fetchJson(path) { 33 - const res = await fetch(path, { cache: "no-cache" }); 41 + const res = await fetch(withBuild(path), { cache: "no-store" }); 34 42 if (!res.ok) throw new Error(`Failed to fetch ${path}: ${res.status}`); 35 43 return res.json(); 36 44 } 37 45 38 46 async function fetchText(path) { 39 - const res = await fetch(path, { cache: "no-cache" }); 47 + const res = await fetch(withBuild(path), { cache: "no-store" }); 40 48 if (!res.ok) throw new Error(`Failed to fetch ${path}: ${res.status}`); 41 49 return res.text(); 42 50 }
+2 -3
site/style.css
··· 163 163 164 164 .site-footer { 165 165 display: flex; 166 - justify-content: flex-end; 166 + justify-content: center; 167 167 padding: 12px 16px; 168 168 border-top: 1px solid var(--border); 169 - background: color-mix(in srgb, var(--panel) 92%, transparent); 170 - backdrop-filter: blur(10px); 169 + background: var(--panel); 171 170 } 172 171 173 172 .footer-link {