77 lines
2.3 KiB
JavaScript
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);
|