Files
workinf_Blender_Wasm/tools/web/web-build-fingerprint.mjs
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

70 lines
2.7 KiB
JavaScript

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);
const candidatePublic = process.env.WEB_DESKTOP_CANDIDATE_PUBLIC, candidateFactory = process.env.WEB_DESKTOP_CANDIDATE_FACTORY;
if (Boolean(candidatePublic) !== Boolean(candidateFactory)) throw new Error("Desktop candidate requires matching public resources and native factory");
if (candidatePublic) walk(path.resolve(candidatePublic), files);
if (candidateFactory) files.push(path.resolve(candidateFactory));
return [...new Set(files)].filter((file) => fs.existsSync(file)).sort();
}
export function fingerprint() {
const hash = crypto.createHash("sha256");
hash.update(JSON.stringify([process.env.WEB_DESKTOP_CANDIDATE_PUBLIC ?? null, process.env.WEB_DESKTOP_CANDIDATE_FACTORY ?? null]));
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);
}