import { readFile } from 'node:fs/promises' import { createHash } from 'node:crypto' import { execFile } from 'node:child_process' import { fileURLToPath } from 'node:url' import { promisify } from 'node:util' import { resolve } from 'node:path' const root = resolve(fileURLToPath(new URL('..', import.meta.url))) const run = promisify(execFile) const packageJson = JSON.parse(await readFile(resolve(root, 'node_modules/@bitbybit-dev/occt/package.json'), 'utf8')) const matrix = JSON.parse(await readFile(resolve(root, 'config/compatibility-matrix.json'), 'utf8')) const sourcePath = resolve(root, matrix.bitbybitSource.localSnapshot) const { stdout: sourceCommitOutput } = await run('git', ['-C', sourcePath, 'rev-parse', 'HEAD']) const sourceCommit = sourceCommitOutput.trim() if (sourceCommit !== matrix.bitbybitSource.commit) throw new Error(`Bitbybit source commit mismatch: expected ${matrix.bitbybitSource.commit}, got ${sourceCommit}.`) if (packageJson.version !== matrix.bitbybitSource.version) throw new Error(`Bitbybit package version mismatch: expected ${matrix.bitbybitSource.version}, got ${packageJson.version}.`) const sourceCppFiles = (await run('git', ['-C', sourcePath, 'ls-tree', '-r', '--name-only', 'HEAD'])).stdout .split('\n').filter((file) => /\.(c|cc|cpp|cxx|h|hh|hpp)$/.test(file)) if (sourceCppFiles.length !== 0) throw new Error(`Bitbybit source snapshot unexpectedly contains native source files (${sourceCppFiles.length}); review the custom binding build inputs.`) const declarationPath = resolve(root, 'node_modules/@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts') const declarations = await readFile(declarationPath, 'utf8') const wasmArtifactPath = resolve(root, matrix.bitbybitSource.wasmArtifact.path) const wasmArtifactHash = createHash('sha256').update(await readFile(wasmArtifactPath)).digest('hex') if (wasmArtifactHash !== matrix.bitbybitSource.wasmArtifact.sha256) throw new Error(`Bitbybit OCCT WASM hash mismatch: expected ${matrix.bitbybitSource.wasmArtifact.sha256}, got ${wasmArtifactHash}.`) const interfaceBody = (name) => { const match = declarations.match(new RegExp(`export interface ${name} extends ClassHandle \\{([\\s\\S]*?)\\n\\}`)) if (!match) throw new Error(`Bitbybit declaration is missing interface ${name}.`) return match[1] } const hasMethod = (body, name) => new RegExp(`\\n\\s*${name}\\(`).test(body) const assert = (condition, message) => { if (!condition) throw new Error(message) } const booleanInterfaces = ['BRepAlgoAPI_Fuse', 'BRepAlgoAPI_Cut', 'BRepAlgoAPI_Common'] const booleanHistorySurface = Object.fromEntries(booleanInterfaces.map((name) => { const body = interfaceBody(name) return [name, { generated: hasMethod(body, 'Generated'), modified: hasMethod(body, 'Modified'), deleted: hasMethod(body, 'IsDeleted'), hasGenerated: hasMethod(body, 'HasGenerated') }] })) const builderBody = interfaceBody('BOPAlgo_Builder') const listBody = interfaceBody('TopTools_ListOfShape') for (const [name, surface] of Object.entries(booleanHistorySurface)) { assert(!surface.generated && !surface.modified && !surface.deleted, `${name} unexpectedly exposes native Generated/Modified/IsDeleted; review the adapter contract.`) assert(surface.hasGenerated, `${name} must retain HasGenerated for capability reporting.`) } assert(hasMethod(builderBody, 'Modified'), 'BOPAlgo_Builder.Modified(shape) is required for the partial history surface.') for (const method of ['Size', 'IsEmpty', 'Append', 'First']) { assert(hasMethod(listBody, method), `TopTools_ListOfShape.${method}() is required for the partial history surface.`) } console.log(JSON.stringify({ provider: 'bitbybit-occt', version: packageJson.version, sourceCommit, sourceForm: matrix.bitbybitSource.sourceForm, wasmArtifact: { status: matrix.bitbybitSource.wasmArtifact.status, path: matrix.bitbybitSource.wasmArtifact.path, sha256: wasmArtifactHash }, missingBuildInputs: matrix.bitbybitSource.missingBuildInputs, declarationPath: 'node_modules/@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts', nativeBooleanHistory: { generated: false, modified: false, deleted: false }, partialBuilderHistory: { modified: true, listIndexAccess: false }, nativeExtension: await (async () => { const result = { status: 'not-built' } for (const [key, relativePath, expectedHash] of [ ['javascript', matrix.nativeOcctHistory.artifact.javascript, matrix.nativeOcctHistory.artifact.javascriptSha256], ['wasm', matrix.nativeOcctHistory.artifact.wasm, matrix.nativeOcctHistory.artifact.wasmSha256] ]) { try { const bytes = await readFile(resolve(root, relativePath)) const hash = createHash('sha256').update(bytes).digest('hex') if (hash !== expectedHash) throw new Error(`${key} hash mismatch: expected ${expectedHash}, got ${hash}.`) result[key] = 'hash-ok' } catch (error) { if (error?.code !== 'ENOENT') throw error } } if (result.javascript === 'hash-ok' && result.wasm === 'hash-ok') result.status = 'built-hash-ok' return result })(), status: 'audit-pass' }, null, 2))