Files
KArtSell.Aegis/docs/Design/kbx-foundation-v36/scripts/validate-template-navigation-v30.mjs
T

68 lines
5.1 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 must=(text,needles,scope)=>needles.forEach(n=>{if(!text.includes(n))failures.push(`${scope} missing ${n}`)})
const manifest=read('packages/kbx-ui/src/registry/templateManifest.ts')
for(const code of ['T01','T02','T03','T04','T05','T06','T07','T08','T09']){
if(!manifest.includes(`code:'${code}'`)) failures.push(`template manifest missing ${code}`)
}
for(const key of ['coreComponents','completionChecks']){
if((manifest.match(new RegExp(key,'g'))??[]).length<9) failures.push(`all T01-T09 must declare ${key}`)
}
must(manifest,['KbxFormGrid','KbxRecordLifecycle','KbxDataGrid','KbxJobProgress','KbxBarcodeCapture'],'template manifest core coverage')
const formSection=read('packages/kbx-ui/src/components/KbxFormSection.vue')
must(formSection,['--kbx-form-section-gap','--kbx-form-section-heading-gap','<slot name="actions"'],'KbxFormSection')
const formGrid=read('packages/kbx-ui/src/components/KbxFormGrid.vue')
must(formGrid,["columns?: 1 | 2",'--kbx-form-grid-column-gap','grid-template-columns:repeat(2,minmax(0,1fr))'],'KbxFormGrid')
const formSpan=read('packages/kbx-ui/src/components/KbxFormSpan.vue')
must(formSpan,["span?: 'cell' | 'full'",'grid-column:1/-1'],'KbxFormSpan')
for(const file of [
'apps/web/src/modules/erp/items/ItemMasterPage.vue',
'apps/web/src/modules/oms/orders/register/OrderRegisterPage.vue',
'apps/web/src/modules/erp/purchases/PurchasePage.vue',
'apps/web/src/modules/erp/inventory-move/InventoryMovePage.vue',
]) if(!read(file).includes('KbxFormGrid')) failures.push(`${file} must use KbxFormGrid`)
const scaffold=read('scripts/create-kbx-screen.mjs')
for(const pair of [
["'list'",'KbxListPage'],["'master'",'KbxMasterPage'],["'transaction'",'KbxTransactionPage'],
["'fast-entry'",'KbxFastEntryPage'],["'master-detail'",'KbxMasterDetailPage'],["'queue'",'KbxQueuePage'],
["'reconcile'",'KbxReconcilePage'],["'import'",'KbxImportPage'],["'wms-mobile'",'KbxWmsMobilePage'],
]) if(!(scaffold.includes(pair[0])&&scaffold.includes(pair[1]))) failures.push(`scaffolder canonical mapping missing ${pair.join(' -> ')}`)
const scaffoldTest=read('scripts/validate-scaffolder.mjs')
must(scaffoldTest,['fast-entry','KbxFastEntryPage','queue','KbxQueuePage','wms-mobile','KbxWmsMobilePage'],'scaffolder validator')
const homeLogic=read('packages/kbx-ui/src/shell/homeNavigation.ts')
must(homeLogic,['buildKbxHomeWorkbench','sourceLabel','dirty','favorite','recent','homePriority','seen'],'Home workbench policy')
const home=read('packages/kbx-ui/src/shell/KbxHomePage.vue')
must(home,['바로 시작','buildKbxHomeWorkbench','미저장 업무','모듈별 업무','sourceLabel'],'Home IA')
if(home.includes('kbx-home__personal')) failures.push('Home must not restore duplicate legacy personal navigation blocks')
const security=read('packages/kbx-ui/src/shell/navigationSecurity.ts')
must(security,['matchKbxRoutePattern','resolveKbxSafeWorkspacePath','candidate.origin!==base.origin',"segment.startsWith(':')"],'navigation capability security')
const routes=read('apps/web/src/router/appRoutes.ts')
must(routes,['screenRoutePatterns','/:pathMatch(.*)*','NotFoundRoutePage.vue'],'router recovery')
const app=read('apps/web/src/shell/KbxAppFrame.vue')
if(app.includes(':screen-title=')||app.includes(':screen-id='))failures.push('AppFrame must not reveal denied screen metadata by default')
must(app,['safeWorkspacePath','screenRoutePatterns','reconcilePreference','allowedEntries.value.find(item=>item.screenId===requested.screenId)','frameEmit(\'profile\')','권한 변경으로'],'AppFrame security/recovery')
const access=read('packages/kbx-ui/src/shell/KbxAccessDenied.vue')
must(access,['menuSearch','홈으로 이동','다른 업무 찾기','revealDetails?:boolean','revealDetails:false'],'AccessDenied recovery')
const notFound=read('packages/kbx-ui/src/shell/KbxRouteNotFound.vue')
must(notFound,['menuSearch','홈으로 이동','화면을 찾을 수 없습니다'],'RouteNotFound recovery')
const header=read('packages/kbx-ui/src/shell/KbxGlobalHeader.vue')
must(header,['modules?: string[]','moduleSelect','<select',"emit('profile')"],'GlobalHeader')
const shell=read('packages/kbx-ui/src/shell/KbxApplicationShell.vue')
must(shell,['moduleOptions','focusedModule','@module-select="selectModule"','preferred-module="focusedModule"',"@profile=\"emit('profile')\""],'ApplicationShell module/profile contract')
const side=read('packages/kbx-ui/src/shell/KbxSideNavigation.vue')
must(side,['preferredModule','moduleSelected'],'SideNavigation module synchronization')
const publicApi=read('packages/kbx-ui/src/index.ts')
for(const name of ['KbxFormGrid','KbxFormSpan','KbxRouteNotFound','buildKbxHomeWorkbench','resolveKbxSafeWorkspacePath']) if(!publicApi.includes(name)) failures.push(`public API missing ${name}`)
if(failures.length){console.error(failures.join('\n'));process.exit(1)}
console.log('v30 template/component/home navigation validation passed: form composition, nine-type scaffolding, workbench IA, route capability recovery, module synchronization, and profile propagation.')