113 lines
6.7 KiB
JavaScript
113 lines
6.7 KiB
JavaScript
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;
|