100 lines
2.7 KiB
JavaScript
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`);
|