@mutmutco/installer-launcher 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,14 +32,342 @@ __export(index_exports, {
32
32
  runInstallEntry: () => runInstallEntry
33
33
  });
34
34
  module.exports = __toCommonJS(index_exports);
35
- var import_node_child_process3 = require("node:child_process");
36
- var import_node_fs9 = require("node:fs");
35
+ var import_node_crypto6 = require("node:crypto");
36
+
37
+ // src/acquisition.ts
38
+ var import_node_crypto3 = require("node:crypto");
39
+ var import_node_fs3 = require("node:fs");
40
+ var import_node_path3 = require("node:path");
41
+
42
+ // src/runtime.ts
43
+ var import_node_crypto = require("node:crypto");
44
+ var import_node_child_process = require("node:child_process");
45
+ var import_node_fs = require("node:fs");
46
+ var import_node_path = require("node:path");
47
+ var NODE_VERSION = "24.20.0";
48
+ var NPM_VERSION = "12.0.2";
49
+ var ARTIFACTS = {
50
+ "win32-x64": ["node-v24.20.0-win-x64.zip", "6cac9ffbca8f6a47091e4b5c772e0606049c3871cb67d900c0cedde630e545ba"],
51
+ "win32-arm64": ["node-v24.20.0-win-arm64.zip", "31c6799744de8a54601643098040c68c3697e56c94e407d61d0e5fa5f34191d7"],
52
+ "darwin-arm64": ["node-v24.20.0-darwin-arm64.tar.gz", "40e5607e5ecb3db9192723776da2d75d966260fc74a7a9e731c1bd67dda96bc8"],
53
+ "linux-x64": ["node-v24.20.0-linux-x64.tar.gz", "855d581f8a4eb1a8117e3426de25fe02770592febcfb31369aee1ffbfee9e8ec"],
54
+ "linux-arm64": ["node-v24.20.0-linux-arm64.tar.gz", "3515603e2487879a39bc75716f1a2affd027500c64ba50e845cf72cb33219013"]
55
+ };
56
+ var NPM_INTEGRITY = "uIXokLlBj6FpNUTQX1PmT5pz7BlIN9QlixX+zdaSNHsd0qUXsbDLr50xzY6Sw7cJVr0uzHKDOle0swmPW/p5Qw==";
57
+ async function verifiedDownload(url, path, algorithm, digest, fetchImpl) {
58
+ const response = await fetchImpl(url);
59
+ if (!response.ok) throw new Error(`runtime download failed (${response.status})`);
60
+ const bytes = Buffer.from(await response.arrayBuffer());
61
+ if ((0, import_node_crypto.createHash)(algorithm).update(bytes).digest(algorithm === "sha512" ? "base64" : "hex") !== digest) throw new Error("runtime archive checksum mismatch");
62
+ (0, import_node_fs.writeFileSync)(path, bytes);
63
+ }
64
+ function command(executable, args) {
65
+ const result = (0, import_node_child_process.spawnSync)(executable, args, { encoding: "utf8", windowsHide: true });
66
+ if (result.error || result.status !== 0) throw new Error(`runtime preparation failed: ${result.error?.message ?? result.stderr.trim()}`);
67
+ return result.stdout.trim();
68
+ }
69
+ async function acquireRuntime(dir, fetchImpl = fetch) {
70
+ const platform = `${process.platform}-${process.arch}`;
71
+ const artifact = ARTIFACTS[platform];
72
+ if (!artifact) throw new Error(`managed runtime does not support ${platform}`);
73
+ const runtimeDir = (0, import_node_path.join)(dir, "runtimes");
74
+ (0, import_node_fs.mkdirSync)(runtimeDir, { recursive: true });
75
+ const receipt = (0, import_node_path.join)(runtimeDir, `node-${NODE_VERSION}-npm-${NPM_VERSION}-${platform}.json`);
76
+ if ((0, import_node_fs.existsSync)(receipt)) {
77
+ const runtime2 = JSON.parse((0, import_node_fs.readFileSync)(receipt, "utf8"));
78
+ for (const executable of [runtime2.node, runtime2.npm]) {
79
+ const rel = (0, import_node_path.relative)((0, import_node_fs.realpathSync)(runtimeDir), (0, import_node_fs.realpathSync)(executable));
80
+ if (rel.startsWith("..") || (0, import_node_path.isAbsolute)(rel)) throw new Error("cached runtime escapes owned directory");
81
+ }
82
+ if (command(runtime2.node, ["--version"]) === `v${NODE_VERSION}` && command(runtime2.node, [runtime2.npm, "--version"]) === NPM_VERSION) return runtime2;
83
+ throw new Error("cached runtime validation failed");
84
+ }
85
+ const root = (0, import_node_fs.mkdtempSync)((0, import_node_path.join)(runtimeDir, "runtime-"));
86
+ const archive = (0, import_node_path.join)(root, artifact[0]);
87
+ await verifiedDownload(`https://nodejs.org/dist/v${NODE_VERSION}/${artifact[0]}`, archive, "sha256", artifact[1], fetchImpl);
88
+ const tar = process.platform === "win32" ? (0, import_node_path.join)(process.env.SystemRoot ?? "C:/Windows", "System32", "tar.exe") : "/usr/bin/tar";
89
+ command(tar, ["-xf", archive, "-C", root]);
90
+ const unpacked = (0, import_node_path.join)(root, artifact[0].replace(/\.(zip|tar\.gz)$/, ""));
91
+ const node = (0, import_node_path.join)(unpacked, process.platform === "win32" ? "node.exe" : "bin/node");
92
+ const npmArchive = (0, import_node_path.join)(root, "npm.tgz");
93
+ await verifiedDownload(`https://registry.npmjs.org/npm/-/npm-${NPM_VERSION}.tgz`, npmArchive, "sha512", NPM_INTEGRITY, fetchImpl);
94
+ const npmRoot = (0, import_node_path.join)(root, "npm");
95
+ (0, import_node_fs.mkdirSync)(npmRoot);
96
+ command(tar, ["-xf", npmArchive, "-C", npmRoot]);
97
+ const installedNpm = (0, import_node_path.join)(unpacked, process.platform === "win32" ? "node_modules/npm" : "lib/node_modules/npm");
98
+ if ((0, import_node_fs.existsSync)(installedNpm)) (0, import_node_fs.renameSync)(installedNpm, (0, import_node_path.join)(root, "npm-bundled"));
99
+ (0, import_node_fs.renameSync)((0, import_node_path.join)(npmRoot, "package"), installedNpm);
100
+ const npm = (0, import_node_path.join)(installedNpm, "bin/npm-cli.js");
101
+ if (command(node, ["--version"]) !== `v${NODE_VERSION}` || command(node, [npm, "--version"]) !== NPM_VERSION) throw new Error("downloaded runtime validation failed");
102
+ const runtime = { node, npm };
103
+ const temporary = `${receipt}.${(0, import_node_crypto.randomUUID)()}.tmp`;
104
+ try {
105
+ (0, import_node_fs.writeFileSync)(temporary, JSON.stringify(runtime), { flag: "wx", mode: 384, flush: true });
106
+ (0, import_node_fs.renameSync)(temporary, receipt);
107
+ } finally {
108
+ (0, import_node_fs.rmSync)(temporary, { force: true });
109
+ }
110
+ return runtime;
111
+ }
112
+
113
+ // src/store.ts
114
+ var import_node_fs2 = require("node:fs");
115
+ var import_node_crypto2 = require("node:crypto");
116
+ var import_node_os = require("node:os");
117
+ var import_node_path2 = require("node:path");
118
+ function defaultProductDir(product) {
119
+ if (process.platform === "win32") {
120
+ const base = process.env.LOCALAPPDATA ?? (0, import_node_path2.join)((0, import_node_os.tmpdir)(), "launcher-fallback");
121
+ return (0, import_node_path2.join)(base, product);
122
+ }
123
+ const home = process.env.HOME ?? (0, import_node_os.tmpdir)();
124
+ return (0, import_node_path2.join)(home, `.${product}`);
125
+ }
126
+ function resolveProductDir(product, explicit) {
127
+ return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
128
+ }
129
+ function tokensPath(dir) {
130
+ return (0, import_node_path2.join)(dir, "tokens.json");
131
+ }
132
+ function statePath(dir) {
133
+ return (0, import_node_path2.join)(dir, "state.json");
134
+ }
135
+ function payloadDir(dir) {
136
+ const acquired = readState(dir)?.acquired;
137
+ if (acquired) return (0, import_node_path2.join)(dir, "candidates", acquired.candidate, "payload");
138
+ return (0, import_node_path2.join)(dir, "payload");
139
+ }
140
+ function readTokens(dir) {
141
+ try {
142
+ const data = JSON.parse((0, import_node_fs2.readFileSync)(tokensPath(dir), "utf8"));
143
+ if (typeof data.accessToken !== "string" || !data.accessToken) return null;
144
+ if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
145
+ const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
146
+ if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
147
+ if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
148
+ return tokens;
149
+ } catch {
150
+ return null;
151
+ }
152
+ }
153
+ function writeTokens(dir, tokens) {
154
+ (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
155
+ try {
156
+ (0, import_node_fs2.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
157
+ `, { mode: 384 });
158
+ } catch {
159
+ (0, import_node_fs2.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
160
+ `);
161
+ }
162
+ }
163
+ function clearTokens(dir) {
164
+ (0, import_node_fs2.rmSync)(tokensPath(dir), { force: true });
165
+ }
166
+ function readState(dir) {
167
+ try {
168
+ const data = JSON.parse((0, import_node_fs2.readFileSync)(statePath(dir), "utf8"));
169
+ if (typeof data.version !== "string" || !data.version) return null;
170
+ const state = { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
171
+ if (data.acquired) {
172
+ if (!/^[a-zA-Z0-9_-]+$/.test(data.acquired.candidate) || typeof data.acquired.node !== "string" || typeof data.acquired.entry !== "string") return null;
173
+ state.acquired = data.acquired;
174
+ }
175
+ return state;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+ function writeState(dir, state) {
181
+ (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
182
+ const temporary = `${statePath(dir)}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
183
+ try {
184
+ (0, import_node_fs2.writeFileSync)(temporary, `${JSON.stringify(state, null, 2)}
185
+ `, { flag: "wx", mode: 384, flush: true });
186
+ (0, import_node_fs2.renameSync)(temporary, statePath(dir));
187
+ } finally {
188
+ (0, import_node_fs2.rmSync)(temporary, { force: true });
189
+ }
190
+ }
191
+ function wipeProductDir(dir) {
192
+ const payload = payloadDir(dir);
193
+ (0, import_node_fs2.rmSync)(tokensPath(dir), { force: true });
194
+ (0, import_node_fs2.rmSync)(statePath(dir), { force: true });
195
+ (0, import_node_fs2.rmSync)(payload, { force: true, recursive: true });
196
+ }
197
+
198
+ // src/acquisition.ts
199
+ function safePath(value) {
200
+ return typeof value === "string" && value.length > 0 && !/[\\:\0]/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
201
+ }
202
+ function argumentsValid(value) {
203
+ return Array.isArray(value) && value.every((arg) => typeof arg === "string" && !arg.includes("\0") && (!arg.includes("$") || arg === "$prefix" || arg === "$version"));
204
+ }
205
+ function readAcquisition(payload) {
206
+ const metadata = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(payload, "payload.json"), "utf8"));
207
+ if (metadata.acquisition === void 0) return null;
208
+ const a = metadata.acquisition;
209
+ if (!a || a.schema !== 1 || !/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(a.package) || !safePath(a.archive) || !a.archive.endsWith(".tgz") || !safePath(a.convergeEntry) || !safePath(a.rollbackEntry) || !safePath(a.runEntry) || !argumentsValid(a.convergeArgs) || !argumentsValid(a.rollbackArgs) || !argumentsValid(a.repairArgs) || !Array.isArray(a.platforms) || !a.platforms.length || a.platforms.some((p) => !["win32-x64", "win32-arm64", "darwin-arm64", "linux-x64", "linux-arm64"].includes(p))) {
210
+ throw new Error("invalid signed acquisition declaration");
211
+ }
212
+ return a;
213
+ }
214
+ function runtimeEnvironment(node, inherited, platform = process.platform) {
215
+ const env = { ...inherited };
216
+ let current = env.PATH ?? "";
217
+ if (platform === "win32") {
218
+ const keys = Object.keys(env).filter((key) => key.toLowerCase() === "path").sort();
219
+ current = (keys.length ? env[keys[0]] : "") ?? "";
220
+ for (const key of keys) delete env[key];
221
+ }
222
+ env.PATH = `${(0, import_node_path3.dirname)(node)}${platform === "win32" ? ";" : ":"}${current}`;
223
+ return env;
224
+ }
225
+ function lockInstallation(dir) {
226
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
227
+ const path = (0, import_node_path3.join)(dir, "installation.lock");
228
+ const owner = `${process.pid}:${(0, import_node_crypto3.randomUUID)()}`;
229
+ try {
230
+ (0, import_node_fs3.writeFileSync)(path, owner, { flag: "wx", mode: 384 });
231
+ } catch (error) {
232
+ if (error.code !== "EEXIST") throw error;
233
+ const recovery = `${path}.recovery`;
234
+ try {
235
+ (0, import_node_fs3.writeFileSync)(recovery, owner, { flag: "wx", mode: 384, flush: true });
236
+ } catch {
237
+ throw new Error("installation lock recovery is already pending");
238
+ }
239
+ try {
240
+ const previous = (0, import_node_fs3.readFileSync)(path, "utf8");
241
+ const pid = Number(previous.split(":")[0]);
242
+ if (!Number.isInteger(pid) || pid <= 0) throw new Error("installation lock is malformed; recovery required");
243
+ try {
244
+ process.kill(pid, 0);
245
+ throw new Error("another installer is running");
246
+ } catch (probe) {
247
+ if (probe.code !== "ESRCH") throw probe;
248
+ }
249
+ (0, import_node_fs3.unlinkSync)(path);
250
+ (0, import_node_fs3.writeFileSync)(path, owner, { flag: "wx", mode: 384, flush: true });
251
+ } finally {
252
+ (0, import_node_fs3.unlinkSync)(recovery);
253
+ }
254
+ }
255
+ let released = false;
256
+ return () => {
257
+ if (!released && (0, import_node_fs3.readFileSync)(path, "utf8") === owner) (0, import_node_fs3.unlinkSync)(path);
258
+ released = true;
259
+ };
260
+ }
261
+ function pendingPath(dir) {
262
+ return (0, import_node_path3.join)(dir, "acquisition-pending.json");
263
+ }
264
+ function candidateRoot(dir, candidate) {
265
+ if (!/^[a-zA-Z0-9_-]+$/.test(candidate)) throw new Error("invalid acquisition candidate");
266
+ return (0, import_node_path3.join)(dir, "candidates", candidate);
267
+ }
268
+ function packageRoot(prefix, acquisition) {
269
+ return (0, import_node_path3.join)(prefix, "node_modules", acquisition.package);
270
+ }
271
+ function entry(root, path) {
272
+ const actual = (0, import_node_fs3.realpathSync)((0, import_node_path3.join)(root, path));
273
+ const rel = (0, import_node_path3.relative)((0, import_node_fs3.realpathSync)(root), actual);
274
+ if (rel.startsWith("..") || (0, import_node_path3.isAbsolute)(rel)) throw new Error("acquired entry escapes package");
275
+ return actual;
276
+ }
277
+ function argv(node, root, file, args, prefix, version) {
278
+ return [node, entry(root, file), ...args.map((arg) => arg === "$prefix" ? prefix : arg === "$version" ? version : arg)];
279
+ }
280
+ function acquisitionRepairCommand(dir, version) {
281
+ const state = readState(dir);
282
+ if (!state?.acquired || state.version !== version) throw new Error("installed acquisition selection is missing");
283
+ const root = candidateRoot(dir, state.acquired.candidate);
284
+ const acquisition = readAcquisition((0, import_node_path3.join)(root, "payload"));
285
+ if (!acquisition) throw new Error("installed acquisition declaration is missing");
286
+ const prefix = (0, import_node_path3.join)(root, "prefix");
287
+ const productRoot = packageRoot(prefix, acquisition);
288
+ const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(productRoot, "package.json"), "utf8"));
289
+ if (pkg.name !== acquisition.package || pkg.version !== version) throw new Error("installed package identity does not match signed release");
290
+ return argv((0, import_node_fs3.realpathSync)(state.acquired.node), productRoot, acquisition.runEntry, acquisition.repairArgs, prefix, version);
291
+ }
292
+ async function recoverAcquisition(dir, run2, env) {
293
+ if (!(0, import_node_fs3.existsSync)(pendingPath(dir))) return;
294
+ const pending = JSON.parse((0, import_node_fs3.readFileSync)(pendingPath(dir), "utf8"));
295
+ if (pending.schema !== 1 || typeof pending.version !== "string" || typeof pending.node !== "string") throw new Error("invalid pending acquisition receipt");
296
+ const root = candidateRoot(dir, pending.candidate);
297
+ if (readState(dir)?.acquired?.candidate === pending.candidate) {
298
+ (0, import_node_fs3.unlinkSync)(pendingPath(dir));
299
+ return;
300
+ }
301
+ const acquisition = readAcquisition((0, import_node_path3.join)(root, "payload"));
302
+ if (!acquisition) throw new Error("pending acquisition lost its signed declaration");
303
+ const prefix = (0, import_node_path3.join)(root, "prefix");
304
+ const command2 = argv(pending.node, packageRoot(prefix, acquisition), acquisition.rollbackEntry, acquisition.rollbackArgs, prefix, pending.version);
305
+ if (!await run2(command2, root, runtimeEnvironment(pending.node, env))) throw new Error("installation recovery is pending; product rollback did not finish");
306
+ if (pending.previous) writeState(dir, pending.previous);
307
+ else if ((0, import_node_fs3.existsSync)(statePath(dir))) (0, import_node_fs3.unlinkSync)(statePath(dir));
308
+ (0, import_node_fs3.unlinkSync)(pendingPath(dir));
309
+ }
310
+ async function installAcquisition(dir, candidate, version, acquisition, options) {
311
+ if (!acquisition.platforms.includes(`${process.platform}-${process.arch}`)) throw new Error("this product does not support this platform");
312
+ const root = candidateRoot(dir, candidate);
313
+ const payload = (0, import_node_path3.join)(root, "payload");
314
+ const prefix = (0, import_node_path3.join)(root, "prefix");
315
+ (0, import_node_fs3.mkdirSync)(prefix);
316
+ const runtime = await (options.runtime ?? acquireRuntime)(dir, options.fetchImpl);
317
+ const env = runtimeEnvironment(runtime.node, options.env);
318
+ const npmEnv = { ...env };
319
+ for (const key of Object.keys(npmEnv)) {
320
+ if (/^npm_config_/i.test(key) || /^(NPM_TOKEN|NODE_AUTH_TOKEN|MM_INSTALLER_TOKEN)$/i.test(key)) delete npmEnv[key];
321
+ }
322
+ const npmrc = (0, import_node_path3.join)(root, "public.npmrc");
323
+ const globalrc = (0, import_node_path3.join)(root, "global.npmrc");
324
+ (0, import_node_fs3.writeFileSync)(npmrc, "registry=https://registry.npmjs.org/\n");
325
+ (0, import_node_fs3.writeFileSync)(globalrc, "");
326
+ const install = [
327
+ runtime.node,
328
+ runtime.npm,
329
+ "install",
330
+ "--prefix",
331
+ prefix,
332
+ "--ignore-scripts",
333
+ "--no-audit",
334
+ "--no-fund",
335
+ "--userconfig",
336
+ npmrc,
337
+ "--globalconfig",
338
+ globalrc,
339
+ "--registry",
340
+ "https://registry.npmjs.org/",
341
+ (0, import_node_path3.resolve)(payload, acquisition.archive)
342
+ ];
343
+ if (!await options.run(install, prefix, npmEnv)) throw new Error("local product archive installation failed");
344
+ const productRoot = packageRoot(prefix, acquisition);
345
+ const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(productRoot, "package.json"), "utf8"));
346
+ if (pkg.name !== acquisition.package || pkg.version !== version) throw new Error("acquired package identity does not match signed release");
347
+ const runEntry = entry(productRoot, acquisition.runEntry);
348
+ const convergence = argv(runtime.node, productRoot, acquisition.convergeEntry, acquisition.convergeArgs, prefix, version);
349
+ entry(productRoot, acquisition.rollbackEntry);
350
+ const pending = { schema: 1, candidate, version, node: runtime.node, previous: readState(dir) };
351
+ (0, import_node_fs3.writeFileSync)(pendingPath(dir), JSON.stringify(pending), { flag: "wx", mode: 384, flush: true });
352
+ try {
353
+ if (!await options.run(convergence, root, env)) throw new Error("product convergence did not finish");
354
+ (options.commit ?? writeState)(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), acquired: { candidate, node: runtime.node, entry: runEntry } });
355
+ } catch (error) {
356
+ await recoverAcquisition(dir, options.run, env);
357
+ throw error;
358
+ }
359
+ (0, import_node_fs3.unlinkSync)(pendingPath(dir));
360
+ }
361
+
362
+ // src/index.ts
363
+ var import_node_child_process4 = require("node:child_process");
364
+ var import_node_fs11 = require("node:fs");
37
365
  var import_node_os4 = require("node:os");
38
- var import_node_path4 = require("node:path");
366
+ var import_node_path6 = require("node:path");
39
367
  var import_node_url2 = require("node:url");
40
368
 
41
369
  // ../face/src/face.ts
42
- var import_node_fs = require("node:fs");
370
+ var import_node_fs4 = require("node:fs");
43
371
 
44
372
  // ../face/src/products.ts
45
373
  var PRODUCTS = Object.freeze({
@@ -158,7 +486,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
158
486
  const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
159
487
  if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
160
488
  try {
161
- (0, import_node_fs.writeSync)(progressFd, `${JSON.stringify(record)}
489
+ (0, import_node_fs4.writeSync)(progressFd, `${JSON.stringify(record)}
162
490
  `);
163
491
  return true;
164
492
  } catch {
@@ -225,7 +553,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
225
553
  var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
226
554
 
227
555
  // ../face/src/spinner.ts
228
- var import_node_fs2 = require("node:fs");
556
+ var import_node_fs5 = require("node:fs");
229
557
  var import_node_worker_threads = require("node:worker_threads");
230
558
  var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
231
559
  var WORKER_SOURCE = `
@@ -263,8 +591,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
263
591
  const write = (text) => {
264
592
  if (stream) stream.write(text);
265
593
  else {
266
- (0, import_node_fs2.writeSync)(2, text);
267
- if (transcriptPath) (0, import_node_fs2.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
594
+ (0, import_node_fs5.writeSync)(2, text);
595
+ if (transcriptPath) (0, import_node_fs5.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
268
596
  `);
269
597
  }
270
598
  };
@@ -337,10 +665,10 @@ var SPINNER_FRAMES = Object.freeze([...FRAMES]);
337
665
  var ALLOWED = new Set(Object.values(GLYPH));
338
666
 
339
667
  // ../face/src/run.ts
340
- var import_node_fs4 = require("node:fs");
668
+ var import_node_fs7 = require("node:fs");
341
669
 
342
670
  // ../face/src/outcome.ts
343
- var import_node_fs3 = require("node:fs");
671
+ var import_node_fs6 = require("node:fs");
344
672
  var counts = ["total", "updated", "failed"];
345
673
  var strings = ["version", "retry", "detail", "logPath"];
346
674
  var flags = ["dryRun", "installed", "deferred", "operationFailed"];
@@ -362,12 +690,12 @@ function validateInstallerOutcome(value) {
362
690
  return { ...facts };
363
691
  }
364
692
  function writeInstallerOutcome(path, value) {
365
- (0, import_node_fs3.writeFileSync)(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
693
+ (0, import_node_fs6.writeFileSync)(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
366
694
  }
367
695
  function readInstallerOutcome(path) {
368
696
  let text;
369
697
  try {
370
- text = (0, import_node_fs3.readFileSync)(path, "utf8");
698
+ text = (0, import_node_fs6.readFileSync)(path, "utf8");
371
699
  } catch (error) {
372
700
  if (error.code === "ENOENT") return void 0;
373
701
  throw error;
@@ -443,7 +771,7 @@ function createInstallerRun(value, options = {}) {
443
771
  product: declaration.product,
444
772
  columns: options.columns,
445
773
  env,
446
- color: tty && options.color !== false && env.NO_COLOR === void 0
774
+ color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
447
775
  });
448
776
  const errors = [];
449
777
  const write = options.write ? (text, channel) => {
@@ -459,7 +787,7 @@ function createInstallerRun(value, options = {}) {
459
787
  if (!text) return;
460
788
  write(text, channel);
461
789
  if (env.MM_FACE_TRANSCRIPT) {
462
- (0, import_node_fs4.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
790
+ (0, import_node_fs7.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
463
791
  `, "utf8");
464
792
  }
465
793
  };
@@ -479,9 +807,13 @@ function createInstallerRun(value, options = {}) {
479
807
  const start = () => {
480
808
  if (started || finished) return;
481
809
  started = true;
810
+ if (options.quiet) return;
482
811
  const welcome = face.welcome();
483
812
  if (tty) lines(welcome);
484
- else if (welcome.length) lines([`${face.identity.name} \u2014 Mutatis Mutandis`, options.operation === "install" ? face.identity.installWarm : face.identity.warm]);
813
+ else if (welcome.length) {
814
+ const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
815
+ lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
816
+ }
485
817
  };
486
818
  const durable = (title, measure, kind) => {
487
819
  spinner.stop();
@@ -511,12 +843,13 @@ function createInstallerRun(value, options = {}) {
511
843
  const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
512
844
  if (!surface) throw new Error("installer run: undeclared surface");
513
845
  start();
514
- const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} \u2192 ${facts.to}` : ` ${facts.to}` : "";
846
+ const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
515
847
  const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
516
848
  if (!status) throw new Error("installer run: unknown surface state");
517
- const activation = facts.state === "updated" && surface.activation ? ` \xB7 ${surface.activation}` : "";
849
+ const separator = tty ? "\xB7" : "-";
850
+ const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
518
851
  const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
519
- durable(`${facts.id}${versions} \xB7 ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
852
+ durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
520
853
  if (facts.detail) run2.relay(facts.detail);
521
854
  },
522
855
  milestone({ step, state, ms }) {
@@ -543,6 +876,20 @@ function createInstallerRun(value, options = {}) {
543
876
  `);
544
877
  }
545
878
  },
879
+ cancel() {
880
+ if (finished) return;
881
+ if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
882
+ env.MM_INSTALLER_OUTCOME_FILE,
883
+ { total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
884
+ );
885
+ start();
886
+ spinner.stop();
887
+ finished = true;
888
+ if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
889
+ lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
890
+ }
891
+ if (tty) lines([face.signOff()]);
892
+ },
546
893
  finish(facts) {
547
894
  if (finished) return;
548
895
  validateInstallerOutcome(facts);
@@ -550,6 +897,7 @@ function createInstallerRun(value, options = {}) {
550
897
  start();
551
898
  spinner.stop();
552
899
  finished = true;
900
+ if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
553
901
  const changed = !facts.version ? "No release target is available." : facts.dryRun ? `Would update ${facts.updated} of ${facts.total} surfaces to ${facts.version}.` : facts.installed ? `Installed ${facts.version} across ${facts.total} surfaces.` : `Updated ${facts.updated} of ${facts.total} surfaces to ${facts.version}.`;
554
902
  const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
555
903
  const body = [
@@ -574,17 +922,17 @@ function createInstallerRun(value, options = {}) {
574
922
  }
575
923
 
576
924
  // src/autoupdate.ts
577
- var import_node_child_process = require("node:child_process");
578
- var import_node_fs5 = require("node:fs");
579
- var import_node_os = require("node:os");
580
- var import_node_path = require("node:path");
925
+ var import_node_child_process2 = require("node:child_process");
926
+ var import_node_fs8 = require("node:fs");
927
+ var import_node_os2 = require("node:os");
928
+ var import_node_path4 = require("node:path");
581
929
  function schedulePlatform(override) {
582
930
  const platform = override ?? process.platform;
583
931
  if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
584
932
  return null;
585
933
  }
586
- function defaultExec(command, args) {
587
- const result = (0, import_node_child_process.spawnSync)(command, args, { encoding: "utf8", windowsHide: true });
934
+ function defaultExec(command2, args) {
935
+ const result = (0, import_node_child_process2.spawnSync)(command2, args, { encoding: "utf8", windowsHide: true });
588
936
  if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
589
937
  return {
590
938
  code: typeof result.status === "number" ? result.status : 1,
@@ -594,8 +942,8 @@ function defaultExec(command, args) {
594
942
  }
595
943
  function homeOf(options) {
596
944
  if (options.homeDir) return options.homeDir;
597
- if (process.platform === "win32") return process.env.USERPROFILE ?? (0, import_node_os.tmpdir)();
598
- return process.env.HOME ?? (0, import_node_os.tmpdir)();
945
+ if (process.platform === "win32") return process.env.USERPROFILE ?? (0, import_node_os2.tmpdir)();
946
+ return process.env.HOME ?? (0, import_node_os2.tmpdir)();
599
947
  }
600
948
  function scheduleName(config) {
601
949
  return `${config.binName} autoupdate`;
@@ -606,13 +954,13 @@ function scheduleLabel(config) {
606
954
  function quoteWindows(arg) {
607
955
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
608
956
  }
609
- function enableSchedule(config, command, options = {}) {
957
+ function enableSchedule(config, command2, options = {}) {
610
958
  const platform = schedulePlatform(options.platform);
611
959
  if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
612
960
  const exec = options.exec ?? defaultExec;
613
961
  const home = homeOf(options);
614
962
  if (platform === "win32") {
615
- const taskLine = command.map(quoteWindows).join(" ");
963
+ const taskLine = command2.map(quoteWindows).join(" ");
616
964
  const result2 = exec("schtasks", ["/Create", "/TN", scheduleName(config), "/TR", taskLine, "/SC", "HOURLY", "/IT", "/F"]);
617
965
  if (result2.code !== 0) {
618
966
  throw new Error(`could not turn autoupdate on: ${firstLine(result2.stdout || result2.stderr) || `exit ${result2.code}`}`);
@@ -621,10 +969,10 @@ function enableSchedule(config, command, options = {}) {
621
969
  }
622
970
  if (platform === "darwin") {
623
971
  const label2 = scheduleLabel(config);
624
- const dir2 = (0, import_node_path.join)(home, "Library", "LaunchAgents");
625
- (0, import_node_fs5.mkdirSync)(dir2, { recursive: true });
626
- const plist = (0, import_node_path.join)(dir2, `${label2}.plist`);
627
- (0, import_node_fs5.writeFileSync)(plist, darwinPlist(label2, command));
972
+ const dir2 = (0, import_node_path4.join)(home, "Library", "LaunchAgents");
973
+ (0, import_node_fs8.mkdirSync)(dir2, { recursive: true });
974
+ const plist = (0, import_node_path4.join)(dir2, `${label2}.plist`);
975
+ (0, import_node_fs8.writeFileSync)(plist, darwinPlist(label2, command2));
628
976
  exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
629
977
  const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
630
978
  if (result2.code !== 0) {
@@ -633,10 +981,10 @@ function enableSchedule(config, command, options = {}) {
633
981
  return;
634
982
  }
635
983
  const label = scheduleLabel(config);
636
- const dir = (0, import_node_path.join)(home, ".config", "systemd", "user");
637
- (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
638
- (0, import_node_fs5.writeFileSync)((0, import_node_path.join)(dir, `${label}.service`), linuxService(command));
639
- (0, import_node_fs5.writeFileSync)((0, import_node_path.join)(dir, `${label}.timer`), linuxTimer(label));
984
+ const dir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
985
+ (0, import_node_fs8.mkdirSync)(dir, { recursive: true });
986
+ (0, import_node_fs8.writeFileSync)((0, import_node_path4.join)(dir, `${label}.service`), linuxService(command2));
987
+ (0, import_node_fs8.writeFileSync)((0, import_node_path4.join)(dir, `${label}.timer`), linuxTimer(label));
640
988
  const reload = exec("systemctl", ["--user", "daemon-reload"]);
641
989
  if (reload.code !== 0) {
642
990
  throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
@@ -661,14 +1009,14 @@ function disableSchedule(config, options = {}) {
661
1009
  if (platform === "darwin") {
662
1010
  const label2 = scheduleLabel(config);
663
1011
  exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
664
- (0, import_node_fs5.rmSync)((0, import_node_path.join)(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
1012
+ (0, import_node_fs8.rmSync)((0, import_node_path4.join)(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
665
1013
  return;
666
1014
  }
667
1015
  const label = scheduleLabel(config);
668
- const dir = (0, import_node_path.join)(home, ".config", "systemd", "user");
1016
+ const dir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
669
1017
  exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
670
- (0, import_node_fs5.rmSync)((0, import_node_path.join)(dir, `${label}.service`), { force: true });
671
- (0, import_node_fs5.rmSync)((0, import_node_path.join)(dir, `${label}.timer`), { force: true });
1018
+ (0, import_node_fs8.rmSync)((0, import_node_path4.join)(dir, `${label}.service`), { force: true });
1019
+ (0, import_node_fs8.rmSync)((0, import_node_path4.join)(dir, `${label}.timer`), { force: true });
672
1020
  }
673
1021
  function querySchedule(config, options = {}) {
674
1022
  const platform = schedulePlatform(options.platform);
@@ -686,20 +1034,20 @@ function querySchedule(config, options = {}) {
686
1034
  return state2;
687
1035
  }
688
1036
  if (platform === "darwin") {
689
- const plist = (0, import_node_path.join)(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
690
- if (!(0, import_node_fs5.existsSync)(plist)) return { supported: true, enabled: false };
1037
+ const plist = (0, import_node_path4.join)(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
1038
+ if (!(0, import_node_fs8.existsSync)(plist)) return { supported: true, enabled: false };
691
1039
  return { supported: true, enabled: true, cadence: "hourly" };
692
1040
  }
693
- const timer = (0, import_node_path.join)(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
694
- if (!(0, import_node_fs5.existsSync)(timer)) return { supported: true, enabled: false };
1041
+ const timer = (0, import_node_path4.join)(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
1042
+ if (!(0, import_node_fs8.existsSync)(timer)) return { supported: true, enabled: false };
695
1043
  const state = { supported: true, enabled: true, cadence: "hourly" };
696
1044
  const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
697
1045
  const stamp = (shown.stdout ?? "").trim();
698
1046
  if (shown.code === 0 && stamp && stamp !== "n/a") state.lastRun = stamp;
699
1047
  return state;
700
1048
  }
701
- function darwinPlist(label, command) {
702
- const args = command.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
1049
+ function darwinPlist(label, command2) {
1050
+ const args = command2.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
703
1051
  return `<?xml version="1.0" encoding="UTF-8"?>
704
1052
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
705
1053
  <plist version="1.0">
@@ -716,8 +1064,8 @@ ${args}
716
1064
  </plist>
717
1065
  `;
718
1066
  }
719
- function linuxService(command) {
720
- const line = command.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
1067
+ function linuxService(command2) {
1068
+ const line = command2.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
721
1069
  return `[Unit]
722
1070
  Description=${"Hourly update check"}
723
1071
  [Service]
@@ -749,7 +1097,7 @@ function firstLine(text) {
749
1097
  }
750
1098
 
751
1099
  // src/config.ts
752
- var import_node_fs6 = require("node:fs");
1100
+ var import_node_fs9 = require("node:fs");
753
1101
  var import_node_sea = require("node:sea");
754
1102
 
755
1103
  // src/module-url.ts
@@ -772,10 +1120,10 @@ function loadProductConfig(options = {}) {
772
1120
  const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
773
1121
  const devFallback = new URL("../config/product.template.json", moduleUrl());
774
1122
  if (explicit) {
775
- return parseProductConfig((0, import_node_fs6.readFileSync)(explicit, "utf8"));
1123
+ return parseProductConfig((0, import_node_fs9.readFileSync)(explicit, "utf8"));
776
1124
  }
777
1125
  try {
778
- return parseProductConfig((0, import_node_fs6.readFileSync)(devFallback, "utf8"));
1126
+ return parseProductConfig((0, import_node_fs9.readFileSync)(devFallback, "utf8"));
779
1127
  } catch {
780
1128
  }
781
1129
  try {
@@ -830,14 +1178,14 @@ function field(record, key) {
830
1178
  }
831
1179
 
832
1180
  // src/login-github.ts
833
- var import_node_child_process2 = require("node:child_process");
834
- var realSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1181
+ var import_node_child_process3 = require("node:child_process");
1182
+ var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
835
1183
  function openBrowser(url) {
836
1184
  if (process.env.LAUNCHER_NO_OPEN === "1") return;
837
1185
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
838
1186
  const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
839
1187
  try {
840
- const child = (0, import_node_child_process2.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
1188
+ const child = (0, import_node_child_process3.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
841
1189
  child.on("error", () => {
842
1190
  });
843
1191
  child.unref();
@@ -951,7 +1299,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
951
1299
  }
952
1300
 
953
1301
  // src/login-google.ts
954
- var import_node_crypto = require("node:crypto");
1302
+ var import_node_crypto4 = require("node:crypto");
955
1303
  var import_node_http = require("node:http");
956
1304
  var b64url = (bytes) => bytes.toString("base64url");
957
1305
  async function loginGoogle(options) {
@@ -959,7 +1307,7 @@ async function loginGoogle(options) {
959
1307
  const server = options.host.replace(/\/+$/, "");
960
1308
  const timeoutMs = options.timeoutMs ?? 5 * 6e4;
961
1309
  const listener = (0, import_node_http.createServer)();
962
- await new Promise((resolve2) => listener.listen(0, "127.0.0.1", resolve2));
1310
+ await new Promise((resolve3) => listener.listen(0, "127.0.0.1", resolve3));
963
1311
  const port = listener.address().port;
964
1312
  const redirectUri = `http://127.0.0.1:${port}/callback`;
965
1313
  try {
@@ -973,9 +1321,9 @@ async function loginGoogle(options) {
973
1321
  if (typeof clientId !== "string" || !clientId) {
974
1322
  throw new Error("the sign-in server returned a malformed registration");
975
1323
  }
976
- const verifier = b64url((0, import_node_crypto.randomBytes)(32));
977
- const challenge = b64url((0, import_node_crypto.createHash)("sha256").update(verifier).digest());
978
- const state = b64url((0, import_node_crypto.randomBytes)(16));
1324
+ const verifier = b64url((0, import_node_crypto4.randomBytes)(32));
1325
+ const challenge = b64url((0, import_node_crypto4.createHash)("sha256").update(verifier).digest());
1326
+ const state = b64url((0, import_node_crypto4.randomBytes)(16));
979
1327
  const authorize = new URL(`${server}/oauth/authorize`);
980
1328
  authorize.search = new URLSearchParams({
981
1329
  response_type: "code",
@@ -986,7 +1334,7 @@ async function loginGoogle(options) {
986
1334
  code_challenge: challenge,
987
1335
  code_challenge_method: "S256"
988
1336
  }).toString();
989
- const code = await new Promise((resolve2, reject) => {
1337
+ const code = await new Promise((resolve3, reject) => {
990
1338
  const timer = setTimeout(() => reject(new Error("sign-in timed out \u2014 run login again.")), timeoutMs);
991
1339
  listener.on("request", (req, res) => {
992
1340
  const url = new URL(req.url ?? "/", redirectUri);
@@ -1004,7 +1352,7 @@ async function loginGoogle(options) {
1004
1352
  }
1005
1353
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("Signed in.", "You can close this tab and go back to the terminal."));
1006
1354
  clearTimeout(timer);
1007
- resolve2(received);
1355
+ resolve3(received);
1008
1356
  });
1009
1357
  const print = options.print ?? ((line) => process.stderr.write(`${line}
1010
1358
  `));
@@ -1064,10 +1412,10 @@ function page(title, body) {
1064
1412
  }
1065
1413
 
1066
1414
  // src/payload.ts
1067
- var import_node_crypto2 = require("node:crypto");
1068
- var import_node_fs7 = require("node:fs");
1069
- var import_node_os2 = require("node:os");
1070
- var import_node_path2 = require("node:path");
1415
+ var import_node_crypto5 = require("node:crypto");
1416
+ var import_node_fs10 = require("node:fs");
1417
+ var import_node_os3 = require("node:os");
1418
+ var import_node_path5 = require("node:path");
1071
1419
 
1072
1420
  // src/canonical.ts
1073
1421
  function canonicalJson(value) {
@@ -1084,7 +1432,7 @@ function encode(value) {
1084
1432
  return JSON.stringify(value);
1085
1433
  }
1086
1434
  if (Array.isArray(value)) {
1087
- return `[${value.map((entry) => encode(entry)).join(",")}]`;
1435
+ return `[${value.map((entry2) => encode(entry2)).join(",")}]`;
1088
1436
  }
1089
1437
  if (typeof value === "object") {
1090
1438
  const record = value;
@@ -1119,7 +1467,7 @@ function canonicalManifestBytes(manifest) {
1119
1467
  }
1120
1468
  function ed25519PublicKey(config) {
1121
1469
  const raw = publicKeyBytes(config);
1122
- return (0, import_node_crypto2.createPublicKey)({
1470
+ return (0, import_node_crypto5.createPublicKey)({
1123
1471
  key: { kty: "OKP", crv: "Ed25519", x: raw.toString("base64url") },
1124
1472
  format: "jwk"
1125
1473
  });
@@ -1128,14 +1476,14 @@ function verifyManifest(manifest, signature, config) {
1128
1476
  try {
1129
1477
  const signatureBytes = Buffer.from(signature, "base64");
1130
1478
  if (signatureBytes.length === 0) return false;
1131
- return (0, import_node_crypto2.verify)(null, canonicalManifestBytes(manifest), ed25519PublicKey(config), signatureBytes);
1479
+ return (0, import_node_crypto5.verify)(null, canonicalManifestBytes(manifest), ed25519PublicKey(config), signatureBytes);
1132
1480
  } catch {
1133
1481
  return false;
1134
1482
  }
1135
1483
  }
1136
- function verifyFileBytes(entry, bytes) {
1137
- if (entry.size !== bytes.length) return false;
1138
- return (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex") === entry.sha256.toLowerCase();
1484
+ function verifyFileBytes(entry2, bytes) {
1485
+ if (entry2.size !== bytes.length) return false;
1486
+ return (0, import_node_crypto5.createHash)("sha256").update(bytes).digest("hex") === entry2.sha256.toLowerCase();
1139
1487
  }
1140
1488
  async function readErrorCode(response) {
1141
1489
  try {
@@ -1166,8 +1514,8 @@ function parseManifest(json) {
1166
1514
  if (typeof record.signature !== "string" || !record.signature) {
1167
1515
  throw new Error("the release manifest is unsigned");
1168
1516
  }
1169
- const files = record.files.map((entry) => {
1170
- const file = entry;
1517
+ const files = record.files.map((entry2) => {
1518
+ const file = entry2;
1171
1519
  if (typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.size !== "number") {
1172
1520
  throw new Error("the release manifest lists a malformed file");
1173
1521
  }
@@ -1207,101 +1555,31 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
1207
1555
  }
1208
1556
  async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
1209
1557
  const fetchImpl = options.fetchImpl ?? fetch;
1210
- const staging = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
1211
- (0, import_node_fs7.mkdirSync)(staging, { recursive: true });
1558
+ const staging = (0, import_node_path5.join)((0, import_node_os3.tmpdir)(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
1559
+ (0, import_node_fs10.mkdirSync)(staging, { recursive: true });
1212
1560
  try {
1213
- for (const entry of manifest.files) {
1214
- const bytes = await fetchFileBytes(config, accessToken, entry.path, fetchImpl);
1215
- if (!verifyFileBytes(entry, bytes)) {
1216
- throw new Error(`file ${entry.path} failed its checksum \u2014 refusing to install anything.`);
1561
+ for (const entry2 of manifest.files) {
1562
+ const bytes = await fetchFileBytes(config, accessToken, entry2.path, fetchImpl);
1563
+ if (!verifyFileBytes(entry2, bytes)) {
1564
+ throw new Error(`file ${entry2.path} failed its checksum \u2014 refusing to install anything.`);
1217
1565
  }
1218
- const dest = (0, import_node_path2.join)(staging, entry.path);
1219
- (0, import_node_fs7.mkdirSync)((0, import_node_path2.dirname)(dest), { recursive: true });
1220
- (0, import_node_fs7.writeFileSync)(dest, bytes);
1566
+ const dest = (0, import_node_path5.join)(staging, entry2.path);
1567
+ (0, import_node_fs10.mkdirSync)((0, import_node_path5.dirname)(dest), { recursive: true });
1568
+ (0, import_node_fs10.writeFileSync)(dest, bytes);
1221
1569
  }
1222
- const target = (0, import_node_path2.join)(dir, "payload");
1223
- (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
1224
- (0, import_node_fs7.rmSync)(target, { force: true, recursive: true });
1225
- (0, import_node_fs7.renameSync)(staging, target);
1570
+ const target = (0, import_node_path5.join)(dir, "payload");
1571
+ (0, import_node_fs10.mkdirSync)(dir, { recursive: true });
1572
+ (0, import_node_fs10.rmSync)(target, { force: true, recursive: true });
1573
+ (0, import_node_fs10.renameSync)(staging, target);
1226
1574
  } catch (error) {
1227
- (0, import_node_fs7.rmSync)(staging, { force: true, recursive: true });
1575
+ (0, import_node_fs10.rmSync)(staging, { force: true, recursive: true });
1228
1576
  throw error;
1229
1577
  }
1230
1578
  return manifest.version;
1231
1579
  }
1232
1580
 
1233
- // src/store.ts
1234
- var import_node_fs8 = require("node:fs");
1235
- var import_node_os3 = require("node:os");
1236
- var import_node_path3 = require("node:path");
1237
- function defaultProductDir(product) {
1238
- if (process.platform === "win32") {
1239
- const base = process.env.LOCALAPPDATA ?? (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "launcher-fallback");
1240
- return (0, import_node_path3.join)(base, product);
1241
- }
1242
- const home = process.env.HOME ?? (0, import_node_os3.tmpdir)();
1243
- return (0, import_node_path3.join)(home, `.${product}`);
1244
- }
1245
- function resolveProductDir(product, explicit) {
1246
- return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
1247
- }
1248
- function tokensPath(dir) {
1249
- return (0, import_node_path3.join)(dir, "tokens.json");
1250
- }
1251
- function statePath(dir) {
1252
- return (0, import_node_path3.join)(dir, "state.json");
1253
- }
1254
- function payloadDir(dir) {
1255
- return (0, import_node_path3.join)(dir, "payload");
1256
- }
1257
- function readTokens(dir) {
1258
- try {
1259
- const data = JSON.parse((0, import_node_fs8.readFileSync)(tokensPath(dir), "utf8"));
1260
- if (typeof data.accessToken !== "string" || !data.accessToken) return null;
1261
- if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
1262
- const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
1263
- if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
1264
- if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
1265
- return tokens;
1266
- } catch {
1267
- return null;
1268
- }
1269
- }
1270
- function writeTokens(dir, tokens) {
1271
- (0, import_node_fs8.mkdirSync)(dir, { recursive: true });
1272
- try {
1273
- (0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
1274
- `, { mode: 384 });
1275
- } catch {
1276
- (0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
1277
- `);
1278
- }
1279
- }
1280
- function clearTokens(dir) {
1281
- (0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
1282
- }
1283
- function readState(dir) {
1284
- try {
1285
- const data = JSON.parse((0, import_node_fs8.readFileSync)(statePath(dir), "utf8"));
1286
- if (typeof data.version !== "string" || !data.version) return null;
1287
- return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
1288
- } catch {
1289
- return null;
1290
- }
1291
- }
1292
- function writeState(dir, state) {
1293
- (0, import_node_fs8.mkdirSync)(dir, { recursive: true });
1294
- (0, import_node_fs8.writeFileSync)(statePath(dir), `${JSON.stringify(state, null, 2)}
1295
- `);
1296
- }
1297
- function wipeProductDir(dir) {
1298
- (0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
1299
- (0, import_node_fs8.rmSync)(statePath(dir), { force: true });
1300
- (0, import_node_fs8.rmSync)(payloadDir(dir), { force: true, recursive: true });
1301
- }
1302
-
1303
1581
  // src/index.ts
1304
- var LAUNCHER_VERSION = true ? "0.1.11" : readVersionFromPackage();
1582
+ var LAUNCHER_VERSION = true ? "0.1.13" : readVersionFromPackage();
1305
1583
  function defaultPrint(message) {
1306
1584
  process.stdout.write(`${message}
1307
1585
  `);
@@ -1313,30 +1591,37 @@ function defaultPrintErr(message) {
1313
1591
  async function run(rawOptions = {}) {
1314
1592
  const print = rawOptions.print ?? defaultPrint;
1315
1593
  const printErr = rawOptions.printErr ?? defaultPrintErr;
1316
- const argv = rawOptions.argv ?? process.argv.slice(2);
1317
- const runAt = argv.indexOf("--run");
1318
- if (runAt >= 0) return runFile(argv[runAt + 1], argv.slice(runAt + 2), printErr);
1594
+ const argv2 = rawOptions.argv ?? process.argv.slice(2);
1595
+ const runAt = argv2.indexOf("--run");
1596
+ if (runAt >= 0) return runFile(argv2[runAt + 1], argv2.slice(runAt + 2), printErr);
1319
1597
  let config;
1320
1598
  try {
1321
- config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv, "--config") });
1599
+ config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv2, "--config") });
1322
1600
  } catch (error) {
1323
1601
  print(`cannot start: ${error.message}`);
1324
1602
  return 2;
1325
1603
  }
1326
- const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(argv, "--dir"));
1327
- const positional = argv.filter((arg) => !arg.startsWith("-"));
1328
- if (argv.includes("--version") || argv.includes("-v")) {
1604
+ const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(argv2, "--dir"));
1605
+ const positional = argv2.filter((arg) => !arg.startsWith("-"));
1606
+ if (argv2.includes("--version") || argv2.includes("-v")) {
1329
1607
  print(`${config.binName} launcher ${LAUNCHER_VERSION}`);
1330
1608
  return 0;
1331
1609
  }
1332
- if (argv.includes("--help") || argv.includes("-h") || positional.length === 0) {
1610
+ if (argv2.includes("--help") || argv2.includes("-h") || positional.length === 0) {
1333
1611
  printUsage(config, print);
1334
1612
  return positional.length === 0 ? 2 : 0;
1335
1613
  }
1336
- const command = positional[0];
1614
+ const command2 = positional[0];
1615
+ let unlock;
1337
1616
  try {
1338
- switch (command) {
1617
+ unlock = lockInstallation(dir);
1618
+ await recoverAcquisition(dir, async (command3, cwd, env) => {
1619
+ const result = (rawOptions.runEntry ?? defaultRunEntry)(command3, cwd, env);
1620
+ return result.ok && (result.code === void 0 || result.code === 0);
1621
+ }, payloadEnv(dir) ?? process.env);
1622
+ switch (command2) {
1339
1623
  case "login":
1624
+ unlock();
1340
1625
  await doLogin(config, dir, rawOptions, print);
1341
1626
  return 0;
1342
1627
  case "logout":
@@ -1348,11 +1633,11 @@ async function run(rawOptions = {}) {
1348
1633
  case "update":
1349
1634
  return await doUpdate(config, dir, rawOptions, print);
1350
1635
  case "doctor":
1351
- return doDoctor(config, dir, rawOptions, print);
1636
+ return doDoctor(config, dir, rawOptions, print, unlock);
1352
1637
  case "autoupdate":
1353
1638
  return doAutoupdate(config, rawOptions, positional.slice(1), print);
1354
1639
  default:
1355
- return doForward(config, dir, rawOptions, command, argv, print);
1640
+ return doForward(config, dir, rawOptions, command2, argv2, print, unlock);
1356
1641
  }
1357
1642
  } catch (error) {
1358
1643
  if (error instanceof NeedsLoginError) {
@@ -1361,12 +1646,14 @@ async function run(rawOptions = {}) {
1361
1646
  }
1362
1647
  print(`failed: ${error.message}`);
1363
1648
  return 1;
1649
+ } finally {
1650
+ unlock?.();
1364
1651
  }
1365
1652
  }
1366
- function flagValue(argv, flag) {
1367
- const index = argv.indexOf(flag);
1653
+ function flagValue(argv2, flag) {
1654
+ const index = argv2.indexOf(flag);
1368
1655
  if (index < 0) return void 0;
1369
- const value = argv[index + 1];
1656
+ const value = argv2[index + 1];
1370
1657
  return value && !value.startsWith("-") ? value : void 0;
1371
1658
  }
1372
1659
  function printUsage(config, print) {
@@ -1380,8 +1667,8 @@ async function runFile(file, args, printErr) {
1380
1667
  printErr("launcher --run needs a file to run.");
1381
1668
  return 1;
1382
1669
  }
1383
- const abs = (0, import_node_path4.resolve)(process.cwd(), file);
1384
- if (!(0, import_node_fs9.existsSync)(abs)) {
1670
+ const abs = (0, import_node_path6.resolve)(process.cwd(), file);
1671
+ if (!(0, import_node_fs11.existsSync)(abs)) {
1385
1672
  printErr(`cannot run ${file}: no such file.`);
1386
1673
  return 1;
1387
1674
  }
@@ -1474,14 +1761,14 @@ async function fetchAccessTokenOrLogin(config, dir, options, print, installer) {
1474
1761
  }
1475
1762
  function readPayloadArgv(dir, key) {
1476
1763
  try {
1477
- const parsed = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(payloadDir(dir), "payload.json"), "utf8"));
1478
- const entry = parsed[key];
1479
- if (typeof entry === "string") {
1480
- const parts = entry.trim().split(/\s+/).filter(Boolean);
1764
+ const parsed = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(payloadDir(dir), "payload.json"), "utf8"));
1765
+ const entry2 = parsed[key];
1766
+ if (typeof entry2 === "string") {
1767
+ const parts = entry2.trim().split(/\s+/).filter(Boolean);
1481
1768
  return parts.length > 0 ? parts : null;
1482
1769
  }
1483
- if (Array.isArray(entry) && entry.every((part) => typeof part === "string" && part.length > 0)) {
1484
- return entry;
1770
+ if (Array.isArray(entry2) && entry2.every((part) => typeof part === "string" && part.length > 0)) {
1771
+ return entry2;
1485
1772
  }
1486
1773
  return null;
1487
1774
  } catch {
@@ -1496,7 +1783,7 @@ function readPayloadRun(dir) {
1496
1783
  }
1497
1784
  function readPayloadVerbs(dir) {
1498
1785
  try {
1499
- const parsed = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(payloadDir(dir), "payload.json"), "utf8"));
1786
+ const parsed = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(payloadDir(dir), "payload.json"), "utf8"));
1500
1787
  const verbs = parsed.verbs;
1501
1788
  if (verbs === "*") return "*";
1502
1789
  if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
@@ -1507,13 +1794,13 @@ function readPayloadVerbs(dir) {
1507
1794
  return null;
1508
1795
  }
1509
1796
  }
1510
- function resolveEntry(entry) {
1511
- return entry[0] === "$self" ? [process.execPath, ...entry.slice(1)] : entry;
1797
+ function resolveEntry(entry2) {
1798
+ return entry2[0] === "$self" ? [process.execPath, ...entry2.slice(1)] : entry2;
1512
1799
  }
1513
- function needsShell(command) {
1800
+ function needsShell(command2) {
1514
1801
  if (process.platform !== "win32") return false;
1515
- if (/\.(cmd|bat)$/i.test(command)) return true;
1516
- return !(0, import_node_fs9.existsSync)(command);
1802
+ if (/\.(cmd|bat)$/i.test(command2)) return true;
1803
+ return !(0, import_node_fs11.existsSync)(command2);
1517
1804
  }
1518
1805
  function quoteForShell(arg) {
1519
1806
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
@@ -1540,11 +1827,11 @@ function parseProgress(raw) {
1540
1827
  }
1541
1828
  return progress;
1542
1829
  }
1543
- function defaultRunEntry(entry, cwd, env) {
1544
- const [command, ...args] = entry;
1545
- const shell = needsShell(command);
1546
- const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
1547
- const spawnEntry = (progress2) => (0, import_node_child_process3.spawnSync)(commandLine, shell ? [] : args, {
1830
+ function defaultRunEntry(entry2, cwd, env) {
1831
+ const [command2, ...args] = entry2;
1832
+ const shell = needsShell(command2);
1833
+ const commandLine = shell ? [command2, ...args].map(quoteForShell).join(" ") : command2;
1834
+ const spawnEntry = (progress2) => (0, import_node_child_process4.spawnSync)(commandLine, shell ? [] : args, {
1548
1835
  cwd,
1549
1836
  stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
1550
1837
  shell,
@@ -1602,11 +1889,41 @@ async function installOrUpdate(config, dir, options, print, update) {
1602
1889
  version = manifest.version;
1603
1890
  installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
1604
1891
  const current = readState(dir);
1605
- const unchanged = update && current?.version === version && (0, import_node_fs9.existsSync)(payloadDir(dir));
1892
+ const unchanged = update && current?.version === version && (0, import_node_fs11.existsSync)(payloadDir(dir));
1893
+ if (unchanged && current.acquired) {
1894
+ installer.surface({ id: config.product, from: version, to: version, state: "current" });
1895
+ return await finishLastMile(config, dir, options, installer, version, true, acquisitionRepairCommand(dir, version));
1896
+ }
1606
1897
  if (!unchanged) {
1607
1898
  installer.phase("download", { state: "running" });
1608
1899
  started = Date.now();
1609
- await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
1900
+ const candidate = (0, import_node_crypto6.randomUUID)();
1901
+ const candidateDir = (0, import_node_path6.join)(dir, "candidates", candidate);
1902
+ await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl });
1903
+ const acquisition = (0, import_node_fs11.existsSync)((0, import_node_path6.join)(candidateDir, "payload", "payload.json")) ? readAcquisition((0, import_node_path6.join)(candidateDir, "payload")) : null;
1904
+ if (acquisition) {
1905
+ installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
1906
+ installer.phase("activate", { state: "running" });
1907
+ let outcome;
1908
+ const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
1909
+ await installAcquisition(dir, candidate, version, acquisition, {
1910
+ fetchImpl,
1911
+ env,
1912
+ run: async (command2, cwd, childEnv) => {
1913
+ const result = options.runEntry ? options.runEntry(command2, cwd, childEnv) : await runInstallEntry(command2, cwd, childEnv, installer, readTokens(dir));
1914
+ if (result.outcome) outcome = validateInstallerOutcome(result.outcome);
1915
+ return result.ok && (result.code === void 0 || result.code === 0) && !result.outcome?.operationFailed && !result.outcome?.failed;
1916
+ }
1917
+ });
1918
+ installer.phase("activate");
1919
+ installer.surface({ id: config.product, from: current?.version, to: version, state: "updated" });
1920
+ installer.finish(outcome ?? { version, total: 1, updated: 1, failed: 0, installed: true });
1921
+ return 0;
1922
+ }
1923
+ const target = (0, import_node_path6.join)(dir, "payload");
1924
+ (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
1925
+ (0, import_node_fs11.rmSync)(target, { recursive: true, force: true });
1926
+ (0, import_node_fs11.renameSync)((0, import_node_path6.join)(candidateDir, "payload"), target);
1610
1927
  writeState(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1611
1928
  installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
1612
1929
  }
@@ -1644,36 +1961,36 @@ function payloadEnv(dir) {
1644
1961
  return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
1645
1962
  }
1646
1963
  var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
1647
- function payloadFileAsCommand(entry, payload) {
1648
- const first = entry[0];
1964
+ function payloadFileAsCommand(entry2, payload) {
1965
+ const first = entry2[0];
1649
1966
  if (!first || first === "$self") return null;
1650
1967
  if (first.includes("/") || first.includes("\\")) return null;
1651
- const candidate = (0, import_node_path4.join)(payload, first);
1652
- if (!(0, import_node_fs9.existsSync)(candidate)) return null;
1968
+ const candidate = (0, import_node_path6.join)(payload, first);
1969
+ if (!(0, import_node_fs11.existsSync)(candidate)) return null;
1653
1970
  if (NEVER_A_PROGRAM.test(first)) return first;
1654
1971
  if (process.platform === "win32") return null;
1655
1972
  try {
1656
- return ((0, import_node_fs9.statSync)(candidate).mode & 73) === 0 ? first : null;
1973
+ return ((0, import_node_fs11.statSync)(candidate).mode & 73) === 0 ? first : null;
1657
1974
  } catch {
1658
1975
  return null;
1659
1976
  }
1660
1977
  }
1661
- async function finishLastMile(config, dir, options, installer, version, unchanged) {
1662
- const entry = readPayloadEntry(dir);
1978
+ async function finishLastMile(config, dir, options, installer, version, unchanged, repairCommand) {
1979
+ const entry2 = repairCommand ?? readPayloadEntry(dir);
1663
1980
  const payload = payloadDir(dir);
1664
- if (!entry) {
1981
+ if (!entry2) {
1665
1982
  installer.finish({
1666
1983
  version,
1667
1984
  total: 1,
1668
1985
  updated: unchanged ? 0 : 1,
1669
1986
  failed: 0,
1670
1987
  installed: true,
1671
- detail: `next step: run ${(0, import_node_path4.join)(payload, config.binName)} to start ${config.product}.`
1988
+ detail: `next step: run ${(0, import_node_path6.join)(payload, config.binName)} to start ${config.product}.`
1672
1989
  });
1673
1990
  return 0;
1674
1991
  }
1675
- const command = resolveEntry(entry);
1676
- const dataFile = payloadFileAsCommand(entry, payload);
1992
+ const command2 = resolveEntry(entry2);
1993
+ const dataFile = payloadFileAsCommand(entry2, payload);
1677
1994
  if (dataFile) {
1678
1995
  installer.finish({
1679
1996
  version,
@@ -1686,8 +2003,9 @@ async function finishLastMile(config, dir, options, installer, version, unchange
1686
2003
  }
1687
2004
  installer.phase("activate", { state: "running" });
1688
2005
  const started = Date.now();
1689
- const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
1690
- const result = options.runEntry ? options.runEntry(command, payload, env) : await runInstallEntry(command, payload, env, installer, readTokens(dir));
2006
+ const inherited = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
2007
+ const env = repairCommand ? runtimeEnvironment(repairCommand[0], inherited) : inherited;
2008
+ const result = options.runEntry ? options.runEntry(command2, payload, env) : await runInstallEntry(command2, payload, env, installer, readTokens(dir));
1691
2009
  for (const progress of result.progress ?? []) installer.milestone(progress);
1692
2010
  const succeeded = result.ok && (result.code === void 0 || result.code === 0);
1693
2011
  const outcome = result.outcome ? validateInstallerOutcome(result.outcome) : void 0;
@@ -1702,18 +2020,18 @@ async function finishLastMile(config, dir, options, installer, version, unchange
1702
2020
  },
1703
2021
  ...!succeeded ? {
1704
2022
  operationFailed: true,
1705
- detail: result.code === void 0 ? `Arming this machine could not start: ${result.error ?? command[0]}` : `Arming this machine did not finish (exit code ${result.code})${result.error?.startsWith("installer outcome:") ? `: ${result.error}` : ""}`,
1706
- retry: `(cd ${payload} && ${command.join(" ")})`
2023
+ detail: result.code === void 0 ? `Arming this machine could not start: ${result.error ?? command2[0]}` : `Arming this machine did not finish (exit code ${result.code})${result.error?.startsWith("installer outcome:") ? `: ${result.error}` : ""}`,
2024
+ retry: `(cd ${payload} && ${command2.join(" ")})`
1707
2025
  } : {}
1708
2026
  });
1709
2027
  return succeeded ? outcome?.operationFailed || outcome?.failed ? 1 : 0 : result.code || 1;
1710
2028
  }
1711
- async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1712
- const [command, ...args] = entry;
1713
- const shell = needsShell(command);
2029
+ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
2030
+ const [command2, ...args] = entry2;
2031
+ const shell = needsShell(command2);
1714
2032
  const progress = !(process.platform === "win32" && shell);
1715
- const outcomeDir = (0, import_node_fs9.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "mm-installer-outcome-"));
1716
- const outcomeFile = (0, import_node_path4.join)(outcomeDir, "outcome.json");
2033
+ const outcomeDir = (0, import_node_fs11.mkdtempSync)((0, import_node_path6.join)((0, import_node_os4.tmpdir)(), "mm-installer-outcome-"));
2034
+ const outcomeFile = (0, import_node_path6.join)(outcomeDir, "outcome.json");
1717
2035
  const childEnv = { ...env, MM_INSTALLER_OUTCOME_FILE: outcomeFile };
1718
2036
  delete childEnv.MM_FACE_TRANSCRIPT;
1719
2037
  delete childEnv.MM_PROGRESS_FD;
@@ -1722,8 +2040,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1722
2040
  const secrets = [tokens?.accessToken, tokens?.refreshToken].filter((value) => Boolean(value));
1723
2041
  const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
1724
2042
  try {
1725
- const result = await new Promise((resolve2) => {
1726
- const child = (0, import_node_child_process3.spawn)(shell ? [command, ...args].map(quoteForShell).join(" ") : command, shell ? [] : args, {
2043
+ const result = await new Promise((resolve3) => {
2044
+ const child = (0, import_node_child_process4.spawn)(shell ? [command2, ...args].map(quoteForShell).join(" ") : command2, shell ? [] : args, {
1727
2045
  cwd,
1728
2046
  shell,
1729
2047
  windowsHide: true,
@@ -1759,8 +2077,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1759
2077
  if (pending.length > 65536) pending = "";
1760
2078
  });
1761
2079
  }
1762
- child.on("error", (error) => resolve2({ ok: false, error: error.message }));
1763
- child.on("close", (code) => resolve2({
2080
+ child.on("error", (error) => resolve3({ ok: false, error: error.message }));
2081
+ child.on("close", (code) => resolve3({
1764
2082
  ok: code === 0,
1765
2083
  ...code !== null ? { code } : {},
1766
2084
  ...code !== 0 ? { error: `exit code ${code}` } : {}
@@ -1773,25 +2091,30 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1773
2091
  return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
1774
2092
  }
1775
2093
  } finally {
1776
- if ((0, import_node_fs9.existsSync)(outcomeFile)) (0, import_node_fs9.unlinkSync)(outcomeFile);
1777
- (0, import_node_fs9.rmdirSync)(outcomeDir);
2094
+ if ((0, import_node_fs11.existsSync)(outcomeFile)) (0, import_node_fs11.unlinkSync)(outcomeFile);
2095
+ (0, import_node_fs11.rmdirSync)(outcomeDir);
1778
2096
  }
1779
2097
  }
1780
- function doForward(config, dir, options, command, argv, print) {
1781
- if (command && (0, import_node_fs9.existsSync)(command)) {
1782
- print(`${command} is a file, not a command \u2014 did you mean \`--run ${command}\`?`);
2098
+ function doForward(config, dir, options, command2, argv2, print, unlock) {
2099
+ if (command2 && (0, import_node_fs11.existsSync)(command2)) {
2100
+ print(`${command2} is a file, not a command \u2014 did you mean \`--run ${command2}\`?`);
1783
2101
  return 2;
1784
2102
  }
1785
- const target = readPayloadRun(dir);
2103
+ const acquired = readState(dir)?.acquired;
2104
+ const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
1786
2105
  const verbs = readPayloadVerbs(dir);
1787
- const declared = verbs === "*" || Array.isArray(verbs) && command !== void 0 && verbs.includes(command);
2106
+ const declared = verbs === "*" || Array.isArray(verbs) && command2 !== void 0 && verbs.includes(command2);
1788
2107
  if (!target || !declared) {
1789
- print(`unknown command: ${command}`);
2108
+ print(`unknown command: ${command2}`);
1790
2109
  printUsage(config, print);
1791
2110
  return 2;
1792
2111
  }
1793
- const forwarded = [...resolveEntry(target), ...argv];
1794
- const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir), payloadEnv(dir));
2112
+ const forwarded = [...resolveEntry(target), ...argv2];
2113
+ const cwd = payloadDir(dir);
2114
+ const inherited = payloadEnv(dir);
2115
+ const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
2116
+ unlock();
2117
+ const result = (options.runEntry ?? defaultRunEntry)(forwarded, cwd, env);
1795
2118
  if (!result.ok && result.code === void 0) {
1796
2119
  print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
1797
2120
  return 1;
@@ -1801,10 +2124,10 @@ function doForward(config, dir, options, command, argv, print) {
1801
2124
  function doAutoupdate(config, options, args, print) {
1802
2125
  const mode = args[0] ?? "status";
1803
2126
  const scheduleOptions = options.autoupdate ?? {};
1804
- const command = options.autoupdate?.command ?? [process.execPath, "update"];
2127
+ const command2 = options.autoupdate?.command ?? [process.execPath, "update"];
1805
2128
  if (mode === "on") {
1806
2129
  try {
1807
- enableSchedule(config, command, scheduleOptions);
2130
+ enableSchedule(config, command2, scheduleOptions);
1808
2131
  } catch (error) {
1809
2132
  print(error.message);
1810
2133
  return 1;
@@ -1836,19 +2159,24 @@ function doAutoupdate(config, options, args, print) {
1836
2159
  if (state.lastRun) print(`last run: ${state.lastRun}`);
1837
2160
  return 0;
1838
2161
  }
1839
- function doDoctor(config, dir, options, print) {
2162
+ function doDoctor(config, dir, options, print, unlock) {
1840
2163
  doLauncherDoctor(config, dir, options, print);
1841
- const target = readPayloadRun(dir);
2164
+ const acquired = readState(dir)?.acquired;
2165
+ const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
1842
2166
  const verbs = readPayloadVerbs(dir);
1843
2167
  const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
1844
2168
  if (!chains) return 0;
1845
- const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir), payloadEnv(dir));
2169
+ const cwd = payloadDir(dir);
2170
+ const inherited = payloadEnv(dir);
2171
+ const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
2172
+ unlock();
2173
+ const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], cwd, env);
1846
2174
  return result.code ?? (result.ok ? 0 : 1);
1847
2175
  }
1848
2176
  function doLauncherDoctor(config, dir, options, print) {
1849
2177
  const tokens = readTokens(dir);
1850
2178
  const state = readState(dir);
1851
- const payloadPresent = (0, import_node_fs9.existsSync)(payloadDir(dir));
2179
+ const payloadPresent = (0, import_node_fs11.existsSync)(payloadDir(dir));
1852
2180
  print(`product: ${config.product}`);
1853
2181
  print(`host: ${config.host}`);
1854
2182
  print(`login: ${config.loginKind}`);
@@ -1860,7 +2188,7 @@ function doLauncherDoctor(config, dir, options, print) {
1860
2188
  print("token: none \u2014 run login first.");
1861
2189
  }
1862
2190
  print(`payload: ${state ? state.version : "none"}${payloadPresent ? "" : " (not downloaded)"}`);
1863
- print(`paths: tokens ${(0, import_node_path4.join)(dir, "tokens.json")}, state ${(0, import_node_path4.join)(dir, "state.json")}, payload ${payloadDir(dir)}`);
2191
+ print(`paths: tokens ${(0, import_node_path6.join)(dir, "tokens.json")}, state ${(0, import_node_path6.join)(dir, "state.json")}, payload ${payloadDir(dir)}`);
1864
2192
  const schedule = querySchedule(config, options.autoupdate ?? {});
1865
2193
  print(
1866
2194
  `auto-update: ${!schedule.supported ? "unsupported on this platform" : schedule.enabled ? `on (${schedule.cadence ?? "hourly"})` : "off"}`