[ci] verify dist deploy artifact and force HTML cache purge

This commit is contained in:
thePR0M3TH3AN committed 2026-02-10 16:52:05 -05:00
1 parent 2e50c7a58f
commit 6fce210044
7 files changed
+66 -4

No files matched your search

+3
View File
@@ -58,6 +58,9 @@ jobs:
- name: Build assets - name: Build assets
run: npm run build run: npm run build
- name: Verify deploy artifact markup and hashed references
run: npm run verify:dist:deploy-artifact
- name: Smoke check hashed dist asset references - name: Smoke check hashed dist asset references
run: npm run smoke:dist-assets run: npm run smoke:dist-assets
+1
View File
@@ -10,4 +10,5 @@
- Static CSS/JS asset freshness is now driven by build-time manifest rewriting (`dist/asset-manifest.json`) instead of runtime `ASSET_VERSION` query-string mutation. - Static CSS/JS asset freshness is now driven by build-time manifest rewriting (`dist/asset-manifest.json`) instead of runtime `ASSET_VERSION` query-string mutation.
- Keep source HTML entry points (`index.html`, `embed.html`) pointing at logical asset paths (for example, `js/index.js` and `css/tailwind.generated.css`). The build pipeline rewrites those references to content-hashed filenames in `dist/`. - Keep source HTML entry points (`index.html`, `embed.html`) pointing at logical asset paths (for example, `js/index.js` and `css/tailwind.generated.css`). The build pipeline rewrites those references to content-hashed filenames in `dist/`.
- Deploy pipelines should always purge HTML entrypoints (`/` + `/index.html` and `/embed.html`) at the edge after publish so clients do not keep serving stale source shells.
- App routing query parameters (such as `?v=` for video pointers) are unchanged and remain part of runtime URL behavior. - App routing query parameters (such as `?v=` for video pointers) are unchanged and remain part of runtime URL behavior.
+1 -1
View File
@@ -3,7 +3,7 @@
publish = "dist" publish = "dist"
[build.environment] [build.environment]
NODE_ENV = "development" NODE_ENV = "production"
[[headers]] [[headers]]
for = "/*" for = "/*"
+1
View File
@@ -42,6 +42,7 @@
"lint:sw-compat": "node scripts/check-sw-compat.mjs", "lint:sw-compat": "node scripts/check-sw-compat.mjs",
"hash:dist": "node scripts/hash-dist.mjs --dist dist --output artifacts/dist-hashes-current.json", "hash:dist": "node scripts/hash-dist.mjs --dist dist --output artifacts/dist-hashes-current.json",
"diff:dist-hashes": "node scripts/diff-dist-hashes.mjs", "diff:dist-hashes": "node scripts/diff-dist-hashes.mjs",
"verify:dist:deploy-artifact": "node scripts/verify-dist-deploy-artifact.mjs",
"purge:cloudflare:changed": "node scripts/purge-cloudflare-changed.mjs", "purge:cloudflare:changed": "node scripts/purge-cloudflare-changed.mjs",
"smoke:dist-assets": "node scripts/check-asset-references.mjs", "smoke:dist-assets": "node scripts/check-asset-references.mjs",
"test:e2e:cache-upgrade": "npx playwright test tests/e2e/cache-upgrade-smoke.spec.ts --project=e2e" "test:e2e:cache-upgrade": "npx playwright test tests/e2e/cache-upgrade-smoke.spec.ts --project=e2e"
+3 -2
View File
@@ -2,6 +2,7 @@
import fs from 'node:fs'; import fs from 'node:fs';
const ALWAYS_PUBLISH_PATHS = new Set(['index.html', 'embed.html', 'sw.min.js', 'site.webmanifest']); const ALWAYS_PUBLISH_PATHS = new Set(['index.html', 'embed.html', 'sw.min.js', 'site.webmanifest']);
const ALWAYS_PURGE_HTML_PATHS = ['index.html', 'embed.html'];
const NON_ASSET_CHANGED_PATHS = new Set(['asset-manifest.json', '_headers', '_redirects']); const NON_ASSET_CHANGED_PATHS = new Set(['asset-manifest.json', '_headers', '_redirects']);
function parseArgs(argv) { function parseArgs(argv) {
@@ -82,8 +83,8 @@ function main() {
const hashedModeFullyActive = unhashedAssetPaths.length === 0; const hashedModeFullyActive = unhashedAssetPaths.length === 0;
const purgePaths = hashedModeFullyActive const purgePaths = hashedModeFullyActive
? changedPaths.filter(isHtmlOrServiceWorkerPath) ? [...changedPaths.filter(isHtmlOrServiceWorkerPath), ...ALWAYS_PURGE_HTML_PATHS]
: changedPaths; : [...changedPaths, ...ALWAYS_PURGE_HTML_PATHS];
const uniquePurgePaths = uniqueSorted(purgePaths); const uniquePurgePaths = uniqueSorted(purgePaths);
+7 -1
View File
@@ -66,7 +66,13 @@ function readChangedPaths(args) {
output.push(...args.changedPaths); output.push(...args.changedPaths);
return [...new Set(output.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b)); const normalized = output.map((value) => value.trim()).filter(Boolean);
if (normalized.includes('index.html')) {
normalized.push('/');
}
return [...new Set(normalized)].sort((a, b) => a.localeCompare(b));
} }
function toAbsoluteUrl(baseUrl, relativePath) { function toAbsoluteUrl(baseUrl, relativePath) {
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
const DIST_DIR = 'dist';
const INDEX_PATH = path.join(DIST_DIR, 'index.html');
const MANIFEST_PATH = path.join(DIST_DIR, 'asset-manifest.json');
const VERSION_MARKUP_PATTERN = /seed\.\s*zap\.\s*subscribe\.\s*<\/h2>\s*<div[^>]*>\s*v:\s*[a-f0-9]{8}\s*•\s*\d{4}-\d{2}-\d{2}\s*<\/div>/i;
function readUtf8(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`Missing required file: ${filePath}`);
}
return fs.readFileSync(filePath, 'utf8');
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function main() {
const indexHtml = readUtf8(INDEX_PATH);
assert(
VERSION_MARKUP_PATTERN.test(indexHtml),
'dist/index.html is missing version markup under the slogan (expected: v: <8-char-hash> • <date>).'
);
const manifest = JSON.parse(readUtf8(MANIFEST_PATH));
const cssHashedPath = manifest['css/tailwind.generated.css'];
const jsHashedPath = manifest['js/index.js'];
assert(cssHashedPath, 'dist/asset-manifest.json is missing css/tailwind.generated.css.');
assert(jsHashedPath, 'dist/asset-manifest.json is missing js/index.js.');
assert(
indexHtml.includes(`href="${cssHashedPath}"`) || indexHtml.includes(`href='${cssHashedPath}'`),
`dist/index.html does not reference manifest CSS asset: ${cssHashedPath}`
);
assert(
indexHtml.includes(`src="${jsHashedPath}"`) || indexHtml.includes(`src='${jsHashedPath}'`),
`dist/index.html does not reference manifest JS asset: ${jsHashedPath}`
);
console.log('Deployment artifact verification passed: version markup and hashed asset references are present in dist/index.html.');
}
main();