104 lines
6.0 KiB
JavaScript
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))
|