dsh-selfupdater 0.3.0 → 0.3.1
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 +54 -9
- package/lib/plugin-updater.mjs +28 -6
- package/lib/updater.mjs +14 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - 升级动作通过锁文件防并发,双端校验。
|
|
11
11
|
*/
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
13
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
15
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
15
16
|
import { fileURLToPath } from 'node:url';
|
|
@@ -127,9 +128,30 @@ function resolveAppDir() {
|
|
|
127
128
|
throw new Error(`无法定位 ${PKG} 的应用目录`);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* 解析工作区目录(插件清单 .dsh/profiles 的真正落盘处)。
|
|
133
|
+
* 与 runner.js 的推导链保持一致:
|
|
134
|
+
* 1. TRIM_DATA_SHARE_PATHS 第一个共享目录(飞牛声明的工作区,runner 会优先用它);
|
|
135
|
+
* 2. HOME 环境变量(runner 启动 DSH 时把 HOME 指到工作区,DSH 的 profile 就在 $HOME/.dsh 下);
|
|
136
|
+
* 3. TRIM_VAR / appDir/data 兜底(本地开发场景)。
|
|
137
|
+
* 注意:不能只看 TRIM_VAR —— 飞牛部署时它指向 app data 目录而非共享目录,
|
|
138
|
+
* 而 DSH 实际把 profiles 写在 HOME/.dsh 下;此前只读 TRIM_VAR 导致
|
|
139
|
+
* "版本更新"卡片始终显示"未发现已安装插件"。
|
|
140
|
+
*/
|
|
131
141
|
function resolveWorkspace(appDir) {
|
|
132
|
-
|
|
142
|
+
const shares = (process.env.TRIM_DATA_SHARE_PATHS ?? '').split(':').map((s) => s.trim()).filter(Boolean);
|
|
143
|
+
const candidates = [
|
|
144
|
+
...shares,
|
|
145
|
+
process.env.HOME ?? '',
|
|
146
|
+
process.env.TRIM_VAR ?? '',
|
|
147
|
+
join(appDir, 'data'),
|
|
148
|
+
].filter((v) => v !== '');
|
|
149
|
+
// 候选目录下若已存在 .dsh 则视为工作区,立即采用;
|
|
150
|
+
// 否则按 runner.js 同样的优先级取第一个候选(与实际落盘位置保持一致)。
|
|
151
|
+
for (const dir of candidates) {
|
|
152
|
+
if (existsSync(join(dir, '.dsh'))) return resolve(dir);
|
|
153
|
+
}
|
|
154
|
+
return resolve(candidates[0] ?? appDir);
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
/** 服务端口:与 runner.js 保持一致的环境变量与默认值。 */
|
|
@@ -255,6 +277,21 @@ async function fetchPluginLatest(pkgName) {
|
|
|
255
277
|
return fetchLatestVersion(pkgName);
|
|
256
278
|
}
|
|
257
279
|
|
|
280
|
+
/**
|
|
281
|
+
* 读本插件自身的已装版本:优先 import 自己的 package.json(ESM 顶层 await
|
|
282
|
+
* 不适合这里,改用 createRequire 同步读取),失败时回退硬编码兜底值。
|
|
283
|
+
* 用途:让"检测更新"能覆盖 dsh-selfupdater 自己 —— 此前清单来自 profile
|
|
284
|
+
* 的 dependencies,但"检测自己"语义上不依赖那份清单。
|
|
285
|
+
*/
|
|
286
|
+
function readOwnVersion() {
|
|
287
|
+
try {
|
|
288
|
+
const require = createRequire(import.meta.url);
|
|
289
|
+
return require('../package.json').version;
|
|
290
|
+
} catch {
|
|
291
|
+
return '0.0.0';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
258
295
|
/* ------------------------------------------------------------------ *
|
|
259
296
|
* apply 入口
|
|
260
297
|
* ------------------------------------------------------------------ */
|
|
@@ -272,6 +309,9 @@ export function apply(ctx) {
|
|
|
272
309
|
const lockFile = join(dshStateDir, 'selfupdate.lock');
|
|
273
310
|
const pluginLockFile = join(dshStateDir, 'pluginupdate.lock');
|
|
274
311
|
const port = servicePort();
|
|
312
|
+
/** 本插件的包名与已装版本(用于"检测自己"的更新能力)。 */
|
|
313
|
+
const SELF_NAME = name;
|
|
314
|
+
const SELF_INSTALLED = readOwnVersion();
|
|
275
315
|
|
|
276
316
|
/** 升级是否正在进行(锁文件存在)。 */
|
|
277
317
|
const isBusy = () => existsSync(lockFile);
|
|
@@ -387,7 +427,11 @@ export function apply(ctx) {
|
|
|
387
427
|
});
|
|
388
428
|
|
|
389
429
|
registerRoute('GET', '/dsh-selfupdater/plugins', (_req, res) => {
|
|
390
|
-
|
|
430
|
+
// 清单 = 自己 + profile 已装插件(自己排最前,保证 UI 始终展示自身)。
|
|
431
|
+
const installed = [
|
|
432
|
+
{ name: SELF_NAME, installedVersion: SELF_INSTALLED },
|
|
433
|
+
...listInstalledPlugins(workspace).filter((p) => p.name !== SELF_NAME),
|
|
434
|
+
];
|
|
391
435
|
const status = pluginStatusView();
|
|
392
436
|
// 把上次检查得到的 latest 缓存合并进列表,UI 无需二次请求。
|
|
393
437
|
const cache = status.updates ?? {};
|
|
@@ -413,12 +457,13 @@ export function apply(ctx) {
|
|
|
413
457
|
sendJson(res, 403, { error: 'untrusted request' });
|
|
414
458
|
return;
|
|
415
459
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
//
|
|
460
|
+
// 清单 = 自己 + profile 已装插件(与 GET /plugins 保持一致)。
|
|
461
|
+
const installed = [
|
|
462
|
+
{ name: SELF_NAME, installedVersion: SELF_INSTALLED },
|
|
463
|
+
...listInstalledPlugins(workspace).filter((p) => p.name !== SELF_NAME),
|
|
464
|
+
];
|
|
465
|
+
// 并发查询所有插件(清单已保证至少包含自己,不可能为空)的最新版;
|
|
466
|
+
// 单个失败不拖垮整体。
|
|
422
467
|
const results = await Promise.allSettled(installed.map(async (p) => {
|
|
423
468
|
const latestVersion = await fetchPluginLatest(p.name);
|
|
424
469
|
return {
|
package/lib/plugin-updater.mjs
CHANGED
|
@@ -122,8 +122,20 @@ const args = parseArgs(process.argv);
|
|
|
122
122
|
|
|
123
123
|
/** DSH 应用目录(含 bin/node 与 node_modules/@deepseek-ai/dsh)。 */
|
|
124
124
|
const appDir = resolve(String(args['app-dir'] ?? ''));
|
|
125
|
-
/**
|
|
126
|
-
|
|
125
|
+
/**
|
|
126
|
+
* 工作区目录(存放 .dsh 状态目录与插件 profile)。
|
|
127
|
+
* 与 index.js 的 resolveWorkspace 推导链一致:共享目录 > HOME > TRIM_VAR > appDir。
|
|
128
|
+
* index.js 已解析出正确 workspace 并通过 --workspace 传入,这里仅在参数缺失时兜底;
|
|
129
|
+
* 不能只信 TRIM_VAR —— 飞牛部署时 profiles 实际写在 $HOME/.dsh 下。
|
|
130
|
+
*/
|
|
131
|
+
const workspace = (() => {
|
|
132
|
+
if (args.workspace) return resolve(String(args.workspace));
|
|
133
|
+
const shares = (process.env.TRIM_DATA_SHARE_PATHS ?? '').split(':').map((s) => s.trim()).filter(Boolean);
|
|
134
|
+
for (const dir of [...shares, process.env.HOME ?? '', process.env.TRIM_VAR ?? '', appDir]) {
|
|
135
|
+
if (dir !== '' && existsSync(join(dir, '.dsh'))) return resolve(dir);
|
|
136
|
+
}
|
|
137
|
+
return resolve(shares[0] ?? process.env.HOME ?? process.env.TRIM_VAR ?? appDir);
|
|
138
|
+
})();
|
|
127
139
|
/** Web 服务端口,健康检查用。 */
|
|
128
140
|
const port = parseInt(String(args.port ?? process.env.DSH_PORT ?? '3081'), 10);
|
|
129
141
|
|
|
@@ -316,18 +328,28 @@ async function main() {
|
|
|
316
328
|
// 锁文件双端校验:路由触发前会检查;这里再补一道防手动重复执行。
|
|
317
329
|
if (existsSync(lockFile)) throw new Error('已有一次插件更新在进行中(锁文件存在)');
|
|
318
330
|
|
|
319
|
-
// 读已安装清单:profile/package.json 的 dependencies
|
|
331
|
+
// 读已安装清单:profile/package.json 的 dependencies 是最权威的数据源;
|
|
332
|
+
// 再补上本插件自身(dsh-selfupdater),保证"自己也能被更新"。
|
|
320
333
|
let deps;
|
|
321
334
|
try {
|
|
322
335
|
deps = JSON.parse(readFileSync(join(profileDir, 'package.json'), 'utf8')).dependencies ?? {};
|
|
323
336
|
} catch {
|
|
324
|
-
|
|
325
|
-
finish(0);
|
|
337
|
+
deps = {};
|
|
326
338
|
}
|
|
339
|
+
/** 本插件自身的已装版本:直接读自己脚本身旁的 package.json,最可靠。 */
|
|
340
|
+
let selfVersion = '';
|
|
341
|
+
try {
|
|
342
|
+
selfVersion = JSON.parse(
|
|
343
|
+
readFileSync(new URL('./../package.json', import.meta.url), 'utf8'),
|
|
344
|
+
).version ?? '';
|
|
345
|
+
} catch { /* 读不到则跳过自更 */ }
|
|
327
346
|
const installed = Object.entries(deps).map(([name, range]) => ({
|
|
328
347
|
name,
|
|
329
348
|
version: String(range).replace(/^[~^]\s*/, ''),
|
|
330
|
-
})).filter((p) => p.version !== '');
|
|
349
|
+
})).filter((p) => p.version !== '' && p.name !== 'dsh-selfupdater');
|
|
350
|
+
if (selfVersion !== '') {
|
|
351
|
+
installed.unshift({ name: 'dsh-selfupdater', version: selfVersion });
|
|
352
|
+
}
|
|
331
353
|
|
|
332
354
|
if (installed.length === 0) {
|
|
333
355
|
setState('idle', '未发现已安装插件,无需更新');
|
package/lib/updater.mjs
CHANGED
|
@@ -130,8 +130,20 @@ const args = parseArgs(process.argv);
|
|
|
130
130
|
|
|
131
131
|
/** DSH 应用目录(含 node_modules / bin/runner.js),由插件本体解析后传入。 */
|
|
132
132
|
const appDir = resolve(String(args['app-dir'] ?? ''));
|
|
133
|
-
/**
|
|
134
|
-
|
|
133
|
+
/**
|
|
134
|
+
* 工作区目录(存放 .dsh 状态目录)。
|
|
135
|
+
* 与 index.js / plugin-updater.mjs 的推导链一致:共享目录 > HOME > TRIM_VAR > appDir。
|
|
136
|
+
* 正常情况下 index.js 已通过 --workspace 传入解析结果,这里仅兜底;
|
|
137
|
+
* 不能只信 TRIM_VAR —— 飞牛部署时 .dsh 实际写在 $HOME(工作区)下。
|
|
138
|
+
*/
|
|
139
|
+
const workspace = (() => {
|
|
140
|
+
if (args.workspace) return resolve(String(args.workspace));
|
|
141
|
+
const shares = (process.env.TRIM_DATA_SHARE_PATHS ?? '').split(':').map((s) => s.trim()).filter(Boolean);
|
|
142
|
+
for (const dir of [...shares, process.env.HOME ?? '', process.env.TRIM_VAR ?? '', appDir]) {
|
|
143
|
+
if (dir !== '' && existsSync(join(dir, '.dsh'))) return resolve(dir);
|
|
144
|
+
}
|
|
145
|
+
return resolve(shares[0] ?? process.env.HOME ?? process.env.TRIM_VAR ?? appDir);
|
|
146
|
+
})();
|
|
135
147
|
/** Web 服务端口,健康检查用。 */
|
|
136
148
|
const port = parseInt(String(args.port ?? process.env.DSH_PORT ?? '3081'), 10);
|
|
137
149
|
/** 要升级的包名。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-selfupdater",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Self-update plugin for DeepSeek Harness: check npm for newer @deepseek-ai/dsh, swap node_modules atomically, restart, health-check and roll back on failure; also supports one-click online update of installed DSH plugins. DSH 主程序与已装插件的一站式在线更新插件。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|