#!/usr/bin/env node import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { Worker } from "node:worker_threads"; import { execFileSync } from "node:child_process"; import { pathToFileURL } from "node:url"; const root = "/home/mes123456/FreeCADWasmDev/working/FreeCAD"; const build = process.argv[2] ?? path.join(root, "freecad-source/build-web/configure/bin"); const batch = JSON.parse(fs.readFileSync(path.join(root, "docs/current/module-batch-manifest.json"), "utf8")); if (batch.status !== "ready" || batch.selected?.module !== "Sketcher" || batch.selected?.tier !== "tier-1-readonly" || batch.selected?.family !== "geometry-value" || batch.records.length !== 3) throw Error("unexpected Sketcher geometry-value batch"); const records = batch.records.map(([apiId, name, disposition, sourcePath]) => ({ apiId, name, family: "geometry-value", sourcePath, disposition })); const out = fs.mkdtempSync("/tmp/freecad-sketcher-geometry-value-"); execFileSync(path.join(root, "web/node_modules/.bin/tsc"), ["--target", "ES2022", "--module", "ESNext", "--moduleResolution", "Bundler", "--strict", "--skipLibCheck", "--outDir", out, "--declaration", "false", "--noEmit", "false", path.join(root, "web/src/facade-envelope.ts"), path.join(root, "web/src/sketcher-python-family-facade.ts")], { cwd: root, stdio: "inherit" }); const facade = await import(pathToFileURL(path.join(out, "sketcher-python-family-facade.js"))); const envelope = await import(pathToFileURL(path.join(out, "facade-envelope.js"))); const worker = new Worker(path.join(root, "tools/sketcher-python-family-facade-smoke-worker.mjs"), { workerData: { buildDirectory: build, facadePath: pathToFileURL(path.join(out, "sketcher-python-family-facade.js")).href, envelopePath: pathToFileURL(path.join(out, "facade-envelope.js")).href } }); const next = () => new Promise((resolve, reject) => { worker.once("message", resolve); worker.once("error", reject); }); const ready = await next(); const request = facade.createSketcherPythonFamilyRequest("sketcher-geometry-value-success", records); worker.postMessage(request); const success = await next(); worker.postMessage(facade.createSketcherPythonFamilyRequest("sketcher-geometry-value-repeat", records)); const repeat = await next(); worker.postMessage(facade.createSketcherPythonFamilyRequest("sketcher-geometry-value-cross", [{ ...records[0], apiId: "py:BIM:method:SketchObject.getPoint" }])); const cross = await next(); worker.postMessage({ ...facade.createSketcherPythonFamilyRequest("sketcher-geometry-value-invalid", records), payload: { ...request.payload, tier: "tier-unsupported" } }); const malformed = await next(); const cancelRequest = facade.createSketcherPythonFamilyRequest("sketcher-family-cancel-1", records); worker.postMessage(cancelRequest); worker.postMessage(envelope.createCancel("geometry-value-cancel", cancelRequest.requestId, facade.SKETCHER_PYTHON_FAMILY_COMMAND)); const cancelled = await next(); worker.postMessage({ kind: "reset", requestId: "geometry-value-reset" }); const reset = await next(); await worker.terminate(); console.log(JSON.stringify({ready, success, repeat, malformed, cancelled, reset})); if (ready.kind !== "ready" || success.kind !== "result" || repeat.kind !== "result" || malformed.kind !== "error" || cancelled.kind !== "error" || reset.requestId !== "geometry-value-reset") throw Error("Sketcher geometry-value sequencing failed"); if (success.result.count !== 3 || success.result.resourcesReleased !== true || success.result.records.some(r => r.status !== "exposed" || r.code !== "native_method_fixture" || r.native !== true || r.resourcesReleased !== true)) throw Error("unexpected native geometry-value result"); if (JSON.stringify(success.result) !== JSON.stringify(repeat.result) || (cross.kind !== "error" && cross.result.records[0].code !== "unmatched_source_record") || cancelled.error.code !== "cancelled") throw Error("Sketcher geometry-value boundary failed"); const hash = v => crypto.createHash("sha256").update(JSON.stringify(v)).digest("hex"); const fileHash = f => crypto.createHash("sha256").update(fs.readFileSync(f)).digest("hex"); const evidence = { schema: "freecad-web-sketcher-geometry-value-module-batch-runtime/v1", taskId: "FW-P6-FPA-Y", batchId: batch.batchId, module: "Sketcher", tier: "tier-1-readonly", family: "geometry-value", status: "pass_with_boundaries", sourceRevision: success.result.records[0].sourceRevision, route: { command: "sketcher.pythonFamilyBatch", native: "freecad-source/src/WebAssembly/FreeCADCoreProbe/SketcherProbe.cpp:fc_web_dispatch_json", facade: "web/src/sketcher-python-family-facade.ts", worker: "web/src/sketcher-python-family-facade-smoke-worker.mjs" }, input: request, inputSha256: hash(request), result: success.result, resultSha256: hash(success.result), replay: { equal: true, resultSha256: hash(repeat.result) }, boundaries: { crossModule: cross, malformed, cancelled, reset }, resources: { resourcesReleased: true, documentMutation: false }, artifacts: { module: "Sketcher", revision: success.result.records[0].sourceRevision, runtimeUrl: "/kernel/FreeCADWebSketcherProbe.js", wasmUrl: "/kernel/FreeCADWebSketcherProbe.wasm", dataUrl: null, assetBaseUrl: "/kernel/", wasmSha256: fileHash(path.join(root, "web/public/kernel/FreeCADWebSketcherProbe.wasm")) } }; const evidencePath = path.join(root, `docs/p6/sketcher-geometry-value-module-batch-${batch.batchId}-runtime.json`); fs.writeFileSync(evidencePath, JSON.stringify(evidence, null, 2) + "\n"); console.log(JSON.stringify({ status: evidence.status, batchId: batch.batchId, count: 3, resultSha256: evidence.resultSha256 }));