Files
Rental-operations-platform/scripts/validate-tokens.mjs
T
2026-06-25 19:06:59 -04:00

51 lines
1.6 KiB
JavaScript

import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, walk } from './lib.mjs';
const sourceCss = await readFile(
path.join(repositoryRoot, 'contracts', 'phase9', 'tokens', 'semantic-tokens_v1.0.css'),
'utf8',
);
const implementationCssPath = path.join(repositoryRoot, 'src', 'styles', 'tokens.css');
const implementationCss = await readFile(implementationCssPath, 'utf8');
const designTokens = JSON.parse(
await readFile(
path.join(repositoryRoot, 'src', 'contracts', 'phase9', 'design-tokens.json'),
'utf8',
),
);
const errors = [];
if (sourceCss !== implementationCss)
errors.push('Implementation tokens.css differs from Phase 9 source.');
const rawColors = new Set();
for (const scale of Object.values(designTokens.raw.color)) {
for (const value of Object.values(scale)) rawColors.add(String(value).toLowerCase());
}
const applicationStyles = await walk(
path.join(repositoryRoot, 'src'),
(file) => file.endsWith('.css') && file !== implementationCssPath,
);
for (const file of applicationStyles) {
const text = (await readFile(file, 'utf8')).toLowerCase();
for (const color of rawColors) {
if (text.includes(color))
errors.push(`${path.relative(repositoryRoot, file)} embeds raw brand color ${color}.`);
}
}
for (const required of [
'--color-canvas',
'--color-text',
'--color-action-primary',
'--color-focus',
]) {
if (!implementationCss.includes(required)) errors.push(`Missing semantic token ${required}.`);
}
if (errors.length > 0) fail(errors);
else
pass(
'Phase 9 semantic tokens are unchanged and application styles contain no raw palette colors.',
);