import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { createRequire } from "node:module"; import { pathToFileURL } from "node:url"; const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); const docs = path.join(root, "docs/p6"); const build = path.resolve(process.argv[2] ?? path.join(root, "freecad-source/build-web/configure/bin")); globalThis.__dirname = `${build}${path.sep}`; globalThis.require = createRequire(import.meta.url); const loader = await import(pathToFileURL(path.join(build, "FreeCADWebReverseEngineeringRuntimeProbe.js"))); const kernel = await loader.default({ wasmBinary: fs.readFileSync(path.join(build, "FreeCADWebReverseEngineeringRuntimeProbe.wasm")), print: () => {}, printErr: () => {} }); const c = (name, type = "number") => kernel.ccall(name, type); const run = () => ({ run: c("fc_web_reverseengineering_save_reopen"), error: c("fc_web_reverseengineering_persistence_error", "string"), path: c("fc_web_reverseengineering_persistence_path", "string"), objectType: c("fc_web_reverseengineering_persistence_object_type", "string"), beforeLength: c("fc_web_reverseengineering_persistence_before_length"), afterLength: c("fc_web_reverseengineering_persistence_after_length"), size: c("fc_web_reverseengineering_persistence_size"), objectCount: c("fc_web_reverseengineering_persistence_object_count") }); const first = run(); const second = run(); const stable = value => ({ run: value.run, error: value.error, path: value.path, objectType: value.objectType, beforeLength: value.beforeLength, afterLength: value.afterLength, objectCount: value.objectCount }); const result = { first, second, deterministic: JSON.stringify(stable(first)) === JSON.stringify(stable(second)), roundTrip: first.run === 1 && first.error === "" && first.path === "/tmp/ReverseEngineeringWeb.FCStd" && first.objectType === "Part::Feature" && first.size > 0 && first.objectCount === 1 && Math.abs(first.beforeLength - first.afterLength) < 1e-9 && first.afterLength > 0 }; const fixture = { module: "ReverseEngineering", action: "save/reopen", sourceEntry: "src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp", points: [[0, 0, 0], [1, 1, 0], [2, 0, 0]], objectType: "Part::Feature" }; const hash = value => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); const wasmPath = path.join(build, "FreeCADWebReverseEngineeringRuntimeProbe.wasm"); const report = { schema: "freecad-web-p6-reverseengineering-action-04-kernel/v1", taskId: "FW-P6-REV-ACT-04-C", module: "ReverseEngineering", action: "save/reopen", result: result.roundTrip && result.deterministic ? "pass" : "fail", fixture: { ...fixture, inputSha256: hash(fixture) }, resultDetail: result, resultSha256: hash(result), commands: ["ninja -C freecad-source/build-web/configure FreeCADWebReverseEngineeringRuntimeProbe -j8", "node tools/run_p6_reverseengineering_action04_kernel.mjs freecad-source/build-web/configure/bin"], artifacts: { wasm: { path: "freecad-source/build-web/configure/bin/FreeCADWebReverseEngineeringRuntimeProbe.wasm", sha256: hash(fs.readFileSync(wasmPath)) }, workerLog: "docs/freecad-p6-reverseengineering-action-04-c-kernel.log" }, boundaries: ["The Part::Feature shape is generated by the official ReverseEngineering.approxCurve().toShape() path before FCStd save/reopen.", "ReverseEngineering has no dedicated persistent DocumentObject type; Part::Feature is the explicit result container for this Web action."] }; fs.writeFileSync(path.join(docs, "reverseengineering-action-04-kernel.json"), `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(path.join(root, "docs/freecad-p6-reverseengineering-action-04-c-kernel.log"), `${JSON.stringify({ result: report.result, roundTrip: result.roundTrip, deterministic: result.deterministic, objectType: first.objectType, beforeLength: first.beforeLength, afterLength: first.afterLength })}\n`); console.log(JSON.stringify({ result: report.result, roundTrip: result.roundTrip, deterministic: result.deterministic, objectType: first.objectType })); if (report.result !== "pass") process.exitCode = 1;