import { access, readFile, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const sourceRoot = resolve(root, process.env.FREECAD_SOURCE_DIR || '.cache/freecad/FreeCAD') const outputPath = resolve(root, process.env.FREECAD_ENTRYPOINT_INVENTORY || 'config/freecad-entrypoint-inventory.json') const sourceInventory = JSON.parse(await readFile(resolve(root, 'config/freecad-source-inventory.json'), 'utf8')) const exists = async (relativePath) => access(resolve(sourceRoot, relativePath)).then(() => true).catch(() => false) const formats = [ ['FCStd', 'project', ['App', 'Gui'], ['src/App/Document.cpp', 'src/Gui/Document.cpp'], 'native-parser', 'read-only'], ['BREP', 'geometry', ['Part'], ['src/Mod/Part/App/FeaturePartImportBrep.cpp'], 'occt-worker', 'experimental'], ['STEP', 'geometry', ['Part', 'Import'], ['src/Mod/Part/App/FeaturePartImportStep.cpp', 'src/Mod/Import/App/WriterStep.cpp'], 'occt-worker', 'experimental'], ['IGES', 'geometry', ['Part', 'Import'], ['src/Mod/Part/App/FeaturePartImportIges.cpp', 'src/Mod/Import/App/WriterIges.cpp'], 'occt-worker', 'experimental'], ['STL', 'mesh', ['Mesh', 'MeshPart'], ['src/Mod/Mesh/App/Importer.cpp', 'src/Mod/Mesh/App/Exporter.cpp'], 'worker-adapter', 'experimental'], ['OBJ', 'mesh', ['Mesh'], ['src/Mod/Mesh/App/Importer.cpp', 'src/Mod/Mesh/App/Exporter.cpp'], 'worker-adapter', 'experimental'], ['PLY', 'mesh', ['Mesh'], ['src/Mod/Mesh/App/Importer.cpp', 'src/Mod/Mesh/App/Exporter.cpp'], 'worker-adapter', 'experimental'], ['DXF', '2d', ['Draft', 'Import'], ['src/Mod/Draft/importDXF.py', 'src/Mod/Import/App/dxf/ImpExpDxf.cpp'], 'worker-adapter', 'read-only'], ['SVG', 'drawing', ['Draft', 'TechDraw'], ['src/Mod/Draft/importSVG.py', 'src/Mod/TechDraw/App/TechDrawExport.cpp'], 'sanitized-parser', 'read-only'], ['PDF', 'drawing', ['TechDraw'], ['src/Mod/TechDraw/App/TechDrawExport.cpp'], 'server-export', 'read-only'], ['IFC', 'bim', ['BIM'], ['src/Mod/BIM/importers/importIFC.py'], 'sandboxed-adapter', 'unsupported'], ['CSV', 'data', ['Spreadsheet'], ['src/Mod/Spreadsheet/App/SheetPyImp.cpp'], 'facade-parser', 'experimental'], ['GCODE', 'manufacturing', ['CAM'], ['src/Mod/CAM/PathCommands.py'], 'sandboxed-adapter', 'unsupported'], ['JT', 'geometry', ['JtReader'], ['src/Mod/JtReader/CMakeLists.txt'], 'proxy-resource', 'unsupported'], ['IDF', 'electronics', ['Idf'], ['src/Mod/Idf/Idf.py'], 'proxy-resource', 'unsupported'], ] const entrypoints = [ ['python-api', 'scripting', ['App', 'Gui'], ['src/App/ApplicationPy.cpp', 'src/Gui/ApplicationPy.cpp'], 'sandboxed-interpreter', 'unsupported'], ['macro-fcmacro', 'scripting', ['Gui'], ['src/Gui/CommandMacro.cpp'], 'blocked-resource', 'unsupported'], ['addon-package', 'extension', ['AddonManager'], ['src/Mod/AddonManager'], 'signed-catalog', 'unsupported'], ['preferences', 'configuration', ['App', 'Gui'], ['src/Base/Parameter.cpp', 'src/Gui/PreferencePages'], 'namespaced-settings', 'experimental'], ['clipboard', 'desktop', ['Gui'], ['src/Gui/MainWindow.cpp'], 'browser-clipboard-permission', 'unsupported'], ['print-job', 'desktop', ['Gui', 'TechDraw'], ['src/Gui/MainWindow.cpp', 'src/Mod/TechDraw/Gui/PagePrinter.cpp'], 'server-export', 'unsupported'], ] const validateEvidence = async (items) => { for (const item of items) for (const evidence of item[3]) if (!(await exists(evidence))) throw new Error(`Missing source evidence: ${evidence}`) } await validateEvidence(formats) await validateEvidence(entrypoints) const formatRecords = formats.map(([id, category, modules, sourceEvidence, webStrategy, compatibility]) => ({ id, category, modules, sourceEvidence, webStrategy, compatibility, roundTrip: compatibility === 'experimental', status: 'inventory-recorded' })) const entrypointRecords = entrypoints.map(([id, category, modules, sourceEvidence, webStrategy, compatibility]) => ({ id, category, modules, sourceEvidence, webStrategy, compatibility, execution: false, status: 'inventory-recorded' })) const coveredModules = new Set([...formatRecords, ...entrypointRecords].flatMap((entry) => entry.modules)) const moduleCoverage = sourceInventory.modules.map((module) => ({ name: module.name, sourcePath: module.sourcePath, coveredBy: [...formatRecords, ...entrypointRecords].filter((entry) => entry.modules.includes(module.name)).map((entry) => entry.id), status: coveredModules.has(module.name) ? 'entrypoint-covered' : 'scope-only' })) const inventory = { schemaVersion: 1, baseline: { freecadVersion: '1.1.1', commit: '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' }, generatedBy: 'scripts/generate-freecad-entrypoint-inventory.mjs', sourceInventory: 'config/freecad-source-inventory.json', formats: formatRecords, entrypoints: entrypointRecords, moduleCoverage, policy: { unsupportedExecution: ['Python API', 'FCMacro', 'addon package', 'clipboard', 'print job'], unknownOrUnlistedEntries: 'fail-inventory-check', untrustedContent: 'never-execute', }, } await writeFile(outputPath, `${JSON.stringify(inventory, null, 2)}\n`, 'utf8') console.log(JSON.stringify({ status: 'entrypoint-inventory-generated', output: outputPath, formatCount: formatRecords.length, entrypointCount: entrypointRecords.length, moduleCount: moduleCoverage.length }, null, 2))