Files
Web_FreeCAD_Bitbybit/scripts/check-freecad-naming-sdk-readiness.mjs
wangdequan f64b78865c
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled
feat: complete production naming and reference lifecycle gates
2026-08-14 10:10:30 -04:00

104 lines
6.0 KiB
JavaScript

import { readFile } from 'node:fs/promises'
import { execFile } from 'node:child_process'
import { promisify } from 'node:util'
import { resolve } from 'node:path'
import {
REQUIRED_FREECAD_NAMING_CALLBACKS,
REQUIRED_FREECAD_NAMING_DEFINITIONS,
REQUIRED_FREECAD_NAMING_LIBRARIES,
exists,
inspectIncludeDirectory,
inspectPlannedLibrary,
inspectPlannedRuntimeAsset,
loadSdkPlan,
resolveFrom,
sha256File,
} from './freecad-naming-sdk-lib.mjs'
const root = resolve(new URL('..', import.meta.url).pathname)
const execFileAsync = promisify(execFile)
const plan = await loadSdkPlan(root)
const fail = (message) => { throw new Error(`FreeCAD WASM naming SDK readiness: ${message}`) }
if (plan.schemaVersion !== 1 || plan.scope !== 'pre-production-sdk-readiness') fail('unsupported plan schema or scope.')
if (plan.baseline?.freecadVersion !== '1.1.1' || plan.baseline?.sourceCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || plan.baseline?.emscriptenVersion !== '3.1.69' || plan.baseline?.target !== 'wasm32-emscripten') fail('baseline is not locked to the required FreeCAD/Emscripten wasm target.')
if (plan.productionPublication !== false) fail('candidate SDK readiness cannot publish a production Worker.')
if (plan.boundary?.freecadNamingBuildStatus !== 'production-linked' || plan.boundary?.exTsn02 !== 'completed' || plan.boundary?.systemExact !== false) fail('plan boundary must identify the completed production linkage while retaining non-exact system status.')
if (JSON.stringify(plan.libraries?.map(({ name }) => name)) !== JSON.stringify(REQUIRED_FREECAD_NAMING_LIBRARIES)) fail('library list or order does not match the production SDK contract.')
if (!REQUIRED_FREECAD_NAMING_CALLBACKS.every((callback) => plan.namingBridge?.requiredExports?.includes(callback))) fail('naming bridge plan does not require all production callbacks.')
if (plan.compileOptions?.cxxStandard !== 'c++20' || plan.compileOptions?.pthread !== true || JSON.stringify(plan.compileOptions?.definitions) !== JSON.stringify(REQUIRED_FREECAD_NAMING_DEFINITIONS)) fail('compile options must lock C++20, pthread and the FreeCAD wasm compatibility definitions.')
const libraries = []
for (const library of plan.libraries) libraries.push(await inspectPlannedLibrary(root, library))
const linkDependencies = []
for (const library of plan.linkDependencies ?? []) linkDependencies.push(await inspectPlannedLibrary(root, library))
const includeDirs = []
for (const includeDir of plan.includeDirs ?? []) includeDirs.push(await inspectIncludeDirectory(root, includeDir))
const runtimeAssets = []
for (const asset of plan.runtimeAssets ?? []) runtimeAssets.push(await inspectPlannedRuntimeAsset(root, asset))
const freeCadInclude = plan.includeDirs.find(({ name }) => name === 'FreeCAD')
let sourceCommit = null
if (freeCadInclude && await exists(resolveFrom(root, freeCadInclude.path))) {
try {
sourceCommit = (await execFileAsync('git', ['-C', resolveFrom(root, freeCadInclude.path), 'rev-parse', 'HEAD'])).stdout.trim()
} catch (error) {
fail(`cannot inspect FreeCAD source commit: ${error instanceof Error ? error.message : String(error)}`)
}
if (sourceCommit !== plan.baseline.sourceCommit) fail(`FreeCAD source commit mismatch: expected ${plan.baseline.sourceCommit}, got ${sourceCommit}.`)
}
const bridgePath = resolveFrom(root, plan.namingBridge.path)
const hostAdapterPath = resolveFrom(root, plan.namingBridge.hostAdapter)
const forceIncludePath = resolveFrom(root, plan.compileOptions.forceInclude)
const bridgePresent = await exists(bridgePath)
const hostAdapterPresent = await exists(hostAdapterPath)
const forceIncludePresent = await exists(forceIncludePath)
let bridgeExports = []
if (bridgePresent) {
const source = await readFile(bridgePath, 'utf8')
bridgeExports = REQUIRED_FREECAD_NAMING_CALLBACKS.filter((callback) => source.includes(callback))
if (bridgeExports.length !== REQUIRED_FREECAD_NAMING_CALLBACKS.length) fail('present naming bridge source omits required callback names.')
}
if (hostAdapterPresent) {
const source = await readFile(hostAdapterPath, 'utf8')
if (!source.includes('Module.preRun') || !source.includes('FREECAD_USER_HOME')) fail('host adapter does not initialize the FreeCAD virtual user environment.')
}
const missingLibraries = libraries.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const missingLinkDependencies = linkDependencies.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const missingIncludes = includeDirs.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const missingRuntimeAssets = runtimeAssets.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const blockers = [
...missingLibraries.map((name) => `${name} wasm static library`),
...missingLinkDependencies.map((name) => `${name} wasm link dependency`),
...missingIncludes.map((name) => `${name} headers`),
...missingRuntimeAssets.map((name) => `${name} runtime asset`),
...(!bridgePresent ? ['FreeCAD private naming Worker bridge'] : []),
...(!hostAdapterPresent ? ['FreeCAD naming host adapter'] : []),
...(!forceIncludePresent ? ['FreeCAD wasm force-include header'] : []),
]
console.log(JSON.stringify({
status: blockers.length === 0 ? 'sdk-readiness-complete' : 'sdk-readiness-incomplete',
availability: blockers.length === 0 ? 'candidate-complete' : 'unavailable',
baseline: plan.baseline,
libraries,
linkDependencies,
includeDirs,
runtimeAssets,
sourceCommit,
namingBridge: {
path: plan.namingBridge.path,
status: bridgePresent ? 'verified' : 'missing',
sha256: bridgePresent ? await sha256File(bridgePath) : null,
hostAdapter: plan.namingBridge.hostAdapter,
hostAdapterSha256: hostAdapterPresent ? await sha256File(hostAdapterPath) : null,
exports: bridgeExports,
},
compileOptions: {
...plan.compileOptions,
forceIncludeSha256: forceIncludePresent ? await sha256File(forceIncludePath) : null,
},
blockers,
readyLibraries: libraries.length - missingLibraries.length,
requiredLibraries: libraries.length,
publishToWorker: false,
boundary: plan.boundary,
}, null, 2))