Regression / regression (push) Canceled after 0s
## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 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 未做部分)
85 lines
4.1 KiB
JavaScript
85 lines
4.1 KiB
JavaScript
/* 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;
|
||
}
|