chore(audit): UI 审计工具链增强与噪音根治

- ui-audit.mjs:路由发现扩展至 33 路由(standalone 构建产物 + erp-upgrade 系列);
  GA/gtm 请求 mock(gtag/js 空 JS + collect 204);第三方统计噪音与同源
  ERR_ABORTED(重调度成功/prefetch)降级 INFO;LCP/CLS 断言按系统负载门控
  (load >= 5 跳过并注明,防测量环境假阳性);LCP 超阈值热缓存 reload 复测
  取优(lcpCold 留存冷值);clip 探针 sr-only 白名单;onReqFail 防御性修复
- ui-audit-report.mjs(新增):多主题探针聚合 HTML 报告,含截图画廊与 INFO 分级
- hardcoded-color-audit.mjs:bg-black/50 半透明遮罩误报修正(负向前瞻)+ 白名单
- 新增 brand-usage-scan.mjs / brand-ink-migrate.mjs(品牌令牌迁移辅助)
This commit is contained in:
2026-09-20 11:50:58 +08:00
parent 5ecab7b15d
commit 441a748f32
5 changed files with 768 additions and 28 deletions
+56
View File
@@ -0,0 +1,56 @@
/**
* 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 执行。');