@michengai/dsh-archive-manager 1.0.13 → 1.0.15
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/CHANGELOG.md +6 -0
- package/CHANGELOG.zh-CN.md +6 -0
- package/lib/client.js +46 -46
- package/lib/plugin-updater.js +156 -26
- package/package.json +1 -1
package/lib/plugin-updater.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
-
import { basename, dirname, isAbsolute, resolve } from "node:path";
|
|
7
|
+
import { basename, delimiter, dirname, isAbsolute, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
const PLUGIN_UPDATE_HEADER = "x-michengai-plugin-update";
|
|
10
10
|
const PLUGIN_UPDATE_IPC = "apply-plugin-updates";
|
|
@@ -21,9 +21,16 @@ function isTrustedUpdateRequest(request) {
|
|
|
21
21
|
if (!isLoopbackAddress(request.socket?.remoteAddress)) return false;
|
|
22
22
|
const site = header(request, "sec-fetch-site");
|
|
23
23
|
if (site !== void 0 && site !== "same-origin") return false;
|
|
24
|
-
const origin = header(request, "origin");
|
|
25
24
|
const host = header(request, "host");
|
|
26
|
-
if (
|
|
25
|
+
if (host === void 0 || host === "") return false;
|
|
26
|
+
const origin = header(request, "origin");
|
|
27
|
+
if (origin === void 0 || origin === "") {
|
|
28
|
+
try {
|
|
29
|
+
return isLoopbackAddress(new URL(`http://${host}`).hostname);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
27
34
|
try {
|
|
28
35
|
const url = new URL(origin);
|
|
29
36
|
return (url.protocol === "http:" || url.protocol === "https:") && isLoopbackAddress(url.hostname) && url.host === host;
|
|
@@ -48,14 +55,14 @@ function isDshCliEntry(entry, manifest, packageRoot) {
|
|
|
48
55
|
const bin = typeof item.bin === "string" ? item.bin : typeof item.bin === "object" && item.bin !== null ? "dsh" in item.bin ? item.bin.dsh : void 0 : void 0;
|
|
49
56
|
return typeof bin === "string" && bin !== "" && !isAbsolute(bin) && resolve(packageRoot, bin) === resolve(entry);
|
|
50
57
|
}
|
|
51
|
-
function cliEntry() {
|
|
52
|
-
const value =
|
|
58
|
+
function cliEntry(argv = process.argv, cwd = process.cwd(), exists = existsSync) {
|
|
59
|
+
const value = argv[1];
|
|
53
60
|
if (value === void 0 || value === "") return void 0;
|
|
54
|
-
const entry = value.startsWith("file:") ? fileURLToPath(value) : resolve(
|
|
55
|
-
if (!
|
|
61
|
+
const entry = value.startsWith("file:") ? fileURLToPath(value) : resolve(cwd, value);
|
|
62
|
+
if (!exists(entry)) return void 0;
|
|
56
63
|
for (let directory = dirname(entry); ; ) {
|
|
57
64
|
const manifestPath = resolve(directory, "package.json");
|
|
58
|
-
if (
|
|
65
|
+
if (exists(manifestPath)) {
|
|
59
66
|
try {
|
|
60
67
|
if (isDshCliEntry(entry, JSON.parse(readFileSync(manifestPath, "utf8")), directory)) return entry;
|
|
61
68
|
} catch {
|
|
@@ -66,21 +73,98 @@ function cliEntry() {
|
|
|
66
73
|
directory = parent;
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
function optionalService(ctx, name) {
|
|
77
|
+
try {
|
|
78
|
+
return typeof ctx.get === "function" ? ctx.get(name) : void 0;
|
|
79
|
+
} catch {
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isOfficialDesktopHostEntry(entry) {
|
|
84
|
+
if (entry === void 0 || entry === "") return false;
|
|
85
|
+
return entry.replaceAll("\\", "/").includes("/dsh-desktop-host/");
|
|
86
|
+
}
|
|
87
|
+
function stringEnvironment(value) {
|
|
88
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
89
|
+
const env = {};
|
|
90
|
+
for (const [key, item] of Object.entries(value)) {
|
|
91
|
+
if (typeof item === "string") env[key] = item;
|
|
92
|
+
}
|
|
93
|
+
return Object.keys(env).length === 0 ? void 0 : env;
|
|
94
|
+
}
|
|
95
|
+
function packageManagerFrom(value) {
|
|
96
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
97
|
+
const item = value;
|
|
98
|
+
if (typeof item.command !== "string" || item.command === "") return void 0;
|
|
99
|
+
if (!Array.isArray(item.args) || !item.args.every((arg) => typeof arg === "string")) return void 0;
|
|
100
|
+
const env = stringEnvironment(item.env);
|
|
101
|
+
return { command: item.command, args: item.args, ...env === void 0 ? {} : { env } };
|
|
102
|
+
}
|
|
103
|
+
function packageManagerFromArgv(argv, exists, execPath, pathEnv) {
|
|
104
|
+
if (!isOfficialDesktopHostEntry(argv[1])) return void 0;
|
|
105
|
+
const pnpm = argv[5];
|
|
106
|
+
if (typeof pnpm !== "string" || !isAbsolute(pnpm) || !exists(pnpm)) return void 0;
|
|
107
|
+
const bin = argv[6];
|
|
108
|
+
const path = typeof bin === "string" && bin !== "" ? `${bin}${delimiter}${pathEnv ?? ""}` : pathEnv;
|
|
109
|
+
return {
|
|
110
|
+
command: execPath,
|
|
111
|
+
args: ["--expose-internals", pnpm],
|
|
112
|
+
env: { ELECTRON_RUN_AS_NODE: "1", ...typeof path === "string" && path !== "" ? { PATH: path } : {} }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function pluginManagerFrom(value) {
|
|
116
|
+
if (value === null || typeof value !== "object" || typeof value.installBundle !== "function") return void 0;
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
function resolveUpdateRuntime(ctx, options = {}) {
|
|
120
|
+
const argv = options.argv ?? process.argv;
|
|
121
|
+
const env = options.env ?? process.env;
|
|
122
|
+
const cwd = options.cwd ?? process.cwd();
|
|
123
|
+
const home = options.homeDir ?? homedir();
|
|
124
|
+
const exists = options.exists ?? existsSync;
|
|
125
|
+
const execPath = options.execPath ?? process.execPath;
|
|
126
|
+
const profiles = optionalService(ctx, "desktopProfiles");
|
|
127
|
+
const desktopPnpm = optionalService(ctx, "desktopPnpm");
|
|
72
128
|
if (profiles?.current !== void 0) {
|
|
73
129
|
const current = profiles.current;
|
|
74
|
-
if (!validProfileName(current
|
|
130
|
+
if (!validProfileName(current?.name) || typeof current.dir !== "string" || !isAbsolute(current.dir)) {
|
|
75
131
|
throw new Error("\u5F53\u524D Desktop Profile \u4FE1\u606F\u65E0\u6548\uFF0C\u8BF7\u91CD\u542F\u540E\u91CD\u8BD5\u3002");
|
|
76
132
|
}
|
|
77
|
-
|
|
133
|
+
const runnable = typeof desktopPnpm?.runPlugin === "function";
|
|
134
|
+
return {
|
|
135
|
+
profileName: current.name,
|
|
136
|
+
profileDir: resolve(current.dir),
|
|
137
|
+
officialDesktop: false,
|
|
138
|
+
canAutoUpdate: runnable,
|
|
139
|
+
...runnable ? { desktopPnpm } : {}
|
|
140
|
+
};
|
|
78
141
|
}
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
142
|
+
const launched = optionalService(ctx, "profileContext");
|
|
143
|
+
const official = isOfficialDesktopHostEntry(argv[1]) || launched?.name === "desktop";
|
|
144
|
+
const launchedDir = typeof launched?.dir === "string" && isAbsolute(launched.dir) ? resolve(launched.dir) : void 0;
|
|
145
|
+
const projectDir = official && typeof argv[3] === "string" && isAbsolute(argv[3]) ? resolve(argv[3]) : void 0;
|
|
146
|
+
const cwdProfile = official && exists(resolve(cwd, "package.json")) ? resolve(cwd) : void 0;
|
|
147
|
+
const profileDir = launchedDir ?? projectDir ?? cwdProfile ?? resolve(env.DSH_PROFILE_DIR ?? resolve(home, ".dsh", "profiles", "web"));
|
|
148
|
+
const selected = profileNameFromArgv(argv);
|
|
149
|
+
const profileName = validProfileName(launched?.name) ? launched.name : official ? "desktop" : validProfileName(selected) ? selected : validProfileName(basename(profileDir)) ? basename(profileDir) : "web";
|
|
150
|
+
const packageManager = official ? packageManagerFrom(launched?.packageManager) ?? packageManagerFromArgv(argv, exists, execPath, env.PATH) : void 0;
|
|
151
|
+
const pluginManager = official && packageManager === void 0 ? pluginManagerFrom(optionalService(ctx, "pluginManager")) : void 0;
|
|
152
|
+
const entry = official ? void 0 : cliEntry(argv, cwd, exists);
|
|
153
|
+
return {
|
|
154
|
+
profileName,
|
|
155
|
+
profileDir,
|
|
156
|
+
officialDesktop: official,
|
|
157
|
+
canAutoUpdate: packageManager !== void 0 || pluginManager !== void 0 || entry !== void 0,
|
|
158
|
+
...packageManager === void 0 ? {} : { packageManager },
|
|
159
|
+
...pluginManager === void 0 ? {} : { pluginManager },
|
|
160
|
+
...entry === void 0 ? {} : { cliEntry: entry }
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function shouldNotifyParent(target, send = process.send) {
|
|
164
|
+
return target.officialDesktop !== true && target.desktopPnpm === void 0 && target.packageManager === void 0 && typeof send === "function";
|
|
165
|
+
}
|
|
166
|
+
function runtime(ctx) {
|
|
167
|
+
return resolveUpdateRuntime(ctx);
|
|
84
168
|
}
|
|
85
169
|
function parseSemver(value) {
|
|
86
170
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
@@ -146,7 +230,7 @@ async function status(options, target) {
|
|
|
146
230
|
latestCheckFailed: latest === void 0,
|
|
147
231
|
updateAvailable: latest !== void 0 && isNewerVersion(current, latest),
|
|
148
232
|
profileName: target.profileName,
|
|
149
|
-
canAutoUpdate: target.
|
|
233
|
+
canAutoUpdate: target.canAutoUpdate
|
|
150
234
|
};
|
|
151
235
|
}
|
|
152
236
|
async function runCliInstall(target, packageSpec) {
|
|
@@ -181,11 +265,55 @@ async function runCliInstall(target, packageSpec) {
|
|
|
181
265
|
});
|
|
182
266
|
});
|
|
183
267
|
}
|
|
268
|
+
async function runPackageManagerInstall(target, packageSpec) {
|
|
269
|
+
const manager = target.packageManager;
|
|
270
|
+
if (manager === void 0) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002");
|
|
271
|
+
await new Promise((resolvePromise, reject) => {
|
|
272
|
+
const child = spawn(manager.command, [...manager.args, "add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], {
|
|
273
|
+
cwd: target.profileDir,
|
|
274
|
+
windowsHide: true,
|
|
275
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
276
|
+
env: { ...process.env, ...manager.env, NO_COLOR: "1" }
|
|
277
|
+
});
|
|
278
|
+
let detail = "";
|
|
279
|
+
child.stdout?.on("data", (chunk) => {
|
|
280
|
+
detail = (detail + String(chunk)).slice(-4e3);
|
|
281
|
+
});
|
|
282
|
+
child.stderr?.on("data", (chunk) => {
|
|
283
|
+
detail = (detail + String(chunk)).slice(-4e3);
|
|
284
|
+
});
|
|
285
|
+
const timer = setTimeout(() => {
|
|
286
|
+
child.kill();
|
|
287
|
+
reject(new Error("\u66F4\u65B0\u8D85\u65F6\uFF0C\u8BF7\u6539\u7528\u624B\u5DE5\u66F4\u65B0\u3002"));
|
|
288
|
+
}, 10 * 6e4);
|
|
289
|
+
child.once("error", (error) => {
|
|
290
|
+
clearTimeout(timer);
|
|
291
|
+
reject(error);
|
|
292
|
+
});
|
|
293
|
+
child.once("exit", (code) => {
|
|
294
|
+
clearTimeout(timer);
|
|
295
|
+
if (code === 0) resolvePromise();
|
|
296
|
+
else reject(new Error(detail.trim() || `\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(code)}`));
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async function runPluginManagerInstall(target, packageSpec) {
|
|
301
|
+
const manager = target.pluginManager;
|
|
302
|
+
if (manager === void 0) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002");
|
|
303
|
+
const result = await manager.installBundle(packageSpec);
|
|
304
|
+
if (result.application === "applied" || result.application === "restart-required" || result.application === "overridden") return;
|
|
305
|
+
throw new Error(result.error?.message || result.packageResult?.output || "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u670D\u52A1\u7AEF\u65E5\u5FD7\u3002");
|
|
306
|
+
}
|
|
184
307
|
async function install(target, packageSpec) {
|
|
185
|
-
if (target.desktopPnpm
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
308
|
+
if (target.desktopPnpm !== void 0) {
|
|
309
|
+
const handle = target.desktopPnpm.runPlugin(["add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], target.profileDir);
|
|
310
|
+
const result = await handle.done;
|
|
311
|
+
if (result.exitCode !== 0) throw new Error(`\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(result.exitCode)}\u3002`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (target.packageManager !== void 0) return runPackageManagerInstall(target, packageSpec);
|
|
315
|
+
if (target.pluginManager !== void 0) return runPluginManagerInstall(target, packageSpec);
|
|
316
|
+
return runCliInstall(target, packageSpec);
|
|
189
317
|
}
|
|
190
318
|
function json(response, statusCode, value) {
|
|
191
319
|
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -262,9 +390,9 @@ function registerPluginUpdater(ctx, options) {
|
|
|
262
390
|
} finally {
|
|
263
391
|
installing = false;
|
|
264
392
|
}
|
|
265
|
-
const notifyParent = target
|
|
393
|
+
const notifyParent = shouldNotifyParent(target);
|
|
266
394
|
const autoReload = target.desktopPnpm !== void 0 || notifyParent;
|
|
267
|
-
json(response, 200, { ...before, updatedVersion: before.latestVersion, restartRequired: true, autoReload });
|
|
395
|
+
json(response, 200, { ...before, updatedVersion: before.latestVersion, restartRequired: true, autoReload, ...target.officialDesktop ? { restartDesktop: true } : {} });
|
|
268
396
|
if (notifyParent) setTimeout(() => {
|
|
269
397
|
process.send?.(PLUGIN_UPDATE_IPC);
|
|
270
398
|
}, 150).unref?.();
|
|
@@ -289,5 +417,7 @@ export {
|
|
|
289
417
|
isDshCliEntry,
|
|
290
418
|
isNewerVersion,
|
|
291
419
|
isTrustedUpdateRequest,
|
|
292
|
-
registerPluginUpdater
|
|
420
|
+
registerPluginUpdater,
|
|
421
|
+
resolveUpdateRuntime,
|
|
422
|
+
shouldNotifyParent
|
|
293
423
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-archive-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "归档、恢复、收藏和永久删除会话,并诊断损坏的会话日志。 · Archive, restore, favorite, and permanently delete sessions, and diagnose damaged session logs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.22.0",
|