/** * 品牌色 utility 用量盘点(为「文本令牌 / 底色令牌」拆分提供决策依据) * * 统计 src 下所有 `(?:变体前缀:)?(属性)-brand(?:-子token)?(/透明度)?` 的出现次数与文件分布。 * 用法:node scripts/audit/brand-usage-scan.mjs [--files] */ 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 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; } const VARIANTS = '(?:hover:|focus:|focus-visible:|active:|group-hover:|dark:|sm:|md:|lg:|xl:)*'; const PROPS = '(bg|text|border|from|to|via|fill|stroke|ring|divide|decoration|outline|shadow|placeholder|caret|accent)'; const re = new RegExp(`(?:^|[\\s\`"'])${VARIANTS}${PROPS}-(brand(?:-[a-z]+)?)(?:/[0-9]+)?(?=[\\s\`"':/]|$)`, 'g'); const byToken = new Map(); const byFile = new Map(); const byPropToken = new Map(); for (const f of walk(path.join(ROOT, 'src'))) { let s; try { s = fs.readFileSync(f, 'utf8'); } catch { continue; } let m; while ((m = re.exec(s))) { const key = `${m[1]}-${m[2]}`; byToken.set(key, (byToken.get(key) || 0) + 1); byPropToken.set(m[1], (byPropToken.get(m[1]) || 0) + 1); byFile.set(f, (byFile.get(f) || 0) + 1); } } const sortDesc = a => [...a.entries()].sort((x, y) => y[1] - x[1]); console.log('=== brand utility 用量(src 全量)==='); for (const [k, n] of sortDesc(byToken)) console.log(String(n).padStart(5), k); const total = [...byToken.values()].reduce((s, n) => s + n, 0); console.log('总计'.padStart(5), total); console.log('\n=== 按属性聚合 ==='); for (const [k, n] of sortDesc(byPropToken)) console.log(String(n).padStart(5), k); if (process.argv.includes('--files')) { console.log('\n=== 文件分布 Top 25 ==='); for (const [f, n] of sortDesc(byFile).slice(0, 25)) { console.log(String(n).padStart(5), path.relative(ROOT, f)); } }