#!/usr/bin/env node /** * migrate-brand-kole-ui.mjs — 品牌重命名迁移:Aurora Admin → Kole UI * * 用途:把仓库内所有品牌命名一次性迁到新名,含四类命名空间: * 1) 文字品牌名 Aurora Admin / Aurora → Kole UI / Kole * 2) 路径·包名 aurora-admin[-design|-showcase] → kole-ui[-showcase] * 3) 类名前缀 aa- / au- → kole- * 4) 令牌前缀 --au- → --kole- * 另有精确标识符表(aaLogger / __aaCollectResult / AA_DATA 等),逐条列举而非正则, * 避免误伤十六进制哈希(如 9f98c53aa3b2…)与 WCAG「AA」字样。 * * 实现要点: * - 以 latin1 逐字节读写:ASCII 模式在 UTF-8 / GBK 等 ASCII 超集编码下均按字节命中, * 多字节汉字原样透传;CRLF 与无 BOM 状态自然保持。 * - 遇到 UTF-16 BOM 或含 NUL 字节的文件跳过并报告(不做猜测性转码)。 * - 幂等:再次运行第二遍报告 0 处改动。 * * 用法: * node tools/migrate-brand-kole-ui.mjs --dry-run # 只报告,不写盘 * node tools/migrate-brand-kole-ui.mjs # 执行替换 * node tools/migrate-brand-kole-ui.mjs --verify # 扫描残留旧命名(期望 0) * node tools/migrate-brand-kole-ui.mjs --only=Aa # 只跑「来源串含 Aa」的规则 * ^ 用于增量补规则:全量重跑会把「刻意记录旧名」的 * 对照表(CHANGELOG 2.0.0 段、AGENTS §七)一起改写, * 增量场景一律用 --only 限定规则。 */ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const SELF = fileURLToPath(import.meta.url); const ROOT = path.resolve(path.dirname(SELF), '..'); const argv = process.argv.slice(2); const DRY = argv.includes('--dry-run'); const VERIFY = argv.includes('--verify'); const ONLY = (argv.find((a) => a.startsWith('--only=')) || '').slice('--only='.length) || null; /** --only= 时只保留「来源串含该子串」的规则。 */ function pick(rules) { if (!ONLY) return rules; return rules.filter(([from]) => String(from).includes(ONLY)); } const SKIP_DIRS = new Set([ '.git', 'node_modules', 'dist', 'dist-deploy', '.tmp', '.playwright-mcp', '.zcode', ]); const SKIP_EXT = new Set([ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.bmp', '.exe', '.dll', '.zip', '.gz', '.tgz', '.tar', '.woff', '.woff2', '.ttf', '.otf', '.eot', '.pdf', '.mp4', '.webm', ]); /** 有序替换表:字面量必须排在能作为其子串的规则之后(长优先)。 */ const LITERAL_RULES = [ // 1. 包名 / 镜像名(必须先于 aurora-admin) ['aurora-admin-design', 'kole-ui'], ['aurora-admin-showcase', 'kole-ui-showcase'], ['aurora-admin', 'kole-ui'], // 2. 文字品牌名 ['Aurora Admin', 'Kole UI'], ['Aurora', 'Kole'], ['aurora', 'kole'], ['AURORA', 'KOLE'], // 3. 令牌前缀(必须先于裸 au-) ['--au-', '--kole-'], // 4. 类名前缀 ['aa-', 'kole-'], // 5. 精确标识符(逐条列举,避开哈希误伤) ['AA_VERIFY_DEPS_DIR', 'KOLE_VERIFY_DEPS_DIR'], ['AA_DATA', 'KOLE_DATA'], ['AA_PORT', 'KOLE_PORT'], ['aaLogger', 'koleLogger'], ['__aaTranslateThemePanel', '__koleTranslateThemePanel'], ['__aaRefreshModeUI', '__koleRefreshModeUI'], ['__aaChangelogReady', '__koleChangelogReady'], ['__aaCollectResult', '__koleCollectResult'], ['__aaDetailReady', '__koleDetailReady'], ['__aaBehaviors', '__koleBehaviors'], ['__aaBehCache', '__koleBehCache'], ['__aaVersion', '__koleVersion'], ['__aaResult', '__koleResult'], ['__aaRun', '__koleRun'], ['aaZoom', 'koleZoom'], ['aaSlide', 'koleSlide'], ['aaFade', 'koleFade'], ]; /** 正则规则:`au-` 前必须不是字母数字连字符(排除 beau- 这类词内误伤)。 */ const REGEX_RULES = [ [/(? path.resolve(f) !== path.resolve(SELF)); const skipped = []; const changed = []; const ruleTotals = new Map(); for (const f of files) { const buf = fs.readFileSync(f); if (buf.length >= 2 && ((buf[0] === 0xff && buf[1] === 0xfe) || (buf[0] === 0xfe && buf[1] === 0xff))) { skipped.push([path.relative(ROOT, f), 'UTF-16 BOM']); continue; } const src = buf.toString('latin1'); if (VERIFY) { const found = []; for (const re of VERIFY_PATTERNS) { const m = src.match(new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g')); if (m) found.push(`${re.source} ×${m.length}`); } if (found.length) changed.push([path.relative(ROOT, f), found.join(', ')]); continue; } const [out, hits] = applyRules(src); if (out === src) continue; const rel = path.relative(ROOT, f).split(path.sep).join('/'); let total = 0; for (const [from, , n] of hits) { total += n; ruleTotals.set(from, (ruleTotals.get(from) || 0) + n); } changed.push([rel, `${total} 处`]); if (!DRY) fs.writeFileSync(f, Buffer.from(out, 'latin1')); } const mode = VERIFY ? 'verify(残留扫描)' : DRY ? 'dry-run(未写盘)' : 'apply(已写盘)'; console.log(`\n[migrate-brand] 模式 ${mode} · 扫描 ${files.length} 个文本文件 · 命中 ${changed.length} 个\n`); for (const [rel, info] of changed) console.log(` ${rel} — ${info}`); if (skipped.length) { console.log(`\n跳过(非 ASCII 超集编码):`); for (const [rel, why] of skipped) console.log(` ${rel} (${why})`); } if (!VERIFY) { console.log(`\n规则命中统计:`); for (const [rule, n] of [...ruleTotals.entries()].sort((a, b) => b[1] - a[1])) { console.log(` ${String(n).padStart(6)} ${rule}`); } } console.log(''); if (VERIFY && changed.length) process.exitCode = 1;