dsh-selfupdater 0.4.27 → 0.4.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/updater.mjs +69 -4
- package/package.json +1 -1
package/lib/updater.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import net from 'node:net';
|
|
|
15
15
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync
|
|
16
16
|
, rmSync, writeFileSync } from 'node:fs';
|
|
17
17
|
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
18
19
|
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { applyFnosPatches } from './fnos-patches.mjs';
|
|
20
21
|
|
|
@@ -227,6 +228,35 @@ function pnpmCommand() {
|
|
|
227
228
|
throw new Error('找不到随应用打包的 pnpm,无法在 staging 安装新版');
|
|
228
229
|
}
|
|
229
230
|
|
|
231
|
+
/**
|
|
232
|
+
* 确保 koffi 编译所需的 cmake 可用:koffi 的 cnoke 构建系统依赖 CMake >= 3.10,
|
|
233
|
+
* 其 npm 包内只有编译头文件、没有预编译二进制,NAS 缺 cmake 时脚本会静默失败
|
|
234
|
+
* (pnpm 对失败的构建脚本仅告警不拦截),导致换装后的宿主 boot 崩溃。
|
|
235
|
+
* - 已存在 → 返回 null(无需注入 PATH);
|
|
236
|
+
* - 缺失 → 尝试 python3 -m pip install --user cmake(Debian 12+ 需
|
|
237
|
+
* --break-system-packages 绕过 PEP 668),成功后把 ~/.local/bin 前置进 PATH;
|
|
238
|
+
* - 仍不可用 → 抛错并给出手动安装指引(此时尚未开始下载,升级干净中止)。
|
|
239
|
+
* @returns {Promise<string|null>} 需要前置到 PATH 的目录(无需时为 null)
|
|
240
|
+
*/
|
|
241
|
+
async function ensureCmake() {
|
|
242
|
+
const localBin = join(process.env.HOME || homedir(), '.local', 'bin');
|
|
243
|
+
const probe = (extraEnv) => runCommand('cmake', ['--version'],
|
|
244
|
+
extraEnv ? { env: { ...process.env, ...extraEnv } } : {});
|
|
245
|
+
if ((await probe()).code === 0) return null;
|
|
246
|
+
log('[preflight] 未检测到 cmake(koffi 编译必需),尝试 python3 -m pip install --user cmake …');
|
|
247
|
+
for (const extra of [['--break-system-packages'], []]) {
|
|
248
|
+
const r = await runCommand('python3', ['-m', 'pip', 'install', '--user', 'cmake', ...extra], {});
|
|
249
|
+
if (r.code === 0) break;
|
|
250
|
+
log(`[preflight] pip 安装 cmake 失败: ${(r.stderr || r.stdout || '').trim().slice(-200)}`);
|
|
251
|
+
}
|
|
252
|
+
const localEnv = { PATH: `${localBin}:${process.env.PATH ?? ''}` };
|
|
253
|
+
if ((await probe(localEnv)).code === 0) {
|
|
254
|
+
log('[preflight] cmake 已通过 pip --user 安装并注入本次安装的 PATH');
|
|
255
|
+
return localBin;
|
|
256
|
+
}
|
|
257
|
+
throw new Error('NAS 缺少 cmake(koffi 编译必需,自动安装失败)。请 SSH 执行: sudo apt-get update && sudo apt-get install -y cmake 后重试升级');
|
|
258
|
+
}
|
|
259
|
+
|
|
230
260
|
/* ------------------------------------------------------------------ *
|
|
231
261
|
* 升级各阶段
|
|
232
262
|
* ------------------------------------------------------------------ */
|
|
@@ -332,6 +362,9 @@ async function downloadIntoStaging(target, registryBase) {
|
|
|
332
362
|
// 构建产物跨版本污染),保证无人值守可执行。
|
|
333
363
|
writeFileSync(join(stagingDir, '.npmrc'), 'node-linker=hoisted\nside-effects-cache=false\n');
|
|
334
364
|
const pnpm = pnpmCommand();
|
|
365
|
+
// koffi 需要 cmake 才能编译(fs-ext 走 node-gyp,工具链通常已随系统就绪);
|
|
366
|
+
// 缺失时先尝试自动补齐,避免装完才发现缺产物再回滚一次。
|
|
367
|
+
const cmakePath = await ensureCmake();
|
|
335
368
|
setState('downloading', `正在下载 ${PKG}@${target} …`);
|
|
336
369
|
// pnpm 不认 npm 风格的 --omit/--no-audit/--no-fund(0.4.18 实测踩坑):
|
|
337
370
|
// 排除 dev 依赖用 --prod;--registry 让安装与版本查询走同一个源,
|
|
@@ -343,11 +376,21 @@ async function downloadIntoStaging(target, registryBase) {
|
|
|
343
376
|
...(registryBase ? ['--registry', registryBase] : []),
|
|
344
377
|
], {
|
|
345
378
|
cwd: stagingDir,
|
|
346
|
-
env: {
|
|
379
|
+
env: {
|
|
380
|
+
...process.env,
|
|
381
|
+
CI: 'true',
|
|
382
|
+
npm_config_node_linker: 'hoisted',
|
|
383
|
+
...(cmakePath ? { PATH: `${cmakePath}:${process.env.PATH ?? ''}` } : {}),
|
|
384
|
+
},
|
|
347
385
|
});
|
|
348
386
|
if (result.code !== 0) {
|
|
349
387
|
throw new Error(`staging 安装失败(pnpm exit ${result.code}): ${result.stderr.slice(-400)}`);
|
|
350
388
|
}
|
|
389
|
+
// pnpm 对失败的构建脚本仅告警不拦截(cnoke 缺 cmake 时就是这样静默失败的),
|
|
390
|
+
// 必须把输出尾部落盘,问题才可诊断。尾部保留 3000 字符:500 字符在多包
|
|
391
|
+
// 并行构建时会截掉关键包的输出(0.4.28 排查 koffi 时被截断坑了一把)。
|
|
392
|
+
if (result.stdout.trim()) log(`[pnpm] 输出尾部: ${result.stdout.trim().slice(-3000).replace(/\s*\n+\s*/g, ' | ')}`);
|
|
393
|
+
if (result.stderr.trim()) log(`[pnpm] 告警尾部: ${result.stderr.trim().slice(-3000).replace(/\s*\n+\s*/g, ' | ')}`);
|
|
351
394
|
// 校验装到的确实是目标版本,防止镜像滞后悄悄装了旧版。
|
|
352
395
|
const staged = JSON.parse(readFileSync(join(stagingDir, 'node_modules', PKG, 'package.json'), 'utf8')).version;
|
|
353
396
|
if (staged !== target) throw new Error(`staging 版本不符:期望 ${target},实际 ${staged}`);
|
|
@@ -356,16 +399,38 @@ async function downloadIntoStaging(target, registryBase) {
|
|
|
356
399
|
verifyNativeModules();
|
|
357
400
|
}
|
|
358
401
|
|
|
402
|
+
/**
|
|
403
|
+
* koffi 原生二进制存在性校验(2.x / 3.x 双结构兼容)。
|
|
404
|
+
* - 2.x:cnoke 把 prebuild 解压到 koffi/build/koffi/<平台>/koffi.node;
|
|
405
|
+
* - 3.x 起架构重构:二进制改为 @koromix/koffi-<os>-<arch> 平台子包分发
|
|
406
|
+
* (esbuild 模式),主包 build/ 下不再落任何 .node —— 只认老路径会把
|
|
407
|
+
* 装好的 3.x 误判为缺失(0.4.28 实测踩坑,升级被自家校验拦下)。
|
|
408
|
+
* 任一结构命中即视为齐备。
|
|
409
|
+
*/
|
|
410
|
+
function koffiBinaryFound() {
|
|
411
|
+
// 2.x 老结构:build/ 下递归找 .node
|
|
412
|
+
if (findFileByExt(join(stagingDir, 'node_modules', 'koffi', 'build'), '.node')) return true;
|
|
413
|
+
// 3.x 新结构:@koromix/koffi-<os>-<arch> 平台子包任一含 .node 即通过
|
|
414
|
+
try {
|
|
415
|
+
const koromixDir = join(stagingDir, 'node_modules', '@koromix');
|
|
416
|
+
for (const entry of readdirSync(koromixDir, { withFileTypes: true })) {
|
|
417
|
+
if (entry.isDirectory() && entry.name.startsWith('koffi-')
|
|
418
|
+
&& findFileByExt(join(koromixDir, entry.name), '.node')) return true;
|
|
419
|
+
}
|
|
420
|
+
} catch { /* 无 @koromix 目录:2.x 或平台包未装上 */ }
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
359
424
|
/**
|
|
360
425
|
* 校验 staging 内原生模块的构建产物是否齐全。
|
|
361
|
-
* - koffi
|
|
426
|
+
* - koffi:见 koffiBinaryFound(2.x/3.x 双结构兼容);
|
|
362
427
|
* - fs-ext:node-gyp 编译,产物在 build/Release/fs-ext.node(NAS 需编译工具链)。
|
|
363
428
|
* 任一缺失即抛错,终止本次升级(尚未换装,旧版原样运行,天然安全)。
|
|
364
429
|
*/
|
|
365
430
|
function verifyNativeModules() {
|
|
366
431
|
const problems = [];
|
|
367
|
-
if (!
|
|
368
|
-
problems.push('koffi 缺少原生二进制(install
|
|
432
|
+
if (!koffiBinaryFound()) {
|
|
433
|
+
problems.push('koffi 缺少原生二进制(install 脚本未执行或平台子包未装上)');
|
|
369
434
|
}
|
|
370
435
|
const fsExtDir = join(stagingDir, 'node_modules', 'fs-ext');
|
|
371
436
|
if (!existsSync(join(fsExtDir, 'fs-ext.js'))) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-selfupdater",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.29",
|
|
4
4
|
"description": "Self-update plugin for DeepSeek Harness: DSH core upgrades via detached swap script; plugin self-update installs in-place without killing the host and prompts for restart. DSH 主程序与已装插件的一站式在线更新插件。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|