Files
dsh_shenxian/scripts/verify-models-dict.mjs
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

133 lines
6.3 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
/**
* verify-models-dict.mjs —— 「模型设置」页(`poc/business-plugins/lib/client.js` 的 ms.* 分区)
* 的**词典完整性 + 版式防线**交叉校验(档案 91)。
*
* 为什么需要(两个都真踩过):
* ① **漏键是静默的**:2026-09-14 重写该页时把 `ms.needUrl` / `ms.needModels` 从词典里删掉、
* 却仍被 `submitCustom()` 引用 —— 页面只会在校验失败时把**键名原样**显示给用户,
* 构建与语法检查**都不会报错**。zh/en 也容易只改一边。
* ② **换行是版式问题、不是逻辑问题**:用户截图报「内置 DeepSeek 换行 + 两个按钮换行」,
* 根因是那 4 列表格用了**自动列宽**。改用官方卡片行后,只要 `rowName` / `rowActions`
* 的 `white-space:nowrap` 与 `rowActions` 的 `margin-left:auto` 任一被删,缺陷就会复发
* —— 而肉眼很难在构建期发现。这里把它钉成断言。
*
* 用法:node scripts/verify-models-dict.mjs 退出码 0=全绿 / 1=有失败
*/
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
const read = (p) => readFileSync(join(ROOT, p), 'utf8')
let failed = 0
const ok = (name, cond, extra = '') => {
console.log((cond ? ' ✓ ' : ' ✗ ') + name + (extra ? ' ' + extra : ''))
if (!cond) failed++
}
const src = read('poc/business-plugins/lib/client.js')
// ── ① 取出 zh / en 两个词典对象字面量(按花括号配平截取,不用 eval 全文件)──
/** 从 `var <name> = {` 起按花括号配平截出对象字面量文本。 */
function grabDict(name) {
const at = src.indexOf('var ' + name + ' = {')
if (at < 0) return null
const start = src.indexOf('{', at)
let depth = 0
for (let i = start; i < src.length; i++) {
const c = src[i]
if (c === '{') depth++
else if (c === '}') {
depth--
if (depth === 0) return src.slice(start, i + 1)
}
}
return null
}
const zhText = grabDict('zh')
const enText = grabDict('en')
ok('取到 zh 词典对象', zhText !== null)
ok('取到 en 词典对象', enText !== null)
if (zhText === null || enText === null) process.exit(1)
/** 只取**顶层**键:按缩进深度 1 的行首 `"k":` 判定,避免把嵌套对象里的键也算进来。 */
function keysOf(text) {
const keys = []
const dups = []
const seen = new Set()
for (const m of text.matchAll(/^\t{3}"([^"]+)":/gm)) {
if (seen.has(m[1])) dups.push(m[1])
seen.add(m[1])
keys.push(m[1])
}
return { keys, dups }
}
const zh = keysOf(zhText)
const en = keysOf(enText)
ok('zh 无重复键(重复会让后者静默胜出)', zh.dups.length === 0, zh.dups.length ? '-> ' + zh.dups.join(', ') : '')
ok('en 无重复键', en.dups.length === 0, en.dups.length ? '-> ' + en.dups.join(', ') : '')
const zhSet = new Set(zh.keys)
const enSet = new Set(en.keys)
const onlyZh = zh.keys.filter((k) => !enSet.has(k))
const onlyEn = en.keys.filter((k) => !zhSet.has(k))
ok('zh / en 键数一致', zh.keys.length === en.keys.length, `-> zh ${zh.keys.length} / en ${en.keys.length}`)
ok('zh ⊆ en', onlyZh.length === 0, onlyZh.length ? '-> 仅 zh 有: ' + onlyZh.join(', ') : '')
ok('en ⊆ zh', onlyEn.length === 0, onlyEn.length ? '-> 仅 en 有: ' + onlyEn.join(', ') : '')
// ── ② 代码里引用的键必须都声明了(本轮真漏过)──
// ⚠️ 2026-09-14 扩到**全词典**(原来只查 `ms.*`):`pa.*` 那批一样会漏,
// 而漏键在页面上只表现为"把键名原样显示给用户",构建与语法检查都不报错。
const used = new Set()
for (const m of src.matchAll(/\bt\("([A-Za-z0-9_.]+)"\)/g)) used.add(m[1])
// MS_ERR 的值是把后端错误码映射成的词典键
{
const block = /var MS_ERR = \{([\s\S]*?)\n\t{3}\};/.exec(src)
if (block) for (const m of block[1].matchAll(/"(ms\.[A-Za-z0-9_.]+)"/g)) used.add(m[1])
}
ok('词典引用点非空', used.size > 100, `-> 引用键 ${used.size} 个`)
const missing = [...used].filter((k) => !zhSet.has(k))
ok('被引用的词典键全部已声明', missing.length === 0, missing.length ? '-> 缺失: ' + missing.join(', ') : '')
// ── ③ 版式防线:卡片行的「不换行」三件套(少任何一个,用户报的换行就会复发)──
const cssHas = (selector, decl) => {
const re = new RegExp('\\.' + selector + '\\{[^}]*' + decl + '[^}]*\\}')
return re.test(src)
}
ok('.ms-rowName 有 white-space:nowrap', cssHas('ms-rowName', 'white-space:nowrap'))
ok('.ms-rowActions 有 white-space:nowrap', cssHas('ms-rowActions', 'white-space:nowrap'))
ok('.ms-rowActions 有 margin-left:auto(动作右对齐,官方 rowActions 同)', cssHas('ms-rowActions', 'margin-left:auto'))
ok('.ms-rowHead 是 flex 且 align-items:center', cssHas('ms-rowHead', 'align-items:center'))
ok('.ms-addActions 可换行(两个虚线按钮窄屏时上下堆叠)', cssHas('ms-addActions', 'flex-wrap:wrap'))
ok('.ms-addBtn 有 flex:1 1 0 + min-width:180px(官方 addButton 同)', cssHas('ms-addBtn', 'min-width:180px'))
// ── ④ 新的新增流程确实在(两个虚线入口 + 已无 4 列表格)──
ok('默认态有「添加提供方」入口', src.includes('ms.addProvider'))
ok('默认态有「添加自定义提供方」入口', src.includes('ms.addCustom'))
// 描述字符串很长,最容易混进未转义的 `"` 把 JSON 写坏(2026-09-14 实际踩过一次)
{
let pkgsOk = true
for (const p of ['package.json', 'poc/business-plugins/package.json']) {
try {
JSON.parse(read(p))
} catch {
pkgsOk = false
}
}
ok('package.json 均为合法 JSON', pkgsOk)
}
ok('「我已添加的提供方」已从表格改为 ul.ms-rows', /className: "ms-rows"/.test(src))
{
// 「模型设置」分区内不应再出现表格(其它页面仍可用 pa-tbl)
const from = src.indexOf('function ModelSettingsSection()')
const to = src.indexOf('ctx.slots.inject("settings.section"')
const body = from >= 0 && to > from ? src.slice(from, to) : ''
ok('「模型设置」分区内无 pa-tbl 残留', body.includes('pa-tbl') === false)
}
console.log(failed === 0 ? '\n全部通过(模型设置:词典一致性 + 版式防线)' : `\n${failed} 项失败`)
process.exit(failed === 0 ? 0 : 1)