104 lines
4.3 KiB
JavaScript
104 lines
4.3 KiB
JavaScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const toolsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
const projectRoot = path.resolve(toolsRoot, "..");
|
|
const packageRoot = path.join(projectRoot, "web", "offline-package");
|
|
const docsRoot = path.join(projectRoot, "docs");
|
|
const assembler = path.join(toolsRoot, "assemble_web_offline_package.mjs");
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-web-repro-"));
|
|
|
|
function walk(root) {
|
|
const files = [];
|
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
const absolute = path.join(root, entry.name);
|
|
if (entry.isDirectory()) files.push(...walk(absolute));
|
|
else if (entry.isFile()) files.push(absolute);
|
|
}
|
|
return files.sort();
|
|
}
|
|
|
|
function sha256(filePath) {
|
|
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
}
|
|
|
|
function snapshot(destination) {
|
|
for (const source of walk(packageRoot)) {
|
|
const relative = path.relative(packageRoot, source);
|
|
const target = path.join(destination, relative);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.copyFileSync(source, target);
|
|
}
|
|
}
|
|
|
|
function normalizeJson(value, file) {
|
|
const copy = JSON.parse(JSON.stringify(value));
|
|
if (file === "manifest.json") {
|
|
copy.generatedAt = "<timestamp>";
|
|
copy.manifestFile.selfSha256 = "<self-hash>";
|
|
const versionEntry = copy.files.find((entry) => entry.path === "version.json");
|
|
if (versionEntry) versionEntry.sha256 = "<timestamp-content-hash>";
|
|
}
|
|
if (file === "version.json") copy.generatedAt = "<timestamp>";
|
|
return JSON.stringify(copy, null, 2) + "\n";
|
|
}
|
|
|
|
function normalizedContent(root, relative) {
|
|
const file = path.join(root, relative);
|
|
if (relative === "manifest.json" || relative === "version.json") return normalizeJson(JSON.parse(fs.readFileSync(file, "utf8")), relative);
|
|
return fs.readFileSync(file);
|
|
}
|
|
|
|
const first = path.join(tempRoot, "first");
|
|
const second = path.join(tempRoot, "second");
|
|
execFileSync(process.execPath, [assembler], { cwd: projectRoot, stdio: "ignore" });
|
|
snapshot(first);
|
|
const firstManifest = JSON.parse(fs.readFileSync(path.join(first, "manifest.json"), "utf8"));
|
|
execFileSync(process.execPath, [assembler], { cwd: projectRoot, stdio: "ignore" });
|
|
snapshot(second);
|
|
|
|
const firstFiles = new Set(walk(first).map((file) => path.relative(first, file).split(path.sep).join("/")));
|
|
const secondFiles = new Set(walk(second).map((file) => path.relative(second, file).split(path.sep).join("/")));
|
|
const differences = [];
|
|
for (const file of new Set([...firstFiles, ...secondFiles])) {
|
|
if (!firstFiles.has(file) || !secondFiles.has(file)) {
|
|
differences.push({ file, type: "file-set-difference" });
|
|
continue;
|
|
}
|
|
const left = normalizedContent(first, file);
|
|
const right = normalizedContent(second, file);
|
|
if (!Buffer.from(left).equals(Buffer.from(right))) differences.push({ file, type: "content-difference" });
|
|
}
|
|
|
|
const result = {
|
|
schema: "freecad-web-offline-package-repro/v1",
|
|
package: "FreeCAD/web/offline-package",
|
|
firstManifestSourceRevision: firstManifest.sourceRevision,
|
|
fixedInputs: [
|
|
"FreeCAD/web/dist",
|
|
"FreeCAD/docs/web-license-notices.md",
|
|
"FreeCAD/docs/web-license-inventory.json",
|
|
"FreeCAD/freecad-source revision",
|
|
],
|
|
allowedVariableFields: ["version.json.generatedAt", "manifest.json.generatedAt", "manifest.json.manifestFile.selfSha256"],
|
|
differences,
|
|
snapshotHashes: {
|
|
firstManifest: sha256(path.join(first, "manifest.json")),
|
|
secondManifest: sha256(path.join(second, "manifest.json")),
|
|
finalManifest: sha256(path.join(packageRoot, "manifest.json")),
|
|
},
|
|
summary: {
|
|
comparedFileCount: firstFiles.size,
|
|
differenceCount: differences.length,
|
|
result: differences.length === 0 ? "pass_reproducible_except_declared_timestamps" : "fail_reproducibility_difference",
|
|
},
|
|
};
|
|
fs.writeFileSync(path.join(docsRoot, "freecad-web-offline-package-repro.json"), `${JSON.stringify(result, null, 2)}\n`);
|
|
console.log(`compared ${result.summary.comparedFileCount} files; differences=${result.summary.differenceCount}`);
|
|
console.log(`result=${result.summary.result}`);
|
|
if (differences.length > 0) process.exitCode = 1;
|