import fs from 'node:fs' import crypto from 'node:crypto' const source='contracts/screens/kbx.screen-recipes.json' const scenarioSource='contracts/testing/kbx.test-scenarios.json' const data=JSON.parse(fs.readFileSync(source,'utf8')) const scenarios=JSON.parse(fs.readFileSync(scenarioSource,'utf8')) const scenarioById=new Map(scenarios.scenarios.map(scenario=>[scenario.id,scenario])) const sha=crypto.createHash('sha256').update(fs.readFileSync(source)).digest('hex') const scenarioSha=crypto.createHash('sha256').update(fs.readFileSync(scenarioSource)).digest('hex') const byCode=Object.fromEntries(data.recipes.map(recipe=>[recipe.code,recipe])) const verification=data.recipes.map(recipe=>{ const canonical=recipe.canonicalScenarioIds.flatMap(id=>scenarioById.has(id)?[scenarioById.get(id)]:[]) const kinds=[...new Set(canonical.map(item=>item.kind))].sort() const tags=[...new Set(canonical.flatMap(item=>item.tags??[]))].sort() const evidence=[...new Set(canonical.flatMap(item=>item.evidence??[]))].sort() const missingScenarioKinds=recipe.testProfile.requiredScenarioKinds.filter(item=>!kinds.includes(item)) const missingTags=recipe.testProfile.requiredTags.filter(item=>!tags.includes(item)) const missingEvidence=recipe.testProfile.requiredEvidence.filter(item=>!evidence.includes(item)) return { code:recipe.code, type:recipe.type, canonicalScenarioIds:recipe.canonicalScenarioIds, scenarioKinds:kinds, tags, evidence, requiredChecks:recipe.testProfile.requiredChecks, missingScenarioKinds, missingTags, missingEvidence, complete:!missingScenarioKinds.length&&!missingTags.length&&!missingEvidence.length, } }) fs.writeFileSync('generated/screen-recipe-manifest.json',JSON.stringify({schemaVersion:data.schemaVersion,contractVersion:data.contractVersion,sourceSha256:sha,recipes:data.recipes},null,2)+'\n') fs.writeFileSync('generated/screen-recipe-verification-manifest.json',JSON.stringify({schemaVersion:1,recipeContractVersion:data.contractVersion,scenarioContractVersion:scenarios.contractVersion,recipeSourceSha256:sha,scenarioSourceSha256:scenarioSha,recipes:verification},null,2)+'\n') const ts=`// generated from ${source}; do not edit.\n// SHA256: ${sha}\nimport type { KbxScreenRecipeDefinition } from '../screen'\n\nexport const kbxScreenRecipeContractVersion=${JSON.stringify(data.contractVersion)} as const\nexport const kbxScreenRecipeCatalog=${JSON.stringify(byCode,null,2)} as const satisfies Record\nexport const kbxScreenRecipeVerificationCatalog=${JSON.stringify(Object.fromEntries(verification.map(item=>[item.code,item])),null,2)} as const\n` fs.writeFileSync('packages/kbx-contracts/src/generated/screenRecipeCatalog.ts',ts) console.log(`screen recipe contracts generated: recipes=${data.recipes.length}, verification=${verification.filter(item=>item.complete).length}/${verification.length}`)