import { mkdir, writeFile } from 'node:fs/promises' import { resolve, relative } from 'node:path' import { REQUIRED_FREECAD_NAMING_CALLBACKS, REQUIRED_FREECAD_NAMING_DEFINITIONS, inspectIncludeDirectory, inspectPlannedLibrary, inspectPlannedRuntimeAsset, loadSdkPlan, resolveFrom, exists, sha256File, } from './freecad-naming-sdk-lib.mjs' const root = resolve(new URL('..', import.meta.url).pathname) const outputRoot = process.env.FREECAD_WASM_SDK_OUTPUT_DIR ? resolve(root, process.env.FREECAD_WASM_SDK_OUTPUT_DIR) : resolve(root, '.cache/toolchains/freecad-naming-sdk') const plan = await loadSdkPlan(root) const fail = (message) => { throw new Error(`FreeCAD WASM SDK manifest generation: ${message}`) } const libraries = [] for (const library of [...(plan.libraries ?? []), ...(plan.linkDependencies ?? [])]) { const inspected = await inspectPlannedLibrary(root, library) if (inspected.status !== 'verified') fail(`missing ${library.name} wasm static library: ${resolveFrom(root, library.path)}`) libraries.push({ name: library.name, path: relative(outputRoot, resolveFrom(root, library.path)), sha256: inspected.sha256, }) } const includeDirs = [] for (const includeDir of plan.includeDirs ?? []) { const inspected = await inspectIncludeDirectory(root, includeDir) if (inspected.status !== 'verified') fail(`include directory ${includeDir.name} is incomplete: ${inspected.missingHeaders.join(', ')}`) includeDirs.push(relative(outputRoot, resolveFrom(root, includeDir.path))) } const bridge = resolveFrom(root, plan.namingBridge.path) if (!await exists(bridge)) fail(`missing naming bridge source: ${bridge}`) const hostAdapter = resolveFrom(root, plan.namingBridge.hostAdapter) if (!await exists(hostAdapter)) fail(`missing naming host adapter: ${hostAdapter}`) const forceInclude = resolveFrom(root, plan.compileOptions.forceInclude) if (!await exists(forceInclude)) fail(`missing force-include header: ${forceInclude}`) if (plan.compileOptions.cxxStandard !== 'c++20' || plan.compileOptions.pthread !== true || JSON.stringify(plan.compileOptions.definitions) !== JSON.stringify(REQUIRED_FREECAD_NAMING_DEFINITIONS)) fail('compile options do not match the locked SDK contract') const bridgeSha256 = await sha256File(bridge) const runtimeAssets = [] for (const asset of plan.runtimeAssets ?? []) { const inspected = await inspectPlannedRuntimeAsset(root, asset) if (inspected.status !== 'verified') fail(`runtime asset ${asset.name} is incomplete: ${inspected.missingFiles.join(', ')}`) runtimeAssets.push({ name: asset.name, path: relative(outputRoot, resolveFrom(root, asset.path)), preloadTo: asset.preloadTo, files: inspected.files, }) } const manifest = { schemaVersion: 1, freecadVersion: plan.baseline.freecadVersion, sourceCommit: plan.baseline.sourceCommit, emscriptenVersion: plan.baseline.emscriptenVersion, qtTarget: plan.baseline.target, pythonTarget: plan.baseline.target, includeDirs, libraries, namingBridge: { source: relative(outputRoot, bridge), sha256: bridgeSha256, hostAdapter: relative(outputRoot, hostAdapter), hostAdapterSha256: await sha256File(hostAdapter), exports: [...REQUIRED_FREECAD_NAMING_CALLBACKS], }, compileOptions: { cxxStandard: plan.compileOptions.cxxStandard, pthread: plan.compileOptions.pthread, forceInclude: relative(outputRoot, forceInclude), forceIncludeSha256: await sha256File(forceInclude), definitions: [...plan.compileOptions.definitions], }, runtimeAssets, productionPublication: false, boundary: { ...plan.boundary }, } await mkdir(outputRoot, { recursive: true }) const output = resolve(outputRoot, 'manifest.json') await writeFile(output, `${JSON.stringify(manifest, null, 2)}\n`) console.log(JSON.stringify({ status: 'freecad-naming-sdk-manifest-generated', output, productionPublished: false, systemExact: false, }, null, 2))