dsh-wechat-remote 1.7.6 → 1.7.9
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 +10 -6
- package/assets/plugin.tgz +0 -0
- package/assets/release.json +11 -51
- package/bin/dsh-discovery.mjs +14 -4
- package/bin/native-recovery.mjs +44 -0
- package/bin/release-selection.mjs +2 -2
- package/bin/setup.mjs +100 -24
- package/lib/install-control.js +53 -29
- package/lib/install-profile.js +114 -159
- package/lib/install-runtime.js +17 -0
- package/lib/native-recovery.js +7478 -0
- package/lib/update-download.js +162 -36
- package/lib/update-policy.js +9 -1
- package/lib/update-worker.js +255 -186
- package/package.json +1 -1
package/lib/update-worker.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* Generated from the shared plugin installation sources. */
|
|
2
2
|
|
|
3
3
|
// src/update-worker.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs4 from "node:fs";
|
|
5
|
+
import path4 from "node:path";
|
|
6
6
|
import http from "node:http";
|
|
7
|
-
import { spawn as
|
|
7
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8
8
|
import { createHash as createHash2, createPublicKey } from "node:crypto";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
10
|
|
|
11
11
|
// src/secure-file.ts
|
|
12
12
|
import { execFileSync } from "node:child_process";
|
|
@@ -76,62 +76,71 @@ function writePrivateJsonAtomic(file, value) {
|
|
|
76
76
|
// src/install-profile.ts
|
|
77
77
|
import fs from "node:fs";
|
|
78
78
|
import path from "node:path";
|
|
79
|
-
import {
|
|
80
|
-
import {
|
|
81
|
-
|
|
82
|
-
// src/install-runtime.ts
|
|
83
|
-
var INSTALL_PNPM_VERSION = "11.22.0";
|
|
84
|
-
|
|
85
|
-
// src/install-profile.ts
|
|
79
|
+
import { createHash, randomBytes as randomBytes2 } from "node:crypto";
|
|
80
|
+
import { spawn, execFile } from "node:child_process";
|
|
86
81
|
var PLUGIN_PACKAGE = "@harness-remote/dsh-wechat-remote";
|
|
87
82
|
function safeProfileName(value) {
|
|
88
83
|
return /^[A-Za-z0-9_-]{1,80}$/.test(value);
|
|
89
84
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
85
|
+
function backupProfile(profile, backup) {
|
|
86
|
+
if (fs.existsSync(backup)) throw new Error("\u672C\u6B21\u5B89\u88C5\u5907\u4EFD\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\u3002");
|
|
87
|
+
fs.cpSync(profile, backup, {
|
|
88
|
+
recursive: true,
|
|
89
|
+
dereference: false,
|
|
90
|
+
verbatimSymlinks: true,
|
|
91
|
+
mode: fs.constants.COPYFILE_FICLONE,
|
|
92
|
+
filter(source, target) {
|
|
93
|
+
if (!fs.lstatSync(source).isSymbolicLink()) return true;
|
|
94
|
+
const type = process.platform === "win32" ? fs.statSync(source, { throwIfNoEntry: false })?.isDirectory() ? "junction" : "file" : void 0;
|
|
95
|
+
const link = fs.readlinkSync(source);
|
|
96
|
+
fs.symlinkSync(type === "junction" ? path.resolve(path.dirname(source), link) : link, target, type);
|
|
97
|
+
return false;
|
|
101
98
|
}
|
|
102
|
-
};
|
|
103
|
-
walk(root);
|
|
99
|
+
});
|
|
104
100
|
}
|
|
105
101
|
function installToolPath(directory, runtime) {
|
|
106
102
|
const bin = path.join(directory, "tool-bin");
|
|
107
103
|
fs.mkdirSync(bin, { mode: 448 });
|
|
108
|
-
const runner = path.join(bin, "pnpm-run.cjs");
|
|
109
|
-
fs.writeFileSync(runner, `require('node:child_process').spawnSync(${JSON.stringify(runtime.executable)},[${JSON.stringify(runtime.cli)},...process.argv.slice(2)],{stdio:'inherit',shell:false,windowsHide:true}).status===0?process.exit(0):process.exit(1)
|
|
110
|
-
`, { mode: 384 });
|
|
111
104
|
if (process.platform === "win32") {
|
|
112
|
-
if (/["\r\n]/.test(runtime.executable)) throw new Error("
|
|
113
|
-
fs.writeFileSync(path.join(bin, "pnpm.cmd"),
|
|
114
|
-
"${runtime.executable.replace(/%/g, "%%")}" "%~dp0pnpm-run.cjs" %*\r
|
|
115
|
-
`, { mode: 448 });
|
|
105
|
+
if (/["\r\n]/.test(runtime.executable + runtime.cli)) throw new Error("\u5B89\u88C5\u5DE5\u5177\u8DEF\u5F84\u5305\u542B\u65E0\u6548\u5B57\u7B26\u3002");
|
|
106
|
+
fs.writeFileSync(path.join(bin, "pnpm.cmd"), '@echo off\r\nsetlocal DisableDelayedExpansion\r\n"%HARNESS_INSTALL_NODE%" "%HARNESS_INSTALL_PNPM%" %*\r\n', { mode: 448 });
|
|
116
107
|
} else {
|
|
117
|
-
|
|
118
|
-
fs.writeFileSync(path.join(bin, "pnpm"), `#!/bin/sh
|
|
119
|
-
exec ${quote(runtime.executable)} ${quote(runner)} "$@"
|
|
120
|
-
`, { mode: 448 });
|
|
108
|
+
fs.writeFileSync(path.join(bin, "pnpm"), '#!/bin/sh\nexec "$HARNESS_INSTALL_NODE" "$HARNESS_INSTALL_PNPM" "$@"\n', { mode: 448 });
|
|
121
109
|
}
|
|
122
110
|
return bin;
|
|
123
111
|
}
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
var NativeInstallError = class extends Error {
|
|
113
|
+
constructor(message, mayStillBeRunning = false) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.mayStillBeRunning = mayStillBeRunning;
|
|
116
|
+
this.name = "NativeInstallError";
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
function runNativePlugin(cli, profile, home, toolPath, runtime, logFile, archiveName, timeoutMs = 6e5, operation = "add") {
|
|
120
|
+
if (!safeProfileName(profile) || !/^harness-remote-[\w.+-]+\.tgz$/.test(archiveName)) throw new Error("\u65E0\u6548\u7684\u5B89\u88C5\u76EE\u6807\u3002");
|
|
126
121
|
return new Promise((resolve, reject) => {
|
|
127
122
|
const log = fs.openSync(logFile, "a", 384);
|
|
123
|
+
const env = { ...process.env };
|
|
124
|
+
const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path");
|
|
125
|
+
const inheritedPath = pathKey ? env[pathKey] : "";
|
|
126
|
+
if (process.platform === "win32") {
|
|
127
|
+
for (const key of Object.keys(env)) if (key.toLowerCase() === "path") delete env[key];
|
|
128
|
+
}
|
|
129
|
+
Object.assign(env, {
|
|
130
|
+
DSH_HOME: home,
|
|
131
|
+
PATH: toolPath + path.delimiter + (inheritedPath || ""),
|
|
132
|
+
HARNESS_INSTALL_NODE: runtime.executable,
|
|
133
|
+
HARNESS_INSTALL_PNPM: runtime.cli,
|
|
134
|
+
CI: "true",
|
|
135
|
+
COREPACK_ENABLE_AUTO_PIN: "0",
|
|
136
|
+
npm_config_manage_package_manager_versions: "false"
|
|
137
|
+
});
|
|
128
138
|
const child = spawn(runtime.executable, [
|
|
129
139
|
cli,
|
|
130
140
|
"plugin",
|
|
131
141
|
"--profile",
|
|
132
142
|
profile,
|
|
133
|
-
"add",
|
|
134
|
-
"file:harness-remote-update.tgz",
|
|
143
|
+
...operation === "install" ? ["install"] : ["add", `file:${archiveName}`],
|
|
135
144
|
"--ignore-scripts",
|
|
136
145
|
"--config.frozen-lockfile=false",
|
|
137
146
|
"--prefer-offline",
|
|
@@ -141,78 +150,142 @@ function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
|
|
|
141
150
|
cwd: home,
|
|
142
151
|
shell: false,
|
|
143
152
|
windowsHide: true,
|
|
153
|
+
detached: process.platform !== "win32",
|
|
144
154
|
stdio: ["ignore", log, log],
|
|
145
|
-
env
|
|
146
|
-
...process.env,
|
|
147
|
-
DSH_HOME: home,
|
|
148
|
-
PATH: toolPath + path.delimiter + (process.env.PATH || ""),
|
|
149
|
-
CI: "true",
|
|
150
|
-
COREPACK_ENABLE_AUTO_PIN: "0",
|
|
151
|
-
npm_config_manage_package_manager_versions: "false"
|
|
152
|
-
}
|
|
155
|
+
env
|
|
153
156
|
});
|
|
154
157
|
fs.closeSync(log);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
clearTimeout(timer);
|
|
161
|
-
reject(new Error("\u65E0\u6CD5\u542F\u52A8 DSH \u539F\u751F\u5B89\u88C5\u7A0B\u5E8F\u3002"));
|
|
162
|
-
});
|
|
163
|
-
child.once("close", (code) => {
|
|
158
|
+
let finished = false, terminating = false, closed = false, terminationTimer;
|
|
159
|
+
const fail = (message, uncertain = false) => finish(new NativeInstallError(`${message} \u5B89\u88C5\u65E5\u5FD7\uFF1A${logFile}`, uncertain));
|
|
160
|
+
const finish = (error) => {
|
|
161
|
+
if (finished) return;
|
|
162
|
+
finished = true;
|
|
164
163
|
clearTimeout(timer);
|
|
165
|
-
|
|
164
|
+
clearTimeout(terminationTimer);
|
|
165
|
+
error ? reject(error) : resolve();
|
|
166
|
+
};
|
|
167
|
+
const timer = setTimeout(() => {
|
|
168
|
+
if (closed) return;
|
|
169
|
+
terminating = true;
|
|
170
|
+
terminationTimer = setTimeout(() => fail("\u5B89\u88C5\u8D85\u65F6\uFF0C\u5C1A\u4E0D\u80FD\u786E\u8BA4\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF1B\u672A\u81EA\u52A8\u56DE\u9000\uFF0C\u8BF7\u52FF\u91CD\u590D\u5B89\u88C5\u3002", true), 15e3);
|
|
171
|
+
if (process.platform === "win32") {
|
|
172
|
+
if (!child.pid) return fail("\u5B89\u88C5\u7A0B\u5E8F\u672A\u542F\u52A8\u3002");
|
|
173
|
+
execFile(
|
|
174
|
+
path.join(process.env.SystemRoot || "C:\\Windows", "System32", "taskkill.exe"),
|
|
175
|
+
["/PID", String(child.pid), "/T", "/F"],
|
|
176
|
+
{ windowsHide: true, timeout: 1e4 },
|
|
177
|
+
(error) => {
|
|
178
|
+
if (error) fail("\u5B89\u88C5\u8D85\u65F6\uFF0C\u65E0\u6CD5\u786E\u8BA4\u5B89\u88C5\u8FDB\u7A0B\u6811\u5DF2\u9000\u51FA\uFF1B\u672A\u81EA\u52A8\u56DE\u9000\uFF0C\u8BF7\u52FF\u91CD\u590D\u5B89\u88C5\u3002", true);
|
|
179
|
+
else if (closed) fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002");
|
|
180
|
+
else child.once("close", () => fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002"));
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
try {
|
|
185
|
+
if (child.pid) process.kill(-child.pid, "SIGKILL");
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error.code !== "ESRCH") return fail("\u65E0\u6CD5\u505C\u6B62\u8D85\u65F6\u7684\u5B89\u88C5\u8FDB\u7A0B\uFF1B\u672A\u81EA\u52A8\u56DE\u9000\u3002", true);
|
|
188
|
+
}
|
|
189
|
+
if (closed) fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002");
|
|
190
|
+
else child.once("close", () => fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002"));
|
|
191
|
+
}
|
|
192
|
+
}, timeoutMs);
|
|
193
|
+
child.once("error", (error) => fail(`\u65E0\u6CD5\u542F\u52A8 DSH \u539F\u751F\u5B89\u88C5\u7A0B\u5E8F\uFF08${error.code || error.name}\uFF09\u3002`));
|
|
194
|
+
child.once("close", (code, signal) => {
|
|
195
|
+
closed = true;
|
|
196
|
+
if (terminating) return;
|
|
197
|
+
if (code === 0) finish();
|
|
198
|
+
else fail(`DSH \u539F\u751F\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${signal ? `\u4FE1\u53F7 ${signal}` : `\u9000\u51FA\u7801 ${code}`}\uFF09\u3002`);
|
|
166
199
|
});
|
|
167
200
|
});
|
|
168
201
|
}
|
|
169
|
-
async function
|
|
170
|
-
const scope = path.basename(job.profile);
|
|
171
|
-
if (!safeProfileName(scope) ||
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
filter: (p) => !["node_modules", ".harness-remote-update.lock"].includes(path.basename(p))
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
let before = null;
|
|
183
|
-
const filename = path.join(staged, "package.json");
|
|
184
|
-
if (fs.existsSync(filename)) {
|
|
185
|
-
before = JSON.parse(fs.readFileSync(filename, "utf8"));
|
|
186
|
-
for (const [name, spec] of Object.entries(before.dependencies || {})) {
|
|
187
|
-
if (name !== PLUGIN_PACKAGE && !/^[~^]?\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(String(spec))) throw new Error("\u5176\u4ED6\u63D2\u4EF6\u4F7F\u7528\u4E86\u672C\u5730\u94FE\u63A5\u6216\u7279\u6B8A\u6765\u6E90\uFF0C\u672A\u4FEE\u6539\u5F53\u524D\u5B89\u88C5\u3002");
|
|
188
|
-
}
|
|
189
|
-
for (const e of fs.readdirSync(staged, { withFileTypes: true })) if (e.isSymbolicLink()) throw new Error("profile \u914D\u7F6E\u5305\u542B\u94FE\u63A5\uFF0C\u672A\u4FEE\u6539\u5F53\u524D\u5B89\u88C5\u3002");
|
|
190
|
-
if (before.packageManager && !/^pnpm@\d+\.\d+\.\d+(?:\+.*)?$/.test(before.packageManager)) throw new Error("\u5F53\u524D profile \u4F7F\u7528\u4E86\u5176\u4ED6\u5305\u7BA1\u7406\u5668\uFF0C\u672A\u4FEE\u6539\u5B89\u88C5\u3002");
|
|
191
|
-
writePrivateJsonAtomic(filename, { ...before, dependencies: {
|
|
192
|
-
...before.dependencies,
|
|
193
|
-
[PLUGIN_PACKAGE]: "file:harness-remote-update.tgz"
|
|
194
|
-
}, packageManager: `pnpm@${INSTALL_PNPM_VERSION}` });
|
|
195
|
-
}
|
|
196
|
-
fs.copyFileSync(path.join(job.directory, "release.tgz"), path.join(staged, "harness-remote-update.tgz"));
|
|
202
|
+
async function installProfile(job) {
|
|
203
|
+
const scope = path.basename(job.profile), home = path.dirname(path.dirname(job.profile));
|
|
204
|
+
if (!safeProfileName(scope) || path.dirname(job.profile) !== path.join(home, "profiles") || !/^[\w.+-]{1,80}$/.test(job.targetVersion)) throw new Error("\u5B89\u88C5\u76EE\u6807\u4E0D\u660E\u786E\u3002");
|
|
205
|
+
fs.mkdirSync(job.profile, { recursive: true, mode: 448 });
|
|
206
|
+
const archive = path.join(job.directory, "release.tgz");
|
|
207
|
+
const digest = createHash("sha256").update(fs.readFileSync(archive)).digest("hex");
|
|
208
|
+
const archiveBase = `harness-remote-${job.targetVersion}-${digest}`;
|
|
209
|
+
const suffix = fs.existsSync(path.join(job.profile, `${archiveBase}.tgz`)) ? `-${randomBytes2(8).toString("hex")}` : "";
|
|
210
|
+
const archiveName = `${archiveBase}${suffix}.tgz`;
|
|
211
|
+
fs.copyFileSync(archive, path.join(job.profile, archiveName), fs.constants.COPYFILE_EXCL);
|
|
197
212
|
const tools = installToolPath(job.directory, job.runtime);
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
213
|
+
const logFile = path.join(job.directory, "install.log");
|
|
214
|
+
const deadline = Date.now() + 6e5;
|
|
215
|
+
const run2 = (operation = "add") => runNativePlugin(job.cli, scope, home, tools, job.runtime, logFile, archiveName, Math.max(1, deadline - Date.now()), operation);
|
|
216
|
+
try {
|
|
217
|
+
await run2();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const layoutError = /^\s*(?:\[ERR_PNPM_|ERR_PNPM_)(?:UNEXPECTED_VIRTUAL_STORE|UNEXPECTED_STORE|MODULES_BREAKING_CHANGE|VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF|PUBLIC_HOIST_PATTERN_DIFF|HOIST_PATTERN_DIFF)(?:\]|\s)/m;
|
|
220
|
+
if (!(error instanceof NativeInstallError) || error.mayStillBeRunning || !layoutError.test(fs.readFileSync(logFile, "utf8"))) throw error;
|
|
221
|
+
fs.appendFileSync(logFile, "\nRestoring native pnpm layout with dsh plugin install.\n");
|
|
222
|
+
await run2("install");
|
|
223
|
+
await run2();
|
|
224
|
+
}
|
|
225
|
+
const installed = path.join(job.profile, "node_modules", PLUGIN_PACKAGE);
|
|
202
226
|
if (JSON.parse(fs.readFileSync(path.join(installed, "package.json"), "utf8")).version !== job.targetVersion) throw new Error("\u5B89\u88C5\u540E\u63D2\u4EF6\u7248\u672C\u4E0D\u5339\u914D\u3002");
|
|
203
|
-
const after = JSON.parse(fs.readFileSync(
|
|
227
|
+
const after = JSON.parse(fs.readFileSync(path.join(job.profile, "package.json"), "utf8"));
|
|
204
228
|
if (!after.dsh?.profile?.bundles?.includes(PLUGIN_PACKAGE)) throw new Error("DSH \u5C1A\u672A\u5C06\u63D2\u4EF6\u6CE8\u518C\u4E3A\u539F\u751F profile \u5C42\u3002");
|
|
205
|
-
for (const name of Object.keys(before?.dependencies || {})) if (name !== PLUGIN_PACKAGE) {
|
|
206
|
-
if (hash(path.join(job.profile, "node_modules", name, "package.json")) !== hash(path.join(staged, "node_modules", name, "package.json"))) throw new Error("\u5B89\u88C5\u8BD5\u56FE\u6539\u53D8\u5176\u4ED6\u63D2\u4EF6\uFF0C\u539F\u5B89\u88C5\u4FDD\u6301\u4E0D\u53D8\u3002");
|
|
207
|
-
}
|
|
208
|
-
assertRelocatableProfile(staged);
|
|
209
|
-
return staged;
|
|
210
229
|
}
|
|
211
230
|
|
|
212
|
-
// src/install-
|
|
231
|
+
// src/install-runtime.ts
|
|
213
232
|
import fs2 from "node:fs";
|
|
214
233
|
import path2 from "node:path";
|
|
215
|
-
import {
|
|
234
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
235
|
+
var INSTALL_PNPM_VERSION = "11.22.0";
|
|
236
|
+
async function pinInstallRuntime(runtime, directory) {
|
|
237
|
+
let root = path2.dirname(runtime.cli);
|
|
238
|
+
while (!fs2.existsSync(path2.join(root, "package.json"))) {
|
|
239
|
+
const parent = path2.dirname(root);
|
|
240
|
+
if (parent === root) throw new Error("\u65E0\u6CD5\u5B9A\u4F4D\u5B89\u88C5\u5DE5\u5177\u5305\u3002");
|
|
241
|
+
root = parent;
|
|
242
|
+
}
|
|
243
|
+
const manifest = JSON.parse(fs2.readFileSync(path2.join(root, "package.json"), "utf8"));
|
|
244
|
+
if (manifest.name !== "pnpm" || manifest.version !== runtime.version) throw new Error("\u5B89\u88C5\u5DE5\u5177\u7248\u672C\u4E0D\u4E00\u81F4\u3002");
|
|
245
|
+
const target = path2.join(directory, "install-runtime");
|
|
246
|
+
if (fs2.existsSync(target)) throw new Error("\u672C\u6B21\u5B89\u88C5\u5DE5\u5177\u76EE\u5F55\u5DF2\u5B58\u5728\u3002");
|
|
247
|
+
fs2.cpSync(root, target, { recursive: true, dereference: false, verbatimSymlinks: true, mode: fs2.constants.COPYFILE_FICLONE });
|
|
248
|
+
const pinned = { ...runtime, cli: path2.join(target, path2.relative(root, runtime.cli)) };
|
|
249
|
+
await verifyInstallRuntime(pinned);
|
|
250
|
+
return pinned;
|
|
251
|
+
}
|
|
252
|
+
function verifyInstallRuntime(runtime) {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
const child = spawn2(runtime.executable, [runtime.cli, "--version"], {
|
|
255
|
+
windowsHide: true,
|
|
256
|
+
shell: false,
|
|
257
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
258
|
+
env: { ...process.env, COREPACK_ENABLE_AUTO_PIN: "0", npm_config_manage_package_manager_versions: "false" }
|
|
259
|
+
});
|
|
260
|
+
let stdout = "", size = 0, settled = false;
|
|
261
|
+
const finish = (error) => {
|
|
262
|
+
if (settled) return;
|
|
263
|
+
settled = true;
|
|
264
|
+
clearTimeout(timer);
|
|
265
|
+
error ? reject(error) : resolve();
|
|
266
|
+
};
|
|
267
|
+
const timer = setTimeout(() => {
|
|
268
|
+
child.kill();
|
|
269
|
+
finish(new Error("\u5B89\u88C5\u5DE5\u5177\u542F\u52A8\u8D85\u65F6\uFF0C\u672A\u4FEE\u6539\u63D2\u4EF6\u3002"));
|
|
270
|
+
}, 15e3);
|
|
271
|
+
child.stdout.on("data", (b) => {
|
|
272
|
+
size += b.length;
|
|
273
|
+
if (size > 16384) {
|
|
274
|
+
child.kill();
|
|
275
|
+
finish(new Error("\u5B89\u88C5\u5DE5\u5177\u54CD\u5E94\u5F02\u5E38\u3002"));
|
|
276
|
+
} else stdout += b.toString();
|
|
277
|
+
});
|
|
278
|
+
child.stderr.on("data", () => {
|
|
279
|
+
});
|
|
280
|
+
child.once("error", () => finish(new Error("\u65E0\u6CD5\u542F\u52A8\u5B89\u88C5\u5DE5\u5177\uFF0C\u672A\u4FEE\u6539\u63D2\u4EF6\u3002")));
|
|
281
|
+
child.once("close", (code) => finish(code === 0 && stdout.trim() === runtime.version ? void 0 : new Error("\u5B89\u88C5\u5DE5\u5177\u8FD0\u884C\u9A8C\u8BC1\u5931\u8D25\uFF0C\u672A\u4FEE\u6539\u63D2\u4EF6\u3002")));
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/install-lifecycle.ts
|
|
286
|
+
import fs3 from "node:fs";
|
|
287
|
+
import path3 from "node:path";
|
|
288
|
+
import { execFileSync as execFileSync2, spawn as spawn3 } from "node:child_process";
|
|
216
289
|
var serviceName = (s) => /^[A-Za-z0-9_.@-]{1,180}$/.test(s);
|
|
217
290
|
function run(command, args) {
|
|
218
291
|
return execFileSync2(command, args, { encoding: "utf8", windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
@@ -220,7 +293,7 @@ function run(command, args) {
|
|
|
220
293
|
function validateManager(value) {
|
|
221
294
|
if (value.kind === "process") return;
|
|
222
295
|
if (value.kind === "systemd" && process.platform === "linux" && serviceName(value.unit) && value.unit.endsWith(".service")) return;
|
|
223
|
-
if (value.kind === "launchd" && process.platform === "darwin" && serviceName(value.label) && /^gui\/\d+$/.test(value.domain) && value.domain === `gui/${process.getuid?.()}` &&
|
|
296
|
+
if (value.kind === "launchd" && process.platform === "darwin" && serviceName(value.label) && /^gui\/\d+$/.test(value.domain) && value.domain === `gui/${process.getuid?.()}` && path3.isAbsolute(value.plist) && fs3.statSync(value.plist).isFile()) return;
|
|
224
297
|
throw new Error("\u65E0\u6CD5\u786E\u8BA4\u539F\u540E\u53F0\u670D\u52A1\uFF0C\u672A\u505C\u6B62 DSH\u3002");
|
|
225
298
|
}
|
|
226
299
|
function stopManagedHost(manager) {
|
|
@@ -236,7 +309,7 @@ function startManagedHost(manager) {
|
|
|
236
309
|
else throw new Error("\u666E\u901A DSH \u8FDB\u7A0B\u5FC5\u987B\u7531\u539F\u73AF\u5883\u542F\u52A8\u3002");
|
|
237
310
|
}
|
|
238
311
|
function finishUpdateWorker(manager, directory) {
|
|
239
|
-
const id =
|
|
312
|
+
const id = path3.basename(directory);
|
|
240
313
|
if (manager?.kind !== "launchd" || !/^[a-f0-9]{32}$/.test(id)) return;
|
|
241
314
|
try {
|
|
242
315
|
run("/bin/launchctl", ["remove", `dsh.wechat.update.${id}`]);
|
|
@@ -247,21 +320,21 @@ function finishUpdateWorker(manager, directory) {
|
|
|
247
320
|
// src/update-worker.ts
|
|
248
321
|
function releaseOwnedUpdateLock(lock, id) {
|
|
249
322
|
try {
|
|
250
|
-
if (
|
|
323
|
+
if (fs4.readFileSync(lock, "utf8") === id) fs4.unlinkSync(lock);
|
|
251
324
|
} catch {
|
|
252
325
|
}
|
|
253
326
|
}
|
|
254
327
|
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
255
|
-
var hashFile = (f) => createHash2("sha256").update(
|
|
328
|
+
var hashFile = (f) => createHash2("sha256").update(fs4.readFileSync(f)).digest("hex");
|
|
256
329
|
function within(parent, child) {
|
|
257
|
-
const relative =
|
|
258
|
-
return Boolean(relative) && !relative.startsWith("..") && !
|
|
330
|
+
const relative = path4.relative(parent, child);
|
|
331
|
+
return Boolean(relative) && !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
259
332
|
}
|
|
260
333
|
function safePlainDirectory(p) {
|
|
261
|
-
if (!
|
|
334
|
+
if (!fs4.statSync(p).isDirectory() || fs4.lstatSync(p).isSymbolicLink() || fs4.realpathSync(p) !== path4.resolve(p)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u662F\u53EF\u5B89\u5168\u66FF\u6362\u7684\u72EC\u7ACB\u76EE\u5F55");
|
|
262
335
|
}
|
|
263
336
|
function validateJob(job) {
|
|
264
|
-
if (!/^[a-f0-9]{32}$/.test(job.id) ||
|
|
337
|
+
if (!/^[a-f0-9]{32}$/.test(job.id) || path4.basename(job.directory) !== job.id || !within(path4.join(job.home, "harness-remote-updates"), job.directory) || !within(path4.join(job.home, "profiles"), job.profile) || path4.dirname(job.profile) !== path4.join(job.home, "profiles") || !within(job.home, job.stateFile) || !Number.isInteger(job.parentPid) || job.parentPid < 1 || ![job.webPort, job.gatePort, job.localPort].every((p) => Number.isInteger(p) && p > 0 && p <= 65535) || job.argv[0] !== job.cli || !/^[\w.+-]{1,80}$/.test(job.targetVersion)) throw new Error("\u66F4\u65B0\u4EFB\u52A1\u8303\u56F4\u6821\u9A8C\u5931\u8D25");
|
|
265
338
|
safePlainDirectory(job.profile);
|
|
266
339
|
safePlainDirectory(job.directory);
|
|
267
340
|
if (job.identityFile && !within(job.home, job.identityFile)) throw new Error("\u8282\u70B9\u8EAB\u4EFD\u6587\u4EF6\u4E0D\u5C5E\u4E8E\u5F53\u524D DSH");
|
|
@@ -270,7 +343,7 @@ function validateJob(job) {
|
|
|
270
343
|
if (u.protocol !== "http:" || u.hostname !== "127.0.0.1" || !u.port || u.username || u.password || u.pathname !== "/" || u.search || u.hash) throw new Error("\u5B89\u88C5\u63A7\u5236\u5730\u5740\u65E0\u6548");
|
|
271
344
|
validateManager(job.manager);
|
|
272
345
|
}
|
|
273
|
-
for (const f of [job.executable, job.cli, job.pnpm, ...job.previousVersion === "0.0.0" ? [] : [job.stateFile]]) if (!
|
|
346
|
+
for (const f of [job.executable, job.cli, job.pnpm, ...job.previousVersion === "0.0.0" ? [] : [job.stateFile]]) if (!fs4.statSync(f).isFile()) throw new Error("\u5B89\u88C5\u8FD0\u884C\u65F6\u5DF2\u53D8\u5316");
|
|
274
347
|
}
|
|
275
348
|
async function control(job, operation, input = {}) {
|
|
276
349
|
let res;
|
|
@@ -296,7 +369,7 @@ async function beforeRpc(job, method, payload = {}) {
|
|
|
296
369
|
return job.controlOrigin ? control(job, "read", { method, payload }) : rpc(job, method, payload);
|
|
297
370
|
}
|
|
298
371
|
async function rpc(job, method, payload = {}, deadline) {
|
|
299
|
-
const state = JSON.parse(
|
|
372
|
+
const state = JSON.parse(fs4.readFileSync(job.stateFile, "utf8"));
|
|
300
373
|
const res = await fetch(`http://127.0.0.1:${job.gatePort}/api/${method}`, {
|
|
301
374
|
method: "POST",
|
|
302
375
|
headers: { authorization: `Bearer ${state.token}`, "content-type": "application/json", "x-harness-update-probe": job.statusToken },
|
|
@@ -311,38 +384,38 @@ async function rpc(job, method, payload = {}, deadline) {
|
|
|
311
384
|
function durableSnapshot(job) {
|
|
312
385
|
const result = {};
|
|
313
386
|
const walk = (dir) => {
|
|
314
|
-
if (!
|
|
315
|
-
for (const e of
|
|
316
|
-
const file =
|
|
317
|
-
if (e.isSymbolicLink())
|
|
318
|
-
if (e.isDirectory()) walk(file);
|
|
319
|
-
else if (e.isFile()) result[
|
|
387
|
+
if (!fs4.existsSync(dir)) return;
|
|
388
|
+
for (const e of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
389
|
+
const file = path4.join(dir, e.name);
|
|
390
|
+
if (e.isSymbolicLink()) result[path4.relative(job.home, file)] = createHash2("sha256").update(fs4.readlinkSync(file)).digest("hex");
|
|
391
|
+
else if (e.isDirectory()) walk(file);
|
|
392
|
+
else if (e.isFile()) result[path4.relative(job.home, file)] = hashFile(file);
|
|
320
393
|
}
|
|
321
394
|
};
|
|
322
|
-
walk(
|
|
323
|
-
walk(
|
|
324
|
-
walk(
|
|
325
|
-
for (const e of
|
|
395
|
+
walk(path4.join(job.home, "sessions"));
|
|
396
|
+
walk(path4.join(job.home, "attachments"));
|
|
397
|
+
walk(path4.join(job.home, "harness-remote"));
|
|
398
|
+
for (const e of fs4.readdirSync(job.home, { withFileTypes: true })) {
|
|
326
399
|
if (e.isFile() && /(?:identity|settings|credentials|public|gate-wechat)/i.test(e.name) && !e.name.endsWith(".log")) {
|
|
327
|
-
const f =
|
|
400
|
+
const f = path4.join(job.home, e.name);
|
|
328
401
|
if (f === job.stateFile) continue;
|
|
329
402
|
result[e.name] = hashFile(f);
|
|
330
403
|
}
|
|
331
404
|
}
|
|
332
|
-
if (
|
|
333
|
-
const state = JSON.parse(
|
|
334
|
-
result["$binding"] = createHash2("sha256").update(JSON.stringify([state.token, state.
|
|
405
|
+
if (fs4.existsSync(job.stateFile)) {
|
|
406
|
+
const state = JSON.parse(fs4.readFileSync(job.stateFile, "utf8"));
|
|
407
|
+
result["$binding"] = createHash2("sha256").update(JSON.stringify([state.token, state.publicIdentityNodeId])).digest("hex");
|
|
335
408
|
}
|
|
336
409
|
return result;
|
|
337
410
|
}
|
|
338
411
|
function assertPreserved(before, after) {
|
|
339
|
-
for (const [key,
|
|
412
|
+
for (const [key, hash] of Object.entries(before)) if (after[key] !== hash) throw new Error("\u5347\u7EA7\u540E\u6570\u636E\u6821\u9A8C\u4E0D\u4E00\u81F4\uFF1B\u505C\u6B62\u81EA\u52A8\u64CD\u4F5C\u5E76\u4FDD\u7559\u5907\u4EFD");
|
|
340
413
|
}
|
|
341
414
|
function migrateLegacyGrantOwner(job) {
|
|
342
|
-
if (job.previousVersion !== "1.5.5" || !job.identityFile || !
|
|
343
|
-
const state = JSON.parse(
|
|
415
|
+
if (job.previousVersion !== "1.5.5" || !job.identityFile || !fs4.existsSync(job.identityFile)) return;
|
|
416
|
+
const state = JSON.parse(fs4.readFileSync(job.stateFile, "utf8"));
|
|
344
417
|
if (state.publicIdentityNodeId) return;
|
|
345
|
-
const identity = JSON.parse(
|
|
418
|
+
const identity = JSON.parse(fs4.readFileSync(job.identityFile, "utf8"));
|
|
346
419
|
const publicKey = createPublicKey(identity.privateKeyPem).export({ format: "der", type: "spki" });
|
|
347
420
|
const savedKey = createPublicKey(identity.publicKeyPem).export({ format: "der", type: "spki" });
|
|
348
421
|
const nodeId = createHash2("sha256").update(publicKey).digest().subarray(0, 18).toString("base64url");
|
|
@@ -380,20 +453,20 @@ function start(job) {
|
|
|
380
453
|
startManagedHost(job.manager);
|
|
381
454
|
return;
|
|
382
455
|
}
|
|
383
|
-
const log =
|
|
384
|
-
const child =
|
|
456
|
+
const log = fs4.openSync(path4.join(job.directory, "restart.log"), "a", 384);
|
|
457
|
+
const child = spawn4(job.executable, [...job.execArgv, ...job.argv], {
|
|
385
458
|
cwd: job.cwd,
|
|
386
459
|
env: { ...process.env, HARNESS_REMOTE_UPDATE_JOB: job.directory },
|
|
387
460
|
stdio: ["ignore", log, log],
|
|
388
461
|
detached: true,
|
|
389
462
|
windowsHide: true
|
|
390
463
|
});
|
|
391
|
-
|
|
464
|
+
fs4.closeSync(log);
|
|
392
465
|
child.on("error", () => {
|
|
393
466
|
});
|
|
394
467
|
child.unref();
|
|
395
468
|
if (!child.pid) throw new Error("\u65E0\u6CD5\u542F\u52A8\u539F DSH \u547D\u4EE4");
|
|
396
|
-
writePrivateJsonAtomic(
|
|
469
|
+
writePrivateJsonAtomic(path4.join(job.directory, "restarted-process.json"), { pid: child.pid, cli: job.cli, home: job.home, webPort: job.webPort });
|
|
397
470
|
return child;
|
|
398
471
|
}
|
|
399
472
|
async function stopOriginal(job) {
|
|
@@ -436,20 +509,20 @@ async function stopRestarted(child, timeoutMs = 3e4, forceTimeoutMs = 5e3) {
|
|
|
436
509
|
}
|
|
437
510
|
function captureCandidateLock(home, child) {
|
|
438
511
|
if (!child.pid || child.exitCode !== null || child.signalCode !== null) return;
|
|
439
|
-
const filename =
|
|
512
|
+
const filename = path4.join(home, ".credentials.yaml.lock");
|
|
440
513
|
try {
|
|
441
|
-
const info =
|
|
514
|
+
const info = fs4.lstatSync(filename);
|
|
442
515
|
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > 32) return;
|
|
443
|
-
const value =
|
|
516
|
+
const value = fs4.readFileSync(filename, "utf8");
|
|
444
517
|
let owned = value === `${child.pid}
|
|
445
518
|
`;
|
|
446
519
|
if (!owned && value === "" && process.platform === "linux") {
|
|
447
520
|
const directory = `/proc/${child.pid}/fd`;
|
|
448
|
-
owned =
|
|
521
|
+
owned = fs4.readdirSync(directory).some((fd) => {
|
|
449
522
|
try {
|
|
450
|
-
const file =
|
|
451
|
-
if (
|
|
452
|
-
const opened =
|
|
523
|
+
const file = path4.join(directory, fd);
|
|
524
|
+
if (fs4.readlinkSync(file) !== filename) return false;
|
|
525
|
+
const opened = fs4.statSync(file);
|
|
453
526
|
return opened.dev === info.dev && opened.ino === info.ino;
|
|
454
527
|
} catch {
|
|
455
528
|
return false;
|
|
@@ -463,13 +536,13 @@ function captureCandidateLock(home, child) {
|
|
|
463
536
|
function retireCandidateLock(lock, child, directory) {
|
|
464
537
|
if (!lock || child.pid !== lock.pid || child.exitCode === null && child.signalCode === null) return;
|
|
465
538
|
try {
|
|
466
|
-
const info =
|
|
539
|
+
const info = fs4.lstatSync(lock.filename);
|
|
467
540
|
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.dev !== lock.dev || info.ino !== lock.ino || info.size > 32) return;
|
|
468
|
-
const value =
|
|
541
|
+
const value = fs4.readFileSync(lock.filename, "utf8");
|
|
469
542
|
if (value !== "" && value !== `${lock.pid}
|
|
470
543
|
`) return;
|
|
471
|
-
const saved =
|
|
472
|
-
if (!
|
|
544
|
+
const saved = path4.join(directory, "candidate-credentials-lock.before-rollback");
|
|
545
|
+
if (!fs4.existsSync(saved)) fs4.renameSync(lock.filename, saved);
|
|
473
546
|
} catch {
|
|
474
547
|
}
|
|
475
548
|
}
|
|
@@ -487,20 +560,15 @@ async function stopChild(pid) {
|
|
|
487
560
|
}
|
|
488
561
|
async function executeUpdate(job, progress, quiesce) {
|
|
489
562
|
validateJob(job);
|
|
490
|
-
|
|
491
|
-
const previous = path3.join(job.directory, "profile-before");
|
|
563
|
+
const previous = path4.join(job.directory, "profile-before");
|
|
492
564
|
const emit = (phase, n, message) => progress({ phase, progress: n, message, terminal: false });
|
|
493
|
-
let stopped = false, disposed = false,
|
|
565
|
+
let stopped = false, disposed = false, modified = false, newChild;
|
|
494
566
|
let before = {}, sessionIds = [], readableIds = [];
|
|
495
567
|
try {
|
|
496
|
-
emit("
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
cli: job.cli,
|
|
501
|
-
targetVersion: job.targetVersion,
|
|
502
|
-
runtime: { executable: job.executable, cli: job.pnpm, version: INSTALL_PNPM_VERSION }
|
|
503
|
-
});
|
|
568
|
+
emit("preparing", 25, "\u51C6\u5907\u5B89\u88C5\u5DE5\u5177\uFF0C\u5F53\u524D\u8282\u70B9\u4ECD\u53EF\u4F7F\u7528");
|
|
569
|
+
const runtime = await pinInstallRuntime({ executable: job.executable, cli: job.pnpm, version: INSTALL_PNPM_VERSION }, job.directory);
|
|
570
|
+
job.pnpm = runtime.cli;
|
|
571
|
+
writePrivateJsonAtomic(path4.join(job.directory, "job.json"), job);
|
|
504
572
|
emit("checking", 50, "\u786E\u8BA4\u4F1A\u8BDD\u7A7A\u95F2\u5E76\u4FDD\u5B58\u72B6\u6001");
|
|
505
573
|
const old = job.controlOrigin ? await control(job, "describe") : await describe(job);
|
|
506
574
|
if (old.pluginVersion !== job.previousVersion) throw new Error("\u5F53\u524D\u63D2\u4EF6\u5728\u68C0\u67E5\u540E\u53D1\u751F\u53D8\u5316");
|
|
@@ -517,29 +585,17 @@ async function executeUpdate(job, progress, quiesce) {
|
|
|
517
585
|
await quiesce();
|
|
518
586
|
disposed = Boolean(job.controlOrigin);
|
|
519
587
|
before = durableSnapshot(job);
|
|
520
|
-
writePrivateJsonAtomic(
|
|
521
|
-
emit("
|
|
522
|
-
const backup = path3.join(job.directory, "home-before");
|
|
523
|
-
fs3.mkdirSync(backup, { mode: 448 });
|
|
524
|
-
for (const entry of fs3.readdirSync(job.home, { withFileTypes: true })) {
|
|
525
|
-
if (["harness-remote-updates", "profiles"].includes(entry.name)) continue;
|
|
526
|
-
if (entry.isSymbolicLink()) throw new Error("\u6570\u636E\u76EE\u5F55\u5305\u542B\u5916\u90E8\u94FE\u63A5\uFF0C\u8BF7\u624B\u5DE5\u5907\u4EFD\u540E\u66F4\u65B0");
|
|
527
|
-
fs3.cpSync(path3.join(job.home, entry.name), path3.join(backup, entry.name), { recursive: true });
|
|
528
|
-
}
|
|
529
|
-
emit("restarting", 70, "\u6B63\u5728\u91CD\u542F\u5F53\u524D DSH\uFF0C\u8FDE\u63A5\u4F1A\u6682\u65F6\u65AD\u5F00");
|
|
588
|
+
writePrivateJsonAtomic(path4.join(job.directory, "before-hashes.json"), before);
|
|
589
|
+
emit("installing", 60, "\u6B63\u5728\u5B89\u88C5\u63D2\u4EF6\uFF0C\u5F53\u524D DSH \u8FDE\u63A5\u4F1A\u6682\u65F6\u65AD\u5F00");
|
|
530
590
|
await stopOriginal(job);
|
|
531
591
|
stopped = true;
|
|
532
592
|
assertPreserved(before, durableSnapshot(job));
|
|
593
|
+
backupProfile(job.profile, previous);
|
|
533
594
|
migrateLegacyGrantOwner(job);
|
|
534
595
|
safePlainDirectory(job.profile);
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
swapped = true;
|
|
539
|
-
} catch (error) {
|
|
540
|
-
fs3.renameSync(previous, job.profile);
|
|
541
|
-
throw error;
|
|
542
|
-
}
|
|
596
|
+
modified = true;
|
|
597
|
+
await installProfile({ profile: job.profile, directory: job.directory, cli: job.cli, targetVersion: job.targetVersion, runtime });
|
|
598
|
+
emit("restarting", 75, "\u5B89\u88C5\u5B8C\u6210\uFF0C\u6B63\u5728\u6062\u590D\u5F53\u524D DSH");
|
|
543
599
|
newChild = start(job);
|
|
544
600
|
emit("verifying", 85, "\u68C0\u67E5\u63D2\u4EF6\u7248\u672C\u3001\u8282\u70B9\u8EAB\u4EFD\u548C\u4F1A\u8BDD");
|
|
545
601
|
await healthy(job, job.targetVersion);
|
|
@@ -548,15 +604,23 @@ async function executeUpdate(job, progress, quiesce) {
|
|
|
548
604
|
const after = (await rpc(job, "session.list")).items.map((s) => s.sessionId).sort();
|
|
549
605
|
if (JSON.stringify(after) !== JSON.stringify(sessionIds)) throw new Error("\u91CD\u542F\u540E\u4F1A\u8BDD\u5217\u8868\u4E0D\u4E00\u81F4");
|
|
550
606
|
for (const id of readableIds) await rpc(job, "session.history", { sessionId: id, maxMessages: 1 });
|
|
551
|
-
writePrivateJsonAtomic(
|
|
607
|
+
writePrivateJsonAtomic(path4.join(job.directory, "verification-complete.json"), { id: job.id });
|
|
552
608
|
return { phase: "complete", progress: 100, message: "\u63D2\u4EF6\u66F4\u65B0\u5B8C\u6210\uFF0CDSH \u5DF2\u6062\u590D\uFF1B\u539F\u8282\u70B9\u65E0\u9700\u91CD\u65B0\u914D\u5BF9\u3002", terminal: true, ok: true };
|
|
553
609
|
} catch (error) {
|
|
554
610
|
let rollback = false;
|
|
555
|
-
writePrivateJsonAtomic(
|
|
611
|
+
writePrivateJsonAtomic(path4.join(job.directory, "failure.json"), {
|
|
556
612
|
message: error instanceof Error ? error.message : "unknown",
|
|
557
613
|
stack: error instanceof Error ? error.stack : void 0,
|
|
558
614
|
cause: error instanceof Error && error.cause instanceof Error ? error.cause.message : void 0
|
|
559
615
|
});
|
|
616
|
+
if (error instanceof NativeInstallError && error.mayStillBeRunning) return {
|
|
617
|
+
phase: "attention",
|
|
618
|
+
progress: 100,
|
|
619
|
+
message: error.message,
|
|
620
|
+
terminal: true,
|
|
621
|
+
ok: false,
|
|
622
|
+
rollback: false
|
|
623
|
+
};
|
|
560
624
|
if (job.controlOrigin && !disposed) {
|
|
561
625
|
try {
|
|
562
626
|
disposed = (await control(job, "describe")).quiesced === true;
|
|
@@ -574,19 +638,24 @@ async function executeUpdate(job, progress, quiesce) {
|
|
|
574
638
|
const ownedLock = captureCandidateLock(job.home, newChild);
|
|
575
639
|
await stopRestarted(newChild);
|
|
576
640
|
retireCandidateLock(ownedLock, newChild, job.directory);
|
|
577
|
-
} else if (
|
|
578
|
-
if (
|
|
579
|
-
|
|
580
|
-
|
|
641
|
+
} else if (modified && job.manager && job.manager.kind !== "process") stopManagedHost(job.manager);
|
|
642
|
+
if (modified) {
|
|
643
|
+
fs4.renameSync(job.profile, path4.join(job.directory, "profile-failed"));
|
|
644
|
+
fs4.renameSync(previous, job.profile);
|
|
645
|
+
}
|
|
646
|
+
if (job.previousVersion === "0.0.0") {
|
|
647
|
+
const recovery = await import(pathToFileURL(path4.join(job.directory, "native-recovery.js")).href);
|
|
648
|
+
await recovery.verifyNativeRestore(job, () => start(job), sessionIds, readableIds);
|
|
649
|
+
} else {
|
|
650
|
+
start(job);
|
|
651
|
+
await healthy(job, job.previousVersion);
|
|
581
652
|
}
|
|
582
|
-
start(job);
|
|
583
|
-
await healthy(job, job.previousVersion);
|
|
584
653
|
assertPreserved(before, durableSnapshot(job));
|
|
585
654
|
rollback = true;
|
|
586
|
-
writePrivateJsonAtomic(
|
|
655
|
+
writePrivateJsonAtomic(path4.join(job.directory, "verification-complete.json"), { id: job.id });
|
|
587
656
|
} catch (rollbackError) {
|
|
588
657
|
try {
|
|
589
|
-
writePrivateJsonAtomic(
|
|
658
|
+
writePrivateJsonAtomic(path4.join(job.directory, "rollback-failure.json"), {
|
|
590
659
|
message: rollbackError instanceof Error ? rollbackError.message : "unknown",
|
|
591
660
|
stack: rollbackError instanceof Error ? rollbackError.stack : void 0
|
|
592
661
|
});
|
|
@@ -599,14 +668,14 @@ async function executeUpdate(job, progress, quiesce) {
|
|
|
599
668
|
}
|
|
600
669
|
}
|
|
601
670
|
async function workerMain(filename) {
|
|
602
|
-
const job = JSON.parse(
|
|
671
|
+
const job = JSON.parse(fs4.readFileSync(filename, "utf8"));
|
|
603
672
|
validateJob(job);
|
|
604
673
|
if (!job.controlOrigin && (process.ppid !== job.parentPid || !process.connected)) throw new Error("Updater requires its initiating parent");
|
|
605
674
|
let status = { phase: "starting", progress: 20, message: "\u6B63\u5728\u51C6\u5907\u66F4\u65B0", terminal: false };
|
|
606
675
|
const record = (value) => {
|
|
607
676
|
status = value;
|
|
608
677
|
try {
|
|
609
|
-
writePrivateJsonAtomic(
|
|
678
|
+
writePrivateJsonAtomic(path4.join(job.directory, "result.json"), value);
|
|
610
679
|
} catch (error) {
|
|
611
680
|
console.error("Update progress journal unavailable:", error.code || "write-failed");
|
|
612
681
|
}
|
|
@@ -639,11 +708,11 @@ async function workerMain(filename) {
|
|
|
639
708
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
640
709
|
try {
|
|
641
710
|
if (job.controlOrigin) {
|
|
642
|
-
writePrivateJsonAtomic(
|
|
711
|
+
writePrivateJsonAtomic(path4.join(job.directory, "worker-ready.json"), { id: job.id, origin: `http://127.0.0.1:${server.address().port}` });
|
|
643
712
|
let authorized = false;
|
|
644
713
|
for (let i = 0; i < 150; i++) {
|
|
645
714
|
try {
|
|
646
|
-
authorized = JSON.parse(
|
|
715
|
+
authorized = JSON.parse(fs4.readFileSync(path4.join(job.directory, "authorized.json"), "utf8")).id === job.id;
|
|
647
716
|
} catch {
|
|
648
717
|
}
|
|
649
718
|
if (authorized) break;
|
|
@@ -705,8 +774,8 @@ async function workerMain(filename) {
|
|
|
705
774
|
message: "\u66F4\u65B0\u88AB\u5F02\u5E38\u4E2D\u65AD\uFF0C\u65E0\u6CD5\u786E\u8BA4\u6062\u590D\u7ED3\u679C\u3002\u8BF7\u4FDD\u7559\u4E3B\u673A\u66F4\u65B0\u76EE\u5F55\u5E76\u68C0\u67E5\u539F\u63D2\u4EF6\u5907\u4EFD\uFF0C\u4E0D\u8981\u5220\u9664\u8282\u70B9\u6216\u91CD\u590D\u5B89\u88C5\u3002"
|
|
706
775
|
});
|
|
707
776
|
}
|
|
708
|
-
const lock =
|
|
709
|
-
releaseOwnedUpdateLock(lock, job.id);
|
|
777
|
+
const lock = path4.join(job.profile, ".harness-remote-update.lock");
|
|
778
|
+
if (status.phase !== "attention") releaseOwnedUpdateLock(lock, job.id);
|
|
710
779
|
if (job.controlOrigin) {
|
|
711
780
|
try {
|
|
712
781
|
await control(job, "close");
|
|
@@ -721,7 +790,7 @@ async function workerMain(filename) {
|
|
|
721
790
|
}, 12e4);
|
|
722
791
|
timer.unref();
|
|
723
792
|
}
|
|
724
|
-
if (process.argv[1] &&
|
|
793
|
+
if (process.argv[1] && path4.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
725
794
|
void workerMain(process.argv[2]).catch(() => {
|
|
726
795
|
process.exitCode = 1;
|
|
727
796
|
if (process.connected) process.disconnect();
|