dsh-selfupdater 0.4.18 → 0.4.20

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.
@@ -648,7 +648,10 @@ function apply(ctx) {
648
648
  ...data,
649
649
  message: agedMessage('dsh', data.message, FINAL_MSG_STATES.includes(data.state)),
650
650
  };
651
- upgradeStarting = false; // 服务端状态已接管视觉
651
+ // 仅当服务端给出"进行中"或终态时才撤销乐观视觉:perform 受理初期
652
+ // 状态可能仍是旧的 idle,若此时撤销会退回"发现新版本"且按钮恢复
653
+ // 可点,造成重复点击(0.4.19 实测踩坑)。
654
+ if (isBusyState(latestStatus) || FINAL_MSG_STATES.includes(data.state)) upgradeStarting = false;
652
655
  } catch { /* 服务重启期间拉不到状态属正常:保持 starting 视觉 */ }
653
656
  refresh();
654
657
  // 升级中高频轮询,空闲低频保活。
package/lib/index.js CHANGED
@@ -310,6 +310,15 @@ export function apply(ctx) {
310
310
  sendJson(res, 500, { error: `写入锁文件失败:${err.message}` });
311
311
  return;
312
312
  }
313
+ // 受理即落盘 running 状态:给前端乐观视觉一个服务端依据(转圈 + 按钮禁用),
314
+ // 避免宿主退出前轮询仍读到旧的 idle/"发现新版本" 而误判升级没发生、
315
+ // 允许重复点击。latestVersion 等历史字段原样保留,供归位判定使用。
316
+ writeStatus(dshStateDir, {
317
+ ...readStatus(dshStateDir),
318
+ state: 'running',
319
+ message: '升级已受理,服务即将切换到升级脚本…',
320
+ updatedAt: new Date().toISOString(),
321
+ });
313
322
  const child = spawn(process.execPath, [
314
323
  join(PLUGIN_ROOT, 'lib', 'updater.mjs'),
315
324
  '--pid', String(process.pid),
package/lib/updater.mjs CHANGED
@@ -280,7 +280,8 @@ async function fetchLatestRelease(pkg) {
280
280
  const errors = [];
281
281
  results.forEach((r, i) => {
282
282
  if (r.status === 'fulfilled') {
283
- if (best === null || isNewer(r.value.version, best.version)) best = r.value;
283
+ // 记录胜出版本来自哪个 registry,供 staging 安装用 --registry 同源下载
284
+ if (best === null || isNewer(r.value.version, best.version)) best = { ...r.value, base: targets[i].base };
284
285
  } else {
285
286
  errors.push(`${targets[i].base}/${targets[i].tag}: ${r.reason.message}`);
286
287
  }
@@ -289,29 +290,36 @@ async function fetchLatestRelease(pkg) {
289
290
  return best;
290
291
  }
291
292
 
292
- /** 仅取最新版本号(主流程使用)。 */
293
- async function fetchLatestVersion(pkg) {
294
- return (await fetchLatestRelease(pkg)).version;
295
- }
296
-
297
293
  /**
298
294
  * 阶段一:下载。把新版安装进独立 staging 目录。
299
295
  * 关键点:pnpm 默认用符号链接布局,搬走 node_modules 后链接会全部失效;
300
296
  * 通过 node-linker=hoisted 强制实体文件布局,保证 staging/node_modules
301
297
  * 可以整体 rename 到 APP_DIR。
298
+ * @param target - 目标版本号(精确锁定)
299
+ * @param registryBase - 版本查询胜出的 registry 源,安装与查询保持同源
302
300
  */
303
- async function downloadIntoStaging(target) {
301
+ async function downloadIntoStaging(target, registryBase) {
304
302
  rmSync(stagingDir, { recursive: true, force: true });
305
303
  mkdirSync(stagingDir, { recursive: true });
304
+ // 精确锁定版本:alpha 渠道发版快,用 ^ 范围可能解析到比 target 更新的
305
+ // 版本,撞上"staged 必须严格等于 target"的校验而误报失败。
306
306
  writeFileSync(
307
307
  join(stagingDir, 'package.json'),
308
- JSON.stringify({ name: 'dsh-selfupdate-staging', private: true, dependencies: { [PKG]: `^${target}` } }, null, 2),
308
+ JSON.stringify({ name: 'dsh-selfupdate-staging', private: true, dependencies: { [PKG]: target } }, null, 2),
309
309
  );
310
310
  // 实体文件布局 + 关闭交互确认,保证无人值守可执行。
311
311
  writeFileSync(join(stagingDir, '.npmrc'), 'node-linker=hoisted\n');
312
312
  const pnpm = pnpmCommand();
313
313
  setState('downloading', `正在下载 ${PKG}@${target} …`);
314
- const result = await runCommand(pnpm.file, [...pnpm.args, 'install', '--omit=dev', '--no-audit', '--no-fund'], {
314
+ // pnpm 不认 npm 风格的 --omit/--no-audit/--no-fund(0.4.18 实测踩坑):
315
+ // 排除 dev 依赖用 --prod;--registry 让安装与版本查询走同一个源,
316
+ // 避免"官方源不通、镜像查到版本却装不下来"的割裂。
317
+ const result = await runCommand(pnpm.file, [
318
+ ...pnpm.args,
319
+ 'install',
320
+ '--prod',
321
+ ...(registryBase ? ['--registry', registryBase] : []),
322
+ ], {
315
323
  cwd: stagingDir,
316
324
  env: { ...process.env, CI: 'true', npm_config_node_linker: 'hoisted' },
317
325
  });
@@ -420,7 +428,9 @@ async function main() {
420
428
  status = { currentVersion: current, startedAt: new Date().toISOString(), trigger: 'manual' };
421
429
  setState('downloading', '正在查询 npm 最新版本 …');
422
430
 
423
- const target = await fetchLatestVersion(PKG);
431
+ // fetchLatestRelease 除版本号外还带回胜出的 registry 源,供 staging 同源安装
432
+ const release = await fetchLatestRelease(PKG);
433
+ const target = release.version;
424
434
  status.latestVersion = target;
425
435
  if (!isNewer(target, current)) {
426
436
  setState('idle', `当前已是最新版本(${current})`);
@@ -428,7 +438,7 @@ async function main() {
428
438
  }
429
439
 
430
440
  log(`发现新版本:${current} -> ${target}`);
431
- await downloadIntoStaging(target);
441
+ await downloadIntoStaging(target, release.base);
432
442
  swapNodeModules();
433
443
 
434
444
  setState('restarting', '正在重启 DeepSeek Harness …');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-selfupdater",
3
- "version": "0.4.18",
3
+ "version": "0.4.20",
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",