dsh-selfupdater 0.4.27 → 0.4.28

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.
Files changed (2) hide show
  1. package/lib/updater.mjs +43 -1
  2. 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,20 @@ async function downloadIntoStaging(target, registryBase) {
343
376
  ...(registryBase ? ['--registry', registryBase] : []),
344
377
  ], {
345
378
  cwd: stagingDir,
346
- env: { ...process.env, CI: 'true', npm_config_node_linker: 'hoisted' },
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
+ // 必须把输出尾部落盘,问题才可诊断。
391
+ if (result.stdout.trim()) log(`[pnpm] 输出尾部: ${result.stdout.trim().slice(-500).replace(/\s*\n+\s*/g, ' | ')}`);
392
+ if (result.stderr.trim()) log(`[pnpm] 告警尾部: ${result.stderr.trim().slice(-500).replace(/\s*\n+\s*/g, ' | ')}`);
351
393
  // 校验装到的确实是目标版本,防止镜像滞后悄悄装了旧版。
352
394
  const staged = JSON.parse(readFileSync(join(stagingDir, 'node_modules', PKG, 'package.json'), 'utf8')).version;
353
395
  if (staged !== target) throw new Error(`staging 版本不符:期望 ${target},实际 ${staged}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-selfupdater",
3
- "version": "0.4.27",
3
+ "version": "0.4.28",
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",