@senguoyun/dsh-arkme 0.1.12 → 0.1.13

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.
Files changed (144) hide show
  1. package/README.md +2 -2
  2. package/assets/desktop_call/avatar-lin-xiaoman.jpeg +0 -0
  3. package/assets/desktop_call/avatar-mother.jpg +0 -0
  4. package/assets/desktop_call/avatar-self.png +0 -0
  5. package/assets/desktop_call/call-add-linear.svg +13 -0
  6. package/assets/desktop_call/icon-scan-add-contact.svg +4 -0
  7. package/assets/desktop_call/profile-circle-linear.svg +11 -0
  8. package/assets/desktop_call/user-add-linear.svg +15 -0
  9. package/cordis.patch.yml +4 -1
  10. package/docs/consumer-plugin-contract.md +13 -4
  11. package/docs/extension-market-controls.md +1 -1
  12. package/docs/gui-production-integration.md +1 -1
  13. package/lib/client.js +27533 -5149
  14. package/lib/client.js.map +1 -1
  15. package/lib/extension-profile-restart-helper.js +19 -3
  16. package/lib/index.js +1498 -380
  17. package/lib/{install-store-D6P-dH2i.js → install-store-Sn3Ccxfu.js} +32 -5
  18. package/lib/persistent-extension.js +1 -1
  19. package/lib/{persistent-runtime-kUpXXUZI.js → persistent-runtime-Cki0XC3L.js} +826 -183
  20. package/lib/plugin-updater-helper-CiPlNqBc.js +346 -0
  21. package/lib/plugin-updater-helper.js +2 -250
  22. package/lib/sdk.js +114 -2
  23. package/lib/types/arkme-service.d.ts +30 -2
  24. package/lib/types/arkme-service.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeAppUpdateDialog.d.ts +5 -0
  26. package/lib/types/client/ArkmeAppUpdateDialog.d.ts.map +1 -0
  27. package/lib/types/client/ArkmeCallHistorySurface.d.ts +57 -0
  28. package/lib/types/client/ArkmeCallHistorySurface.d.ts.map +1 -0
  29. package/lib/types/client/ArkmeContactAddSurface.d.ts +9 -0
  30. package/lib/types/client/ArkmeContactAddSurface.d.ts.map +1 -0
  31. package/lib/types/client/ArkmeExtensionPublishDialog.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeExtensionReviews.d.ts +10 -0
  33. package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeMarketplace.d.ts +153 -0
  35. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -0
  36. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  37. package/lib/types/client/ArkmeQuickAdd.d.ts +12 -0
  38. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -0
  39. package/lib/types/client/ArkmeRecordingSurface.d.ts +109 -3
  40. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  41. package/lib/types/client/ArkmeSettingsSurface.d.ts +20 -0
  42. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  43. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  44. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +12 -0
  45. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  46. package/lib/types/client/ArkmeWorldSurface.d.ts +30 -0
  47. package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -0
  48. package/lib/types/client/api.d.ts +1 -1
  49. package/lib/types/client/api.d.ts.map +1 -1
  50. package/lib/types/client/app-update-store.d.ts +35 -0
  51. package/lib/types/client/app-update-store.d.ts.map +1 -0
  52. package/lib/types/client/demo-audio.d.ts +2 -0
  53. package/lib/types/client/demo-audio.d.ts.map +1 -0
  54. package/lib/types/client/extension-market-model.d.ts +1 -0
  55. package/lib/types/client/extension-market-model.d.ts.map +1 -1
  56. package/lib/types/client/index.d.ts +8 -2
  57. package/lib/types/client/index.d.ts.map +1 -1
  58. package/lib/types/client/personal-test-edition.d.ts +17 -0
  59. package/lib/types/client/personal-test-edition.d.ts.map +1 -0
  60. package/lib/types/client/ui-controller.d.ts +4 -1
  61. package/lib/types/client/ui-controller.d.ts.map +1 -1
  62. package/lib/types/extensions/bundle-artifact.d.ts +22 -0
  63. package/lib/types/extensions/bundle-artifact.d.ts.map +1 -1
  64. package/lib/types/extensions/install-store.d.ts +1 -0
  65. package/lib/types/extensions/install-store.d.ts.map +1 -1
  66. package/lib/types/extensions/manager.d.ts +35 -2
  67. package/lib/types/extensions/manager.d.ts.map +1 -1
  68. package/lib/types/extensions/owned-inventory.d.ts +2 -2
  69. package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
  70. package/lib/types/extensions/owned-refs.d.ts +1 -1
  71. package/lib/types/extensions/owned-refs.d.ts.map +1 -1
  72. package/lib/types/extensions/owned-types.d.ts +18 -5
  73. package/lib/types/extensions/owned-types.d.ts.map +1 -1
  74. package/lib/types/extensions/profile-owned-inventory.d.ts +1 -0
  75. package/lib/types/extensions/profile-owned-inventory.d.ts.map +1 -1
  76. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  77. package/lib/types/extensions/publish-client.d.ts +47 -2
  78. package/lib/types/extensions/publish-client.d.ts.map +1 -1
  79. package/lib/types/extensions/signature.d.ts +31 -1
  80. package/lib/types/extensions/signature.d.ts.map +1 -1
  81. package/lib/types/extensions/types.d.ts +71 -5
  82. package/lib/types/extensions/types.d.ts.map +1 -1
  83. package/lib/types/host-api.d.ts.map +1 -1
  84. package/lib/types/index.d.ts +8 -2
  85. package/lib/types/index.d.ts.map +1 -1
  86. package/lib/types/outgoing-call-assets.d.ts.map +1 -1
  87. package/lib/types/plugin-update-artifact.d.ts +46 -0
  88. package/lib/types/plugin-update-artifact.d.ts.map +1 -0
  89. package/lib/types/plugin-update.d.ts +20 -11
  90. package/lib/types/plugin-update.d.ts.map +1 -1
  91. package/lib/types/plugin-updater-helper.d.ts +11 -0
  92. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  93. package/lib/types/recording-presentation.d.ts +2 -2
  94. package/lib/types/recording-presentation.d.ts.map +1 -1
  95. package/lib/types/sdk/index.d.ts +63 -7
  96. package/lib/types/sdk/index.d.ts.map +1 -1
  97. package/lib/types/services/bot-service.d.ts +5 -1
  98. package/lib/types/services/bot-service.d.ts.map +1 -1
  99. package/lib/types/services/contact-service.d.ts +25 -0
  100. package/lib/types/services/contact-service.d.ts.map +1 -0
  101. package/lib/types/services/group-service.d.ts +4 -1
  102. package/lib/types/services/group-service.d.ts.map +1 -1
  103. package/lib/types/services/recording-service.d.ts +4 -2
  104. package/lib/types/services/recording-service.d.ts.map +1 -1
  105. package/lib/types/services/service.d.ts +1 -0
  106. package/lib/types/services/service.d.ts.map +1 -1
  107. package/lib/types/services/world-service.d.ts +13 -0
  108. package/lib/types/services/world-service.d.ts.map +1 -1
  109. package/lib/types/tools/business/contacts/index.d.ts +4 -0
  110. package/lib/types/tools/business/contacts/index.d.ts.map +1 -0
  111. package/lib/types/tools/business/groups/create.d.ts +2 -0
  112. package/lib/types/tools/business/groups/create.d.ts.map +1 -0
  113. package/lib/types/tools/business/groups/index.d.ts +1 -0
  114. package/lib/types/tools/business/groups/index.d.ts.map +1 -1
  115. package/lib/types/tools/business/index.d.ts.map +1 -1
  116. package/lib/types/tools/business/recordings/index.d.ts +2 -1
  117. package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
  118. package/lib/types/tools/business/world/index.d.ts +2 -0
  119. package/lib/types/tools/business/world/index.d.ts.map +1 -1
  120. package/lib/types/tools/business/world/mine.d.ts +2 -0
  121. package/lib/types/tools/business/world/mine.d.ts.map +1 -0
  122. package/lib/types/tools/business/world/voiceprint-invite.d.ts +2 -0
  123. package/lib/types/tools/business/world/voiceprint-invite.d.ts.map +1 -0
  124. package/lib/types/tools/extensions/index.d.ts.map +1 -1
  125. package/lib/types/tools/extensions/publish-conversation.d.ts +8 -1
  126. package/lib/types/tools/extensions/publish-conversation.d.ts.map +1 -1
  127. package/lib/types/tools/ports/contacts.d.ts +12 -0
  128. package/lib/types/tools/ports/contacts.d.ts.map +1 -0
  129. package/lib/types/tools/ports/groups.d.ts +4 -1
  130. package/lib/types/tools/ports/groups.d.ts.map +1 -1
  131. package/lib/types/tools/ports/index.d.ts +3 -2
  132. package/lib/types/tools/ports/index.d.ts.map +1 -1
  133. package/lib/types/tools/ports/recordings.d.ts +3 -2
  134. package/lib/types/tools/ports/recordings.d.ts.map +1 -1
  135. package/lib/types/tools/ports/world.d.ts +12 -1
  136. package/lib/types/tools/ports/world.d.ts.map +1 -1
  137. package/lib/types/tools/prompts/business.d.ts.map +1 -1
  138. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  139. package/lib/types/types.d.ts +48 -3
  140. package/lib/types/types.d.ts.map +1 -1
  141. package/package.json +5 -4
  142. package/lib/plugin-update-install-state-CIsOCjWK.js +0 -67
  143. package/lib/types/client/ArkmeExtensionCenter.d.ts +0 -74
  144. package/lib/types/client/ArkmeExtensionCenter.d.ts.map +0 -1
@@ -0,0 +1,346 @@
1
+ import { n as prepareProfilePackageManager } from "./profile-package-manager-DvzGgkqe.js";
2
+ import { closeSync, existsSync, openSync, readFileSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
4
+ import { dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawn, spawnSync } from "node:child_process";
7
+ import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
8
+ import semver from "semver";
9
+ //#region src/plugin-update-install-state.ts
10
+ const PHASES = /* @__PURE__ */ new Set([
11
+ "idle",
12
+ "preparing",
13
+ "downloading",
14
+ "verifying",
15
+ "installing",
16
+ "restarting",
17
+ "succeeded",
18
+ "failed",
19
+ "rolled-back"
20
+ ]);
21
+ function boundedString(value, maxLength) {
22
+ if (typeof value !== "string") return "";
23
+ const normalized = value.trim();
24
+ return normalized.length <= maxLength ? normalized : normalized.slice(0, maxLength);
25
+ }
26
+ function parsePluginUpdateInstallSnapshot(value) {
27
+ if (value === null || typeof value !== "object") return void 0;
28
+ const source = value;
29
+ const phase = source.phase;
30
+ const jobId = boundedString(source.jobId, 128);
31
+ const previousVersion = boundedString(source.previousVersion, 128);
32
+ const targetVersion = boundedString(source.targetVersion, 128);
33
+ const message = boundedString(source.message, 500);
34
+ const updatedAtMillis = source.updatedAtMillis;
35
+ if (source.schemaVersion !== 1 || !PHASES.has(phase) || jobId === "" || previousVersion === "" || targetVersion === "" || typeof updatedAtMillis !== "number" || !Number.isFinite(updatedAtMillis) || updatedAtMillis <= 0) return void 0;
36
+ return {
37
+ schemaVersion: 1,
38
+ jobId,
39
+ phase,
40
+ previousVersion,
41
+ targetVersion,
42
+ message,
43
+ updatedAtMillis
44
+ };
45
+ }
46
+ var PluginUpdateInstallStateStore = class {
47
+ path;
48
+ constructor(directory) {
49
+ this.path = join(directory, "plugin-update-install-state.json");
50
+ }
51
+ async read() {
52
+ try {
53
+ return parsePluginUpdateInstallSnapshot(JSON.parse(await readFile(this.path, "utf8")));
54
+ } catch (error) {
55
+ if (error.code === "ENOENT") return void 0;
56
+ return;
57
+ }
58
+ }
59
+ async write(snapshot) {
60
+ const directory = dirname(this.path);
61
+ await mkdir(directory, {
62
+ recursive: true,
63
+ mode: 448
64
+ });
65
+ await chmod(directory, 448);
66
+ const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
67
+ await writeFile(temporary, `${JSON.stringify(snapshot, void 0, 2)}\n`, { mode: 384 });
68
+ await chmod(temporary, 384);
69
+ await rename(temporary, this.path);
70
+ await chmod(this.path, 384);
71
+ }
72
+ };
73
+ //#endregion
74
+ //#region src/plugin-updater-helper.ts
75
+ const PARENT_EXIT_TIMEOUT_MS = 2e4;
76
+ const HEALTH_TIMEOUT_MS = 45e3;
77
+ function nonEmptyString(value, maxLength = 4096) {
78
+ if (typeof value !== "string") return void 0;
79
+ const normalized = value.trim();
80
+ return normalized !== "" && normalized.length <= maxLength ? normalized : void 0;
81
+ }
82
+ function stringArray(value, options = {}) {
83
+ if (!Array.isArray(value)) return void 0;
84
+ const result = value.map((item) => nonEmptyString(item));
85
+ if (result.some((item) => item === void 0) || !options.allowEmpty && result.length === 0) return void 0;
86
+ return result;
87
+ }
88
+ function parsePluginUpdaterPlan(value) {
89
+ if (value === null || typeof value !== "object") throw new Error("updater plan must be an object");
90
+ const source = value;
91
+ const jobId = nonEmptyString(source.jobId, 128);
92
+ const execPath = nonEmptyString(source.execPath);
93
+ const execArgv = stringArray(source.execArgv, { allowEmpty: true });
94
+ const dshBinPath = nonEmptyString(source.dshBinPath);
95
+ const dshHome = nonEmptyString(source.dshHome);
96
+ const profileName = nonEmptyString(source.profileName, 64);
97
+ const previousVersion = nonEmptyString(source.previousVersion, 128);
98
+ const previousSpec = nonEmptyString(source.previousSpec, 4096);
99
+ const targetVersion = nonEmptyString(source.targetVersion, 128);
100
+ const targetArtifactPath = nonEmptyString(source.targetArtifactPath, 4096);
101
+ const previousArtifactPath = nonEmptyString(source.previousArtifactPath, 4096);
102
+ const stateDirectory = nonEmptyString(source.stateDirectory);
103
+ const healthUrl = nonEmptyString(source.healthUrl);
104
+ const logPath = nonEmptyString(source.logPath);
105
+ const restartArgv = stringArray(source.restartArgv);
106
+ if (source.schemaVersion !== 1 || jobId === void 0 || execPath === void 0 || execArgv === void 0 || dshBinPath === void 0 || dshHome === void 0 || profileName === void 0 || previousVersion === void 0 || previousSpec === void 0 || targetVersion === void 0 || targetArtifactPath === void 0 || stateDirectory === void 0 || healthUrl === void 0 || logPath === void 0 || restartArgv === void 0 || source.parentPid === void 0 || typeof source.parentPid !== "number" || !Number.isSafeInteger(source.parentPid) || source.parentPid <= 0) throw new Error("updater plan is incomplete");
107
+ if (!isAbsolute(targetArtifactPath) || !existsSync(targetArtifactPath) || previousArtifactPath !== void 0 && (!isAbsolute(previousArtifactPath) || !existsSync(previousArtifactPath))) throw new Error("updater plan artifact paths must be existing absolute files");
108
+ const url = new URL(healthUrl);
109
+ if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("updater health URL must be loopback HTTP");
110
+ return {
111
+ schemaVersion: 1,
112
+ jobId,
113
+ parentPid: source.parentPid,
114
+ execPath,
115
+ execArgv,
116
+ dshBinPath,
117
+ restartArgv,
118
+ dshHome,
119
+ profileName,
120
+ previousVersion,
121
+ previousSpec,
122
+ targetVersion,
123
+ targetArtifactPath,
124
+ ...previousArtifactPath === void 0 ? {} : { previousArtifactPath },
125
+ stateDirectory,
126
+ healthUrl: url.toString(),
127
+ logPath
128
+ };
129
+ }
130
+ function processAlive(pid) {
131
+ try {
132
+ process.kill(pid, 0);
133
+ return true;
134
+ } catch (error) {
135
+ return error.code !== "ESRCH";
136
+ }
137
+ }
138
+ async function waitForProcessExit(pid, timeoutMs) {
139
+ const deadline = Date.now() + timeoutMs;
140
+ while (processAlive(pid) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 200));
141
+ return !processAlive(pid);
142
+ }
143
+ function isLocalPackageSpec(spec) {
144
+ return /^(?:link:|file:)/.test(spec);
145
+ }
146
+ function buildTargetInstallArgs(plan) {
147
+ return [
148
+ ...plan.execArgv,
149
+ plan.dshBinPath,
150
+ "plugin",
151
+ "--profile",
152
+ plan.profileName,
153
+ "add",
154
+ `file:${plan.targetArtifactPath}`
155
+ ];
156
+ }
157
+ function runTargetInstall(plan) {
158
+ return spawnSync(plan.execPath, buildTargetInstallArgs(plan), {
159
+ env: {
160
+ ...process.env,
161
+ DSH_HOME: plan.dshHome
162
+ },
163
+ stdio: "inherit",
164
+ shell: false
165
+ }).status === 0;
166
+ }
167
+ function runRollbackInstall(plan) {
168
+ const fallbackSpec = plan.previousArtifactPath === void 0 ? plan.previousSpec : `file:${plan.previousArtifactPath}`;
169
+ if (plan.previousArtifactPath === void 0 && !isLocalPackageSpec(fallbackSpec)) return false;
170
+ const args = [
171
+ ...plan.execArgv,
172
+ plan.dshBinPath,
173
+ "plugin",
174
+ "--profile",
175
+ plan.profileName,
176
+ "add",
177
+ fallbackSpec
178
+ ];
179
+ return spawnSync(plan.execPath, args, {
180
+ env: {
181
+ ...process.env,
182
+ DSH_HOME: plan.dshHome
183
+ },
184
+ stdio: "inherit",
185
+ shell: false
186
+ }).status === 0;
187
+ }
188
+ function installedProfileVersion(plan) {
189
+ try {
190
+ return JSON.parse(readFileSync(join(plan.dshHome, "profiles", plan.profileName, "node_modules", "@senguoyun", "dsh-arkme", "package.json"), "utf8")).version ?? "";
191
+ } catch {
192
+ return "";
193
+ }
194
+ }
195
+ function restartDsh(plan) {
196
+ const child = spawn(plan.execPath, plan.restartArgv, {
197
+ detached: true,
198
+ env: {
199
+ ...process.env,
200
+ DSH_HOME: plan.dshHome
201
+ },
202
+ stdio: "inherit",
203
+ shell: false
204
+ });
205
+ child.unref();
206
+ return child;
207
+ }
208
+ async function waitForHealthy(plan, expectedVersion) {
209
+ const deadline = Date.now() + HEALTH_TIMEOUT_MS;
210
+ while (Date.now() < deadline) {
211
+ const installedVersion = installedProfileVersion(plan);
212
+ try {
213
+ const response = await fetch(plan.healthUrl, {
214
+ method: "POST",
215
+ headers: {
216
+ "Content-Type": "application/json",
217
+ Origin: new URL(plan.healthUrl).origin
218
+ },
219
+ body: JSON.stringify({ operation: "plugin.update.status" }),
220
+ signal: AbortSignal.timeout(2e3)
221
+ });
222
+ if (response.ok) {
223
+ const body = await response.json();
224
+ if (body.ok === true && body.value?.installedVersion === expectedVersion) return true;
225
+ }
226
+ } catch {}
227
+ if (installedVersion === expectedVersion) try {
228
+ if ((await fetch(new URL("/", plan.healthUrl), { signal: AbortSignal.timeout(2e3) })).ok) return true;
229
+ } catch {}
230
+ await new Promise((resolve) => setTimeout(resolve, 500));
231
+ }
232
+ return false;
233
+ }
234
+ function managedOperations(overrides) {
235
+ return {
236
+ runRollbackInstall,
237
+ waitForHealthy,
238
+ ...overrides
239
+ };
240
+ }
241
+ function planWithReplacementHealthUrl(plan, replacementUrl) {
242
+ const replacement = new URL(replacementUrl);
243
+ if (replacement.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(replacement.hostname)) throw new Error("managed plugin update replacement URL must be loopback HTTP");
244
+ const health = new URL(plan.healthUrl);
245
+ health.protocol = replacement.protocol;
246
+ health.hostname = replacement.hostname;
247
+ health.port = replacement.port;
248
+ return {
249
+ ...plan,
250
+ healthUrl: health.toString()
251
+ };
252
+ }
253
+ async function writePhase(store, plan, phase, message) {
254
+ const snapshot = {
255
+ schemaVersion: 1,
256
+ jobId: plan.jobId,
257
+ phase,
258
+ previousVersion: plan.previousVersion,
259
+ targetVersion: plan.targetVersion,
260
+ message,
261
+ updatedAtMillis: Date.now()
262
+ };
263
+ await store.write(snapshot);
264
+ return snapshot;
265
+ }
266
+ async function rollbackAndRestart(plan, store, rolledBackMessage) {
267
+ if (!runRollbackInstall(plan)) {
268
+ await writePhase(store, plan, "failed", "更新失败,旧版本恢复也失败;请使用更新命令手动修复。");
269
+ return;
270
+ }
271
+ restartDsh(plan);
272
+ const healthy = await waitForHealthy(plan, plan.previousVersion);
273
+ await writePhase(store, plan, healthy ? "rolled-back" : "failed", healthy ? rolledBackMessage : "已恢复旧版本文件,但 DSH 未能自动启动。");
274
+ }
275
+ async function runPluginUpdater(planPath) {
276
+ const plan = parsePluginUpdaterPlan(JSON.parse(await readFile(planPath, "utf8")));
277
+ await unlink(planPath).catch(() => void 0);
278
+ const store = new PluginUpdateInstallStateStore(plan.stateDirectory);
279
+ if (!await waitForProcessExit(plan.parentPid, PARENT_EXIT_TIMEOUT_MS)) {
280
+ await writePhase(store, plan, "failed", "旧 DSH 进程未能退出,更新已取消。");
281
+ return;
282
+ }
283
+ try {
284
+ prepareProfilePackageManager(plan.dshHome, plan.profileName);
285
+ } catch (error) {
286
+ restartDsh(plan);
287
+ const detail = error instanceof Error ? error.message : String(error);
288
+ await writePhase(store, plan, "failed", await waitForHealthy(plan, plan.previousVersion) ? `Profile pnpm 不可用,更新已取消并重新启动旧版本:${detail}` : `Profile pnpm 不可用,更新已取消,但旧版本未能自动启动:${detail}`);
289
+ return;
290
+ }
291
+ await writePhase(store, plan, "installing", `正在安装 ${plan.targetVersion}…`);
292
+ if (!runTargetInstall(plan)) {
293
+ await writePhase(store, plan, "failed", "新版本安装失败,正在恢复旧版本…");
294
+ await rollbackAndRestart(plan, store, "新版本安装失败,已自动恢复旧版本。");
295
+ return;
296
+ }
297
+ const installedVersion = installedProfileVersion(plan);
298
+ if (semver.valid(installedVersion) === null || semver.lt(installedVersion, plan.targetVersion)) {
299
+ await writePhase(store, plan, "failed", "未安装预期插件版本,正在恢复旧版本…");
300
+ await rollbackAndRestart(plan, store, "插件版本不符合预期,已自动恢复旧版本。");
301
+ return;
302
+ }
303
+ plan.targetVersion = installedVersion;
304
+ await writePhase(store, plan, "restarting", "安装完成,正在重启 DSH…");
305
+ const child = restartDsh(plan);
306
+ if (await waitForHealthy(plan, plan.targetVersion)) {
307
+ await writePhase(store, plan, "succeeded", `已更新到 ${plan.targetVersion}。`);
308
+ return;
309
+ }
310
+ child.kill("SIGTERM");
311
+ if (child.pid !== void 0) await waitForProcessExit(child.pid, 1e4);
312
+ await writePhase(store, plan, "failed", "新版本健康检查失败,正在恢复旧版本…");
313
+ await rollbackAndRestart(plan, store, "新版本启动失败,已自动恢复旧版本。");
314
+ }
315
+ async function finalizeManagedPluginUpdate(planPath, replacementUrl, overrides = {}) {
316
+ const plan = planWithReplacementHealthUrl(parsePluginUpdaterPlan(JSON.parse(await readFile(planPath, "utf8"))), replacementUrl);
317
+ const store = new PluginUpdateInstallStateStore(plan.stateDirectory);
318
+ if (!await managedOperations(overrides).waitForHealthy(plan, plan.targetVersion)) throw new Error("managed plugin update did not become healthy");
319
+ await writePhase(store, plan, "succeeded", `已更新到 ${plan.targetVersion}。`);
320
+ await unlink(planPath);
321
+ }
322
+ async function rollbackManagedPluginUpdate(planPath, overrides = {}) {
323
+ const plan = parsePluginUpdaterPlan(JSON.parse(await readFile(planPath, "utf8")));
324
+ const store = new PluginUpdateInstallStateStore(plan.stateDirectory);
325
+ if (!managedOperations(overrides).runRollbackInstall(plan)) {
326
+ await writePhase(store, plan, "failed", "更新失败,旧版本恢复也失败;请使用更新命令手动修复。");
327
+ throw new Error("managed plugin update rollback failed");
328
+ }
329
+ await writePhase(store, plan, "rolled-back", "已恢复旧版本文件,正在由 Arkme 重启 DSH…");
330
+ await unlink(planPath);
331
+ }
332
+ async function main() {
333
+ const planPath = process.argv[2];
334
+ if (planPath === void 0) throw new Error("updater plan path is required");
335
+ const plan = parsePluginUpdaterPlan(JSON.parse(await readFile(planPath, "utf8")));
336
+ const logFd = openSync(plan.logPath, "a", 384);
337
+ try {
338
+ closeSync(logFd);
339
+ await runPluginUpdater(planPath);
340
+ } catch (error) {
341
+ await writePhase(new PluginUpdateInstallStateStore(plan.stateDirectory), plan, "failed", error instanceof Error ? error.message : String(error));
342
+ }
343
+ }
344
+ if (process.argv[1] !== void 0 && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
345
+ //#endregion
346
+ export { runPluginUpdater as a, rollbackManagedPluginUpdate as i, finalizeManagedPluginUpdate as n, PluginUpdateInstallStateStore as o, parsePluginUpdaterPlan as r, buildTargetInstallArgs as t };
@@ -1,250 +1,2 @@
1
- import { t as PluginUpdateInstallStateStore } from "./plugin-update-install-state-CIsOCjWK.js";
2
- import { n as prepareProfilePackageManager } from "./profile-package-manager-DvzGgkqe.js";
3
- import { closeSync, openSync, readFileSync } from "node:fs";
4
- import { join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { spawn, spawnSync } from "node:child_process";
7
- import { readFile, unlink } from "node:fs/promises";
8
- import semver from "semver";
9
- //#region src/plugin-updater-helper.ts
10
- const PACKAGE_NAME = "@senguoyun/dsh-arkme";
11
- const PARENT_EXIT_TIMEOUT_MS = 2e4;
12
- const HEALTH_TIMEOUT_MS = 45e3;
13
- function nonEmptyString(value, maxLength = 4096) {
14
- if (typeof value !== "string") return void 0;
15
- const normalized = value.trim();
16
- return normalized !== "" && normalized.length <= maxLength ? normalized : void 0;
17
- }
18
- function stringArray(value, options = {}) {
19
- if (!Array.isArray(value)) return void 0;
20
- const result = value.map((item) => nonEmptyString(item));
21
- if (result.some((item) => item === void 0) || !options.allowEmpty && result.length === 0) return void 0;
22
- return result;
23
- }
24
- function parsePluginUpdaterPlan(value) {
25
- if (value === null || typeof value !== "object") throw new Error("updater plan must be an object");
26
- const source = value;
27
- const jobId = nonEmptyString(source.jobId, 128);
28
- const execPath = nonEmptyString(source.execPath);
29
- const execArgv = stringArray(source.execArgv, { allowEmpty: true });
30
- const dshBinPath = nonEmptyString(source.dshBinPath);
31
- const dshHome = nonEmptyString(source.dshHome);
32
- const profileName = nonEmptyString(source.profileName, 64);
33
- const previousVersion = nonEmptyString(source.previousVersion, 128);
34
- const previousSpec = nonEmptyString(source.previousSpec, 4096);
35
- const targetVersion = nonEmptyString(source.targetVersion, 128);
36
- const stateDirectory = nonEmptyString(source.stateDirectory);
37
- const healthUrl = nonEmptyString(source.healthUrl);
38
- const logPath = nonEmptyString(source.logPath);
39
- const restartArgv = stringArray(source.restartArgv);
40
- if (source.schemaVersion !== 1 || jobId === void 0 || execPath === void 0 || execArgv === void 0 || dshBinPath === void 0 || dshHome === void 0 || profileName === void 0 || previousVersion === void 0 || previousSpec === void 0 || targetVersion === void 0 || stateDirectory === void 0 || healthUrl === void 0 || logPath === void 0 || restartArgv === void 0 || source.parentPid === void 0 || typeof source.parentPid !== "number" || !Number.isSafeInteger(source.parentPid) || source.parentPid <= 0) throw new Error("updater plan is incomplete");
41
- const url = new URL(healthUrl);
42
- if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("updater health URL must be loopback HTTP");
43
- return {
44
- schemaVersion: 1,
45
- jobId,
46
- parentPid: source.parentPid,
47
- execPath,
48
- execArgv,
49
- dshBinPath,
50
- restartArgv,
51
- dshHome,
52
- profileName,
53
- previousVersion,
54
- previousSpec,
55
- targetVersion,
56
- stateDirectory,
57
- healthUrl: url.toString(),
58
- logPath
59
- };
60
- }
61
- function processAlive(pid) {
62
- try {
63
- process.kill(pid, 0);
64
- return true;
65
- } catch (error) {
66
- return error.code !== "ESRCH";
67
- }
68
- }
69
- async function waitForProcessExit(pid, timeoutMs) {
70
- const deadline = Date.now() + timeoutMs;
71
- while (processAlive(pid) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 200));
72
- return !processAlive(pid);
73
- }
74
- function isLocalPackageSpec(spec) {
75
- return /^(?:link:|file:|git\+|https?:)/.test(spec);
76
- }
77
- function buildTargetInstallArgs(plan) {
78
- return [
79
- ...plan.execArgv,
80
- plan.dshBinPath,
81
- "plugin",
82
- "--profile",
83
- plan.profileName,
84
- "add",
85
- `${PACKAGE_NAME}@${plan.targetVersion}`
86
- ];
87
- }
88
- function versionInstallArgs(plan, version) {
89
- return [
90
- ...plan.execArgv,
91
- plan.dshBinPath,
92
- "plugin",
93
- "--profile",
94
- plan.profileName,
95
- "add",
96
- `${PACKAGE_NAME}@${version}`
97
- ];
98
- }
99
- function runTargetInstall(plan) {
100
- return spawnSync(plan.execPath, buildTargetInstallArgs(plan), {
101
- env: {
102
- ...process.env,
103
- DSH_HOME: plan.dshHome
104
- },
105
- stdio: "inherit",
106
- shell: false
107
- }).status === 0;
108
- }
109
- function runRollbackInstall(plan) {
110
- const args = isLocalPackageSpec(plan.previousSpec) ? [
111
- ...plan.execArgv,
112
- plan.dshBinPath,
113
- "plugin",
114
- "--profile",
115
- plan.profileName,
116
- "add",
117
- plan.previousSpec
118
- ] : versionInstallArgs(plan, plan.previousVersion);
119
- return spawnSync(plan.execPath, args, {
120
- env: {
121
- ...process.env,
122
- DSH_HOME: plan.dshHome
123
- },
124
- stdio: "inherit",
125
- shell: false
126
- }).status === 0;
127
- }
128
- function installedProfileVersion(plan) {
129
- try {
130
- return JSON.parse(readFileSync(join(plan.dshHome, "profiles", plan.profileName, "node_modules", "@senguoyun", "dsh-arkme", "package.json"), "utf8")).version ?? "";
131
- } catch {
132
- return "";
133
- }
134
- }
135
- function restartDsh(plan) {
136
- const child = spawn(plan.execPath, plan.restartArgv, {
137
- detached: true,
138
- env: {
139
- ...process.env,
140
- DSH_HOME: plan.dshHome
141
- },
142
- stdio: "inherit",
143
- shell: false
144
- });
145
- child.unref();
146
- return child;
147
- }
148
- async function waitForHealthy(plan, expectedVersion) {
149
- const deadline = Date.now() + HEALTH_TIMEOUT_MS;
150
- while (Date.now() < deadline) {
151
- const installedVersion = installedProfileVersion(plan);
152
- try {
153
- const response = await fetch(plan.healthUrl, {
154
- method: "POST",
155
- headers: {
156
- "Content-Type": "application/json",
157
- Origin: new URL(plan.healthUrl).origin
158
- },
159
- body: JSON.stringify({ operation: "plugin.update.status" }),
160
- signal: AbortSignal.timeout(2e3)
161
- });
162
- if (response.ok) {
163
- const body = await response.json();
164
- if (body.ok === true && body.value?.installedVersion === expectedVersion) return true;
165
- }
166
- } catch {}
167
- if (installedVersion === expectedVersion) try {
168
- if ((await fetch(new URL("/", plan.healthUrl), { signal: AbortSignal.timeout(2e3) })).ok) return true;
169
- } catch {}
170
- await new Promise((resolve) => setTimeout(resolve, 500));
171
- }
172
- return false;
173
- }
174
- async function writePhase(store, plan, phase, message) {
175
- const snapshot = {
176
- schemaVersion: 1,
177
- jobId: plan.jobId,
178
- phase,
179
- previousVersion: plan.previousVersion,
180
- targetVersion: plan.targetVersion,
181
- message,
182
- updatedAtMillis: Date.now()
183
- };
184
- await store.write(snapshot);
185
- return snapshot;
186
- }
187
- async function rollbackAndRestart(plan, store, rolledBackMessage) {
188
- if (!runRollbackInstall(plan)) {
189
- await writePhase(store, plan, "failed", "更新失败,旧版本恢复也失败;请使用更新命令手动修复。");
190
- return;
191
- }
192
- restartDsh(plan);
193
- const healthy = await waitForHealthy(plan, plan.previousVersion);
194
- await writePhase(store, plan, healthy ? "rolled-back" : "failed", healthy ? rolledBackMessage : "已恢复旧版本文件,但 DSH 未能自动启动。");
195
- }
196
- async function runPluginUpdater(planPath) {
197
- const plan = parsePluginUpdaterPlan(JSON.parse(await readFile(planPath, "utf8")));
198
- await unlink(planPath).catch(() => void 0);
199
- const store = new PluginUpdateInstallStateStore(plan.stateDirectory);
200
- if (!await waitForProcessExit(plan.parentPid, PARENT_EXIT_TIMEOUT_MS)) {
201
- await writePhase(store, plan, "failed", "旧 DSH 进程未能退出,更新已取消。");
202
- return;
203
- }
204
- try {
205
- prepareProfilePackageManager(plan.dshHome, plan.profileName);
206
- } catch (error) {
207
- restartDsh(plan);
208
- const detail = error instanceof Error ? error.message : String(error);
209
- await writePhase(store, plan, "failed", await waitForHealthy(plan, plan.previousVersion) ? `Profile pnpm 不可用,更新已取消并重新启动旧版本:${detail}` : `Profile pnpm 不可用,更新已取消,但旧版本未能自动启动:${detail}`);
210
- return;
211
- }
212
- await writePhase(store, plan, "installing", `正在安装 ${plan.targetVersion}…`);
213
- if (!runTargetInstall(plan)) {
214
- await writePhase(store, plan, "failed", "新版本安装失败,正在恢复旧版本…");
215
- await rollbackAndRestart(plan, store, "新版本安装失败,已自动恢复旧版本。");
216
- return;
217
- }
218
- const installedVersion = installedProfileVersion(plan);
219
- if (semver.valid(installedVersion) === null || semver.lt(installedVersion, plan.targetVersion)) {
220
- await writePhase(store, plan, "failed", "Registry 未安装预期版本,正在恢复旧版本…");
221
- await rollbackAndRestart(plan, store, "Registry 版本不符合预期,已自动恢复旧版本。");
222
- return;
223
- }
224
- plan.targetVersion = installedVersion;
225
- await writePhase(store, plan, "restarting", "安装完成,正在重启 DSH…");
226
- const child = restartDsh(plan);
227
- if (await waitForHealthy(plan, plan.targetVersion)) {
228
- await writePhase(store, plan, "succeeded", `已更新到 ${plan.targetVersion}。`);
229
- return;
230
- }
231
- child.kill("SIGTERM");
232
- if (child.pid !== void 0) await waitForProcessExit(child.pid, 1e4);
233
- await writePhase(store, plan, "failed", "新版本健康检查失败,正在恢复旧版本…");
234
- await rollbackAndRestart(plan, store, "新版本启动失败,已自动恢复旧版本。");
235
- }
236
- async function main() {
237
- const planPath = process.argv[2];
238
- if (planPath === void 0) throw new Error("updater plan path is required");
239
- const plan = parsePluginUpdaterPlan(JSON.parse(await readFile(planPath, "utf8")));
240
- const logFd = openSync(plan.logPath, "a", 384);
241
- try {
242
- closeSync(logFd);
243
- await runPluginUpdater(planPath);
244
- } catch (error) {
245
- await writePhase(new PluginUpdateInstallStateStore(plan.stateDirectory), plan, "failed", error instanceof Error ? error.message : String(error));
246
- }
247
- }
248
- if (process.argv[1] !== void 0 && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
249
- //#endregion
250
- export { buildTargetInstallArgs, parsePluginUpdaterPlan, runPluginUpdater };
1
+ import { a as runPluginUpdater, i as rollbackManagedPluginUpdate, n as finalizeManagedPluginUpdate, r as parsePluginUpdaterPlan, t as buildTargetInstallArgs } from "./plugin-updater-helper-CiPlNqBc.js";
2
+ export { buildTargetInstallArgs, finalizeManagedPluginUpdate, parsePluginUpdaterPlan, rollbackManagedPluginUpdate, runPluginUpdater };