/** * 「临时目录写不进去」⇒ 人话。**纯函数,可被反面样本喂**。 * * # 为什么它必须是一个函数,而不是散在写点里的 try/catch * * 2026-09-14 实测:`/tmp` 是满的 tmpfs,`bavail` 一度真是 **0**。此时这条 * `npm test` 红的是 * * not ok 323 - ★巨大的 message 行不进内存也不影响解析 * error: 'ENOSPC: no space left on device, write' * * 那条红的**形状指向内存**(用例名里就写着"不进内存",而它恰好是往临时目录 * 写文件的用例)⇒ 下一个踩到的人会去 `session-scan.mjs` 找一个**不存在**的 * 内存缺陷。翻译成人话("这是环境问题,不是内存缺陷")就治这个。 * * # 为什么抽出来(只有一个调用点也值得抽) * * **不是为了复用,是为了可被反面样本喂**:`translateEnvError` 能被直接喂一个 * 构造出来的 ENOSPC 错误,验"该翻译的翻译了、不该翻译的原样返回"。 * 反例:第一版把这个判断留在 `session-scan.test.mjs` 里,判据只能靠**读源码文本** * (断言文件里出现 `/ENOSPC/`)—— 而那段解释性注释里本来就有 "ENOSPC" 这个词, * 于是**删掉整个翻译逻辑、只留注释,判据照样绿**。这正是 * `permission-note.test.mjs` 警告过的"钉装饰不钉机制"。 * * # 为什么不去和 `deploy/check-deploy-drift.mjs` 的 `describeEnvError` 合并 * * 判据相同、措辞不同,看起来该合并 —— 但不合:**`deploy/` 的独立性比去重值钱** * (那份文件头整段在讲"服务不该依赖仓库是否存在",`deploy/` 下的工具同理, * 让它 import 插件目录里的模块等于把它绑死在插件的目录结构上)。 * **第三份拷贝出现时再考虑共用。** * * # 变异自检的纪律(这条是踩出来的) * * 验证本函数"真的被接上"要注入变异(删掉调用、看判据红不红)。做这件事时: * **先证明你能撤回来,再注入**;而且**还原路径不能依赖被测对象** —— * 2026-09-14 那次把备份写进 `/tmp`(正是当时被占满的那个资源),备份没写成, * 变异却已经覆盖了源文件。现在只对"已在 HEAD 里干净提交"的文件做变异, * 还原一律走 `git checkout HEAD -- `。 * * @param {unknown} e 捕获到的错误 * @returns {{ translated: boolean, error: Error }} 翻译过的新错误,或原样返回 */ export function translateEnvError(e) { const message = e && typeof e.message === 'string' ? e.message : String(e ?? ''); const isEnospc = (e && e.code === 'ENOSPC') || /no space left on device/i.test(message); if (!isEnospc) return { translated: false, error: e }; const err = new Error( '环境不足:临时目录写不进去(ENOSPC)—— 这是环境问题,不是内存缺陷。' + '药方:TMPDIR=<有空间的目录> npm test' ); err.code = 'ENOSPC'; err.cause = e; return { translated: true, error: err }; }