@michengai/dsh-archive-manager 1.0.13 → 1.0.16

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.
@@ -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 (origin === void 0 || host === void 0) return false;
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 = process.argv[1];
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(process.cwd(), value);
55
- if (!existsSync(entry)) return void 0;
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 (existsSync(manifestPath)) {
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 runtime(ctx) {
70
- const profiles = ctx.get?.("desktopProfiles");
71
- const desktopPnpm = ctx.get?.("desktopPnpm");
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.name) || typeof current.dir !== "string" || !isAbsolute(current.dir)) {
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
- return { profileName: current.name, profileDir: resolve(current.dir), ...typeof desktopPnpm?.runPlugin === "function" ? { desktopPnpm } : {} };
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 profileDir = resolve(process.env.DSH_PROFILE_DIR ?? resolve(homedir(), ".dsh", "profiles", "web"));
80
- const selected = profileNameFromArgv(process.argv);
81
- const profileName = validProfileName(selected) ? selected : validProfileName(basename(profileDir)) ? basename(profileDir) : "web";
82
- const entry = cliEntry();
83
- return { profileName, profileDir, ...entry === void 0 ? {} : { cliEntry: entry } };
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.desktopPnpm !== void 0 || target.cliEntry !== void 0
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 === void 0) return runCliInstall(target, packageSpec);
186
- const handle = target.desktopPnpm.runPlugin(["add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], target.profileDir);
187
- const result = await handle.done;
188
- if (result.exitCode !== 0) throw new Error(`\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(result.exitCode)}\u3002`);
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.desktopPnpm === void 0 && typeof process.send === "function";
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/lib/projcache.js CHANGED
@@ -326,7 +326,7 @@ function installArchiveProjectionCacheGuards(cache, table) {
326
326
  }
327
327
  const projectionOverlay = Symbol.for("dsh-archive-manager.projcache-overlay");
328
328
  async function installArchiveProjectionCache(cache) {
329
- const target = cache;
329
+ const target = Reflect.get(cache, symbols.original) ?? cache;
330
330
  if (target[projectionOverlay] === true) return async () => {
331
331
  };
332
332
  const domain = await target.ctx.storageDomain.open(safeProjectionCacheDomainSpec);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "1.0.13",
3
+ "version": "1.0.16",
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",