#!/usr/bin/env node /** * 颜色验证脚本 * * 检查组件文件中是否还有硬编码的十六进制颜色值 * 用法: node scripts/validate-colors.js */ const fs = require('fs'); const path = require('path'); // 硬编码颜色的正则模式 const hardcodedColorPatterns = [ // Tailwind 类名中的十六进制颜色: bg-[#hex], text-[#hex], border-[#hex] /(bg|text|border|from|to|via)-\[#[0-9a-fA-F]{3,8}\]/g, // 内联样式中的十六进制颜色 /(?:background|color|border)(?:Color)?:\s*['"]?#[0-9a-fA-F]{3,8}/g, // 对象样式中的十六进制颜色 /['"](?:background|color|border)(?:Color)?['"]\s*:\s*['"]#[0-9a-fA-F]{3,8}/g, ]; // 允许的例外情况(特殊设计需求) const allowedExceptions = [ // rgba/hsla 透明度值(毛玻璃效果等) /rgba?\(/, /hsla?\(/, // 渐变色(gradient-to-br from-green-400 to-emerald-500 等非硬编码) // 注意:这里不排除硬编码的渐变,会被上面的模式捕获 ]; // 需要检查的目录 const componentsDir = path.join(__dirname, '../components'); // 收集结果 const results = { files: [], totalViolations: 0, }; /** * 检查单个文件 */ function checkFile(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const violations = []; // 按行分析 const lines = content.split('\n'); lines.forEach((line, index) => { hardcodedColorPatterns.forEach(pattern => { const matches = line.match(pattern); if (matches) { // 检查是否是允许的例外 const isException = allowedExceptions.some(exceptionPattern => exceptionPattern.test(line) ); if (!isException) { matches.forEach(match => { violations.push({ line: index + 1, content: line.trim(), match: match, }); }); } } }); }); if (violations.length > 0) { results.files.push({ path: path.relative(process.cwd(), filePath), violations: violations, count: violations.length, }); results.totalViolations += violations.length; } return violations.length === 0; } /** * 递归扫描目录 */ function scanDirectory(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); entries.forEach(entry => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanDirectory(fullPath); } else if (entry.isFile() && /\.(tsx|ts|jsx|js)$/.test(entry.name)) { checkFile(fullPath); } }); } /** * 打印结果 */ function printResults() { console.log('\n='.repeat(80)); console.log('🎨 设计系统颜色验证报告'); console.log('='.repeat(80)); console.log(); if (results.totalViolations === 0) { console.log('✅ 太棒了!没有发现硬编码颜色值。'); console.log(); console.log('所有组件都已成功迁移到设计系统!'); } else { console.log(`⚠️ 发现 ${results.totalViolations} 处硬编码颜色值,分布在 ${results.files.length} 个文件中:`); console.log(); results.files.forEach(file => { console.log(`📄 ${file.path} (${file.count} 处问题)`); console.log('─'.repeat(80)); // 按行号分组相同的问题 const lineMap = new Map(); file.violations.forEach(v => { if (!lineMap.has(v.line)) { lineMap.set(v.line, []); } lineMap.get(v.line).push(v.match); }); lineMap.forEach((matches, lineNum) => { const firstViolation = file.violations.find(v => v.line === lineNum); console.log(` 行 ${lineNum}: ${firstViolation.content.substring(0, 100)}${firstViolation.content.length > 100 ? '...' : ''}`); matches.forEach(match => { console.log(` ↳ ${match}`); }); }); console.log(); }); console.log('='.repeat(80)); console.log('💡 建议:'); console.log(' • 将硬编码颜色替换为设计系统的 token (如 text-text-primary, bg-bg-base 等)'); console.log(' • 检查是否有特殊设计需求需要保留硬编码'); console.log(' • 参考 /styles/design-system.css 中定义的颜色变量'); } console.log('='.repeat(80)); console.log(); // 退出码 process.exit(results.totalViolations > 0 ? 1 : 0); } // 执行扫描 console.log('🔍 正在扫描组件文件...'); scanDirectory(componentsDir); printResults();