import { readFile, stat } from 'node:fs/promises'; import path from 'node:path'; import { fail, pass, repositoryRoot, walk } from './lib.mjs'; const inventoryPath = path.join(repositoryRoot, 'docs/phase12/COMPONENT_INVENTORY_v1.0.json'); const inventory = JSON.parse(await readFile(inventoryPath, 'utf8')); const errors = []; const allowedStatus = new Set(['implemented', 'partially-implemented', 'blocked', 'excluded']); const names = new Set(); if (inventory.phase !== 12 || !Array.isArray(inventory.entries)) { errors.push('Component inventory must identify Phase 12 and contain an entries array.'); } for (const entry of inventory.entries ?? []) { if (names.has(entry.name)) errors.push(`Duplicate inventory component: ${entry.name}`); names.add(entry.name); if (!allowedStatus.has(entry.status)) errors.push(`${entry.name}: invalid status ${entry.status}`); for (const field of ['sourceSpecification', 'variants', 'openIssues', 'homepageConsumers']) { if (!Array.isArray(entry[field])) errors.push(`${entry.name}: ${field} must be an array.`); } if ( !entry.tests || !['unit', 'interaction', 'accessibility', 'visual'].every( (key) => typeof entry.tests[key] === 'boolean', ) ) { errors.push(`${entry.name}: test coverage flags are incomplete.`); } if (entry.status === 'blocked' && entry.openIssues.length === 0) { errors.push(`${entry.name}: blocked components require an issue reference.`); } } for (const required of [ 'Button', 'ActionLink', 'FormField', 'Tabs', 'Accordion', 'Dialog', 'Alert', 'Card', 'Workflow', 'Comparison', 'Metric', 'CTA', 'ProductPreview', ]) { if (!names.has(required)) errors.push(`Required component missing from inventory: ${required}`); } const componentFiles = await walk(path.join(repositoryRoot, 'src/components'), (file) => /\.(?:ts|tsx|css)$/.test(file), ); const forbiddenRawToken = /var\(--color-(?:blue|orange|gray|green|amber|red|navy)-/g; const forbiddenLiteralColor = /(?:#[0-9a-f]{3,8}\b|rgba?\(|hsla?\()/gi; for (const file of componentFiles) { const text = await readFile(file, 'utf8'); const relative = path.relative(repositoryRoot, file); if (file.endsWith('.css') && forbiddenRawToken.test(text)) errors.push(`${relative}: consumes a raw palette token.`); if (file.endsWith('.css') && forbiddenLiteralColor.test(text)) errors.push(`${relative}: contains a literal color.`); if (/href=["']#["']/.test(text)) errors.push(`${relative}: contains a placeholder href="#".`); } for (const artifact of [ 'COMPONENT_SPECIFICATION_TRACEABILITY_v1.0.csv', 'VARIANT_INVENTORY_v1.0.csv', 'ACCESSIBILITY_BEHAVIOR_MATRIX_v1.0.csv', 'RTL_BEHAVIOR_MATRIX_UPDATE_v1.0.csv', 'RESPONSIVE_VALIDATION_MATRIX_v1.0.csv', 'STORY_AND_FIXTURE_INVENTORY_v1.0.csv', 'TEST_COVERAGE_REPORT_v1.0.csv', 'COMPONENT_CATALOG_v1.0.md', ]) { try { await stat(path.join(repositoryRoot, 'docs/phase12', artifact)); } catch { errors.push(`Required Phase 12 artifact missing: ${artifact}`); } } const fixturePage = await readFile( path.join(repositoryRoot, 'src/app/[locale]/component-lab/page.tsx'), 'utf8', ); if (!fixturePage.includes("COMPONENT_FIXTURES_ENABLED !== 'true'")) { errors.push('Component fixture route is not protected by the production environment guard.'); } if (errors.length > 0) fail(errors); else pass( `${inventory.entries.length} inventory entries and ${componentFiles.length} component source files satisfy Phase 12 static controls.`, );