import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import ts from "typescript"; const webRoot = fileURLToPath(new URL("../", import.meta.url)); // React views and scene hosts are display-layer owners: Three.js is permitted // there; Facades/Workers must continue to consume OCCT WASM mesh payloads. const displayOwners = new Set([ "src/main.ts", "src/import-render-bridge.ts", "src/render-object-registry.ts", "src/scene-session-registry.ts", "src/bim-frame-view.tsx", "src/draft-geometry-task.tsx", "src/openscad-desktop-view.tsx", "src/scene-camera-commands.ts", "src/scene-command-host.ts", "src/scene-overlap-picker.ts", "src/techdraw-active-view.tsx", "src/templatepymod-desktop-task.tsx", "src/test-view-example-task.tsx", "src/web-view-task.tsx", ]); const proceduralGeometry = new Set(["BoxGeometry", "SphereGeometry", "CylinderGeometry", "ConeGeometry", "ExtrudeGeometry", "ShapeGeometry", "TorusGeometry", "TorusKnotGeometry", "LatheGeometry", "TubeGeometry", "PolyhedronGeometry", "TetrahedronGeometry", "OctahedronGeometry", "DodecahedronGeometry", "IcosahedronGeometry"]); const alternateGeometry = /^(?:@jscad\/|jscad(?:$|\/)|three-csg(?:-ts)?(?:$|\/)|three-bvh-csg(?:$|\/)|manifold-3d(?:$|\/))/; export function inspectGeometrySource(filename, source) { const tree = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true, filename.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS); const findings = []; const namespaces = new Set(); const named = new Map(); let usesThree = false; const add = (node, code, message) => findings.push({ path: filename, line: tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1, code, message }); const dependency = (node, value) => { if (alternateGeometry.test(value)) add(node, "alternate_geometry_backend", `Use the provided OCCT backend, not ${value}`); if (value === "three" || value.startsWith("three/")) { usesThree = true; if (!displayOwners.has(filename)) add(node, "three_outside_display", "Three.js belongs to the reviewed display layer, not a Facade or Worker"); } }; for (const statement of tree.statements) { if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && !statement.importClause?.isTypeOnly) { const value = statement.moduleSpecifier.text; dependency(statement, value); if (value === "three") { const binding = statement.importClause?.namedBindings; if (binding && ts.isNamespaceImport(binding)) namespaces.add(binding.name.text); if (binding && ts.isNamedImports(binding)) for (const item of binding.elements) { if (!item.isTypeOnly) named.set(item.name.text, item.propertyName?.text ?? item.name.text); } } } if (ts.isExportDeclaration(statement) && !statement.isTypeOnly && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) dependency(statement, statement.moduleSpecifier.text); } const visit = node => { if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === "require")) { if (node.arguments[0] && ts.isStringLiteral(node.arguments[0])) dependency(node, node.arguments[0].text); } if (ts.isNewExpression(node)) { const target = node.expression; const name = ts.isIdentifier(target) ? named.get(target.text) : ts.isPropertyAccessExpression(target) && ts.isIdentifier(target.expression) && namespaces.has(target.expression.text) ? target.name.text : undefined; if (proceduralGeometry.has(name)) add(node, "three_procedural_cad", `${name} cannot replace native CAD geometry; render the WASM mesh payload`); } ts.forEachChild(node, visit); }; visit(tree); return { findings, usesThree }; } export function auditGeometryBoundary(root = webRoot) { const started = performance.now(); const findings = []; const renderers = []; let sourceFiles = 0; const visit = directory => { for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const file = path.join(directory, entry.name); if (entry.isDirectory()) visit(file); else if (entry.isFile() && /\.(?:ts|tsx|js|mjs)$/.test(entry.name) && !entry.name.endsWith(".d.ts")) { const name = path.relative(root, file).split(path.sep).join("/"); const result = inspectGeometrySource(name, fs.readFileSync(file, "utf8")); sourceFiles += 1; findings.push(...result.findings); if (result.usesThree) renderers.push(name); } } }; visit(path.join(root, "src")); const artifacts = ["public/kernel/FreeCADWebKernel.js", "public/kernel/FreeCADWebKernel.wasm"].map(name => { const file = path.join(root, name); let bytes = 0; try { bytes = fs.statSync(file).size; if (!bytes) throw new Error("empty artifact"); if (name.endsWith(".wasm")) { const descriptor = fs.openSync(file, "r"); const header = Buffer.alloc(8); try { fs.readSync(descriptor, header, 0, 8, 0); } finally { fs.closeSync(descriptor); } if (!header.equals(Buffer.from([0, 97, 115, 109, 1, 0, 0, 0]))) throw new Error("invalid WASM header"); } } catch (error) { findings.push({ path: name, code: "provided_kernel_unavailable", message: error.message }); } return { path: name, bytes }; }); return { schema: "freecad-web-geometry-boundary/v1", status: findings.length ? "failed" : "checked", sourceFiles, renderers: renderers.sort(), artifacts, findings, singleRuntimeVerified: false, scope: "Static frontend ownership and supplied artifact checks only; not proof of all native algorithms or a single WASM instance.", totalSeconds: Number(((performance.now() - started) / 1000).toFixed(3)) }; } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { const report = auditGeometryBoundary(); console.log(JSON.stringify(report)); process.exitCode = report.findings.length ? 1 : 0; }