@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.
- package/README.md +2 -2
- package/assets/desktop_call/avatar-lin-xiaoman.jpeg +0 -0
- package/assets/desktop_call/avatar-mother.jpg +0 -0
- package/assets/desktop_call/avatar-self.png +0 -0
- package/assets/desktop_call/call-add-linear.svg +13 -0
- package/assets/desktop_call/icon-scan-add-contact.svg +4 -0
- package/assets/desktop_call/profile-circle-linear.svg +11 -0
- package/assets/desktop_call/user-add-linear.svg +15 -0
- package/cordis.patch.yml +4 -1
- package/docs/consumer-plugin-contract.md +13 -4
- package/docs/extension-market-controls.md +1 -1
- package/docs/gui-production-integration.md +1 -1
- package/lib/client.js +27533 -5149
- package/lib/client.js.map +1 -1
- package/lib/extension-profile-restart-helper.js +19 -3
- package/lib/index.js +1498 -380
- package/lib/{install-store-D6P-dH2i.js → install-store-Sn3Ccxfu.js} +32 -5
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-kUpXXUZI.js → persistent-runtime-Cki0XC3L.js} +826 -183
- package/lib/plugin-updater-helper-CiPlNqBc.js +346 -0
- package/lib/plugin-updater-helper.js +2 -250
- package/lib/sdk.js +114 -2
- package/lib/types/arkme-service.d.ts +30 -2
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/client/ArkmeAppUpdateDialog.d.ts +5 -0
- package/lib/types/client/ArkmeAppUpdateDialog.d.ts.map +1 -0
- package/lib/types/client/ArkmeCallHistorySurface.d.ts +57 -0
- package/lib/types/client/ArkmeCallHistorySurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeContactAddSurface.d.ts +9 -0
- package/lib/types/client/ArkmeContactAddSurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeExtensionPublishDialog.d.ts.map +1 -1
- package/lib/types/client/ArkmeExtensionReviews.d.ts +10 -0
- package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
- package/lib/types/client/ArkmeMarketplace.d.ts +153 -0
- package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -0
- package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
- package/lib/types/client/ArkmeQuickAdd.d.ts +12 -0
- package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -0
- package/lib/types/client/ArkmeRecordingSurface.d.ts +109 -3
- package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSettingsSurface.d.ts +20 -0
- package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts +12 -0
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/ArkmeWorldSurface.d.ts +30 -0
- package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -0
- package/lib/types/client/api.d.ts +1 -1
- package/lib/types/client/api.d.ts.map +1 -1
- package/lib/types/client/app-update-store.d.ts +35 -0
- package/lib/types/client/app-update-store.d.ts.map +1 -0
- package/lib/types/client/demo-audio.d.ts +2 -0
- package/lib/types/client/demo-audio.d.ts.map +1 -0
- package/lib/types/client/extension-market-model.d.ts +1 -0
- package/lib/types/client/extension-market-model.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +8 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/personal-test-edition.d.ts +17 -0
- package/lib/types/client/personal-test-edition.d.ts.map +1 -0
- package/lib/types/client/ui-controller.d.ts +4 -1
- package/lib/types/client/ui-controller.d.ts.map +1 -1
- package/lib/types/extensions/bundle-artifact.d.ts +22 -0
- package/lib/types/extensions/bundle-artifact.d.ts.map +1 -1
- package/lib/types/extensions/install-store.d.ts +1 -0
- package/lib/types/extensions/install-store.d.ts.map +1 -1
- package/lib/types/extensions/manager.d.ts +35 -2
- package/lib/types/extensions/manager.d.ts.map +1 -1
- package/lib/types/extensions/owned-inventory.d.ts +2 -2
- package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
- package/lib/types/extensions/owned-refs.d.ts +1 -1
- package/lib/types/extensions/owned-refs.d.ts.map +1 -1
- package/lib/types/extensions/owned-types.d.ts +18 -5
- package/lib/types/extensions/owned-types.d.ts.map +1 -1
- package/lib/types/extensions/profile-owned-inventory.d.ts +1 -0
- package/lib/types/extensions/profile-owned-inventory.d.ts.map +1 -1
- package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
- package/lib/types/extensions/publish-client.d.ts +47 -2
- package/lib/types/extensions/publish-client.d.ts.map +1 -1
- package/lib/types/extensions/signature.d.ts +31 -1
- package/lib/types/extensions/signature.d.ts.map +1 -1
- package/lib/types/extensions/types.d.ts +71 -5
- package/lib/types/extensions/types.d.ts.map +1 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts +8 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/outgoing-call-assets.d.ts.map +1 -1
- package/lib/types/plugin-update-artifact.d.ts +46 -0
- package/lib/types/plugin-update-artifact.d.ts.map +1 -0
- package/lib/types/plugin-update.d.ts +20 -11
- package/lib/types/plugin-update.d.ts.map +1 -1
- package/lib/types/plugin-updater-helper.d.ts +11 -0
- package/lib/types/plugin-updater-helper.d.ts.map +1 -1
- package/lib/types/recording-presentation.d.ts +2 -2
- package/lib/types/recording-presentation.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +63 -7
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/bot-service.d.ts +5 -1
- package/lib/types/services/bot-service.d.ts.map +1 -1
- package/lib/types/services/contact-service.d.ts +25 -0
- package/lib/types/services/contact-service.d.ts.map +1 -0
- package/lib/types/services/group-service.d.ts +4 -1
- package/lib/types/services/group-service.d.ts.map +1 -1
- package/lib/types/services/recording-service.d.ts +4 -2
- package/lib/types/services/recording-service.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +1 -0
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/world-service.d.ts +13 -0
- package/lib/types/services/world-service.d.ts.map +1 -1
- package/lib/types/tools/business/contacts/index.d.ts +4 -0
- package/lib/types/tools/business/contacts/index.d.ts.map +1 -0
- package/lib/types/tools/business/groups/create.d.ts +2 -0
- package/lib/types/tools/business/groups/create.d.ts.map +1 -0
- package/lib/types/tools/business/groups/index.d.ts +1 -0
- package/lib/types/tools/business/groups/index.d.ts.map +1 -1
- package/lib/types/tools/business/index.d.ts.map +1 -1
- package/lib/types/tools/business/recordings/index.d.ts +2 -1
- package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
- package/lib/types/tools/business/world/index.d.ts +2 -0
- package/lib/types/tools/business/world/index.d.ts.map +1 -1
- package/lib/types/tools/business/world/mine.d.ts +2 -0
- package/lib/types/tools/business/world/mine.d.ts.map +1 -0
- package/lib/types/tools/business/world/voiceprint-invite.d.ts +2 -0
- package/lib/types/tools/business/world/voiceprint-invite.d.ts.map +1 -0
- package/lib/types/tools/extensions/index.d.ts.map +1 -1
- package/lib/types/tools/extensions/publish-conversation.d.ts +8 -1
- package/lib/types/tools/extensions/publish-conversation.d.ts.map +1 -1
- package/lib/types/tools/ports/contacts.d.ts +12 -0
- package/lib/types/tools/ports/contacts.d.ts.map +1 -0
- package/lib/types/tools/ports/groups.d.ts +4 -1
- package/lib/types/tools/ports/groups.d.ts.map +1 -1
- package/lib/types/tools/ports/index.d.ts +3 -2
- package/lib/types/tools/ports/index.d.ts.map +1 -1
- package/lib/types/tools/ports/recordings.d.ts +3 -2
- package/lib/types/tools/ports/recordings.d.ts.map +1 -1
- package/lib/types/tools/ports/world.d.ts +12 -1
- package/lib/types/tools/ports/world.d.ts.map +1 -1
- package/lib/types/tools/prompts/business.d.ts.map +1 -1
- package/lib/types/tools/registry/registrar.d.ts.map +1 -1
- package/lib/types/types.d.ts +48 -3
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +5 -4
- package/lib/plugin-update-install-state-CIsOCjWK.js +0 -67
- package/lib/types/client/ArkmeExtensionCenter.d.ts +0 -74
- 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
|
|
2
|
-
|
|
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 };
|