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))