Files
aurora-admin/tools/migrate-brand-kole-ui.mjs
T
aurora-admin f1fbfc2ddb
Regression / regression (push) Canceled after 0s
feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
## 品牌标识(本次会话)

起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」,
那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」,
移动端站已是「K」;移动端文档站则完全没有 favicon)。

- 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边;
  描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」
- 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark);
  顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28))
- 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg)
- 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留)
- 移动端 7 个模板补 favicon(此前计数 0)

验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/
theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致;
两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。

## 并行会话成果(本次一并入库)

- 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具
  + IconPreview 预览页 + ICON-SPEC.md 冻结规格
- 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁
- PC 组件:103 个大后台组件 / 组件11 批次
- uni-app:PC 端试点 + 移动端端实现 + 真实编译验证

## 工程

- .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库)
- CHANGELOG 补品牌标识条目
- ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
2026-09-21 10:05:48 +08:00

194 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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=<substr> 时只保留「来源串含该子串」的规则。 */
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 = [
[/(?<![A-Za-z0-9-])au-/g, 'kole-'],
// 包公开导出名 `AaButton` / 命名空间导入 `import * as Aa` → `KoleButton` / `Kole`。
// 负向先行断言排除 `Aardvark` 这类「Aa + 小写」的英文词。
[/\bAa(?![a-z])/g, 'Kole'],
];
const VERIFY_PATTERNS = [
/aurora/i,
/\baa-/,
/--au-/,
/(?<![A-Za-z0-9-])au-/,
/\bAa(?![a-z])/,
/__aa[A-Z]/,
/\baaLogger\b/,
/AA_(DATA|PORT|VERIFY_DEPS_DIR)/,
/aa(Demo|Badge|Tab|Menu|Card|Btn)/,
];
function applyRules(src) {
let out = src;
const hits = [];
for (const [from, to] of pick(LITERAL_RULES)) {
const n = out.split(from).length - 1;
if (n) {
hits.push([from, to, n]);
out = out.split(from).join(to);
}
}
for (const [re, to] of pick(REGEX_RULES)) {
const n = (out.match(re) || []).length;
if (n) {
hits.push([String(re), to, n]);
out = out.replace(re, to);
}
}
return [out, hits];
}
function walk(dir, acc = []) {
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, ent.name);
if (ent.isDirectory()) {
if (SKIP_DIRS.has(ent.name)) continue;
walk(p, acc);
} else if (ent.isFile()) {
if (SKIP_EXT.has(path.extname(ent.name).toLowerCase())) continue;
acc.push(p);
}
}
return acc;
}
const files = walk(ROOT).filter((f) => 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;