56 lines
4.6 KiB
JavaScript
56 lines
4.6 KiB
JavaScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
const root=process.cwd();const failures=[]
|
|
const read=p=>fs.readFileSync(path.join(root,p),'utf8')
|
|
const json=p=>JSON.parse(read(p))
|
|
const must=(text,needles,scope)=>needles.forEach(needle=>{if(!text.includes(needle))failures.push(`${scope} missing ${needle}`)})
|
|
|
|
const recipes=json('contracts/screens/kbx.screen-recipes.json')
|
|
if(recipes.recipes.length!==9)failures.push(`screen recipes expected 9, found ${recipes.recipes.length}`)
|
|
const expected={T01:'list',T02:'master',T03:'transaction',T04:'fast-entry',T05:'master-detail',T06:'queue',T07:'reconcile',T08:'import',T09:'wms-mobile'}
|
|
const scenarioIds=new Set(json('contracts/testing/kbx.test-scenarios.json').scenarios.map(item=>item.id))
|
|
for(const [code,type] of Object.entries(expected)){
|
|
const recipe=recipes.recipes.find(item=>item.code===code)
|
|
if(!recipe)continue
|
|
if(recipe.type!==type)failures.push(`${code} recipe type must be ${type}`)
|
|
for(const key of ['templateComponent','defaultCommands','requiredPolicies','recoveryPolicies','securityPolicies','canonicalScenarioIds','scaffoldSurfaces'])if(!recipe[key]||!recipe[key].length&&key!=='defaultCommands')failures.push(`${code} recipe missing ${key}`)
|
|
for(const scenarioId of recipe.canonicalScenarioIds)if(!scenarioIds.has(scenarioId))failures.push(`${code} recipe references unknown scenario ${scenarioId}`)
|
|
}
|
|
|
|
const screenContract=read('packages/kbx-contracts/src/screen.ts')
|
|
must(screenContract,['KbxScreenTemplateCode','KbxScreenRecipeDefinition','templateCode: KbxScreenTemplateCode'],'screen recipe/type contract')
|
|
const generator=read('scripts/generate-screen-recipe-contracts.mjs')
|
|
must(generator,['generated/screen-recipe-manifest.json','screenRecipeCatalog.ts'],'screen recipe generator')
|
|
const generated=json('generated/screen-recipe-manifest.json')
|
|
if(generated.recipes.length!==9)failures.push('generated screen recipe manifest must contain 9 recipes')
|
|
|
|
const screenManifest=json('generated/screen-manifest.json')
|
|
for(const screen of screenManifest){
|
|
const recipe=recipes.recipes.find(item=>item.code===screen.templateCode)
|
|
if(!recipe)failures.push(`${screen.id} missing/unknown templateCode ${screen.templateCode}`)
|
|
else if(recipe.type!==screen.type)failures.push(`${screen.id} ${screen.templateCode}/${screen.type} mismatch`)
|
|
}
|
|
|
|
const frame=read('packages/kbx-ui/src/components/KbxScreenFrame.vue')
|
|
must(frame,['KbxScreenTemplateCode','props.screen.templateCode!==props.templateCode'],'runtime templateCode guard')
|
|
const wms=read('packages/kbx-ui/src/wms/KbxWmsMobilePage.vue')
|
|
must(wms,["props.screen.templateCode!=='T09'"],'T09 runtime recipe guard')
|
|
|
|
const scaffolder=read('scripts/create-kbx-screen.mjs')
|
|
must(scaffolder,["contracts/screens/kbx.screen-recipes.json",'--write-permission is required','permissionKind===\'write\'',"templateCode: '${recipe.code}'",'canonical scenarios:'],'recipe-driven secure scaffolder')
|
|
if(scaffolder.includes("const commandMap ="))failures.push('scaffolder must not keep a second hard-coded commandMap beside recipe contract')
|
|
|
|
const navigation=read('packages/kbx-contracts/src/navigation.ts')
|
|
must(navigation,['KbxHomeAttentionSource','KbxHomeAttentionItem','operationId?: string','notificationId?: string'],'Home attention contract')
|
|
const homeNav=read('packages/kbx-ui/src/shell/homeNavigation.ts')
|
|
must(homeNav,['buildKbxHomeAttention','operation-failed','notification-urgent','operation-running','allowedIds.has'],'Home attention orchestration')
|
|
const home=read('packages/kbx-ui/src/shell/KbxHomePage.vue')
|
|
must(home,['buildKbxHomeAttention',"@click=\"emit('attention',item)\""],'Home attention UI')
|
|
if(home.includes(':operations="homeOperations"'))failures.push('HomePage must receive operations as props, not reference AppFrame state directly')
|
|
must(home,['operations?:KbxOperationRun[]','notifications?:KbxUserNotification[]','kbx-home__attention-list','attention:[item:KbxHomeAttentionItem]'],'Home actionable workbench')
|
|
const app=read('apps/web/src/shell/KbxAppFrame.vue')
|
|
must(app,['homeOperations=computed','homeNotifications=computed','openHomeAttention','safeWorkspacePath(item.screenId,candidate)','알림에 포함된 허용되지 않은 이동 경로를 차단했습니다.',':operations="homeOperations"',':notifications="homeNotifications"','@attention="openHomeAttention"'],'Home runtime security orchestration')
|
|
|
|
if(failures.length){console.error(failures.join('\n'));process.exit(1)}
|
|
console.log(`v35 recipe/navigation validation passed: recipes=${recipes.recipes.length}, screens=${screenManifest.length}; explicit templateCode, canonical scenario closure, secure write scaffolding, and actionable Home attention routing are enforced.`)
|