- 待发箱地基: V51 ai_outbox 表 + 手写 OutboxRepo(claim CAS/崩溃残留复位/瞬时失败保留) + outbox.rs 调度器(KV 默认关/Notify 唤醒/会话忙跳过不丢)
- Wake 异步唤醒原语: 三条件(delay/file-appear/process-exit)+纯判定+注入走正典 spawn_agentic_session,默认关
- 上下文预算随模型窗口: model_context.rs 三级优先(池显式>预设表>回退 128K);上界取 1M 而非 128K——max_tokens 仅裁剪预算,卡 128K 等于不治根因
- 文件租约补纯函数与单测(未另造双轨:LEASE 早随 cb260ab 落地) / 跨会话召回补纯函数单测(主体早已落地)
- 子代理: 完整卡已消化 + 子会话压缩层 + P3 批量
- 隧道/中继: 指令离线不丢 + Hello proto 版本 + 心跳与 runbook
- df-mcp: 写工具后落 KV 变更标记(供 GUI 轮询兜底,补独立 stdio 进程无感知)
- 迁移链顶点改单一真值常量 LATEST_SCHEMA_VERSION(修 2 处因 V51 失效的断言)
- 治理: i18n 特殊字符提交门禁(含反引号覆盖 + 10 自测) / 根目录卫生门禁 / 看板归档器 / 单一 lockfile(bun) / swap-install 落 temp/ 且兼容无 Get-FileHash
- 场景护栏: 流式正文可见性 e2e(playwright 免真机事件回放) + 列表续写/外置句柄 node 测试
112 lines
4.1 KiB
JavaScript
112 lines
4.1 KiB
JavaScript
// vue-i18n 消息预检:揪出只在 **prod runtime** 抛错的未转义特殊字符(字面 @ / | / 裸 {})。
|
|
//
|
|
// 背景:vue-i18n 9 把 message 里的字面 `@` 当 linked-message 语法、`|` 当 plural 分隔符,
|
|
// 未转义会编译报 "Invalid linked format"。该错误 dev 宽松回退、`vue-tsc` 捕获不到(message 是
|
|
// 字符串值非类型),只在打包装出的应用里炸——已两次踩坑(2026-06-15 白屏 / 2026-10-10 输入框消失),
|
|
// 且两次静态检查全绿。故前置到提交门禁 + 可手动跑:`node scripts/check-i18n.cjs`
|
|
//
|
|
// 修法:字面量用 vue-i18n 的插值转义,如 `{'@'}`(渲染仍显示 @)。
|
|
//
|
|
// 依赖 @intlify/message-compiler(vue-i18n 的编译器);解析不到时只告警不拦截(避免门禁误伤)。
|
|
//
|
|
// 扫描/编译逻辑抽为可导出函数(scanText / scanDirectory / compileMessage),供
|
|
// scripts/check-i18n.test.cjs 自测;CLI 入口行为保持不变。
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
let baseCompile = null;
|
|
try {
|
|
({ baseCompile } = require('@intlify/message-compiler'));
|
|
} catch {
|
|
baseCompile = null;
|
|
}
|
|
|
|
const DIRS = ['src/i18n/zh-CN', 'src/i18n/en'];
|
|
// key: 'value' / "value" / `value`(带转义),三种字面量都取——
|
|
// 反引号模板字面量同样可能含未转义 @/|/裸{},原先只匹配引号 → 门禁假阴性(2026-10-10 对抗审查发现)。
|
|
const KV_RE = /(?:^|[\s,{])([A-Za-z0-9_$]+)\s*:\s*([`'"])((?:\\.|(?!\2).)*)\2/gm;
|
|
|
|
// 编译单个消息值,返回错误数组(@intlify 风格:{ message })
|
|
function compileMessage(val) {
|
|
const errs = [];
|
|
try {
|
|
baseCompile(val, { onError: (e) => errs.push(e), onWarn: () => {} });
|
|
} catch (e) {
|
|
errs.push(e);
|
|
}
|
|
return errs;
|
|
}
|
|
|
|
// 扫描一段源码文本,返回 { checked, violations }
|
|
// - checked: 含 vue-i18n 特殊字符(@ { |)并已送编译的值个数
|
|
// - violations: 编译报错的条目 [{ key, value, message }]
|
|
function scanText(txt) {
|
|
const violations = [];
|
|
let checked = 0;
|
|
KV_RE.lastIndex = 0;
|
|
let m;
|
|
while ((m = KV_RE.exec(txt))) {
|
|
const key = m[1];
|
|
// 模板字面量里的 ${...} 插值对 vue-i18n 无意义,编译前替换为占位符避免假报错
|
|
const val = m[2] === '`' ? m[3].replace(/\$\{[^}]*\}/g, 'X') : m[3];
|
|
// 只编译含 vue-i18n 特殊字符的值(@ { |),其余不可能因语法报错
|
|
if (!/[@{|]/.test(val)) continue;
|
|
checked++;
|
|
const errs = compileMessage(val);
|
|
if (errs.length) violations.push({ key, value: val, message: errs[0].message });
|
|
}
|
|
return { checked, violations };
|
|
}
|
|
|
|
// 扫描目录下所有 .ts,返回 { checked, violations }(violations 带 file 字段)
|
|
function scanDirectory(dir) {
|
|
const violations = [];
|
|
let checked = 0;
|
|
let files;
|
|
try {
|
|
files = fs.readdirSync(dir);
|
|
} catch {
|
|
return { checked, violations };
|
|
}
|
|
for (const f of files) {
|
|
if (!f.endsWith('.ts')) continue;
|
|
const res = scanText(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
checked += res.checked;
|
|
for (const v of res.violations) violations.push({ file: f, ...v });
|
|
}
|
|
return { checked, violations };
|
|
}
|
|
|
|
// CLI 主流程:返回进程退出码(0 通过 / 1 失败)
|
|
function run() {
|
|
if (!baseCompile) {
|
|
console.warn('[i18n-check] 未找到 @intlify/message-compiler,跳过消息编译预检(需 npm 依赖)');
|
|
return 0;
|
|
}
|
|
let checked = 0;
|
|
let failed = 0;
|
|
for (const dir of DIRS) {
|
|
const res = scanDirectory(dir);
|
|
checked += res.checked;
|
|
for (const v of res.violations) {
|
|
failed++;
|
|
console.error(`✗ ${dir}/${v.file} :: ${v.key} => ${v.message}`);
|
|
console.error(` 值: ${JSON.stringify(v.value)}`);
|
|
console.error(` 提示: 字面 @ 须转义为 {'@'}(同理 | 与裸 {})`);
|
|
}
|
|
}
|
|
|
|
if (failed > 0) {
|
|
console.error(`[i18n-check] 失败 ${failed} 处(共编译 ${checked} 个含特殊字符的值)——prod 运行时会抛 Invalid linked format`);
|
|
return 1;
|
|
}
|
|
console.log(`[i18n-check] 通过(编译 ${checked} 个含特殊字符的值,0 失败)`);
|
|
return 0;
|
|
}
|
|
|
|
if (require.main === module) {
|
|
process.exit(run());
|
|
}
|
|
|
|
module.exports = { compileMessage, scanText, scanDirectory, run, KV_RE, DIRS };
|