82 lines
4.9 KiB
JavaScript
82 lines
4.9 KiB
JavaScript
import { spawnSync } from 'node:child_process'
|
|
import { createHash } from 'node:crypto'
|
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const shardCount = Number(process.env.FREECAD_GUI_COMMAND_SHARD_COUNT || 100)
|
|
if (!Number.isInteger(shardCount) || shardCount < 2 || shardCount > 256) throw new Error('FREECAD_GUI_COMMAND_SHARD_COUNT must be an integer from 2 through 256.')
|
|
const mergeOnly = process.argv.includes('--merge-only')
|
|
const shardArgument = process.argv.find((argument) => argument.startsWith('--shard='))
|
|
const requestedShard = shardArgument ? Number(shardArgument.slice('--shard='.length)) : null
|
|
if (mergeOnly && shardArgument) throw new Error('--merge-only and --shard cannot be used together.')
|
|
if (shardArgument && (!Number.isInteger(requestedShard) || requestedShard < 0 || requestedShard >= shardCount)) throw new Error(`--shard must be an integer from 0 through ${shardCount - 1}.`)
|
|
|
|
if (!mergeOnly) {
|
|
const shardIndexes = requestedShard == null ? Array.from({ length: shardCount }, (_, index) => index) : [requestedShard]
|
|
for (const index of shardIndexes) {
|
|
console.log(`[freecad-gui-command-shards] execute ${index + 1}/${shardCount}`)
|
|
const execution = spawnSync(process.execPath, ['scripts/run-freecad-reference-probe.mjs'], {
|
|
cwd: root,
|
|
env: {
|
|
...process.env,
|
|
FREECAD_ORACLE_PROFILE: 'desktop',
|
|
FREECAD_REFERENCE_SCOPE: 'gui-commands',
|
|
FREECAD_GUI_COMMAND_SHARD: `${index}/${shardCount}`,
|
|
},
|
|
stdio: 'inherit',
|
|
})
|
|
if (execution.status !== 0) throw new Error(`FreeCAD GUI command shard ${index}/${shardCount} failed with exit ${execution.status}.`)
|
|
}
|
|
}
|
|
if (requestedShard != null) {
|
|
console.log(JSON.stringify({ status: 'freecad-gui-command-shard-pass', shard: requestedShard, shardCount }, null, 2))
|
|
process.exit(0)
|
|
}
|
|
|
|
const shardReports = []
|
|
for (let index = 0; index < shardCount; index += 1) {
|
|
const relativePath = `.cache/freecad/reference-desktop-gui-commands-${index}-of-${shardCount}.json`
|
|
const bytes = await readFile(resolve(root, relativePath))
|
|
shardReports.push({ relativePath, bytes, report: JSON.parse(bytes) })
|
|
}
|
|
|
|
const first = shardReports[0].report
|
|
const universeCount = first.guiCommands?.commandUniverseCount
|
|
const universeSha256 = first.guiCommands?.commandUniverseSha256
|
|
const commands = []
|
|
let expectedStart = 0
|
|
for (const [index, { report }] of shardReports.entries()) {
|
|
const gui = report.guiCommands
|
|
if (report.baselineId !== 'freecad-1.1.1' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.probeScope !== 'gui-commands') throw new Error(`GUI command shard ${index} has the wrong baseline.`)
|
|
if (gui?.shard?.index !== index || gui.shard.count !== shardCount || gui.shard.start !== expectedStart || gui.shard.end <= gui.shard.start) throw new Error(`GUI command shard ${index} has a non-contiguous range.`)
|
|
if (gui.commandUniverseCount !== universeCount || gui.commandUniverseSha256 !== universeSha256) throw new Error(`GUI command shard ${index} has a different command universe.`)
|
|
if (gui.stateCommandCount !== gui.commands?.length || gui.stateCommandCount !== gui.shard.end - gui.shard.start) throw new Error(`GUI command shard ${index} has an incomplete state range.`)
|
|
if (JSON.stringify(report.modules) !== JSON.stringify(first.modules) || JSON.stringify(gui.workbenches) !== JSON.stringify(first.guiCommands.workbenches)) throw new Error(`GUI command shard ${index} changed module or workbench registration evidence.`)
|
|
commands.push(...gui.commands)
|
|
expectedStart = gui.shard.end
|
|
}
|
|
if (expectedStart !== universeCount || commands.length !== universeCount || new Set(commands.map((command) => command.id)).size !== universeCount) throw new Error('Merged GUI command shards do not cover the command universe exactly once.')
|
|
const mergedUniverseSha256 = createHash('sha256').update(commands.map((command) => command.id).join('\n')).digest('hex')
|
|
if (mergedUniverseSha256 !== universeSha256) throw new Error('Merged GUI command order does not match the declared command universe hash.')
|
|
|
|
const merged = {
|
|
...first,
|
|
guiCommands: {
|
|
...first.guiCommands,
|
|
stateCommandCount: commands.length,
|
|
shard: null,
|
|
commands,
|
|
mergedShards: shardReports.map(({ relativePath, bytes, report }) => ({
|
|
path: relativePath,
|
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
start: report.guiCommands.shard.start,
|
|
end: report.guiCommands.shard.end,
|
|
})),
|
|
},
|
|
}
|
|
const output = resolve(root, '.cache/freecad/reference-desktop-gui-commands.json')
|
|
await writeFile(output, `${JSON.stringify(merged, null, 2)}\n`)
|
|
console.log(JSON.stringify({ status: 'freecad-gui-command-shards-merged', shardCount, commandCount: commands.length, commandUniverseSha256: universeSha256, output }, null, 2))
|