import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { fail, pass, repositoryRoot, walk } from './lib.mjs'; const errors = []; const componentTokenPath = path.join(repositoryRoot, 'src', 'styles', 'component-tokens.css'); const componentTokens = await readFile(componentTokenPath, 'utf8'); const requiredTokens = [ '--surface-page', '--surface-primary', '--surface-elevated', '--surface-muted', '--text-primary', '--text-secondary', '--text-inverse', '--border-standard', '--border-strong', '--interactive-primary', '--interactive-conversion', '--interactive-danger', '--focus-ring', '--status-success-surface', '--status-warning-surface', '--status-error-surface', '--status-info-surface', '--overlay-scrim', '--control-disabled-surface', '--selection-surface', '--skeleton-base', '--shadow-card', '--container-wide', '--layer-overlay', '--type-display-size', ]; for (const token of requiredTokens) { if (!componentTokens.includes(token)) errors.push(`Missing Phase 11 component token ${token}.`); } if (!componentTokens.includes("html[data-theme='dark']")) { errors.push('Component token layer has no explicit dark-theme mapping.'); } if (!componentTokens.includes("html[data-theme-preference='system']")) { errors.push('Component token layer has no CSS-only system-theme fallback.'); } const cssFiles = await walk(path.join(repositoryRoot, 'src'), (file) => file.endsWith('.css')); const declarationFiles = new Set([ path.join(repositoryRoot, 'src', 'styles', 'tokens.css'), componentTokenPath, ]); const literalColor = /(?:#[0-9a-f]{3,8}\b|\brgba?\(|\bhsla?\()/gi; const rawPaletteReference = /var\(--color-(?:blue|orange|navy|gray|green|amber|red)-/gi; for (const file of cssFiles) { if (declarationFiles.has(file)) continue; const text = await readFile(file, 'utf8'); if (literalColor.test(text)) { errors.push( `${path.relative(repositoryRoot, file)} contains a literal color outside token declarations.`, ); } literalColor.lastIndex = 0; if (rawPaletteReference.test(text)) { errors.push(`${path.relative(repositoryRoot, file)} consumes a raw palette token.`); } rawPaletteReference.lastIndex = 0; } const shellFiles = [ 'src/components/app-shell/SiteHeader.tsx', 'src/components/app-shell/MobileNavigation.tsx', 'src/components/app-shell/SiteFooter.tsx', 'src/app/[locale]/loading.tsx', 'src/app/[locale]/error.tsx', 'src/app/[locale]/not-found.tsx', 'src/app/[locale]/[slug]/page.tsx', ]; for (const relativePath of shellFiles) { try { await readFile(path.join(repositoryRoot, relativePath), 'utf8'); } catch { errors.push(`Required Phase 11 shell file is missing: ${relativePath}`); } } if (errors.length) fail(errors); else { pass('Phase 11 component tokens, theme fallback, and application-shell foundations are present.'); }