Files
FreeCADWasmDev/tools/generate_web_license_artifact_map.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

154 lines
5.8 KiB
JavaScript

import crypto from "node:crypto";
import fs from "node:fs";
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 workspaceRoot = path.resolve(projectRoot, "..");
const distRoot = path.join(projectRoot, "web", "dist");
const evidencePath = path.join(projectRoot, "docs", "web-license-evidence.json");
const outputPath = path.join(projectRoot, "docs", "web-license-artifact-map.json");
const evidence = JSON.parse(fs.readFileSync(evidencePath, "utf8"));
const sourceRevision = execFileSync("git", ["-C", path.join(projectRoot, "freecad-source"), "rev-parse", "HEAD"], {
encoding: "utf8",
}).trim();
const dependencyRows = [...evidence.sourceDependencies, ...evidence.webDependencies];
const dependencyByProject = new Map(dependencyRows.map((row) => [row.project, row]));
const artifactRules = [
{
match: (relative) => relative === "index.html",
sources: ["FreeCAD/web/index.html"],
dependencies: ["FreeCAD Web UI"],
unresolved: [],
},
{
match: (relative) => /^assets\/index-[^/]+\.js$/.test(relative),
sources: [
"FreeCAD/web/src/main.ts",
"FreeCAD/web/src/storage.ts",
"FreeCAD/web/src/mesh-cache.ts",
"FreeCAD/web/src/recovery.ts",
"FreeCAD/web/src/worker-protocol.ts",
],
dependencies: ["FreeCAD Web UI", "three", "lucide"],
unresolved: [],
},
{
match: (relative) => /^assets\/kernel\.worker-[^/]+\.js$/.test(relative),
sources: ["FreeCAD/web/src/kernel.worker.ts", "FreeCAD/web/src/worker-protocol.ts"],
dependencies: ["FreeCAD Web UI"],
unresolved: [],
},
{
match: (relative) => /^kernel\/FreeCADWebKernel\.(js|wasm)$/.test(relative),
sources: [
"FreeCAD/freecad-source/src/WebAssembly/FreeCADWebBridge.cpp",
"FreeCAD/freecad-source/src/WebAssembly/CMakeLists.txt",
],
dependencies: ["FreeCAD Web UI", "Open CASCADE"],
unresolved: [
{
name: "Emscripten",
version: "3.1.6",
reason: "build tool generated JavaScript/WASM glue; no runtime package is shipped",
evidence: ["FreeCAD/docs/freecad-web-bridge-build.log", "FreeCAD/freecad-source/build-web/bridge/build.ninja"],
},
],
},
{
match: (relative) => relative === "samples/freecad-sample.step",
sources: ["FreeCAD/freecad-source/data/tests/Step/as1-ac-214_small.stp"],
dependencies: ["FreeCAD Web UI"],
unresolved: [],
},
];
function sha256(filePath) {
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
}
function filesUnder(root) {
const files = [];
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
const entryPath = path.join(root, entry.name);
if (entry.isDirectory()) files.push(...filesUnder(entryPath));
else if (entry.isFile()) files.push(entryPath);
}
return files.sort();
}
function dependencyRecord(project) {
const row = dependencyByProject.get(project);
if (!row) throw new Error(`missing license evidence for dependency: ${project}`);
return {
id: `${project}@${row.version}`,
project: row.project,
version: row.version,
licenseLabel: row.licenseLabel,
evidence: row.evidence,
sha256: row.sha256,
status: row.status,
};
}
if (!fs.existsSync(distRoot)) throw new Error(`artifact directory does not exist: ${distRoot}`);
const artifacts = filesUnder(distRoot).map((absolutePath) => {
const relative = path.relative(distRoot, absolutePath).split(path.sep).join("/");
const rule = artifactRules.find((candidate) => candidate.match(relative));
if (!rule) throw new Error(`unmapped release artifact: ${relative}`);
for (const source of rule.sources) {
if (!fs.existsSync(path.join(workspaceRoot, source))) throw new Error(`missing source for ${relative}: ${source}`);
}
for (const unresolved of rule.unresolved) {
for (const item of unresolved.evidence) {
if (!fs.existsSync(path.join(workspaceRoot, item))) throw new Error(`missing unresolved evidence for ${relative}: ${item}`);
}
}
return {
path: `FreeCAD/web/dist/${relative}`,
size: fs.statSync(absolutePath).size,
sha256: sha256(absolutePath),
sources: rule.sources,
dependencyRefs: rule.dependencies.map(dependencyRecord),
unresolvedDependencies: rule.unresolved,
buildEvidence: relative.startsWith("kernel/")
? ["FreeCAD/docs/freecad-web-bridge-build.log", "FreeCAD/freecad-source/build-web/bridge/build.ninja"]
: ["FreeCAD/web/package.json", "FreeCAD/web/package-lock.json"],
};
});
const result = {
schema: "freecad-web-license-artifact-map/v1",
sourceRevision,
artifactRoot: "FreeCAD/web/dist",
generatedBy: "FreeCAD/tools/generate_web_license_artifact_map.mjs",
build: {
ui: "npm run build (FreeCAD/web/package.json)",
kernel: "cmake --build FreeCAD/freecad-source/build-web/bridge --target FreeCADWebKernel",
evidence: [
"FreeCAD/web/package.json",
"FreeCAD/web/package-lock.json",
"FreeCAD/docs/freecad-web-bridge-build.log",
"FreeCAD/freecad-source/build-web/bridge/build.ninja",
],
},
licenseEvidence: "FreeCAD/docs/web-license-evidence.json",
artifacts,
summary: {
artifactCount: artifacts.length,
unmappedArtifactCount: 0,
artifactsWithUnresolvedBuildTools: artifacts.filter((artifact) => artifact.unresolvedDependencies.length > 0).length,
result: "pass_all_release_files_mapped_unresolved_build_tools_explicit",
},
};
fs.writeFileSync(outputPath, `${JSON.stringify(result, null, 2)}\n`);
console.log(`mapped ${artifacts.length} release artifacts`);
console.log(`unresolved build-tool annotations: ${result.summary.artifactsWithUnresolvedBuildTools}`);
console.log(`wrote ${outputPath}`);