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