60 lines
5.6 KiB
JavaScript
60 lines
5.6 KiB
JavaScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
const root = path.resolve(new URL(".", import.meta.url).pathname, "../..");
|
|
const reportPath = path.join(root, "tests/golden/WBV2/full-source-parity-validation-report.json");
|
|
const logRoot = path.join(root, "tests/golden/WBV2/full-source-parity-validation-logs");
|
|
fs.mkdirSync(logRoot, { recursive: true });
|
|
const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
const commands = [
|
|
["context", "node", ["tools/web/check-task-context.mjs"]],
|
|
["governance", "node", ["tools/web/check-context-governance.mjs"]],
|
|
["inventory", "npm", ["--prefix", "web", "run", "test:wbv2-inventory"]],
|
|
["dynamic-inventory", "node", ["tools/web/check-source-dynamic-inventory.mjs"]],
|
|
["completeness", "node", ["tools/web/audit-wbv2-feature-completeness.mjs"]],
|
|
["feature-smoke-evidence", "node", ["tools/web/check-wbv2-feature-smoke-plan.mjs"]],
|
|
["submodule-smoke-evidence", "node", ["tools/web/check-wbv2-submodule-smoke-plan.mjs"]],
|
|
["typecheck", "npm", ["--prefix", "web", "run", "typecheck"]],
|
|
["lint", "npm", ["--prefix", "web", "run", "lint"]],
|
|
["unit", "node", ["tools/web/run-unit-tests.mjs"]],
|
|
["local-deps", "npm", ["--prefix", "web", "run", "check:local-deps"]],
|
|
["source-sync", "node", ["tools/web/check-web-engine-source-sync.mjs"]],
|
|
["deployment-contract", "node", ["tools/web/check-deployment-contract.mjs"]],
|
|
["deployment-http", "node", ["tools/web/check-deployment-http.mjs"]],
|
|
["deployment-cache", "node", ["tools/web/check-deployment-cache.mjs"]],
|
|
["v1-acceptance", "npm", ["--prefix", "web", "run", "release:v1-acceptance"]],
|
|
["v1-acceptance-evidence", "npm", ["--prefix", "web", "run", "test:v1-acceptance-evidence"]],
|
|
["v1-acceptance-release-binding", "npm", ["--prefix", "web", "run", "test:v1-acceptance-release-binding"]],
|
|
["full-e2e", "npm", ["--prefix", "web", "run", "test:e2e", "--", "--workers=1"]],
|
|
["release-browser", "npm", ["--prefix", "web", "run", "test:browser"]],
|
|
["release-performance", "npm", ["--prefix", "web", "run", "test:release-performance"]],
|
|
["release-package", "npm", ["--prefix", "web", "run", "test:release-package"]],
|
|
["offline-reproducibility", "npm", ["--prefix", "web", "run", "release:offline"]],
|
|
["deployment-runbook", "npm", ["--prefix", "web", "run", "test:deployment-runbook"]],
|
|
["upgrade-runbook", "npm", ["--prefix", "web", "run", "test:upgrade-runbook"]],
|
|
["rollback-runbook", "npm", ["--prefix", "web", "run", "test:rollback-runbook"]],
|
|
["operations-rehearsal", "npm", ["--prefix", "web", "run", "test:operations-rehearsal"]],
|
|
["deployed-site", "node", ["tools/web/check-deployed-web-blender.mjs"]],
|
|
];
|
|
const records = [];
|
|
for (const [id, command, args] of commands) {
|
|
const started = Date.now();
|
|
const result = spawnSync(command, args, { cwd: root, encoding: "utf8", env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", BLENDER_BIN: process.env.BLENDER_BIN ?? "build_blender_native/bin/blender" }, maxBuffer: 64 * 1024 * 1024 });
|
|
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
const logPath = path.join(logRoot, `${String(records.length + 1).padStart(2, "0")}-${id}.log`);
|
|
fs.writeFileSync(logPath, `${output}\n`);
|
|
records.push({ id, command: [command, ...args].join(" "), exitCode: result.status ?? 1, durationMs: Date.now() - started, log: { path: path.relative(root, logPath).replaceAll(path.sep, "/"), sha256: sha(logPath), bytes: fs.statSync(logPath).size } });
|
|
process.stdout.write(`[${records.length}/${commands.length}] ${id} exit=${records.at(-1).exitCode} durationMs=${records.at(-1).durationMs}\n`);
|
|
}
|
|
const inventory = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/WBV2/dynamic-inventory.json"), "utf8"));
|
|
const catalog = JSON.parse(fs.readFileSync(path.join(root, "web/app/src/capabilities/blender-feature-route-catalog.json"), "utf8"));
|
|
const sourceParityStatus = inventory.coverage?.unverifiedUnits === 0 && catalog.unclassifiedCount === 0 ? "READY" : "PARTIAL";
|
|
const webProductStatus = records.filter((item) => ["context", "governance", "inventory", "dynamic-inventory", "completeness", "feature-smoke-evidence", "submodule-smoke-evidence", "typecheck", "lint", "unit", "local-deps", "source-sync", "full-e2e", "release-browser"].includes(item.id)).every((item) => item.exitCode === 0) ? "READY" : "BLOCKED";
|
|
const releaseStatus = records.filter((item) => ["deployment-contract", "deployment-http", "deployment-cache", "release-package", "offline-reproducibility", "deployment-runbook", "upgrade-runbook", "rollback-runbook", "operations-rehearsal", "deployed-site"].includes(item.id)).every((item) => item.exitCode === 0) ? "READY" : "BLOCKED";
|
|
const report = { schemaVersion: 1, reportId: "WBV2-FULL-SOURCE-PARITY-VALIDATION-001", generatedAt: new Date().toISOString(), sourceParityStatus, webProductStatus, releaseStatus, inventory: { unitCount: inventory.summary.unitCount, verifiedUnits: inventory.coverage.verifiedUnits, unverifiedUnits: inventory.coverage.unverifiedUnits, sourceTreeSha256: catalog.sourceTreeSha256, classifiedCount: catalog.classifiedCount, unclassifiedCount: catalog.unclassifiedCount }, records, policy: `route classification is not behavioral completion; ${inventory.coverage.unverifiedUnits} unverified source units remain explicit` };
|
|
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
process.stdout.write(`web-source-parity-validation sourceParity=${sourceParityStatus} webProduct=${webProductStatus} release=${releaseStatus} report=${reportPath}\n`);
|
|
process.exit(records.every((item) => item.exitCode === 0) ? 0 : 1);
|