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.
@@ -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,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 { 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
+ 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("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 });
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
- 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 });
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
- function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
125
- if (!safeProfileName(profile)) throw new Error("\u65E0\u6548\u7684 DSH profile \u540D\u79F0\u3002");
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
- 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) => {
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
- 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"));
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 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"));
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
- 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");
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(filename, "utf8"));
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-lifecycle.ts
230
+ // src/install-runtime.ts
213
231
  import fs2 from "node:fs";
214
232
  import path2 from "node:path";
215
- import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
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?.()}` && path2.isAbsolute(value.plist) && fs2.statSync(value.plist).isFile()) return;
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 = path2.basename(directory);
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 (fs3.readFileSync(lock, "utf8") === id) fs3.unlinkSync(lock);
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(fs3.readFileSync(f)).digest("hex");
327
+ var hashFile = (f) => createHash2("sha256").update(fs4.readFileSync(f)).digest("hex");
256
328
  function within(parent, child) {
257
- const relative = path3.relative(parent, child);
258
- return Boolean(relative) && !relative.startsWith("..") && !path3.isAbsolute(relative);
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 (!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");
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) || 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");
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 (!fs3.statSync(f).isFile()) throw new Error("\u5B89\u88C5\u8FD0\u884C\u65F6\u5DF2\u53D8\u5316");
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(fs3.readFileSync(job.stateFile, "utf8"));
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 (!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);
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(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 })) {
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 = path3.join(job.home, e.name);
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 (fs3.existsSync(job.stateFile)) {
333
- const state = JSON.parse(fs3.readFileSync(job.stateFile, "utf8"));
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, 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");
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 || !fs3.existsSync(job.identityFile)) return;
343
- const state = JSON.parse(fs3.readFileSync(job.stateFile, "utf8"));
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(fs3.readFileSync(job.identityFile, "utf8"));
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 = fs3.openSync(path3.join(job.directory, "restart.log"), "a", 384);
384
- const child = spawn3(job.executable, [...job.execArgv, ...job.argv], {
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
- fs3.closeSync(log);
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(path3.join(job.directory, "restarted-process.json"), { pid: child.pid, cli: job.cli, home: job.home, webPort: job.webPort });
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 = path3.join(home, ".credentials.yaml.lock");
511
+ const filename = path4.join(home, ".credentials.yaml.lock");
440
512
  try {
441
- const info = fs3.lstatSync(filename);
513
+ const info = fs4.lstatSync(filename);
442
514
  if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > 32) return;
443
- const value = fs3.readFileSync(filename, "utf8");
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 = fs3.readdirSync(directory).some((fd) => {
520
+ owned = fs4.readdirSync(directory).some((fd) => {
449
521
  try {
450
- const file = path3.join(directory, fd);
451
- if (fs3.readlinkSync(file) !== filename) return false;
452
- const opened = fs3.statSync(file);
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 = fs3.lstatSync(lock.filename);
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 = fs3.readFileSync(lock.filename, "utf8");
540
+ const value = fs4.readFileSync(lock.filename, "utf8");
469
541
  if (value !== "" && value !== `${lock.pid}
470
542
  `) return;
471
- const saved = path3.join(directory, "candidate-credentials-lock.before-rollback");
472
- if (!fs3.existsSync(saved)) fs3.renameSync(lock.filename, saved);
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
- let staged = "";
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, swapped = false, newChild;
564
+ let stopped = false, disposed = false, modified = false, newChild;
494
565
  let before = {}, sessionIds = [], readableIds = [];
495
566
  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
- });
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(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");
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
- 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
- }
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(path3.join(job.directory, "verification-complete.json"), { id: job.id });
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(path3.join(job.directory, "failure.json"), {
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 (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);
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(path3.join(job.directory, "verification-complete.json"), { id: job.id });
654
+ writePrivateJsonAtomic(path4.join(job.directory, "verification-complete.json"), { id: job.id });
587
655
  } catch (rollbackError) {
588
656
  try {
589
- writePrivateJsonAtomic(path3.join(job.directory, "rollback-failure.json"), {
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(fs3.readFileSync(filename, "utf8"));
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(path3.join(job.directory, "result.json"), value);
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(path3.join(job.directory, "worker-ready.json"), { id: job.id, origin: `http://127.0.0.1:${server.address().port}` });
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(fs3.readFileSync(path3.join(job.directory, "authorized.json"), "utf8")).id === job.id;
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 = path3.join(job.profile, ".harness-remote-update.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] && path3.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
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();