feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
74
scripts/generate-freecad-source-inventory.mjs
Normal file
74
scripts/generate-freecad-source-inventory.mjs
Normal file
@@ -0,0 +1,74 @@
|
||||
import { readFile, readdir, writeFile, access } from 'node:fs/promises'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const sourceRoot = resolve(process.env.FREECAD_SOURCE_DIR || join(root, '.cache/freecad/FreeCAD'))
|
||||
const outputPath = resolve(process.env.FREECAD_INVENTORY_FILE || join(root, 'config/freecad-source-inventory.json'))
|
||||
const baselineCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d'
|
||||
const cmakeAliases = {
|
||||
AddonManager: 'ADDONMGR',
|
||||
JtReader: 'JTREADER',
|
||||
MeshPart: 'MESH_PART',
|
||||
PartDesign: 'PART_DESIGN',
|
||||
TechDraw: 'TECHDRAW',
|
||||
ReverseEngineering: 'REVERSEENGINEERING',
|
||||
OpenSCAD: 'OPENSCAD',
|
||||
}
|
||||
const read = async (path) => readFile(path, 'utf8')
|
||||
const exists = async (path) => access(path).then(() => true).catch(() => false)
|
||||
const moduleRoot = join(sourceRoot, 'src/Mod')
|
||||
if (!(await exists(moduleRoot))) throw new Error(`FreeCAD source directory is unavailable: ${moduleRoot}`)
|
||||
|
||||
const buildOptions = await read(join(sourceRoot, 'cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake'))
|
||||
const optionDefaults = new Map([...buildOptions.matchAll(/option\(BUILD_([A-Z0-9_]+)[^\n]*?\s(ON|OFF)\)/g)].map((match) => [match[1], match[2] === 'ON']))
|
||||
const moduleNames = (await readdir(moduleRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort()
|
||||
const commandPattern = /(?:addCommand|addCommand\s*\(|registerCommand)\s*\(\s*["']([^"']+)["']/g
|
||||
const commandIds = (text) => [...text.matchAll(commandPattern)].map((match) => match[1]).filter((value, index, values) => values.indexOf(value) === index).sort()
|
||||
|
||||
const modules = []
|
||||
for (const name of moduleNames) {
|
||||
const modulePath = join(moduleRoot, name)
|
||||
const option = cmakeAliases[name] || name.toUpperCase()
|
||||
const cmakePath = join(modulePath, 'CMakeLists.txt')
|
||||
const appPath = join(modulePath, 'App')
|
||||
const guiPath = join(modulePath, 'Gui')
|
||||
const cmake = await exists(cmakePath) ? await read(cmakePath) : ''
|
||||
const pythonFiles = []
|
||||
const collectPython = async (directory) => {
|
||||
if (!(await exists(directory))) return
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
const path = join(directory, entry.name)
|
||||
if (entry.isDirectory()) await collectPython(path)
|
||||
else if (entry.name.endsWith('.py')) pythonFiles.push(path)
|
||||
}
|
||||
}
|
||||
await collectPython(modulePath)
|
||||
const commandNames = []
|
||||
for (const path of pythonFiles) {
|
||||
const text = await read(path)
|
||||
commandNames.push(...commandIds(text))
|
||||
}
|
||||
modules.push({
|
||||
name,
|
||||
sourcePath: `src/Mod/${name}`,
|
||||
cmakeOption: `BUILD_${option}`,
|
||||
cmakeDefault: optionDefaults.has(option) ? optionDefaults.get(option) : null,
|
||||
hasCMake: Boolean(cmake),
|
||||
hasApp: await exists(appPath),
|
||||
hasGui: await exists(guiPath),
|
||||
pythonFileCount: pythonFiles.length,
|
||||
commandIds: [...new Set(commandNames)].sort(),
|
||||
runtimeStatus: 'requires-freecad-reference-probe',
|
||||
})
|
||||
}
|
||||
|
||||
const inventory = {
|
||||
schemaVersion: 1,
|
||||
baseline: { freecadVersion: '1.1.1', commit: baselineCommit, sourcePath: '.cache/freecad/FreeCAD' },
|
||||
generatedBy: 'scripts/generate-freecad-source-inventory.mjs',
|
||||
moduleCount: modules.length,
|
||||
modules,
|
||||
}
|
||||
if (modules.length !== 34) throw new Error(`Expected 34 FreeCAD modules, found ${modules.length}.`)
|
||||
await writeFile(outputPath, `${JSON.stringify(inventory, null, 2)}\n`, 'utf8')
|
||||
console.log(JSON.stringify({ status: 'source-inventory-generated', output: outputPath, moduleCount: modules.length, commandCount: modules.reduce((total, module) => total + module.commandIds.length, 0) }, null, 2))
|
||||
Reference in New Issue
Block a user