- 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>
196 lines
5.6 KiB
JavaScript
Executable File
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(); |