/** * text-brand → text-brand-ink 批量迁移(暗黑模式品牌文字色修复) * * 背景:`text-brand` 与 `bg-brand` 原本共用 `--color-brand-rgb`,无法只在深色下提亮文字 * (提亮会连按钮底色一起改,红底白字 5.58:1 → 3.75:1)。故拆分出文字专用通道 * `--color-brand-ink-rgb`,本脚本把「文字用法」迁到新 utility `text-brand-ink`。 * * 匹配规则: * - 只匹配独立的 `text-brand`(后不接 `-` 或字母数字),保留 `text-brand-light` 等子 token * - 保留变体前缀(hover: / focus: / group-hover: / sm: 等)与透明度修饰符(/80) * * 用法: * node scripts/audit/brand-ink-migrate.mjs # 预演,只打印不落盘 * node scripts/audit/brand-ink-migrate.mjs --apply # 实际写入 */ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const APPLY = process.argv.includes('--apply'); const EXTS = new Set(['.tsx', '.ts', '.css', '.jsx', '.js', '.mdx']); const SKIP = new Set(['node_modules', '.next', '.git', '_archive', 'dist', 'coverage']); function walk(dir, out = []) { for (const e of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, e.name); if (e.isDirectory()) { if (!SKIP.has(e.name)) walk(p, out); } else if (EXTS.has(path.extname(e.name))) out.push(p); } return out; } // 后不接 `-` 或字母数字 → 排除 text-brand-light / text-brand-ink(幂等) const re = /text-brand(?![-\w])/g; let fileCount = 0, hitCount = 0; const perFile = []; for (const f of walk(path.join(ROOT, 'src'))) { const before = fs.readFileSync(f, 'utf8'); const after = before.replace(re, 'text-brand-ink'); if (after === before) continue; const n = (before.match(re) || []).length; hitCount += n; fileCount++; perFile.push({ f: path.relative(ROOT, f), n }); if (APPLY) fs.writeFileSync(f, after); } perFile.sort((a, b) => b.n - a.n); console.log(`${APPLY ? '已写入' : '预演(未落盘)'}:${hitCount} 处 / ${fileCount} 文件`); for (const { f, n } of perFile.slice(0, 20)) console.log(String(n).padStart(4), f); if (perFile.length > 20) console.log(` ... 另有 ${perFile.length - 20} 个文件`); if (!APPLY) console.log('\n确认无误后加 --apply 执行。');