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

135 lines
5.0 KiB
JavaScript

#!/usr/bin/env node
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";
const root = process.cwd();
const sourceRoot = path.join(root, "freecad-source");
const docsRoot = path.join(root, "docs");
const outputDir = path.join(docsRoot, "p6");
const manifestPath = path.join(outputDir, "baseline.json");
const logPath = path.join(outputDir, "baseline.log");
fs.mkdirSync(outputDir, { recursive: true });
const log = [];
function command(file, args, cwd = root) {
const rendered = [file, ...args].join(" ");
try {
const stdout = execFileSync(file, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
log.push(`$ (cd ${cwd} && ${rendered})\n${stdout}`);
return { command: rendered, stdout, exitCode: 0 };
} catch (error) {
const stdout = String(error.stdout ?? "").trim();
const stderr = String(error.stderr ?? "").trim();
log.push(`$ (cd ${cwd} && ${rendered})\nexit=${error.status ?? 1}\n${stdout}\n${stderr}`.trim());
return { command: rendered, stdout, stderr, exitCode: error.status ?? 1 };
}
}
function sha256(file) {
const hash = crypto.createHash("sha256");
hash.update(fs.readFileSync(file));
return hash.digest("hex");
}
function existing(paths) {
return paths.find((candidate) => fs.existsSync(candidate)) ?? null;
}
const revision = command("git", ["rev-parse", "HEAD"], sourceRoot);
const status = command("git", ["status", "--porcelain=v1"], sourceRoot);
const diffStat = command("git", ["diff", "--stat"], sourceRoot);
const node = command(process.execPath, ["--version"]);
const npm = command("npm", ["--version"]);
const cmake = command("cmake", ["--version"]);
const chromeCandidates = [
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
"/opt/google/chrome/google-chrome",
];
const chromePath = existing(chromeCandidates);
const chromeVersion = chromePath ? command(chromePath, ["--version"]) : { command: "chrome --version", stdout: "", exitCode: 127 };
if (!chromePath) log.push("chrome executable not found; no alternate browser was attempted");
const cacheCandidates = [
path.join(sourceRoot, "build-web", "configure", "CMakeCache.txt"),
path.join(sourceRoot, "build-web", "CMakeCache.txt"),
];
const cachePath = existing(cacheCandidates);
const cache = cachePath ? fs.readFileSync(cachePath, "utf8") : "";
const cacheKeys = ["FREECAD_WEB:BOOL", "CMAKE_BUILD_TYPE:STRING", "CMAKE_TOOLCHAIN_FILE:FILEPATH", "CMAKE_GENERATOR:INTERNAL"];
const cacheValues = Object.fromEntries(cacheKeys.map((key) => [key, cache.match(new RegExp(`^${key.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")}=([^\\r\\n]*)`, "m"))?.[1] ?? null]));
const inputRelPaths = [
"docs/web-parity-matrix.json",
"docs/workbench-inventory.json",
"docs/object-inventory.json",
"docs/format-api-inventory.json",
"docs/freecad-web-release-gate.json",
"docs/current/FREECAD_WEB_MIGRATION_ANALYSIS.md",
"docs/current/FREECAD_WEB_EXECUTION_PLAN.md",
"docs/current/FREECAD_WEB_PROGRESS.md",
];
const inputs = inputRelPaths.map((relativePath) => {
const absolutePath = path.join(root, relativePath);
return {
path: relativePath,
exists: fs.existsSync(absolutePath),
size: fs.existsSync(absolutePath) ? fs.statSync(absolutePath).size : null,
sha256: fs.existsSync(absolutePath) ? sha256(absolutePath) : null,
};
});
const result = chromePath && revision.exitCode === 0 && inputs.every((item) => item.exists) ? "pass" : "blocked";
const report = {
schema: "freecad-web-p6-baseline/v1",
result,
generatedAt: new Date().toISOString(),
host: {
platform: process.platform,
arch: process.arch,
osRelease: os.release(),
cwd: root,
},
source: {
root: "FreeCAD/freecad-source",
revision: revision.stdout || null,
dirty: status.stdout.length > 0,
statusLines: status.stdout ? status.stdout.split("\n") : [],
diffStat: diffStat.stdout,
},
inputs,
build: {
configureDirectory: cachePath ? path.relative(root, path.dirname(cachePath)) : null,
cache: cacheValues,
},
tools: {
node: node.stdout || null,
npm: npm.stdout || null,
cmake: cmake.stdout.split("\n")[0] || null,
},
chrome: {
path: chromePath,
version: chromeVersion.stdout || null,
executableExitCode: chromeVersion.exitCode,
mode: "headless",
viewport: { width: 1440, height: 900, deviceScaleFactor: 1 },
network: "intercept-and-require-zero-external-requests",
},
policy: {
browser: "Google Chrome only",
nonChromeBrowsersAttempted: false,
dirtyWorktreePreserved: true,
},
};
fs.writeFileSync(manifestPath, `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(logPath, `${log.join("\n\n")}\n\nresult=${result}\nmanifest=${path.relative(root, manifestPath)}\n`);
console.log(JSON.stringify({ schema: report.schema, result, revision: report.source.revision, chrome: report.chrome.version, inputCount: inputs.length }));
process.exitCode = result === "pass" ? 0 : 1;