把散落在代码里的真实部署值统一收进 config/,代码改为引用配置, 使仓库副本/开源导出不再带出生产域名、IP、内网路径与凭据。 新增 config/:platform.env.example(模板)· load.sh(shell 加载器)· index.cjs(node 加载器)· README.md(键一览与优先级)。 真实值放 config/platform.env —— 已 .gitignore 排除,不入库、不进导出。 TS 侧新增 src/platform-paths.ts 作部署路径的唯一解析处(零副作用): platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath。 config.ts 接入这些字段;内置中继种子由生产 URL 改为空(改由 DSHS_OVERLAY_BOOTSTRAP_SEEDS 提供)。修掉 5 处硬编码绝对路径, src/** 注释中性化 116 行/53 文件。 scripts/** 36 个内部运维脚本:真令牌/PG 口令/隧道目标/主机号/路径 一律改从配置取;web/wake.html 的注册域白名单改为运行时从 location.hostname 推导;test/** 夹具 119 行/13 文件改 RFC 2606/5737 保留值,并把「内置种子必须为空」固化为回归断言。 取证:tsc 0 错;npm test 373/375(唯一失败 lease 属既有); 全仓扫描(大小写不敏感)代码面涉密标识 = 0;已部署 47 并零回归 (/opt/dsh/* 未搬家,/var/lib/dshs/platform 未被误建)。
94 lines
3.8 KiB
JavaScript
94 lines
3.8 KiB
JavaScript
/**
|
||
* `web/i18n.js` 的品牌文案渲染回归(2026-09-19,档案 134 配套)。
|
||
*
|
||
* 为什么单独钉这一条:品牌标识从 DeepSeek 图形换成了**文字**(中文「能力网络」(2026-09-19
|
||
* 定名)/英文及其他语言 `CapabilityNet`)。词条漏配或 key 写错时,i18n 会**静默回退英文**
|
||
* —— 页面上看不出报错,只有中文用户看到英文名。这类"静默失败"必须靠测试钉住。
|
||
*
|
||
* 做法:用 `node:vm` 跑**仓库里那份真实的 `web/i18n.js`**(不是复制一遍逻辑),
|
||
* 配一个够用的最小 DOM 桩,断言六种语言解析路径下的**实际渲染结果**。
|
||
*
|
||
* ⚠️ 桩里必须注入 `URLSearchParams` / `URL`:`vm` 上下文只含 ECMAScript 内置、
|
||
* 不含 Web API;少了它们 `new URLSearchParams(location.search)` 会抛 ReferenceError,
|
||
* 被 i18n.js 的 try/catch 吞掉 ⇒ 表现为"`?lang=` 不生效"的**假失败**(第一版踩过)。
|
||
*/
|
||
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { readFileSync } from 'node:fs'
|
||
import vm from 'node:vm'
|
||
|
||
const SRC = readFileSync(new URL('../web/i18n.js', import.meta.url), 'utf8')
|
||
|
||
/** 造一个够 i18n.js 用的最小元素桩(只需要 getAttribute / textContent)。 */
|
||
function makeEl(attrs = {}) {
|
||
return {
|
||
_attrs: { ...attrs },
|
||
textContent: attrs.__text ?? '',
|
||
className: '',
|
||
innerHTML: '',
|
||
style: {},
|
||
getAttribute(name) {
|
||
return Object.prototype.hasOwnProperty.call(this._attrs, name) ? this._attrs[name] : null
|
||
},
|
||
setAttribute(name, value) {
|
||
this._attrs[name] = value
|
||
},
|
||
appendChild() {},
|
||
addEventListener() {},
|
||
querySelectorAll() {
|
||
return []
|
||
},
|
||
}
|
||
}
|
||
|
||
/** 在最小 DOM 桩里跑一次 i18n.js,返回品牌节点的实际渲染结果与解析出的语言。 */
|
||
function renderBrand({ search = '', cookie = '', language = 'en-US' } = {}) {
|
||
const brandNode = makeEl({ 'data-i18n': 'brand.name', __text: 'CapabilityNet' })
|
||
const sandbox = {
|
||
console,
|
||
setTimeout: () => {},
|
||
URLSearchParams,
|
||
URL,
|
||
document: {
|
||
readyState: 'complete',
|
||
cookie,
|
||
documentElement: makeEl(),
|
||
querySelectorAll(selector) {
|
||
return selector === '[data-i18n]' ? [brandNode] : []
|
||
},
|
||
createElement: () => makeEl(),
|
||
addEventListener: () => {},
|
||
},
|
||
navigator: { language, languages: [language] },
|
||
location: { search, href: `https://example.net/login.html${search}`, reload: () => {} },
|
||
window: {},
|
||
}
|
||
sandbox.window = sandbox
|
||
vm.createContext(sandbox)
|
||
vm.runInContext(SRC, sandbox, { filename: 'web/i18n.js' })
|
||
return { brand: brandNode.textContent, lang: sandbox.window.I18N.lang }
|
||
}
|
||
|
||
test('品牌文案:中文显示「能力网络」,英文及其他语言显示 CapabilityNet', () => {
|
||
const cases = [
|
||
['默认(浏览器 en-US)', { language: 'en-US' }, 'CapabilityNet'],
|
||
['中文(浏览器 zh-CN)', { language: 'zh-CN' }, '能力网络'],
|
||
['中文(?lang=zh)', { search: '?lang=zh', language: 'en-US' }, '能力网络'],
|
||
['英文(?lang=en 覆盖中文浏览器)', { search: '?lang=en', language: 'zh-CN' }, 'CapabilityNet'],
|
||
['cookie dsh_lang=zh', { cookie: 'dsh_lang=zh', language: 'en-US' }, '能力网络'],
|
||
['未支持语言(ja)⇒ 回退英文', { language: 'ja-JP' }, 'CapabilityNet'],
|
||
]
|
||
for (const [label, opts, expected] of cases) {
|
||
const got = renderBrand(opts)
|
||
assert.equal(got.brand, expected, label)
|
||
}
|
||
})
|
||
|
||
test('品牌文案:不会退化成裸 key、不留空', () => {
|
||
for (const language of ['en-US', 'zh-CN', 'ja-JP', 'de-DE']) {
|
||
const { brand } = renderBrand({ language })
|
||
assert.notEqual(brand, 'brand.name', `裸 key 泄露(${language})`)
|
||
assert.ok(brand.trim() !== '', `空文案(${language})`)
|
||
}
|
||
})
|