import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const webRoot = path.join(root, "web"); function walk(directory, output) { if (!fs.existsSync(directory)) return; for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".cache" || entry.name === ".vite") continue; const file = path.join(directory, entry.name); if (entry.isDirectory()) walk(file, output); else if (entry.isFile()) output.push(file); } } export function inputFiles() { const files = [ path.join(webRoot, "package.json"), path.join(webRoot, "package-lock.json"), path.join(webRoot, "tsconfig.json"), path.join(webRoot, "app/vite.config.ts"), ]; walk(path.join(webRoot, "app/src"), files); walk(path.join(webRoot, "protocol"), files); walk(path.join(webRoot, "app/public"), files); return [...new Set(files)].filter((file) => fs.existsSync(file)).sort(); } export function fingerprint() { const hash = crypto.createHash("sha256"); for (const file of inputFiles()) { // Read nanosecond metadata as bigint. Without the explicit option // `mtimeNs` is undefined in Node, so same-sized edits can reuse stale // bundles indefinitely. const stat = fs.statSync(file, { bigint: true }); hash.update(path.relative(root, file)); hash.update("\0"); hash.update(stat.size.toString()); hash.update("\0"); hash.update(stat.mtimeNs.toString()); hash.update("\n"); } return hash.digest("hex"); } export function statePath(name) { return path.join(webRoot, ".cache", name); } export function readState(name) { try { return JSON.parse(fs.readFileSync(statePath(name), "utf8")); } catch { return null; } } export function writeState(name, value) { const file = statePath(name); fs.mkdirSync(path.dirname(file), { recursive: true }); const temporary = `${file}.tmp-${process.pid}`; fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`); fs.renameSync(temporary, file); }