Files
blackcanyontickets/reactrebuild0825/scripts/validate-theme.js
dzinesco aa81eb5adb feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality
- Implement territory management with manager performance tracking
- Add seatmap components for venue layout management
- Create customer management features with modal interface
- Add advanced hooks for dashboard flags and territory data
- Implement seat selection and venue management utilities
- Add type definitions for ticketing and seatmap systems

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 09:25:10 -06:00

196 lines
5.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Theme Validation Script
* Ensures no hardcoded colors are used in the codebase
*/
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Color patterns to detect
const FORBIDDEN_PATTERNS = [
// Hardcoded hex colors
/#[0-9a-fA-F]{3,6}/g,
// Tailwind color classes we don't want
/\b(slate|gray|zinc|neutral|stone)-\d+/g,
/\b(red|green|blue|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose)-\d+/g,
// Generic color names in class names
/\b(text|bg|border)-(white|black)\b/g,
// RGB/RGBA functions
/rgba?\s*\([^)]+\)/g,
// HSL functions
/hsla?\s*\([^)]+\)/g,
// Gradient utilities with hardcoded colors
/\bfrom-(slate|gray|white|black|red|green|blue|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose)-\d+/g,
/\bto-(slate|gray|white|black|red|green|blue|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose)-\d+/g,
];
// Files to check
const EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.css'];
const EXCLUDED_DIRS = ['node_modules', '.git', 'dist', 'build'];
const EXCLUDED_FILES = [
'tailwind.config.js', // Allow CSS variables in config
'tokens.css', // Allow generated CSS variables
'validate-theme.js', // Allow this script itself
];
// Allowed exceptions (for documentation, comments, etc.)
const ALLOWED_EXCEPTIONS = [
// Comments about colors
/\/\*.*?(#[0-9a-fA-F]{3,6}|slate-\d+).*?\*\//g,
/\/\/.*?(#[0-9a-fA-F]{3,6}|slate-\d+)/g,
// String literals (not class names)
/"[^"]*?(#[0-9a-fA-F]{3,6}|slate-\d+)[^"]*?"/g,
/'[^']*?(#[0-9a-fA-F]{3,6}|slate-\d+)[^']*?'/g,
// Console.log and similar
/console\.(log|warn|error).*?(#[0-9a-fA-F]{3,6}|slate-\d+)/g,
];
function getAllFiles(dir, extensions = EXTENSIONS) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat && stat.isDirectory()) {
if (!EXCLUDED_DIRS.includes(file)) {
results = results.concat(getAllFiles(filePath, extensions));
}
} else {
const ext = path.extname(file);
const basename = path.basename(file);
if (extensions.includes(ext) && !EXCLUDED_FILES.includes(basename)) {
results.push(filePath);
}
}
});
return results;
}
function removeAllowedExceptions(content) {
let cleaned = content;
ALLOWED_EXCEPTIONS.forEach(pattern => {
cleaned = cleaned.replace(pattern, '');
});
return cleaned;
}
function validateFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const cleanedContent = removeAllowedExceptions(content);
const violations = [];
FORBIDDEN_PATTERNS.forEach(pattern => {
const matches = [...cleanedContent.matchAll(pattern)];
matches.forEach(match => {
const lineNumber = content.substring(0, match.index).split('\n').length;
violations.push({
file: filePath,
line: lineNumber,
match: match[0],
pattern: pattern.toString(),
});
});
});
return violations;
}
function main() {
console.log('🎨 Validating theme system...\n');
const projectRoot = path.join(__dirname, '..');
const srcDir = path.join(projectRoot, 'src');
const files = getAllFiles(srcDir);
console.log(`Checking ${files.length} files for hardcoded colors...\n`);
let totalViolations = 0;
const violationsByFile = {};
files.forEach(file => {
const violations = validateFile(file);
if (violations.length > 0) {
violationsByFile[file] = violations;
totalViolations += violations.length;
}
});
if (totalViolations === 0) {
console.log('✅ No hardcoded colors found! Theme system is clean.\n');
// Additional check: ensure semantic classes are being used
console.log('🔍 Checking for proper semantic token usage...\n');
const semanticPatterns = [
/\btext-text-(primary|secondary|muted|inverse|disabled)\b/g,
/\bbg-bg-(primary|secondary|tertiary|elevated|overlay)\b/g,
/\bborder-border\b/g,
/\baccent-(primary|secondary|gold)-\d+/g,
];
let semanticUsageCount = 0;
files.forEach(file => {
const content = fs.readFileSync(file, 'utf8');
semanticPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches) {
semanticUsageCount += matches.length;
}
});
});
console.log(`✅ Found ${semanticUsageCount} instances of semantic token usage.\n`);
return process.exit(0);
}
console.log(`❌ Found ${totalViolations} hardcoded color violations:\n`);
Object.entries(violationsByFile).forEach(([file, violations]) => {
console.log(`📄 ${file}:`);
violations.forEach(violation => {
console.log(` Line ${violation.line}: "${violation.match}"`);
});
console.log('');
});
console.log('🚨 VALIDATION FAILED!\n');
console.log('Please replace hardcoded colors with semantic tokens from the design system.');
console.log('See THEMING.md for guidance on proper token usage.\n');
console.log('Common replacements:');
console.log(' text-slate-900 → text-text-primary');
console.log(' text-slate-600 → text-text-secondary');
console.log(' bg-white → bg-bg-primary');
console.log(' border-slate-200 → border-border');
console.log(' #3b82f6 → Use accent-primary-500 or similar\n');
process.exit(1);
}
main();