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.
@@ -1,12 +1,12 @@
1
1
  /* Generated from the shared plugin installation sources. */
2
2
 
3
3
  // src/update-worker.ts
4
- import fs3 from "node:fs";
5
- import path3 from "node:path";
4
+ import fs4 from "node:fs";
5
+ import path4 from "node:path";
6
6
  import http from "node:http";
7
- import { spawn as spawn3 } from "node:child_process";
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 { spawn } from "node:child_process";
80
- import { createHash } from "node:crypto";
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
- var hash = (file) => createHash("sha256").update(fs.readFileSync(file)).digest("hex");
91
- function assertRelocatableProfile(root) {
92
- const walk = (directory) => {
93
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
94
- const file = path.join(directory, entry.name);
95
- if (entry.isSymbolicLink()) {
96
- const relative = path.relative(root, fs.realpathSync(file));
97
- if (path.isAbsolute(fs.readlinkSync(file)) || !relative || relative.startsWith("..") || path.isAbsolute(relative)) {
98
- throw new Error("\u63D2\u4EF6\u4F9D\u8D56\u5305\u542B\u4E0D\u53EF\u8FC1\u79FB\u7684\u94FE\u63A5\uFF0C\u672A\u4FEE\u6539\u5F53\u524D\u5B89\u88C5\u3002");
99
- }
100
- } else if (entry.isDirectory()) walk(file);
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("Node \u5B89\u88C5\u8DEF\u5F84\u4E0D\u53D7\u652F\u6301\u3002");
113
- fs.writeFileSync(path.join(bin, "pnpm.cmd"), `@echo off\r
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
- const quote = (s) => "'" + s.replace(/'/g, `'"'"'`) + "'";
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
- function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
125
- if (!safeProfileName(profile)) throw new Error("\u65E0\u6548\u7684 DSH profile \u540D\u79F0\u3002");
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
- const timer = setTimeout(() => {
156
- child.kill();
157
- reject(new Error("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u539F\u63D2\u4EF6\u672A\u66FF\u6362\u3002"));
158
- }, 24e4);
159
- child.once("error", () => {
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
- code === 0 ? resolve() : reject(new Error("\u5B89\u88C5\u672A\u5B8C\u6210\uFF0C\u539F\u63D2\u4EF6\u672A\u66FF\u6362\uFF1B\u8BE6\u60C5\u5DF2\u4FDD\u7559\u5728\u672C\u673A\u5B89\u88C5\u65E5\u5FD7\u3002"));
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 stageProfile(job) {
170
- const scope = path.basename(job.profile);
171
- if (!safeProfileName(scope) || !fs.statSync(job.directory).isDirectory()) throw new Error("\u5B89\u88C5\u76EE\u6807\u4E0D\u660E\u786E\u3002");
172
- const stagingHome = path.join(job.directory, "staging-home"), staged = path.join(stagingHome, "profiles", scope);
173
- fs.mkdirSync(staged, { recursive: true, mode: 448 });
174
- if (fs.existsSync(job.profile)) {
175
- if (fs.lstatSync(job.profile).isSymbolicLink() || fs.realpathSync(job.profile) !== path.resolve(job.profile)) throw new Error("\u4E0D\u652F\u6301\u81EA\u52A8\u66FF\u6362\u94FE\u63A5\u5F62\u5F0F\u7684 profile\u3002");
176
- fs.cpSync(job.profile, staged, {
177
- recursive: true,
178
- dereference: false,
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
- await runNativePlugin(job.cli, scope, stagingHome, tools, job.runtime, path.join(job.directory, "install.log"));
199
- const installed = fs.realpathSync(path.join(staged, "node_modules", PLUGIN_PACKAGE));
200
- const relative = path.relative(staged, installed);
201
- if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error("\u6682\u5B58\u63D2\u4EF6\u4E0D\u5728\u5B89\u88C5\u76EE\u5F55\u5185\u3002");
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(filename, "utf8"));
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-lifecycle.ts
231
+ // src/install-runtime.ts
213
232
  import fs2 from "node:fs";
214
233
  import path2 from "node:path";
215
- import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
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?.()}` && path2.isAbsolute(value.plist) && fs2.statSync(value.plist).isFile()) return;
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 = path2.basename(directory);
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 (fs3.readFileSync(lock, "utf8") === id) fs3.unlinkSync(lock);
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(fs3.readFileSync(f)).digest("hex");
328
+ var hashFile = (f) => createHash2("sha256").update(fs4.readFileSync(f)).digest("hex");
256
329
  function within(parent, child) {
257
- const relative = path3.relative(parent, child);
258
- return Boolean(relative) && !relative.startsWith("..") && !path3.isAbsolute(relative);
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 (!fs3.statSync(p).isDirectory() || fs3.lstatSync(p).isSymbolicLink() || fs3.realpathSync(p) !== path3.resolve(p)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u662F\u53EF\u5B89\u5168\u66FF\u6362\u7684\u72EC\u7ACB\u76EE\u5F55");
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) || path3.basename(job.directory) !== job.id || !within(path3.join(job.home, "harness-remote-updates"), job.directory) || !within(path3.join(job.home, "profiles"), job.profile) || path3.dirname(job.profile) !== path3.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.includes(job.cli) || !job.argv.includes("web") || !/^[\w.+-]{1,80}$/.test(job.targetVersion)) throw new Error("\u66F4\u65B0\u4EFB\u52A1\u8303\u56F4\u6821\u9A8C\u5931\u8D25");
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 (!fs3.statSync(f).isFile()) throw new Error("\u5B89\u88C5\u8FD0\u884C\u65F6\u5DF2\u53D8\u5316");
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(fs3.readFileSync(job.stateFile, "utf8"));
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 (!fs3.existsSync(dir)) return;
315
- for (const e of fs3.readdirSync(dir, { withFileTypes: true })) {
316
- const file = path3.join(dir, e.name);
317
- if (e.isSymbolicLink()) throw new Error("\u53D7\u4FDD\u62A4\u7684\u6570\u636E\u76EE\u5F55\u542B\u94FE\u63A5\uFF0C\u9700\u624B\u5DE5\u66F4\u65B0");
318
- if (e.isDirectory()) walk(file);
319
- else if (e.isFile()) result[path3.relative(job.home, file)] = hashFile(file);
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(path3.join(job.home, "sessions"));
323
- walk(path3.join(job.home, "attachments"));
324
- walk(path3.join(job.home, "harness-remote"));
325
- for (const e of fs3.readdirSync(job.home, { withFileTypes: true })) {
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 = path3.join(job.home, e.name);
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 (fs3.existsSync(job.stateFile)) {
333
- const state = JSON.parse(fs3.readFileSync(job.stateFile, "utf8"));
334
- result["$binding"] = createHash2("sha256").update(JSON.stringify([state.token, state.wechatBindings])).digest("hex");
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, hash2] of Object.entries(before)) if (after[key] !== hash2) 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");
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 || !fs3.existsSync(job.identityFile)) return;
343
- const state = JSON.parse(fs3.readFileSync(job.stateFile, "utf8"));
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(fs3.readFileSync(job.identityFile, "utf8"));
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 = fs3.openSync(path3.join(job.directory, "restart.log"), "a", 384);
384
- const child = spawn3(job.executable, [...job.execArgv, ...job.argv], {
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
- fs3.closeSync(log);
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(path3.join(job.directory, "restarted-process.json"), { pid: child.pid, cli: job.cli, home: job.home, webPort: job.webPort });
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 = path3.join(home, ".credentials.yaml.lock");
512
+ const filename = path4.join(home, ".credentials.yaml.lock");
440
513
  try {
441
- const info = fs3.lstatSync(filename);
514
+ const info = fs4.lstatSync(filename);
442
515
  if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > 32) return;
443
- const value = fs3.readFileSync(filename, "utf8");
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 = fs3.readdirSync(directory).some((fd) => {
521
+ owned = fs4.readdirSync(directory).some((fd) => {
449
522
  try {
450
- const file = path3.join(directory, fd);
451
- if (fs3.readlinkSync(file) !== filename) return false;
452
- const opened = fs3.statSync(file);
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 = fs3.lstatSync(lock.filename);
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 = fs3.readFileSync(lock.filename, "utf8");
541
+ const value = fs4.readFileSync(lock.filename, "utf8");
469
542
  if (value !== "" && value !== `${lock.pid}
470
543
  `) return;
471
- const saved = path3.join(directory, "candidate-credentials-lock.before-rollback");
472
- if (!fs3.existsSync(saved)) fs3.renameSync(lock.filename, saved);
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
- let staged = "";
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, swapped = false, newChild;
565
+ let stopped = false, disposed = false, modified = false, newChild;
494
566
  let before = {}, sessionIds = [], readableIds = [];
495
567
  try {
496
- emit("staging", 25, "\u6682\u5B58\u66F4\u65B0\u4E0E\u4F9D\u8D56\uFF0C\u5F53\u524D\u8282\u70B9\u4ECD\u53EF\u4F7F\u7528");
497
- staged = await stageProfile({
498
- profile: job.profile,
499
- directory: job.directory,
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(path3.join(job.directory, "before-hashes.json"), before);
521
- emit("backup", 60, "\u5907\u4EFD\u914D\u7F6E\u4E0E\u6570\u636E");
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
- fs3.renameSync(job.profile, previous);
536
- try {
537
- fs3.renameSync(staged, job.profile);
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(path3.join(job.directory, "verification-complete.json"), { id: job.id });
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(path3.join(job.directory, "failure.json"), {
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 (swapped && job.manager && job.manager.kind !== "process") stopManagedHost(job.manager);
578
- if (swapped) {
579
- fs3.renameSync(job.profile, path3.join(job.directory, "profile-failed"));
580
- fs3.renameSync(previous, job.profile);
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(path3.join(job.directory, "verification-complete.json"), { id: job.id });
655
+ writePrivateJsonAtomic(path4.join(job.directory, "verification-complete.json"), { id: job.id });
587
656
  } catch (rollbackError) {
588
657
  try {
589
- writePrivateJsonAtomic(path3.join(job.directory, "rollback-failure.json"), {
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(fs3.readFileSync(filename, "utf8"));
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(path3.join(job.directory, "result.json"), value);
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(path3.join(job.directory, "worker-ready.json"), { id: job.id, origin: `http://127.0.0.1:${server.address().port}` });
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(fs3.readFileSync(path3.join(job.directory, "authorized.json"), "utf8")).id === job.id;
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 = path3.join(job.profile, ".harness-remote-update.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] && path3.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
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();