import { spawnSync } from 'node:child_process' import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { mkdir, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const executable = resolve(root, '.cache/freecad/install-desktop/bin/FreeCAD') const probe = resolve(root, 'scripts/freecad-gui-workflow-oracle.py') const sysroot = resolve(root, '.cache/freecad/sysroot') const outputDirectory = resolve(root, '.cache/freecad/gui-workflow-oracle') const reportPath = resolve(root, 'config/freecad-gui-workflow-oracle.json') const marker = 'FREECAD_GUI_WORKFLOW_RESULT=' const timeoutMs = Number(process.env.FREECAD_GUI_WORKFLOW_TIMEOUT_MS || 120_000) const fail = (message) => { throw new Error(`FreeCAD GUI workflow oracle: ${message}`) } await mkdir(outputDirectory, { recursive: true }) const emptyConfig = '\n\n' const runProbe = (state) => { const resultFile = resolve(outputDirectory, `partdesign-pad-${state}-result.json`) const recoveryFile = resolve(outputDirectory, 'partdesign-pad-recovery.FCStd') const configDirectory = resolve(outputDirectory, `config-${state}`) const userConfig = resolve(configDirectory, 'user.cfg') const systemConfig = resolve(configDirectory, 'system.cfg') mkdirSync(configDirectory, { recursive: true }) writeFileSync(userConfig, emptyConfig) writeFileSync(systemConfig, emptyConfig) rmSync(resultFile, { force: true }) if (state === 'recovery') rmSync(recoveryFile, { force: true }) const runtimeEnv = { ...process.env, 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}` : ''}`, MATPLOTLIBRC: resolve(sysroot, 'usr/share/matplotlib/mpl-data/matplotlibrc'), MPLBACKEND: 'Agg', FREECAD_GUI_WORKFLOW_STATE: state, FREECAD_GUI_WORKFLOW_RESULT_FILE: resultFile, FREECAD_GUI_WORKFLOW_RECOVERY_FILE: recoveryFile, } const execution = spawnSync('xvfb-run', ['-a', executable, '--user-cfg', userConfig, '--system-cfg', systemConfig, '--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), probe], { cwd: root, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs, env: runtimeEnv, }) const output = `${execution.stdout ?? ''}\n${execution.stderr ?? ''}` const markerLine = output.split(/\r?\n/).find((line) => line.startsWith(marker)) const resultBytes = (() => { try { return readFileSync(resultFile, 'utf8') } catch { return '' } })() if (execution.error || (execution.status !== 0 && !resultBytes && !markerLine)) fail(`${state} native probe exited with ${execution.status}: ${[execution.error?.message, output.trim()].filter(Boolean).join('\n')}`) const result = JSON.parse(resultBytes || markerLine.slice(marker.length)) if (result.freecadVersion !== '1.1.1' || result.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail(`${state} native baseline mismatch: ${JSON.stringify(result)}`) if (result.workflowId !== 'partdesign-pad-task' || result.commandId !== 'PartDesign_Pad' || result.state !== state || result.success !== true) fail(`Pad ${state} workflow failed: ${JSON.stringify(result)}`) return result } const success = runProbe('success') const disabled = runProbe('disabled') const failure = runProbe('failure') const cancel = runProbe('cancel') const recovery = runProbe('recovery') const report = { schemaVersion: 1, baseline: { freecadVersion: success.freecadVersion, commit: success.gitCommit, profile: 'desktop-xvfb-isolated-config', }, generatedBy: './npmw run probe:freecad-gui-workflow', checkedBy: './npmw run check:freecad-gui-workflow', family: { id: success.workflowId, primaryCommand: success.commandId, exactTask: 'EX-UI-03', source: '.cache/freecad/FreeCAD/src/Mod/PartDesign/Gui/Command.cpp', }, workflows: { success, disabled, failure, cancel, recovery, }, remainingStates: [], exactPromotionReady: false, } await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`) console.log(JSON.stringify({ status: 'freecad-gui-workflow-generated', family: report.family.id, command: report.family.primaryCommand, capturedStates: Object.keys(report.workflows), bodyTip: success.after.bodyTip, volume: success.after.pad.volume, taskOpened: success.taskPanel.opened.activeDialog, taskClosed: !success.taskPanel.closed.activeDialog, disabledWithoutDocument: disabled.after.sideEffects.documentCount === 0, disabledWithoutTask: !disabled.after.sideEffects.taskPanel.activeDialog, disabledWithoutTransaction: !disabled.after.sideEffects.transaction.active, failureDiagnostic: failure.diagnostic.warnings[0].text, failureRolledBack: !failure.after.padExists && !failure.after.transaction.active, cancelPreviewVolume: cancel.taskPanel.preview.volume, cancelRolledBack: !cancel.after.padExists && !cancel.after.transaction.active, recoveryVolume: recovery.persistence.resavedReopened.padVolume, recoveryResaved: recovery.persistence.resavedReopened.padExists, report: 'config/freecad-gui-workflow-oracle.json', }, null, 2))