Files
aurora-admin/tools/lib/parse-string-literal-array.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

85 lines
4.1 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.
/* Kole UI —— defineEmits 数组字面量的安全解析器(构建期唯一可信实现)
*
* 用途:tools/precompute.mjs 阶段 C 预计算 API 表时,从 frameworks/*.vue* 的
* `defineEmits([...])` 抽取事件名。
*
* 安全背景(实测缺陷 P2 / 构建链同源缺陷):
* 旧实现把数组字面量交给函数构造器求值(等价于动态执行代码)。
* 任何能改 frameworks/*.vue* 的人(内部威胁 / 投毒 PR)只要写
* defineEmits([1, globalThis.__pwned = 'x'])
* 就会在**构建机**(CI 往往带 secrets)上执行任意 JS。本模块只做字面量解析,
* 任何非字符串元素 / 未识别转义 / 裸换行 / 结构不符 → 返回 null(跳过该条,
* 不抽取事件、不抛错、绝不执行任何内容)。
*
* 「同一份实现」的落地方式(重要):
* 本文件是 site/app.js 内联实现(extractComponentAPI 内的 isHexRun +
* parseStringLiteralArray)的**逐行同源副本**。浏览器端 site/app.js 是
* classic script(<script src="app.js">,无模块系统),且 dev-server 的
* 公开白名单(isPublicPath)不含 tools/,因此 site/app.js 无法直接 import 本模块;
* 强行改造会改变文档站对外行为(回归基线)。
* 故由 tools/verify-emits-parse.mjs 的「漂移门」保证两份不漂移:
* 两侧函数在全部 158 个真实 frameworks/*.vue 的 defineEmits 字面量上抽取结果
* 逐例等价,并在对抗语料上同样不执行任何输入。
* 修改任意一侧后必须同步另一侧,否则该 verify 脚本会失败。
*
* 零依赖:只用语言内置能力。
*/
/* 判断 s 是否恰好是 n 位十六进制(供 \xXX / \uXXXX 校验,避免 parseInt 的宽松前缀解析) */
function isHexRun(s, n) {
if (s.length !== n) return false;
for (var h = 0; h < n; h++) {
var cc = s.charCodeAt(h);
if (!((cc >= 48 && cc <= 57) || (cc >= 97 && cc <= 102) || (cc >= 65 && cc <= 70))) return false;
}
return true;
}
/* 解析「元素全部为字符串字面量」的数组字面量(允许空白与尾逗号)。
只按字面量语义还原转义,绝不求值;不匹配 → null。 */
export function parseStringLiteralArray(literal) {
if (typeof literal !== 'string' || literal.charAt(0) !== '[' ||
literal.charAt(literal.length - 1) !== ']') return null;
var body = literal.slice(1, literal.length - 1);
var simple = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' };
var out = [];
var i = 0;
for (;;) {
while (i < body.length && /\s/.test(body.charAt(i))) i++;
if (i >= body.length) break; // 空数组 / 尾逗号结束
var quote = body.charAt(i);
if (quote !== "'" && quote !== '"') return null; // 非字符串字面量 → 整体拒绝
i++;
var value = '';
var closed = false;
while (i < body.length) {
var ch = body.charAt(i);
if (ch === '\\') {
var esc = body.charAt(i + 1);
if (esc === '') return null;
if (esc === quote || esc === '\\') { value += esc; i += 2; continue; }
if (simple[esc] !== undefined) { value += simple[esc]; i += 2; continue; }
if (esc === '\n' || esc === '\r') { i += 2; continue; } // 续行
if (esc === 'x' && isHexRun(body.substr(i + 2, 2), 2)) {
value += String.fromCharCode(parseInt(body.substr(i + 2, 2), 16)); i += 4; continue;
}
if (esc === 'u' && isHexRun(body.substr(i + 2, 4), 4)) {
value += String.fromCharCode(parseInt(body.substr(i + 2, 4), 16)); i += 6; continue;
}
return null; // 未识别转义 → 整体拒绝
}
if (ch === quote) { closed = true; i++; break; }
if (ch === '\n' || ch === '\r') return null; // 字符串内裸换行非法
value += ch;
i++;
}
if (!closed) return null;
out.push(value);
while (i < body.length && /\s/.test(body.charAt(i))) i++;
if (i >= body.length) break;
if (body.charAt(i) !== ',') return null; // 不是逗号分隔 → 整体拒绝
i++;
}
return out;
}