#!/usr/bin/env node
/**
* 批量替换硬编码色值为 SCSS 令牌(CSS style 块内,零视觉风险)
* 用法:
* node scripts/replace-hex-to-token.js --batch gray # 仅灰阶
* node scripts/replace-hex-to-token.js --batch brand # 仅品牌/状态色
* node scripts/replace-hex-to-token.js # 全部
*/
const fs = require('fs')
const path = require('path')
const ROOT = path.resolve(__dirname, '..')
const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'docs'])
const ALL_RULES = [
// ---- 灰阶(neutral)----
['#FFFFFF', '$app-surface'],
['#ffffff', '$app-surface'],
['#FFF', '$app-surface'],
['#fff', '$app-surface'],
['#333333', '$facade-text-strong'],
['#333', '$facade-text-strong'],
['#666666', '$facade-text-body'],
['#666', '$facade-text-body'],
['#999999', '$facade-text-muted'],
['#999', '$facade-text-muted'],
['#F5F5F5', '$facade-fill-2'],
['#f5f5f5', '$facade-fill-2'],
['#F0F0F0', '$facade-fill'],
['#f0f0f0', '$facade-fill'],
['#FAFAFA', '$facade-fill-ghost'],
['#fafafa', '$facade-fill-ghost'],
['#F9FAFB', '$facade-fill-gray-50'],
['#f9fafb', '$facade-fill-gray-50'],
['#BBBBBB', '$facade-text-ghost-light'],
['#bbbbbb', '$facade-text-ghost-light'],
['#BBB', '$facade-text-ghost-light'],
['#bbb', '$facade-text-ghost-light'],
['#CCCCCC', '$facade-text-ghost'],
['#cccccc', '$facade-text-ghost'],
['#CCC', '$facade-text-ghost'],
['#ccc', '$facade-text-ghost'],
['#DDDDDD', '$facade-border-soft'],
['#dddddd', '$facade-border-soft'],
['#DDD', '$facade-border-soft'],
['#ddd', '$facade-border-soft'],
['#EEEEEE', '$facade-hairline-2'],
['#eeeeee', '$facade-hairline-2'],
['#EEE', '$facade-hairline-2'],
['#eee', '$facade-hairline-2'],
// ---- 品牌/状态色(brand/status)----
['#F97316', '$brand-primary'],
['#f97316', '$brand-primary'],
['#FF8C42', '$dim-body'],
['#ff8c42', '$dim-body'],
['#FFF7ED', '$uni-primary-pale'],
['#fff7ed', '$uni-primary-pale'],
['#FB923C', '$brand-primary-light'],
['#fb923c', '$brand-primary-light'],
['#10B981', '$status-success'],
['#10b981', '$status-success'],
['#EF4444', '$status-danger'],
['#ef4444', '$status-danger'],
['#6366F1', '$dim-wisdom'],
['#6366f1', '$dim-wisdom'],
['#C62828', '$status-danger-strong-2'],
['#c62828', '$status-danger-strong-2'],
// ---- 第二轮(高频色,2026-10-20 批量收割)----
['#94A3B8', '$app-text-subtle'],
['#94a3b8', '$app-text-subtle'],
['#1E293B', '$uni-text-color'],
['#1e293b', '$uni-text-color'],
['#64748B', '$uni-text-color-secondary'],
['#64748b', '$uni-text-color-secondary'],
['#E2E8F0', '$uni-border-color-light'],
['#e2e8f0', '$uni-border-color-light'],
['#F5F7FA', '$app-bg'],
['#f5f7fa', '$app-bg'],
['#667EEA', '$accent-indigo'],
['#667eea', '$accent-indigo'],
['#9CA3AF', '$facade-text-gray-400'],
['#9ca3af', '$facade-text-gray-400'],
['#FFF3E0', '$fortune-1-tint'],
['#fff3e0', '$fortune-1-tint'],
['#FED7AA', '$brand-primary-light-2'],
['#fed7aa', '$brand-primary-light-2'],
['#F1F5F9', '$facade-fill-slate'],
['#f1f5f9', '$facade-fill-slate'],
['#4CAF50', '$mat-green'],
['#4caf50', '$mat-green'],
['#E8F5E9', '$mat-green-tint'],
['#e8f5e9', '$mat-green-tint'],
['#F59E0B', '$dim-wealth'],
['#f59e0b', '$dim-wealth'],
['#F8F8F8', '$facade-fill-page'],
['#f8f8f8', '$facade-fill-page'],
['#FEF3C7', '$status-warning-soft'],
['#fef3c7', '$status-warning-soft'],
['#E65100', '$mat-orange-strong'],
['#e65100', '$mat-orange-strong'],
['#2E7D32', '$mat-green-strong'],
['#2e7d32', '$mat-green-strong'],
['#888888', '$facade-text-faint'],
['#888', '$facade-text-faint'],
['#F3F4F6', '$facade-fill-gray-100'],
['#f3f4f6', '$facade-fill-gray-100'],
['#E3F2FD', '$mat-blue-tint'],
['#e3f2fd', '$mat-blue-tint'],
['#EA580C', '$brand-primary-dark'],
['#ea580c', '$brand-primary-dark'],
['#E5E7EB', '$facade-gray-200'],
['#e5e7eb', '$facade-gray-200'],
['#E0E0E0', '$facade-hairline'],
['#e0e0e0', '$facade-hairline'],
['#1F2937', '$facade-text-gray-800'],
['#1f2937', '$facade-text-gray-800'],
['#6B7280', '$facade-text-slate'],
['#6b7280', '$facade-text-slate'],
['#F5FAFE', '$facade-fill-blue-ghost'],
['#f5fafe', '$facade-fill-blue-ghost'],
['#CBD5E1', '$uni-border-color'],
['#cbd5e1', '$uni-border-color'],
['#374151', '$facade-text-gray-700'],
['#374151', '$facade-text-gray-700'],
['#764BA2', '$accent-indigo-deep'],
['#764ba2', '$accent-indigo-deep'],
['#F8FAFC', '$facade-fill-slate-50'],
['#f8fafc', '$facade-fill-slate-50'],
['#FFD700', '$accent-gold'],
['#ffd700', '$accent-gold'],
['#1565C0', '$mat-blue-strong'],
['#1565c0', '$mat-blue-strong'],
['#DC2626', '$status-danger-deep'],
['#dc2626', '$status-danger-deep'],
['#475569', '$facade-text-slate-deep'],
['#475569', '$facade-text-slate-deep'],
['#8B5CF6', '$facade-violet'],
['#8b5cf6', '$facade-violet'],
['#334155', '$facade-text-slate-700'],
['#334155', '$facade-text-slate-700'],
['#FFB366', '$dim-body-light'],
['#ffb366', '$dim-body-light'],
['#FFEBEE', '$fortune-0-tint'],
['#ffebee', '$fortune-0-tint'],
['#34D399', '$status-success-light-3'],
['#34d399', '$status-success-light-3'],
['#1A1A2E', '$app-ink-night'],
['#1a1a2e', '$app-ink-night'],
['#E8E8E8', '$facade-fill-4'],
['#e8e8e8', '$facade-fill-4'],
['#16A34A', '$status-success-strong-2'],
['#16a34a', '$status-success-strong-2'],
['#FEE2E2', '$status-danger-soft'],
['#fee2e2', '$status-danger-soft'],
['#C2410C', '$brand-primary-deep'],
['#c2410c', '$brand-primary-deep'],
['#FFF7E6', '$brand-primary-tint-2'],
['#fff7e6', '$brand-primary-tint-2'],
['#22C55E', '$status-success-2'],
['#22c55e', '$status-success-2'],
['#3B82F6', '$status-info'],
['#3b82f6', '$status-info'],
['#2196F3', '$mat-blue-base'],
['#2196f3', '$mat-blue-base'],
['#D97706', '$status-warning-deep'],
['#d97706', '$status-warning-deep'],
['#0EA5E9', '$uni-info'],
['#0ea5e9', '$uni-info'],
['#DCFCE7', '$status-success-soft-2'],
['#dcfce7', '$status-success-soft-2'],
['#555555', '$facade-text-deep'],
['#555', '$facade-text-deep'],
['#818CF8', '$dim-wisdom-light'],
['#818cf8', '$dim-wisdom-light'],
['#FF9800', '$mat-orange-base'],
['#ff9800', '$mat-orange-base'],
['#F5F9FC', '$uni-bg-color'],
['#f5f9fc', '$uni-bg-color'],
['#F0FDF4', '$status-success-tint-2'],
['#f0fdf4', '$status-success-tint-2'],
['#D1FAE5', '$status-success-soft'],
['#d1fae5', '$status-success-soft'],
['#DBEAFE', '$status-info-soft'],
['#dbeafe', '$status-info-soft'],
['#92400E', '$status-warning-strong'],
['#92400e', '$status-warning-strong'],
].sort((a, b) => b[0].length - a[0].length)
const GRAY_RULES = ALL_RULES.filter(r => { const i = ALL_RULES.indexOf(r); return i < 30 })
const BRAND_RULES = ALL_RULES.filter(r => { const i = ALL_RULES.indexOf(r); return i >= 30 })
const SECOND_RULES = ALL_RULES.filter(r => { const i = ALL_RULES.indexOf(r); return i >= 30 })
const batch = process.argv.find(a => a.startsWith('--batch='))?.split('=')[1]
const rules = batch === 'brand' ? BRAND_RULES : batch === 'gray' ? GRAY_RULES : ALL_RULES
const label = batch === 'brand' ? '品牌/状态色' : batch === 'gray' ? '灰阶' : '全部'
console.log(`\n批次: ${label} 规则数: ${rules.length}\n`)
function walk(dir, out = []) {
for (const name of fs.readdirSync(dir)) {
if (EXCLUDE_DIRS.has(name)) continue
const p = path.join(dir, name)
const st = fs.statSync(p)
if (st.isDirectory()) walk(p, out)
else if (name.endsWith('.vue')) out.push(p)
}
return out
}
function countReplacements(text, rules) {
let n = 0
for (const [hex] of rules) {
const escaped = hex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
n += (text.match(new RegExp(escaped + '(?![0-9a-fA-F])', 'g')) || []).length
}
return n
}
function applyRules(text, rules) {
let result = text
for (const [hex, token] of rules) {
const escaped = hex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
result = result.replace(new RegExp(escaped + '(?![0-9a-fA-F])', 'g'), token)
}
return result
}
function processFile(filePath) {
const src = fs.readFileSync(filePath, 'utf8')
// 按 "
const replCount = countReplacements(body, rules)
if (replCount === 0) continue
const newBody = applyRules(body, rules)
totalRepl += replCount
// 若