Files
FreeCADWasmDev/tools/run_web_release_gate.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

113 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const toolsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
const projectRoot = path.resolve(toolsRoot, "..");
const docsRoot = path.join(projectRoot, "docs");
function readJson(name) { return JSON.parse(fs.readFileSync(path.join(docsRoot, name), "utf8")); }
function sha256(filePath) { return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex"); }
function requireFile(name) { return fs.existsSync(path.join(docsRoot, name)); }
const performance = readJson("freecad-performance-budget.json");
const compatibility = readJson("freecad-browser-compatibility.json");
const recovery = readJson("freecad-power-loss-recovery.json");
const licenseInventory = readJson("web-license-inventory.json");
const artifactMap = readJson("web-license-artifact-map.json");
const offlineManifest = readJson("freecad-web-offline-package-manifest.json");
const staticCheck = readJson("freecad-web-offline-static-check.json");
const functional = readJson("freecad-web-offline-functional.json");
const repro = readJson("freecad-web-offline-package-repro.json");
const rollback = readJson("freecad-web-upgrade-rollback.json");
const required = [
"freecad-performance-budget.json",
"freecad-browser-compatibility.json",
"freecad-power-loss-recovery.json",
"web-license-evidence.json",
"web-license-artifact-map.json",
"web-license-inventory.json",
"web-license-notices.md",
"freecad-web-offline-package-manifest.json",
"freecad-web-offline-static-check.json",
"freecad-web-offline-functional.json",
"freecad-web-offline-package-repro.json",
"freecad-web-upgrade-rollback.json",
];
const missing = required.filter((name) => !requireFile(name));
const chromeOnly = [
"FreeCAD/docs/current/FREECAD_WEB_MIGRATION_ANALYSIS.md",
"FreeCAD/docs/current/FREECAD_WEB_EXECUTION_PLAN.md",
"FreeCAD/docs/current/FREECAD_WEB_PROGRESS.md",
].every((relative) => /Chrome|Chrome only|浏览器测试只执行 Google Chrome|浏览器测试铁律/.test(fs.readFileSync(path.join(projectRoot, relative.replace("FreeCAD/", "")), "utf8")));
const supportedCompatibility = compatibility.summary.supportedCaseFailures === 0
&& compatibility.result === "pass_for_supported_cases_with_explicit_unavailable_and_pending_scope";
const noLicenseBlocks = licenseInventory.summary.blockedDependencies === 0 && artifactMap.summary.unmappedArtifactCount === 0;
const offlinePass = staticCheck.summary.result === "pass_static_package_closed"
&& functional.summary.result === "pass_offline_core_functional_replay"
&& repro.summary.result === "pass_reproducible_except_declared_timestamps";
const rollbackPass = rollback.summary.result === "pass_upgrade_rollback_paths_have_independent_evidence";
const packageFiles = offlineManifest.files.map((entry) => path.join(projectRoot, "web", "offline-package", entry.path));
const packageHashesValid = offlineManifest.files.every((entry) => fs.existsSync(path.join(projectRoot, "web", "offline-package", entry.path)) && sha256(path.join(projectRoot, "web", "offline-package", entry.path)) === entry.sha256);
const checks = [
{ id: "required-evidence", pass: missing.length === 0, detail: missing.length === 0 ? `${required.length} evidence files present` : `missing: ${missing.join(", ")}` },
{ id: "performance-budget", pass: performance.blocked === false && performance.result.startsWith("pass_"), detail: performance.result },
{ id: "chrome-compatibility", pass: supportedCompatibility, detail: "supported Chrome cases have zero failures; non-Chrome excluded by iron rule" },
{ id: "power-loss-recovery", pass: recovery.blocked === false && recovery.result.startsWith("pass_"), detail: recovery.result },
{ id: "license-coverage", pass: noLicenseBlocks, detail: `${artifactMap.summary.artifactCount} artifacts mapped; ${licenseInventory.summary.blockedDependencies} license blocks` },
{ id: "offline-package", pass: offlineManifest.summary.result === "assembled_verified_inputs" && packageHashesValid, detail: `${offlineManifest.summary.fileCount} files hash-verified` },
{ id: "offline-static-functional-repro", pass: offlinePass, detail: `${staticCheck.summary.referenceCount} references, ${functional.summary.passedCases}/${functional.summary.caseCount} Chrome cases, reproducible` },
{ id: "upgrade-rollback", pass: rollbackPass, detail: `${rollback.summary.pathCount} paths preserved previous package and FCStd` },
{ id: "chrome-only-iron-rule", pass: chromeOnly, detail: "current baseline, index and checkpoint state Chrome-only" },
];
const gate = {
schema: "freecad-web-release-gate/v1",
sourceRevision: offlineManifest.sourceRevision,
browserPolicy: "Chrome only; Firefox/WebKit/Safari excluded from execution and release blocking",
checks,
pendingOutsideRelease: [
"FCStd save/reopen UI slice is pending because current browser UI exposes no FCStd save/reopen action",
"native Safari, Firefox and WebKit runs are excluded by the execution iron rule",
"network distribution security and automatic corrupt-package repair are outside this gate",
],
package: {
manifest: "FreeCAD/docs/freecad-web-offline-package-manifest.json",
packageRoot: "FreeCAD/web/offline-package",
packageFileCount: offlineManifest.summary.fileCount,
packageHashesValid,
manifestSha256: sha256(path.join(projectRoot, "web", "offline-package", "manifest.json")),
},
summary: {
checkCount: checks.length,
passedChecks: checks.filter((check) => check.pass).length,
failedChecks: checks.filter((check) => !check.pass).length,
result: checks.every((check) => check.pass) ? "PASS_P5_RELEASE_READY_WITH_EXPLICIT_PENDING_SCOPE" : "BLOCKED_P5_RELEASE_GATE",
},
};
fs.writeFileSync(path.join(docsRoot, "freecad-web-release-gate.json"), `${JSON.stringify(gate, null, 2)}\n`);
const lines = [
"# FreeCAD 1.2 Web P5 发布出口",
"",
"浏览器执行铁律:只使用 Google Chrome;Firefox、WebKit、Safari 不执行、不纳入当前验收或阻断判定。",
"",
`Source revision: \`${gate.sourceRevision}\``,
"",
"| Check | Result | Detail |",
"|---|---|---|",
...checks.map((check) => `| ${check.id} | ${check.pass ? "PASS" : "BLOCKED"} | ${check.detail} |`),
"",
`## Decision: **${gate.summary.result}**`,
"",
"Explicit pending scope:",
...gate.pendingOutsideRelease.map((item) => `- ${item}`),
"",
`Offline package manifest SHA-256: \`${gate.package.manifestSha256}\``,
];
fs.writeFileSync(path.join(docsRoot, "freecad-web-release-gate.md"), `${lines.join("\n")}\n`);
console.log(`P5 gate ${gate.summary.result}; checks=${gate.summary.passedChecks}/${gate.summary.checkCount}`);
if (gate.summary.failedChecks > 0) process.exitCode = 1;