Files
nxdns/web/scripts/stamp-dist.mjs
mokhtar 9f8a5cd753
CI / test (push) Failing after 1m12s
CI / test-aarch64 (push) Failing after 2m27s
CI / frontend (push) Successful in 1m28s
CI / cross (push) Failing after 27s
CI / docker (push) Failing after 24s
milestone 15: make a green run mean a real pass
2026-08-07 01:28:13 +02:00

100 lines
2.7 KiB
JavaScript

#!/usr/bin/env node
// Freshness stamp for web/dist (milestone-15 ruling 5). A stale dist has
// already shipped a crashing settings page once. Write mode runs from web/ as
// part of `npm run build`; check mode runs from the repository root as a
// build.zig system command. Every path resolves from this file's own location
// so both working directories hash the same set.
import { createHash } from "node:crypto";
import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const webRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const distDir = join(webRoot, "dist");
const stampFile = join(distDir, ".src-hash");
const stampRelative = "web/dist/.src-hash";
const inputDirs = ["src", "public"];
const inputFiles = [
"index.html",
"package.json",
"package-lock.json",
"vite.config.ts",
"tsconfig.json",
"tsconfig.app.json",
"tsconfig.node.json",
];
const staleMessage = "web/dist is stale: rebuild the frontend (npm run build)";
function fail(message) {
process.stderr.write(`${message}\n`);
process.exit(1);
}
function walk(relativeDir) {
const absolute = join(webRoot, relativeDir);
let entries;
try {
entries = readdirSync(absolute, { withFileTypes: true });
} catch (err) {
fail(`stamp-dist: cannot read web/${relativeDir}: ${err.message}`);
}
const found = [];
for (const entry of entries) {
const child = `${relativeDir}/${entry.name}`;
if (entry.isDirectory()) {
found.push(...walk(child));
} else if (entry.isFile()) {
found.push(child);
}
}
return found;
}
function inputSet() {
const paths = [...inputFiles, ...inputDirs.flatMap(walk)];
for (const path of inputFiles) {
try {
if (!statSync(join(webRoot, path)).isFile()) fail(`stamp-dist: web/${path} is not a file`);
} catch (err) {
fail(`stamp-dist: cannot stat web/${path}: ${err.message}`);
}
}
// Sorted by path so the digest does not depend on directory order.
return paths.sort();
}
function digest() {
const hash = createHash("sha256");
for (const path of inputSet()) {
hash.update(path);
hash.update("\0");
hash.update(readFileSync(join(webRoot, path)));
hash.update("\0");
}
return hash.digest("hex");
}
const check = process.argv.includes("--check");
const computed = digest();
if (check) {
let recorded;
try {
recorded = readFileSync(stampFile, "utf8").trim();
} catch {
fail(staleMessage);
}
if (recorded !== computed) fail(staleMessage);
process.exit(0);
}
try {
writeFileSync(stampFile, `${computed}\n`);
} catch (err) {
fail(`stamp-dist: cannot write ${stampRelative}: ${err.message}`);
}
process.stdout.write(`${stampRelative} ${computed}\n`);