import { existsSync } from 'node:fs' import { readFile } from 'node:fs/promises' import { spawnSync } from 'node:child_process' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') const sysroot = resolve(root, '.cache/freecad/sysroot') const scriptPath = resolve(root, 'scripts/freecad-property-direction-mutation.py') const outputPath = resolve(root, 'config/freecad-property-direction-mutation.json') if (!existsSync(executable)) throw new Error(`FreeCAD PropertyDirection executable is missing: ${executable}`) const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], { cwd: root, encoding: 'utf8', timeout: 120_000, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, FREECAD_PROPERTY_DIRECTION_MUTATION_OUTPUT: outputPath, PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`, }, }) const output = `${execution.stdout || ''}\n${execution.stderr || ''}` if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_DIRECTION_MUTATION_RESULT=')) throw new Error(`FreeCAD PropertyDirection mutation probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) const report = JSON.parse(await readFile(outputPath, 'utf8')) console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-direction-mutation.json', cases: report.cases.map(({ objectTypeId, propertyName, phases, valueChanged, valueRestored, geometryChanged, geometryRestored, objectsPreserved }) => ({ objectTypeId, propertyName, values: { before: phases.before.value, edited: phases.afterRecompute.value, restored: phases.afterRestoreRecompute.value, }, brepSha256: { before: phases.before.shape.brepSha256, edited: phases.afterRecompute.shape.brepSha256, restored: phases.afterRestoreRecompute.shape.brepSha256, }, valueChanged, valueRestored, geometryChanged, geometryRestored, objectsPreserved, })), }, null, 2))