dsh-selfupdater 0.4.17 → 0.4.18

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/index.js CHANGED
@@ -14,7 +14,7 @@
14
14
  * - 升级动作通过锁文件防并发,双端校验。
15
15
  */
16
16
  import { spawn } from 'node:child_process';
17
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
18
18
  import { dirname, join, resolve } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
20
  import { fetchLatestVersion, installedSelfVersion, isNewer, runPluginUpdateTask } from './plugin-update-task.mjs';
@@ -286,11 +286,22 @@ export function apply(ctx) {
286
286
  return;
287
287
  }
288
288
  // 并发防护:锁文件已存在说明有升级在跑(或上次异常残留未清理)。
289
+ // 残留判定:锁内容损坏,或 startedAt 超过 15 分钟(升级脚本总看门狗
290
+ // 仅 8 分钟,超过必然已死)—— 自动清理放行,避免强杀后永久 409。
289
291
  if (isBusy()) {
290
- sendJson(res, 409, { error: '已有一次升级在进行中' });
291
- return;
292
+ let stale = false;
293
+ try {
294
+ const prev = JSON.parse(readFileSync(lockFile, 'utf8'));
295
+ stale = Date.now() - Number(prev.startedAt ?? 0) > 15 * 60 * 1000;
296
+ } catch { stale = true; }
297
+ if (!stale) {
298
+ sendJson(res, 409, { error: '已有一次升级在进行中' });
299
+ return;
300
+ }
301
+ try { rmSync(lockFile, { force: true }); } catch { /* 清不掉则下次再试 */ }
292
302
  }
293
- // 预置锁文件:updater.mjs 启动后会校验它存在才继续。
303
+ // 预置锁文件:作为并发防护;updater.mjs 见到 by=plugin 的新鲜锁会
304
+ // 视为合法握手接管(见 updater.mjs main 的锁文件握手注释)。
294
305
  // 先确保 .dsh 目录存在(首次安装/手动清理后可能尚无该目录,教训来自插件写状态 ENOENT 问题)。
295
306
  try {
296
307
  mkdirSync(dshStateDir, { recursive: true });
package/lib/updater.mjs CHANGED
@@ -403,9 +403,19 @@ async function main() {
403
403
  mkdirSync(dshStateDir, { recursive: true });
404
404
  const current = currentDshVersion();
405
405
 
406
- // 锁文件双端校验:插件本体触发前会检查;这里再补一道防手动重复执行。
407
- if (existsSync(lockFile)) throw new Error('已有一次升级在进行中(锁文件存在)');
408
- writeFileSync(lockFile, JSON.stringify({ pid: process.pid, startedAt: Date.now() }));
406
+ // 锁文件握手:插件本体 POST /perform 会预写锁文件(by=plugin)作为并发
407
+ // 防护,本脚本见到它应视为合法交接直接接管 —— 旧实现"存在即拒绝"与
408
+ // 插件端的预写约定自相矛盾,导致升级必然秒败(0.4.17 实测踩坑)。
409
+ // 仅当锁是陈旧残留(超过 10 分钟)或来源不明时才判定为重复执行。
410
+ if (existsSync(lockFile)) {
411
+ let handover = false;
412
+ try {
413
+ const prev = JSON.parse(readFileSync(lockFile, 'utf8'));
414
+ handover = prev?.by === 'plugin' && Date.now() - Number(prev.startedAt ?? 0) < 10 * 60 * 1000;
415
+ } catch { handover = false; }
416
+ if (!handover) throw new Error('已有一次升级在进行中(锁文件存在)');
417
+ }
418
+ writeFileSync(lockFile, JSON.stringify({ pid: process.pid, startedAt: Date.now(), by: 'updater' }));
409
419
 
410
420
  status = { currentVersion: current, startedAt: new Date().toISOString(), trigger: 'manual' };
411
421
  setState('downloading', '正在查询 npm 最新版本 …');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-selfupdater",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
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",