90 lines
2.8 KiB
JavaScript
90 lines
2.8 KiB
JavaScript
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.');
|
|
}
|