dsh-selfupdater 0.4.25 → 0.4.27

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.
@@ -107,8 +107,9 @@ function patchCode(code, filePath) {
107
107
  * 任何关键补丁未命中都抛错——调用方(updater.mjs)在换装前中止升级,
108
108
  * 旧版保持原样,天然安全。
109
109
  * @param {string} appRoot - 含 node_modules 的应用根目录(staging 与其同构)
110
+ * @param {(msg: string) => void} [log] - 可选日志函数(升级脚本传入后写入 selfupdate.log)
110
111
  */
111
- export function applyFnosPatches(appRoot) {
112
+ export function applyFnosPatches(appRoot, log = () => {}) {
112
113
  const at = join(appRoot, 'node_modules', '@deepseek-ai');
113
114
  if (!existsSync(at)) throw new Error('找不到 node_modules/@deepseek-ai,staging 结构异常');
114
115
 
@@ -139,5 +140,5 @@ export function applyFnosPatches(appRoot) {
139
140
  if (missed.length > 0) {
140
141
  throw new Error(`关键补丁未命中: ${missed.join('、')}(上游代码可能已变更,需同步更新 fnos-patches.mjs 与 patch.py)`);
141
142
  }
142
- console.log(`[fnos-patches] 补丁应用完成: ${patched} 个文件, 关键标记全部命中`);
143
+ log(`[fnos-patches] 补丁应用完成: ${patched} 个文件, 关键标记全部命中`);
143
144
  }
package/lib/updater.mjs CHANGED
@@ -12,7 +12,8 @@
12
12
  */
13
13
  import { spawn } from 'node:child_process';
14
14
  import net from 'node:net';
15
- import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync
16
+ , rmSync, writeFileSync } from 'node:fs';
16
17
  import { dirname, join, resolve } from 'node:path';
17
18
  import { fileURLToPath } from 'node:url';
18
19
  import { applyFnosPatches } from './fnos-patches.mjs';
@@ -317,10 +318,19 @@ async function downloadIntoStaging(target, registryBase) {
317
318
  // 版本,撞上"staged 必须严格等于 target"的校验而误报失败。
318
319
  writeFileSync(
319
320
  join(stagingDir, 'package.json'),
320
- JSON.stringify({ name: 'dsh-selfupdate-staging', private: true, dependencies: { [PKG]: target } }, null, 2),
321
+ // pnpm.onlyBuiltDependencies 白名单放行原生包的 install 脚本:pnpm 10 默认
322
+ // 拦截构建脚本,koffi(cnoke 解压 vendor 预编译)和 fs-ext(node-gyp 编译)
323
+ // 不跑脚本就没有 native 产物,dsh 0.1.3-alpha.2 起依赖它们,boot 直接崩。
324
+ JSON.stringify({
325
+ name: 'dsh-selfupdate-staging',
326
+ private: true,
327
+ dependencies: { [PKG]: target },
328
+ pnpm: { onlyBuiltDependencies: ['koffi', 'fs-ext'] },
329
+ }, null, 2),
321
330
  );
322
- // 实体文件布局 + 关闭交互确认,保证无人值守可执行。
323
- writeFileSync(join(stagingDir, '.npmrc'), 'node-linker=hoisted\n');
331
+ // 实体文件布局 + 关闭交互确认 + 关闭 side-effects 缓存(防 store 里旧版本的
332
+ // 构建产物跨版本污染),保证无人值守可执行。
333
+ writeFileSync(join(stagingDir, '.npmrc'), 'node-linker=hoisted\nside-effects-cache=false\n');
324
334
  const pnpm = pnpmCommand();
325
335
  setState('downloading', `正在下载 ${PKG}@${target} …`);
326
336
  // pnpm 不认 npm 风格的 --omit/--no-audit/--no-fund(0.4.18 实测踩坑):
@@ -341,6 +351,39 @@ async function downloadIntoStaging(target, registryBase) {
341
351
  // 校验装到的确实是目标版本,防止镜像滞后悄悄装了旧版。
342
352
  const staged = JSON.parse(readFileSync(join(stagingDir, 'node_modules', PKG, 'package.json'), 'utf8')).version;
343
353
  if (staged !== target) throw new Error(`staging 版本不符:期望 ${target},实际 ${staged}`);
354
+ // 原生模块自检:0.1.3-alpha.2 起 dsh 依赖 koffi/fs-ext,缺 native 产物会让
355
+ // 宿主 boot 崩溃(0.4.25 实测踩坑),换装前在此拦截。
356
+ verifyNativeModules();
357
+ }
358
+
359
+ /**
360
+ * 校验 staging 内原生模块的构建产物是否齐全。
361
+ * - koffi:cnoke --prebuild 从 vendor 解压,产物在 build/koffi/ 下的 .node;
362
+ * - fs-ext:node-gyp 编译,产物在 build/Release/fs-ext.node(NAS 需编译工具链)。
363
+ * 任一缺失即抛错,终止本次升级(尚未换装,旧版原样运行,天然安全)。
364
+ */
365
+ function verifyNativeModules() {
366
+ const problems = [];
367
+ if (!findFileByExt(join(stagingDir, 'node_modules', 'koffi', 'build'), '.node')) {
368
+ problems.push('koffi 缺少原生二进制(install 脚本未执行或 prebuild 解压失败)');
369
+ }
370
+ const fsExtDir = join(stagingDir, 'node_modules', 'fs-ext');
371
+ if (!existsSync(join(fsExtDir, 'fs-ext.js'))) {
372
+ problems.push('fs-ext 包文件不完整(缺少 fs-ext.js)');
373
+ } else if (!findFileByExt(join(fsExtDir, 'build'), '.node')) {
374
+ problems.push('fs-ext 缺少编译产物 build/Release/fs-ext.node(NAS 需要 python3/make/g++ 工具链)');
375
+ }
376
+ if (problems.length > 0) throw new Error(`原生模块校验失败:${problems.join(';')}`);
377
+ }
378
+
379
+ /** 递归查找目录下第一个指定扩展名的文件,目录不存在或未找到返回 null。 */
380
+ function findFileByExt(dir, ext) {
381
+ try {
382
+ for (const entry of readdirSync(dir, { recursive: true })) {
383
+ if (entry.endsWith(ext)) return join(dir, entry);
384
+ }
385
+ } catch { /* 目录不存在 */ }
386
+ return null;
344
387
  }
345
388
 
346
389
  /**
@@ -486,7 +529,7 @@ async function main() {
486
529
  // staging 补齐。任何关键补丁未命中都在此抛错中止——此时还没换装,旧版
487
530
  // 原样保留,天然安全。
488
531
  setState('downloading', '正在应用飞牛运行时补丁 …');
489
- applyFnosPatches(stagingDir);
532
+ applyFnosPatches(stagingDir, (msg) => log(msg));
490
533
 
491
534
  swapNodeModules();
492
535
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-selfupdater",
3
- "version": "0.4.25",
3
+ "version": "0.4.27",
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",