Files
dsh_shenxian/scripts/find-ui.mjs
T
admin 0cdbf52c4a chore(开源脱敏): find-ui 扫描器去掉硬编码服务器路径 + 登记档案 140
- scripts/find-ui-scan.py:PROFILE_GLOB 改由 DSHS_USERS_DIR 注入,缺失即显式失败(不再写死
  /var/lib/dshs)⇒ 补上上一轮涉密外置扫描的唯一漏项(大小写不敏感扫描 1 → 0)
- scripts/find-ui.mjs:用 config/index.cjs#usersDir() 取值并随 ssh 命令注入远端;
  解析不出 POSIX 绝对路径即报错退出(⛔ 不静默回落,避免假阴性)
- dsh-server-docs/INDEX.md:登记 04-140(涉密内容外置到配置目录)

验证:py 语法 OK / node --check OK / 缺 env 显式 exit=2 / 实跑命中 8-8 个 UI 分区(与原行为一致)
2026-09-19 15:42:56 +08:00

107 lines
6.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.
#!/usr/bin/env node
/**
* find-ui.mjs —— 「**这个 UI 分区是谁提供的、源码在哪、我能不能改**」一条命令问清楚。
*
* ## 为什么有它(2026-09-15 实录)
* 为搞清设置面板里的「用户设置」是谁渲染的,我在 6 个官方包 + 3 个自研包之间逐个 grep,
* 绕了约 30 次工具调用。根因**不是**代码烂,而是三件事叠加:
* ① 那个 label 用**转义 unicode** 存(`"\u7528\u6237\u8bbe\u7f6e"`)⇒ 按 UTF-8 grep 永远 0 命中;
* ② `portal-entry` 的**源码不在仓里**(只有服务器上的 tgz)⇒ 只能从产物反推;
* ③ **没有"哪个包提供哪个 UI"的索引**,id/order/label 散在各包里。
* 本脚本一次性解决 ①③(② 的处置:把源码补进 `poc/<name>/`,见 `07-实例UI分区登记表.md`)。
*
* ## 用法
* node scripts/find-ui.mjs # 列出全部实例 UI 分区(自研在前)
* node scripts/find-ui.mjs 用户设置 # 按关键词定位(**UTF-8 与转义形态都搜**)
* node scripts/find-ui.mjs locale --grep # 顺带打印命中行上下文
* node scripts/find-ui.mjs --md # 输出 Markdown 表格行(用于刷新 `07-实例UI分区登记表.md`)
* DSHS_SSH=other-host node scripts/find-ui.mjs # 指定 ssh 目标(默认 bt-server)
*
* ## 判据(为什么这么做才对)
* · **权威事实 = 活着的 profile 里装了什么**,不是仓里快照、不是文档库历史副本、不是备份目录;
* · 标签必须**两种形态都搜**(字面量 + `\uXXXX`),否则会像 2026-09-15 那样全域 0 命中;
* · 「能不能改」= 包在 `@dsh-local/`(我们自己的,可改)还是在 `@deepseek-ai/`(官方,R2 不可改)。
*/
import { readFileSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import cfg from '../config/index.cjs'
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
const SSH = process.env.DSHS_SSH ?? 'bt-server'
// 用户数据根:从 `config/` 读(本机权威单一来源),随 ssh 命令注入远端扫描器 ——
// ⛔ 扫描器内不写死服务器布局。解析不出来就**显式失败**,不猜、不静默(静默 0 命中 = 假阴性)。
const USERS_DIR = cfg.usersDir().replace(/\\/g, '/').replace(/\/+$/, '')
if (!USERS_DIR.startsWith('/')) {
console.error(
'!! 解析不出远端用户数据根:' + USERS_DIR + '\n' +
' 本机 config/platform.env 需含 DSHS_DATA_ROOT(或用 DSHS_USERS_DIR 覆盖),见 config/README.md。',
)
process.exit(2)
}
const argv = process.argv.slice(2)
const asMd = argv.includes('--md')
const withGrep = argv.includes('--grep')
const keyword = argv.find((a) => !a.startsWith('--')) ?? ''
// ── 1. 把扫描器喂给服务器执行(脚本不落地在服务器上)──
const scanner = readFileSync(join(ROOT, 'scripts', 'find-ui-scan.py'), 'utf8')
const r = spawnSync(
'ssh',
['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=15', '-o', 'StrictHostKeyChecking=accept-new', SSH, `DSHS_USERS_DIR='${USERS_DIR}' python3 -`],
{ input: scanner, encoding: 'utf8', maxBuffer: 1 << 24 },
)
if (r.status !== 0 && (r.stdout ?? '') === '') {
console.error('!! 远程扫描失败:' + (r.stderr || '').split('\n').filter((l) => !/post-quantum|store now|openssh/.test(l)).join('\n').slice(0, 600))
process.exit(1)
}
const rows = (r.stdout ?? '')
.split('\n')
.filter((l) => l.includes('\t'))
.map((l) => {
const [pkg, origin, file, id, order, label] = l.split('\t')
return { pkg, origin, file, id, order, label }
})
// ── 2. 关键词过滤:**字面量 + 转义形态都搜**(本脚本存在的理由之一)──
const escapeOf = (s) =>
Array.from(s).map((c) => (c.codePointAt(0) > 127 ? '\\u' + c.codePointAt(0).toString(16).padStart(4, '0') : c)).join('')
const kwEsc = escapeOf(keyword).toLowerCase()
const hit = (v) => {
const s = String(v ?? '').toLowerCase()
return s.includes(keyword.toLowerCase()) || (kwEsc !== keyword.toLowerCase() && s.includes(kwEsc))
}
const shown = keyword === '' ? rows : rows.filter((x) => hit(x.id) || hit(x.label) || hit(x.pkg))
// ── 3. 输出 ──
if (asMd) {
console.log('| order | id | label | 提供者 | 来源 |')
console.log('|---|---|---|---|---|')
for (const x of shown) console.log(`| ${x.order} | \`${x.id}\` | ${x.label} | \`${x.pkg}\` | ${x.origin} |`)
} else if (shown.length === 0) {
console.log(`没有匹配「${keyword}」的分区。共扫到 ${rows.length} 个分区:`)
for (const x of rows) console.log(` · ${x.order.toString().padStart(4)} ${x.id} [${x.label}] ${x.pkg} (${x.origin})`)
console.log('\n提示:关键词会同时按 UTF-8 与 \\uXXXX 转义搜;仍找不到 ⇒ 该文案可能不在「分区」层,去 06/07 看。')
} else {
console.log(`命中 ${shown.length} / 共 ${rows.length} 个实例 UI 分区${keyword ? `(关键词「${keyword}」)` : ''}:\n`)
for (const x of shown) {
console.log(`● ${x.label} [id=${x.id} order=${x.order}]`)
console.log(` 提供者:${x.pkg} (${x.origin} ⇒ ${x.origin === '自研' ? '✅ 可改' : '⛔ 官方,R2 不可改'})`)
console.log(` 源码 :${x.file}`)
if (withGrep) {
const g = spawnSync('ssh', ['-o', 'BatchMode=yes', SSH, `grep -n "settings.section" -A 6 "${x.file}" | head -12`], { encoding: 'utf8' })
console.log((g.stdout ?? '').split('\n').map((l) => ' | ' + l).join('\n'))
}
}
const mine = shown.filter((x) => x.origin === '自研')
if (mine.length > 0) {
console.log('\n下一步:')
console.log(' · 自研包 ⇒ **源码应在仓内 `poc/<pkg>/`**;不在 ⇒ 先把部署中的版本取出来入仓(约定:产物只从仓内源码构建)。')
console.log(' · 改完跑 `npm run verify`(含 verify-my-skills / verify-portal-entry 等结构断言),再走投放。')
console.log(' · ⛔ 改分区 ⇒ **同一次改动里刷新 `dsh-server-docs/07-实例UI分区登记表.md`**(可用 `--md` 生成表行)。')
}
}