recipe-manager/frontend/scripts/style-guardrails.mjs

77 lines
2.3 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
const projectRoot = process.cwd();
const scopedFiles = [
'src/App.tsx',
'src/components/MissionControlPanel.tsx',
'src/components/ui/primitives.tsx',
'src/components/ErrorBoundary.tsx',
'src/pages/NotFoundPage.tsx',
'src/pages/RecipeListPage.tsx',
'src/components/RecipeCard.tsx',
'src/components/Toast.tsx',
].map((file) => path.resolve(projectRoot, file));
const bannedPatterns = [
{
name: 'raw-tailwind-palette',
regex:
/\b(?:bg|text|border|ring|from|to|via|outline|decoration)-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}\b/g,
message:
'Use tokenized classes/variables instead of direct Tailwind palette utilities (e.g. bg-slate-200, text-blue-600).',
},
{
name: 'hex-in-arbitrary-class',
regex: /\b(?:bg|text|border|ring|from|to|via)-\[#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\]\b/g,
message:
'Avoid hardcoded hex colors in className. Add/consume a semantic token in tokens.css instead.',
},
];
function readFile(filePath) {
return fs.readFileSync(filePath, 'utf8');
}
function lineNumberAt(text, index) {
return text.slice(0, index).split('\n').length;
}
const findings = [];
for (const filePath of scopedFiles) {
if (!fs.existsSync(filePath)) continue;
const content = readFile(filePath);
for (const rule of bannedPatterns) {
for (const match of content.matchAll(rule.regex)) {
findings.push({
filePath,
line: lineNumberAt(content, match.index ?? 0),
value: match[0],
rule: rule.name,
message: rule.message,
});
}
}
}
if (findings.length === 0) {
console.log('✅ style-guardrails: no blocked raw palette patterns found in scoped files.');
process.exit(0);
}
console.error('❌ style-guardrails: found styling drift patterns in guarded files:\n');
for (const finding of findings) {
const relativePath = path.relative(projectRoot, finding.filePath);
console.error(`- ${relativePath}:${finding.line} [${finding.rule}] ${finding.value}`);
console.error(` ${finding.message}`);
}
console.error('\nSee .harness/docs/styling-governance.md for the contract and escalation path.');
process.exit(1);