dsh-selfupdater 0.4.29 → 0.4.31
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 +65 -14
- package/package.json +1 -1
package/lib/updater.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* node updater.mjs --pid <DSH主进程PID> --app-dir <APP目录> \
|
|
11
11
|
* --workspace <工作区目录> --port <服务端口> [--pkg @deepseek-ai/dsh]
|
|
12
12
|
*/
|
|
13
|
-
import { spawn } from 'node:child_process';
|
|
13
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
14
14
|
import net from 'node:net';
|
|
15
15
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync
|
|
16
16
|
, rmSync, writeFileSync } from 'node:fs';
|
|
@@ -218,14 +218,45 @@ function runCommand(file, cmdArgs, opts = {}) {
|
|
|
218
218
|
});
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* 定位 staging 安装用的 pnpm。
|
|
223
|
+
*
|
|
224
|
+
* 为什么优先系统 PATH 而非自带 pnpm:DSH CLI 装卸插件是裸 spawnSync("pnpm")
|
|
225
|
+
* (无版本固定、无 store 参数),用的是系统 PATH 里的 pnpm。升级换装的
|
|
226
|
+
* node_modules 若由另一个 pnpm(如 FPK 自带的 10.x)安装,node_modules/
|
|
227
|
+
* .modules.yaml 记录的 store 就与 DSH CLI 推导的不一致,pnpm 11 起会以
|
|
228
|
+
* ERR_PNPM_UNEXPECTED_STORE 拒绝一切插件装卸(0.4.30 实测踩坑,即插件市场
|
|
229
|
+
* 报"node_modules 链接到的 store 和当前 pnpm 默认使用的不一致")。
|
|
230
|
+
* 用与 DSH CLI 同源的 pnpm 安装,store 推导天然一致。
|
|
231
|
+
*
|
|
232
|
+
* @returns {{file: string, args: string[], pathPrefix: string|null}}
|
|
233
|
+
* pathPrefix 为 install 子进程需要前置到 PATH 的目录(探测时多看的
|
|
234
|
+
* 常见 bin 目录,保证 spawn('pnpm') 能解析到同一个 pnpm;无需时为 null)。
|
|
235
|
+
*/
|
|
222
236
|
function pnpmCommand() {
|
|
223
|
-
//
|
|
237
|
+
// 探测 PATH 里的 pnpm:前置常见 bin 目录(DSH 进程的 PATH 可能不含它们,
|
|
238
|
+
// 对齐 dshmarket extraPathDirs 的思路——npm 全局 bin、用户级安装位置)。
|
|
239
|
+
const localBin = join(process.env.HOME || homedir(), '.local', 'bin');
|
|
240
|
+
const probe = spawnSync('pnpm', ['--version'], {
|
|
241
|
+
env: { ...process.env, PATH: `${localBin}:${process.env.PATH ?? ''}` },
|
|
242
|
+
shell: process.platform === 'win32',
|
|
243
|
+
encoding: 'utf-8',
|
|
244
|
+
timeout: 15000,
|
|
245
|
+
});
|
|
246
|
+
if (probe.status === 0 && probe.stdout?.trim()) {
|
|
247
|
+
log(`[preflight] staging 使用系统 PATH 的 pnpm ${probe.stdout.trim().split(/\s+/).pop()}`
|
|
248
|
+
+ '(与 DSH CLI 插件装卸同源,store 推导一致)');
|
|
249
|
+
return { file: 'pnpm', args: [], pathPrefix: localBin };
|
|
250
|
+
}
|
|
251
|
+
// 回退:随应用打包的 pnpm(构建时固定安装于 APP_DIR,见 build.sh 步骤 3)。
|
|
252
|
+
// 系统 PATH 没有 pnpm 时 DSH CLI 自己也会装不上插件,此时 store 的一致性
|
|
253
|
+
// 无从谈起,先保证升级本身可用。
|
|
254
|
+
log('[preflight] 系统 PATH 未探测到 pnpm,回退使用随应用打包的 pnpm');
|
|
224
255
|
const cjs = join(appDir, 'lib', 'node_modules', 'pnpm', 'bin', 'pnpm.cjs');
|
|
225
|
-
if (existsSync(cjs)) return { file: process.execPath, args: [cjs] };
|
|
256
|
+
if (existsSync(cjs)) return { file: process.execPath, args: [cjs], pathPrefix: null };
|
|
226
257
|
const bin = join(appDir, 'bin', 'pnpm');
|
|
227
|
-
if (existsSync(bin)) return { file: bin, args: [] };
|
|
228
|
-
throw new Error('
|
|
258
|
+
if (existsSync(bin)) return { file: bin, args: [], pathPrefix: null };
|
|
259
|
+
throw new Error('系统 PATH 与应用目录都找不到 pnpm,无法在 staging 安装新版');
|
|
229
260
|
}
|
|
230
261
|
|
|
231
262
|
/**
|
|
@@ -369,6 +400,9 @@ async function downloadIntoStaging(target, registryBase) {
|
|
|
369
400
|
// pnpm 不认 npm 风格的 --omit/--no-audit/--no-fund(0.4.18 实测踩坑):
|
|
370
401
|
// 排除 dev 依赖用 --prod;--registry 让安装与版本查询走同一个源,
|
|
371
402
|
// 避免"官方源不通、镜像查到版本却装不下来"的割裂。
|
|
403
|
+
// PATH 前缀合成:pnpm 探测与 cmake 注入可能各自带目录,去重后统一前置,
|
|
404
|
+
// 保证 install 子进程能解析到与探测相同的 pnpm。
|
|
405
|
+
const pathPrefixes = [...new Set([pnpm.pathPrefix, cmakePath].filter(Boolean))];
|
|
372
406
|
const result = await runCommand(pnpm.file, [
|
|
373
407
|
...pnpm.args,
|
|
374
408
|
'install',
|
|
@@ -380,7 +414,9 @@ async function downloadIntoStaging(target, registryBase) {
|
|
|
380
414
|
...process.env,
|
|
381
415
|
CI: 'true',
|
|
382
416
|
npm_config_node_linker: 'hoisted',
|
|
383
|
-
...(
|
|
417
|
+
...(pathPrefixes.length > 0
|
|
418
|
+
? { PATH: `${pathPrefixes.join(':')}:${process.env.PATH ?? ''}` }
|
|
419
|
+
: {}),
|
|
384
420
|
},
|
|
385
421
|
});
|
|
386
422
|
if (result.code !== 0) {
|
|
@@ -391,6 +427,14 @@ async function downloadIntoStaging(target, registryBase) {
|
|
|
391
427
|
// 并行构建时会截掉关键包的输出(0.4.28 排查 koffi 时被截断坑了一把)。
|
|
392
428
|
if (result.stdout.trim()) log(`[pnpm] 输出尾部: ${result.stdout.trim().slice(-3000).replace(/\s*\n+\s*/g, ' | ')}`);
|
|
393
429
|
if (result.stderr.trim()) log(`[pnpm] 告警尾部: ${result.stderr.trim().slice(-3000).replace(/\s*\n+\s*/g, ' | ')}`);
|
|
430
|
+
// 诊断留痕:.modules.yaml 记录本次安装实际链接的 store。它与 DSH CLI
|
|
431
|
+
// (系统 PATH 的 pnpm)推导的 store 不一致时,插件装卸会被
|
|
432
|
+
// ERR_PNPM_UNEXPECTED_STORE 整体拒绝——留此日志一眼核对错位。
|
|
433
|
+
try {
|
|
434
|
+
const storeDir = /^storeDir:\s*(.+)$/m
|
|
435
|
+
.exec(readFileSync(join(stagingDir, 'node_modules', '.modules.yaml'), 'utf8'))?.[1];
|
|
436
|
+
if (storeDir) log(`[pnpm] staging node_modules 链接的 store: ${storeDir.trim()}`);
|
|
437
|
+
} catch { /* .modules.yaml 缺失或不可读:留痕失败不影响升级 */ }
|
|
394
438
|
// 校验装到的确实是目标版本,防止镜像滞后悄悄装了旧版。
|
|
395
439
|
const staged = JSON.parse(readFileSync(join(stagingDir, 'node_modules', PKG, 'package.json'), 'utf8')).version;
|
|
396
440
|
if (staged !== target) throw new Error(`staging 版本不符:期望 ${target},实际 ${staged}`);
|
|
@@ -425,18 +469,25 @@ function koffiBinaryFound() {
|
|
|
425
469
|
* 校验 staging 内原生模块的构建产物是否齐全。
|
|
426
470
|
* - koffi:见 koffiBinaryFound(2.x/3.x 双结构兼容);
|
|
427
471
|
* - fs-ext:node-gyp 编译,产物在 build/Release/fs-ext.node(NAS 需编译工具链)。
|
|
428
|
-
*
|
|
472
|
+
*
|
|
473
|
+
* 校验语义是"依赖树里有则必须完整",而非"必须有":上游会调整依赖
|
|
474
|
+
* (0.1.5-alpha.2 起移除了 fs-ext),无条件校验会对本就不存在的包误报,
|
|
475
|
+
* 把成功的升级拦下(0.4.29 实测踩坑)。任一缺失即抛错终止本次升级
|
|
476
|
+
* (尚未换装,旧版原样运行,天然安全)。
|
|
429
477
|
*/
|
|
430
478
|
function verifyNativeModules() {
|
|
431
479
|
const problems = [];
|
|
432
|
-
|
|
480
|
+
const nmDir = join(stagingDir, 'node_modules');
|
|
481
|
+
if (existsSync(join(nmDir, 'koffi')) && !koffiBinaryFound()) {
|
|
433
482
|
problems.push('koffi 缺少原生二进制(install 脚本未执行或平台子包未装上)');
|
|
434
483
|
}
|
|
435
|
-
const fsExtDir = join(
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
484
|
+
const fsExtDir = join(nmDir, 'fs-ext');
|
|
485
|
+
if (existsSync(fsExtDir)) {
|
|
486
|
+
if (!existsSync(join(fsExtDir, 'fs-ext.js'))) {
|
|
487
|
+
problems.push('fs-ext 包文件不完整(缺少 fs-ext.js)');
|
|
488
|
+
} else if (!findFileByExt(join(fsExtDir, 'build'), '.node')) {
|
|
489
|
+
problems.push('fs-ext 缺少编译产物 build/Release/fs-ext.node(NAS 需要 python3/make/g++ 工具链)');
|
|
490
|
+
}
|
|
440
491
|
}
|
|
441
492
|
if (problems.length > 0) throw new Error(`原生模块校验失败:${problems.join(';')}`);
|
|
442
493
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-selfupdater",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.31",
|
|
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",
|