@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.
package/dist/launcher.js CHANGED
@@ -1,10 +1,338 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
5
- import { existsSync as existsSync4, readFileSync as readFileSync5, statSync, mkdtempSync, unlinkSync, rmdirSync } from "node:fs";
4
+ import { randomUUID as randomUUID4 } from "node:crypto";
5
+
6
+ // src/acquisition.ts
7
+ import { randomUUID as randomUUID3 } from "node:crypto";
8
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, realpathSync as realpathSync2 } from "node:fs";
9
+ import { join as join3, resolve, relative as relative2, isAbsolute as isAbsolute2, dirname } from "node:path";
10
+
11
+ // src/runtime.ts
12
+ import { createHash, randomUUID } from "node:crypto";
13
+ import { spawnSync } from "node:child_process";
14
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync, existsSync, renameSync, rmSync, realpathSync } from "node:fs";
15
+ import { join, relative, isAbsolute } from "node:path";
16
+ var NODE_VERSION = "24.20.0";
17
+ var NPM_VERSION = "12.0.2";
18
+ var ARTIFACTS = {
19
+ "win32-x64": ["node-v24.20.0-win-x64.zip", "6cac9ffbca8f6a47091e4b5c772e0606049c3871cb67d900c0cedde630e545ba"],
20
+ "win32-arm64": ["node-v24.20.0-win-arm64.zip", "31c6799744de8a54601643098040c68c3697e56c94e407d61d0e5fa5f34191d7"],
21
+ "darwin-arm64": ["node-v24.20.0-darwin-arm64.tar.gz", "40e5607e5ecb3db9192723776da2d75d966260fc74a7a9e731c1bd67dda96bc8"],
22
+ "linux-x64": ["node-v24.20.0-linux-x64.tar.gz", "855d581f8a4eb1a8117e3426de25fe02770592febcfb31369aee1ffbfee9e8ec"],
23
+ "linux-arm64": ["node-v24.20.0-linux-arm64.tar.gz", "3515603e2487879a39bc75716f1a2affd027500c64ba50e845cf72cb33219013"]
24
+ };
25
+ var NPM_INTEGRITY = "uIXokLlBj6FpNUTQX1PmT5pz7BlIN9QlixX+zdaSNHsd0qUXsbDLr50xzY6Sw7cJVr0uzHKDOle0swmPW/p5Qw==";
26
+ async function verifiedDownload(url, path, algorithm, digest, fetchImpl) {
27
+ const response = await fetchImpl(url);
28
+ if (!response.ok) throw new Error(`runtime download failed (${response.status})`);
29
+ const bytes = Buffer.from(await response.arrayBuffer());
30
+ if (createHash(algorithm).update(bytes).digest(algorithm === "sha512" ? "base64" : "hex") !== digest) throw new Error("runtime archive checksum mismatch");
31
+ writeFileSync(path, bytes);
32
+ }
33
+ function command(executable, args) {
34
+ const result = spawnSync(executable, args, { encoding: "utf8", windowsHide: true });
35
+ if (result.error || result.status !== 0) throw new Error(`runtime preparation failed: ${result.error?.message ?? result.stderr.trim()}`);
36
+ return result.stdout.trim();
37
+ }
38
+ async function acquireRuntime(dir, fetchImpl = fetch) {
39
+ const platform = `${process.platform}-${process.arch}`;
40
+ const artifact = ARTIFACTS[platform];
41
+ if (!artifact) throw new Error(`managed runtime does not support ${platform}`);
42
+ const runtimeDir = join(dir, "runtimes");
43
+ mkdirSync(runtimeDir, { recursive: true });
44
+ const receipt = join(runtimeDir, `node-${NODE_VERSION}-npm-${NPM_VERSION}-${platform}.json`);
45
+ if (existsSync(receipt)) {
46
+ const runtime2 = JSON.parse(readFileSync(receipt, "utf8"));
47
+ for (const executable of [runtime2.node, runtime2.npm]) {
48
+ const rel = relative(realpathSync(runtimeDir), realpathSync(executable));
49
+ if (rel.startsWith("..") || isAbsolute(rel)) throw new Error("cached runtime escapes owned directory");
50
+ }
51
+ if (command(runtime2.node, ["--version"]) === `v${NODE_VERSION}` && command(runtime2.node, [runtime2.npm, "--version"]) === NPM_VERSION) return runtime2;
52
+ throw new Error("cached runtime validation failed");
53
+ }
54
+ const root = mkdtempSync(join(runtimeDir, "runtime-"));
55
+ const archive = join(root, artifact[0]);
56
+ await verifiedDownload(`https://nodejs.org/dist/v${NODE_VERSION}/${artifact[0]}`, archive, "sha256", artifact[1], fetchImpl);
57
+ const tar = process.platform === "win32" ? join(process.env.SystemRoot ?? "C:/Windows", "System32", "tar.exe") : "/usr/bin/tar";
58
+ command(tar, ["-xf", archive, "-C", root]);
59
+ const unpacked = join(root, artifact[0].replace(/\.(zip|tar\.gz)$/, ""));
60
+ const node = join(unpacked, process.platform === "win32" ? "node.exe" : "bin/node");
61
+ const npmArchive = join(root, "npm.tgz");
62
+ await verifiedDownload(`https://registry.npmjs.org/npm/-/npm-${NPM_VERSION}.tgz`, npmArchive, "sha512", NPM_INTEGRITY, fetchImpl);
63
+ const npmRoot = join(root, "npm");
64
+ mkdirSync(npmRoot);
65
+ command(tar, ["-xf", npmArchive, "-C", npmRoot]);
66
+ const installedNpm = join(unpacked, process.platform === "win32" ? "node_modules/npm" : "lib/node_modules/npm");
67
+ if (existsSync(installedNpm)) renameSync(installedNpm, join(root, "npm-bundled"));
68
+ renameSync(join(npmRoot, "package"), installedNpm);
69
+ const npm = join(installedNpm, "bin/npm-cli.js");
70
+ if (command(node, ["--version"]) !== `v${NODE_VERSION}` || command(node, [npm, "--version"]) !== NPM_VERSION) throw new Error("downloaded runtime validation failed");
71
+ const runtime = { node, npm };
72
+ const temporary = `${receipt}.${randomUUID()}.tmp`;
73
+ try {
74
+ writeFileSync(temporary, JSON.stringify(runtime), { flag: "wx", mode: 384, flush: true });
75
+ renameSync(temporary, receipt);
76
+ } finally {
77
+ rmSync(temporary, { force: true });
78
+ }
79
+ return runtime;
80
+ }
81
+
82
+ // src/store.ts
83
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
84
+ import { randomUUID as randomUUID2 } from "node:crypto";
85
+ import { tmpdir } from "node:os";
86
+ import { join as join2 } from "node:path";
87
+ function defaultProductDir(product) {
88
+ if (process.platform === "win32") {
89
+ const base = process.env.LOCALAPPDATA ?? join2(tmpdir(), "launcher-fallback");
90
+ return join2(base, product);
91
+ }
92
+ const home = process.env.HOME ?? tmpdir();
93
+ return join2(home, `.${product}`);
94
+ }
95
+ function resolveProductDir(product, explicit) {
96
+ return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
97
+ }
98
+ function tokensPath(dir) {
99
+ return join2(dir, "tokens.json");
100
+ }
101
+ function statePath(dir) {
102
+ return join2(dir, "state.json");
103
+ }
104
+ function payloadDir(dir) {
105
+ const acquired = readState(dir)?.acquired;
106
+ if (acquired) return join2(dir, "candidates", acquired.candidate, "payload");
107
+ return join2(dir, "payload");
108
+ }
109
+ function readTokens(dir) {
110
+ try {
111
+ const data = JSON.parse(readFileSync2(tokensPath(dir), "utf8"));
112
+ if (typeof data.accessToken !== "string" || !data.accessToken) return null;
113
+ if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
114
+ const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
115
+ if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
116
+ if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
117
+ return tokens;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ function writeTokens(dir, tokens) {
123
+ mkdirSync2(dir, { recursive: true });
124
+ try {
125
+ writeFileSync2(tokensPath(dir), `${JSON.stringify(tokens)}
126
+ `, { mode: 384 });
127
+ } catch {
128
+ writeFileSync2(tokensPath(dir), `${JSON.stringify(tokens)}
129
+ `);
130
+ }
131
+ }
132
+ function clearTokens(dir) {
133
+ rmSync2(tokensPath(dir), { force: true });
134
+ }
135
+ function readState(dir) {
136
+ try {
137
+ const data = JSON.parse(readFileSync2(statePath(dir), "utf8"));
138
+ if (typeof data.version !== "string" || !data.version) return null;
139
+ const state = { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
140
+ if (data.acquired) {
141
+ if (!/^[a-zA-Z0-9_-]+$/.test(data.acquired.candidate) || typeof data.acquired.node !== "string" || typeof data.acquired.entry !== "string") return null;
142
+ state.acquired = data.acquired;
143
+ }
144
+ return state;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+ function writeState(dir, state) {
150
+ mkdirSync2(dir, { recursive: true });
151
+ const temporary = `${statePath(dir)}.${randomUUID2()}.tmp`;
152
+ try {
153
+ writeFileSync2(temporary, `${JSON.stringify(state, null, 2)}
154
+ `, { flag: "wx", mode: 384, flush: true });
155
+ renameSync2(temporary, statePath(dir));
156
+ } finally {
157
+ rmSync2(temporary, { force: true });
158
+ }
159
+ }
160
+ function wipeProductDir(dir) {
161
+ const payload = payloadDir(dir);
162
+ rmSync2(tokensPath(dir), { force: true });
163
+ rmSync2(statePath(dir), { force: true });
164
+ rmSync2(payload, { force: true, recursive: true });
165
+ }
166
+
167
+ // src/acquisition.ts
168
+ function safePath(value) {
169
+ return typeof value === "string" && value.length > 0 && !/[\\:\0]/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
170
+ }
171
+ function argumentsValid(value) {
172
+ return Array.isArray(value) && value.every((arg) => typeof arg === "string" && !arg.includes("\0") && (!arg.includes("$") || arg === "$prefix" || arg === "$version"));
173
+ }
174
+ function readAcquisition(payload) {
175
+ const metadata = JSON.parse(readFileSync3(join3(payload, "payload.json"), "utf8"));
176
+ if (metadata.acquisition === void 0) return null;
177
+ const a = metadata.acquisition;
178
+ 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))) {
179
+ throw new Error("invalid signed acquisition declaration");
180
+ }
181
+ return a;
182
+ }
183
+ function runtimeEnvironment(node, inherited, platform = process.platform) {
184
+ const env = { ...inherited };
185
+ let current = env.PATH ?? "";
186
+ if (platform === "win32") {
187
+ const keys = Object.keys(env).filter((key) => key.toLowerCase() === "path").sort();
188
+ current = (keys.length ? env[keys[0]] : "") ?? "";
189
+ for (const key of keys) delete env[key];
190
+ }
191
+ env.PATH = `${dirname(node)}${platform === "win32" ? ";" : ":"}${current}`;
192
+ return env;
193
+ }
194
+ function lockInstallation(dir) {
195
+ mkdirSync3(dir, { recursive: true });
196
+ const path = join3(dir, "installation.lock");
197
+ const owner = `${process.pid}:${randomUUID3()}`;
198
+ try {
199
+ writeFileSync3(path, owner, { flag: "wx", mode: 384 });
200
+ } catch (error) {
201
+ if (error.code !== "EEXIST") throw error;
202
+ const recovery = `${path}.recovery`;
203
+ try {
204
+ writeFileSync3(recovery, owner, { flag: "wx", mode: 384, flush: true });
205
+ } catch {
206
+ throw new Error("installation lock recovery is already pending");
207
+ }
208
+ try {
209
+ const previous = readFileSync3(path, "utf8");
210
+ const pid = Number(previous.split(":")[0]);
211
+ if (!Number.isInteger(pid) || pid <= 0) throw new Error("installation lock is malformed; recovery required");
212
+ try {
213
+ process.kill(pid, 0);
214
+ throw new Error("another installer is running");
215
+ } catch (probe) {
216
+ if (probe.code !== "ESRCH") throw probe;
217
+ }
218
+ unlinkSync(path);
219
+ writeFileSync3(path, owner, { flag: "wx", mode: 384, flush: true });
220
+ } finally {
221
+ unlinkSync(recovery);
222
+ }
223
+ }
224
+ let released = false;
225
+ return () => {
226
+ if (!released && readFileSync3(path, "utf8") === owner) unlinkSync(path);
227
+ released = true;
228
+ };
229
+ }
230
+ function pendingPath(dir) {
231
+ return join3(dir, "acquisition-pending.json");
232
+ }
233
+ function candidateRoot(dir, candidate) {
234
+ if (!/^[a-zA-Z0-9_-]+$/.test(candidate)) throw new Error("invalid acquisition candidate");
235
+ return join3(dir, "candidates", candidate);
236
+ }
237
+ function packageRoot(prefix, acquisition) {
238
+ return join3(prefix, "node_modules", acquisition.package);
239
+ }
240
+ function entry(root, path) {
241
+ const actual = realpathSync2(join3(root, path));
242
+ const rel = relative2(realpathSync2(root), actual);
243
+ if (rel.startsWith("..") || isAbsolute2(rel)) throw new Error("acquired entry escapes package");
244
+ return actual;
245
+ }
246
+ function argv(node, root, file, args, prefix, version) {
247
+ return [node, entry(root, file), ...args.map((arg) => arg === "$prefix" ? prefix : arg === "$version" ? version : arg)];
248
+ }
249
+ function acquisitionRepairCommand(dir, version) {
250
+ const state = readState(dir);
251
+ if (!state?.acquired || state.version !== version) throw new Error("installed acquisition selection is missing");
252
+ const root = candidateRoot(dir, state.acquired.candidate);
253
+ const acquisition = readAcquisition(join3(root, "payload"));
254
+ if (!acquisition) throw new Error("installed acquisition declaration is missing");
255
+ const prefix = join3(root, "prefix");
256
+ const productRoot = packageRoot(prefix, acquisition);
257
+ const pkg = JSON.parse(readFileSync3(join3(productRoot, "package.json"), "utf8"));
258
+ if (pkg.name !== acquisition.package || pkg.version !== version) throw new Error("installed package identity does not match signed release");
259
+ return argv(realpathSync2(state.acquired.node), productRoot, acquisition.runEntry, acquisition.repairArgs, prefix, version);
260
+ }
261
+ async function recoverAcquisition(dir, run2, env) {
262
+ if (!existsSync3(pendingPath(dir))) return;
263
+ const pending = JSON.parse(readFileSync3(pendingPath(dir), "utf8"));
264
+ if (pending.schema !== 1 || typeof pending.version !== "string" || typeof pending.node !== "string") throw new Error("invalid pending acquisition receipt");
265
+ const root = candidateRoot(dir, pending.candidate);
266
+ if (readState(dir)?.acquired?.candidate === pending.candidate) {
267
+ unlinkSync(pendingPath(dir));
268
+ return;
269
+ }
270
+ const acquisition = readAcquisition(join3(root, "payload"));
271
+ if (!acquisition) throw new Error("pending acquisition lost its signed declaration");
272
+ const prefix = join3(root, "prefix");
273
+ const command2 = argv(pending.node, packageRoot(prefix, acquisition), acquisition.rollbackEntry, acquisition.rollbackArgs, prefix, pending.version);
274
+ if (!await run2(command2, root, runtimeEnvironment(pending.node, env))) throw new Error("installation recovery is pending; product rollback did not finish");
275
+ if (pending.previous) writeState(dir, pending.previous);
276
+ else if (existsSync3(statePath(dir))) unlinkSync(statePath(dir));
277
+ unlinkSync(pendingPath(dir));
278
+ }
279
+ async function installAcquisition(dir, candidate, version, acquisition, options) {
280
+ if (!acquisition.platforms.includes(`${process.platform}-${process.arch}`)) throw new Error("this product does not support this platform");
281
+ const root = candidateRoot(dir, candidate);
282
+ const payload = join3(root, "payload");
283
+ const prefix = join3(root, "prefix");
284
+ mkdirSync3(prefix);
285
+ const runtime = await (options.runtime ?? acquireRuntime)(dir, options.fetchImpl);
286
+ const env = runtimeEnvironment(runtime.node, options.env);
287
+ const npmEnv = { ...env };
288
+ for (const key of Object.keys(npmEnv)) {
289
+ if (/^npm_config_/i.test(key) || /^(NPM_TOKEN|NODE_AUTH_TOKEN|MM_INSTALLER_TOKEN)$/i.test(key)) delete npmEnv[key];
290
+ }
291
+ const npmrc = join3(root, "public.npmrc");
292
+ const globalrc = join3(root, "global.npmrc");
293
+ writeFileSync3(npmrc, "registry=https://registry.npmjs.org/\n");
294
+ writeFileSync3(globalrc, "");
295
+ const install = [
296
+ runtime.node,
297
+ runtime.npm,
298
+ "install",
299
+ "--prefix",
300
+ prefix,
301
+ "--ignore-scripts",
302
+ "--no-audit",
303
+ "--no-fund",
304
+ "--userconfig",
305
+ npmrc,
306
+ "--globalconfig",
307
+ globalrc,
308
+ "--registry",
309
+ "https://registry.npmjs.org/",
310
+ resolve(payload, acquisition.archive)
311
+ ];
312
+ if (!await options.run(install, prefix, npmEnv)) throw new Error("local product archive installation failed");
313
+ const productRoot = packageRoot(prefix, acquisition);
314
+ const pkg = JSON.parse(readFileSync3(join3(productRoot, "package.json"), "utf8"));
315
+ if (pkg.name !== acquisition.package || pkg.version !== version) throw new Error("acquired package identity does not match signed release");
316
+ const runEntry = entry(productRoot, acquisition.runEntry);
317
+ const convergence = argv(runtime.node, productRoot, acquisition.convergeEntry, acquisition.convergeArgs, prefix, version);
318
+ entry(productRoot, acquisition.rollbackEntry);
319
+ const pending = { schema: 1, candidate, version, node: runtime.node, previous: readState(dir) };
320
+ writeFileSync3(pendingPath(dir), JSON.stringify(pending), { flag: "wx", mode: 384, flush: true });
321
+ try {
322
+ if (!await options.run(convergence, root, env)) throw new Error("product convergence did not finish");
323
+ (options.commit ?? writeState)(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), acquired: { candidate, node: runtime.node, entry: runEntry } });
324
+ } catch (error) {
325
+ await recoverAcquisition(dir, options.run, env);
326
+ throw error;
327
+ }
328
+ unlinkSync(pendingPath(dir));
329
+ }
330
+
331
+ // src/index.ts
332
+ import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
333
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync, mkdtempSync as mkdtempSync2, unlinkSync as unlinkSync2, rmdirSync, mkdirSync as mkdirSync6, renameSync as renameSync4, rmSync as rmSync5 } from "node:fs";
6
334
  import { tmpdir as tmpdir4 } from "node:os";
7
- import { dirname as dirname2, join as join4, resolve } from "node:path";
335
+ import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
8
336
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
9
337
 
10
338
  // ../face/src/face.ts
@@ -309,7 +637,7 @@ var ALLOWED = new Set(Object.values(GLYPH));
309
637
  import { appendFileSync as appendFileSync2 } from "node:fs";
310
638
 
311
639
  // ../face/src/outcome.ts
312
- import { readFileSync, writeFileSync } from "node:fs";
640
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
313
641
  var counts = ["total", "updated", "failed"];
314
642
  var strings = ["version", "retry", "detail", "logPath"];
315
643
  var flags = ["dryRun", "installed", "deferred", "operationFailed"];
@@ -331,12 +659,12 @@ function validateInstallerOutcome(value) {
331
659
  return { ...facts };
332
660
  }
333
661
  function writeInstallerOutcome(path, value) {
334
- writeFileSync(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
662
+ writeFileSync4(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
335
663
  }
336
664
  function readInstallerOutcome(path) {
337
665
  let text;
338
666
  try {
339
- text = readFileSync(path, "utf8");
667
+ text = readFileSync4(path, "utf8");
340
668
  } catch (error) {
341
669
  if (error.code === "ENOENT") return void 0;
342
670
  throw error;
@@ -412,7 +740,7 @@ function createInstallerRun(value, options = {}) {
412
740
  product: declaration.product,
413
741
  columns: options.columns,
414
742
  env,
415
- color: tty && options.color !== false && env.NO_COLOR === void 0
743
+ color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
416
744
  });
417
745
  const errors = [];
418
746
  const write = options.write ? (text, channel) => {
@@ -448,9 +776,13 @@ function createInstallerRun(value, options = {}) {
448
776
  const start = () => {
449
777
  if (started || finished) return;
450
778
  started = true;
779
+ if (options.quiet) return;
451
780
  const welcome = face.welcome();
452
781
  if (tty) lines(welcome);
453
- else if (welcome.length) lines([`${face.identity.name} \u2014 Mutatis Mutandis`, options.operation === "install" ? face.identity.installWarm : face.identity.warm]);
782
+ else if (welcome.length) {
783
+ const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
784
+ lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
785
+ }
454
786
  };
455
787
  const durable = (title, measure, kind) => {
456
788
  spinner.stop();
@@ -480,12 +812,13 @@ function createInstallerRun(value, options = {}) {
480
812
  const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
481
813
  if (!surface) throw new Error("installer run: undeclared surface");
482
814
  start();
483
- const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} \u2192 ${facts.to}` : ` ${facts.to}` : "";
815
+ const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
484
816
  const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
485
817
  if (!status) throw new Error("installer run: unknown surface state");
486
- const activation = facts.state === "updated" && surface.activation ? ` \xB7 ${surface.activation}` : "";
818
+ const separator = tty ? "\xB7" : "-";
819
+ const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
487
820
  const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
488
- durable(`${facts.id}${versions} \xB7 ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
821
+ durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
489
822
  if (facts.detail) run2.relay(facts.detail);
490
823
  },
491
824
  milestone({ step, state, ms }) {
@@ -512,6 +845,20 @@ function createInstallerRun(value, options = {}) {
512
845
  `);
513
846
  }
514
847
  },
848
+ cancel() {
849
+ if (finished) return;
850
+ if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
851
+ env.MM_INSTALLER_OUTCOME_FILE,
852
+ { total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
853
+ );
854
+ start();
855
+ spinner.stop();
856
+ finished = true;
857
+ if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
858
+ lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
859
+ }
860
+ if (tty) lines([face.signOff()]);
861
+ },
515
862
  finish(facts) {
516
863
  if (finished) return;
517
864
  validateInstallerOutcome(facts);
@@ -519,6 +866,7 @@ function createInstallerRun(value, options = {}) {
519
866
  start();
520
867
  spinner.stop();
521
868
  finished = true;
869
+ if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
522
870
  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}.`;
523
871
  const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
524
872
  const body = [
@@ -543,17 +891,17 @@ function createInstallerRun(value, options = {}) {
543
891
  }
544
892
 
545
893
  // src/autoupdate.ts
546
- import { spawnSync } from "node:child_process";
547
- import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
548
- import { tmpdir } from "node:os";
549
- import { join } from "node:path";
894
+ import { spawnSync as spawnSync2 } from "node:child_process";
895
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
896
+ import { tmpdir as tmpdir2 } from "node:os";
897
+ import { join as join4 } from "node:path";
550
898
  function schedulePlatform(override) {
551
899
  const platform = override ?? process.platform;
552
900
  if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
553
901
  return null;
554
902
  }
555
- function defaultExec(command, args) {
556
- const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
903
+ function defaultExec(command2, args) {
904
+ const result = spawnSync2(command2, args, { encoding: "utf8", windowsHide: true });
557
905
  if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
558
906
  return {
559
907
  code: typeof result.status === "number" ? result.status : 1,
@@ -563,8 +911,8 @@ function defaultExec(command, args) {
563
911
  }
564
912
  function homeOf(options) {
565
913
  if (options.homeDir) return options.homeDir;
566
- if (process.platform === "win32") return process.env.USERPROFILE ?? tmpdir();
567
- return process.env.HOME ?? tmpdir();
914
+ if (process.platform === "win32") return process.env.USERPROFILE ?? tmpdir2();
915
+ return process.env.HOME ?? tmpdir2();
568
916
  }
569
917
  function scheduleName(config) {
570
918
  return `${config.binName} autoupdate`;
@@ -575,13 +923,13 @@ function scheduleLabel(config) {
575
923
  function quoteWindows(arg) {
576
924
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
577
925
  }
578
- function enableSchedule(config, command, options = {}) {
926
+ function enableSchedule(config, command2, options = {}) {
579
927
  const platform = schedulePlatform(options.platform);
580
928
  if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
581
929
  const exec = options.exec ?? defaultExec;
582
930
  const home = homeOf(options);
583
931
  if (platform === "win32") {
584
- const taskLine = command.map(quoteWindows).join(" ");
932
+ const taskLine = command2.map(quoteWindows).join(" ");
585
933
  const result2 = exec("schtasks", ["/Create", "/TN", scheduleName(config), "/TR", taskLine, "/SC", "HOURLY", "/IT", "/F"]);
586
934
  if (result2.code !== 0) {
587
935
  throw new Error(`could not turn autoupdate on: ${firstLine(result2.stdout || result2.stderr) || `exit ${result2.code}`}`);
@@ -590,10 +938,10 @@ function enableSchedule(config, command, options = {}) {
590
938
  }
591
939
  if (platform === "darwin") {
592
940
  const label2 = scheduleLabel(config);
593
- const dir2 = join(home, "Library", "LaunchAgents");
594
- mkdirSync(dir2, { recursive: true });
595
- const plist = join(dir2, `${label2}.plist`);
596
- writeFileSync2(plist, darwinPlist(label2, command));
941
+ const dir2 = join4(home, "Library", "LaunchAgents");
942
+ mkdirSync4(dir2, { recursive: true });
943
+ const plist = join4(dir2, `${label2}.plist`);
944
+ writeFileSync5(plist, darwinPlist(label2, command2));
597
945
  exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
598
946
  const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
599
947
  if (result2.code !== 0) {
@@ -602,10 +950,10 @@ function enableSchedule(config, command, options = {}) {
602
950
  return;
603
951
  }
604
952
  const label = scheduleLabel(config);
605
- const dir = join(home, ".config", "systemd", "user");
606
- mkdirSync(dir, { recursive: true });
607
- writeFileSync2(join(dir, `${label}.service`), linuxService(command));
608
- writeFileSync2(join(dir, `${label}.timer`), linuxTimer(label));
953
+ const dir = join4(home, ".config", "systemd", "user");
954
+ mkdirSync4(dir, { recursive: true });
955
+ writeFileSync5(join4(dir, `${label}.service`), linuxService(command2));
956
+ writeFileSync5(join4(dir, `${label}.timer`), linuxTimer(label));
609
957
  const reload = exec("systemctl", ["--user", "daemon-reload"]);
610
958
  if (reload.code !== 0) {
611
959
  throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
@@ -630,14 +978,14 @@ function disableSchedule(config, options = {}) {
630
978
  if (platform === "darwin") {
631
979
  const label2 = scheduleLabel(config);
632
980
  exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
633
- rmSync(join(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
981
+ rmSync3(join4(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
634
982
  return;
635
983
  }
636
984
  const label = scheduleLabel(config);
637
- const dir = join(home, ".config", "systemd", "user");
985
+ const dir = join4(home, ".config", "systemd", "user");
638
986
  exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
639
- rmSync(join(dir, `${label}.service`), { force: true });
640
- rmSync(join(dir, `${label}.timer`), { force: true });
987
+ rmSync3(join4(dir, `${label}.service`), { force: true });
988
+ rmSync3(join4(dir, `${label}.timer`), { force: true });
641
989
  }
642
990
  function querySchedule(config, options = {}) {
643
991
  const platform = schedulePlatform(options.platform);
@@ -655,20 +1003,20 @@ function querySchedule(config, options = {}) {
655
1003
  return state2;
656
1004
  }
657
1005
  if (platform === "darwin") {
658
- const plist = join(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
659
- if (!existsSync(plist)) return { supported: true, enabled: false };
1006
+ const plist = join4(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
1007
+ if (!existsSync4(plist)) return { supported: true, enabled: false };
660
1008
  return { supported: true, enabled: true, cadence: "hourly" };
661
1009
  }
662
- const timer = join(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
663
- if (!existsSync(timer)) return { supported: true, enabled: false };
1010
+ const timer = join4(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
1011
+ if (!existsSync4(timer)) return { supported: true, enabled: false };
664
1012
  const state = { supported: true, enabled: true, cadence: "hourly" };
665
1013
  const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
666
1014
  const stamp = (shown.stdout ?? "").trim();
667
1015
  if (shown.code === 0 && stamp && stamp !== "n/a") state.lastRun = stamp;
668
1016
  return state;
669
1017
  }
670
- function darwinPlist(label, command) {
671
- const args = command.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
1018
+ function darwinPlist(label, command2) {
1019
+ const args = command2.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
672
1020
  return `<?xml version="1.0" encoding="UTF-8"?>
673
1021
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
674
1022
  <plist version="1.0">
@@ -685,8 +1033,8 @@ ${args}
685
1033
  </plist>
686
1034
  `;
687
1035
  }
688
- function linuxService(command) {
689
- const line = command.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
1036
+ function linuxService(command2) {
1037
+ const line = command2.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
690
1038
  return `[Unit]
691
1039
  Description=${"Hourly update check"}
692
1040
  [Service]
@@ -718,7 +1066,7 @@ function firstLine(text) {
718
1066
  }
719
1067
 
720
1068
  // src/config.ts
721
- import { readFileSync as readFileSync3 } from "node:fs";
1069
+ import { readFileSync as readFileSync6 } from "node:fs";
722
1070
  import { getAsset } from "node:sea";
723
1071
 
724
1072
  // src/module-url.ts
@@ -740,10 +1088,10 @@ function loadProductConfig(options = {}) {
740
1088
  const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
741
1089
  const devFallback = new URL("../config/product.template.json", moduleUrl());
742
1090
  if (explicit) {
743
- return parseProductConfig(readFileSync3(explicit, "utf8"));
1091
+ return parseProductConfig(readFileSync6(explicit, "utf8"));
744
1092
  }
745
1093
  try {
746
- return parseProductConfig(readFileSync3(devFallback, "utf8"));
1094
+ return parseProductConfig(readFileSync6(devFallback, "utf8"));
747
1095
  } catch {
748
1096
  }
749
1097
  try {
@@ -799,7 +1147,7 @@ function field(record, key) {
799
1147
 
800
1148
  // src/login-github.ts
801
1149
  import { spawn } from "node:child_process";
802
- var realSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1150
+ var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
803
1151
  function openBrowser(url) {
804
1152
  if (process.env.LAUNCHER_NO_OPEN === "1") return;
805
1153
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
@@ -919,7 +1267,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
919
1267
  }
920
1268
 
921
1269
  // src/login-google.ts
922
- import { createHash, randomBytes } from "node:crypto";
1270
+ import { createHash as createHash2, randomBytes } from "node:crypto";
923
1271
  import { createServer } from "node:http";
924
1272
  var b64url = (bytes) => bytes.toString("base64url");
925
1273
  async function loginGoogle(options) {
@@ -927,7 +1275,7 @@ async function loginGoogle(options) {
927
1275
  const server = options.host.replace(/\/+$/, "");
928
1276
  const timeoutMs = options.timeoutMs ?? 5 * 6e4;
929
1277
  const listener = createServer();
930
- await new Promise((resolve2) => listener.listen(0, "127.0.0.1", resolve2));
1278
+ await new Promise((resolve3) => listener.listen(0, "127.0.0.1", resolve3));
931
1279
  const port = listener.address().port;
932
1280
  const redirectUri = `http://127.0.0.1:${port}/callback`;
933
1281
  try {
@@ -942,7 +1290,7 @@ async function loginGoogle(options) {
942
1290
  throw new Error("the sign-in server returned a malformed registration");
943
1291
  }
944
1292
  const verifier = b64url(randomBytes(32));
945
- const challenge = b64url(createHash("sha256").update(verifier).digest());
1293
+ const challenge = b64url(createHash2("sha256").update(verifier).digest());
946
1294
  const state = b64url(randomBytes(16));
947
1295
  const authorize = new URL(`${server}/oauth/authorize`);
948
1296
  authorize.search = new URLSearchParams({
@@ -954,7 +1302,7 @@ async function loginGoogle(options) {
954
1302
  code_challenge: challenge,
955
1303
  code_challenge_method: "S256"
956
1304
  }).toString();
957
- const code = await new Promise((resolve2, reject) => {
1305
+ const code = await new Promise((resolve3, reject) => {
958
1306
  const timer = setTimeout(() => reject(new Error("sign-in timed out \u2014 run login again.")), timeoutMs);
959
1307
  listener.on("request", (req, res) => {
960
1308
  const url = new URL(req.url ?? "/", redirectUri);
@@ -972,7 +1320,7 @@ async function loginGoogle(options) {
972
1320
  }
973
1321
  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."));
974
1322
  clearTimeout(timer);
975
- resolve2(received);
1323
+ resolve3(received);
976
1324
  });
977
1325
  const print = options.print ?? ((line) => process.stderr.write(`${line}
978
1326
  `));
@@ -1032,10 +1380,10 @@ function page(title, body) {
1032
1380
  }
1033
1381
 
1034
1382
  // src/payload.ts
1035
- import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
1036
- import { mkdirSync as mkdirSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
1037
- import { tmpdir as tmpdir2 } from "node:os";
1038
- import { dirname, join as join2 } from "node:path";
1383
+ import { createHash as createHash3, createPublicKey, verify } from "node:crypto";
1384
+ import { mkdirSync as mkdirSync5, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
1385
+ import { tmpdir as tmpdir3 } from "node:os";
1386
+ import { dirname as dirname2, join as join5 } from "node:path";
1039
1387
 
1040
1388
  // src/canonical.ts
1041
1389
  function canonicalJson(value) {
@@ -1052,7 +1400,7 @@ function encode(value) {
1052
1400
  return JSON.stringify(value);
1053
1401
  }
1054
1402
  if (Array.isArray(value)) {
1055
- return `[${value.map((entry) => encode(entry)).join(",")}]`;
1403
+ return `[${value.map((entry2) => encode(entry2)).join(",")}]`;
1056
1404
  }
1057
1405
  if (typeof value === "object") {
1058
1406
  const record = value;
@@ -1101,9 +1449,9 @@ function verifyManifest(manifest, signature, config) {
1101
1449
  return false;
1102
1450
  }
1103
1451
  }
1104
- function verifyFileBytes(entry, bytes) {
1105
- if (entry.size !== bytes.length) return false;
1106
- return createHash2("sha256").update(bytes).digest("hex") === entry.sha256.toLowerCase();
1452
+ function verifyFileBytes(entry2, bytes) {
1453
+ if (entry2.size !== bytes.length) return false;
1454
+ return createHash3("sha256").update(bytes).digest("hex") === entry2.sha256.toLowerCase();
1107
1455
  }
1108
1456
  async function readErrorCode(response) {
1109
1457
  try {
@@ -1134,8 +1482,8 @@ function parseManifest(json) {
1134
1482
  if (typeof record.signature !== "string" || !record.signature) {
1135
1483
  throw new Error("the release manifest is unsigned");
1136
1484
  }
1137
- const files = record.files.map((entry) => {
1138
- const file = entry;
1485
+ const files = record.files.map((entry2) => {
1486
+ const file = entry2;
1139
1487
  if (typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.size !== "number") {
1140
1488
  throw new Error("the release manifest lists a malformed file");
1141
1489
  }
@@ -1175,101 +1523,31 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
1175
1523
  }
1176
1524
  async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
1177
1525
  const fetchImpl = options.fetchImpl ?? fetch;
1178
- const staging = join2(tmpdir2(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
1179
- mkdirSync2(staging, { recursive: true });
1526
+ const staging = join5(tmpdir3(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
1527
+ mkdirSync5(staging, { recursive: true });
1180
1528
  try {
1181
- for (const entry of manifest.files) {
1182
- const bytes = await fetchFileBytes(config, accessToken, entry.path, fetchImpl);
1183
- if (!verifyFileBytes(entry, bytes)) {
1184
- throw new Error(`file ${entry.path} failed its checksum \u2014 refusing to install anything.`);
1529
+ for (const entry2 of manifest.files) {
1530
+ const bytes = await fetchFileBytes(config, accessToken, entry2.path, fetchImpl);
1531
+ if (!verifyFileBytes(entry2, bytes)) {
1532
+ throw new Error(`file ${entry2.path} failed its checksum \u2014 refusing to install anything.`);
1185
1533
  }
1186
- const dest = join2(staging, entry.path);
1187
- mkdirSync2(dirname(dest), { recursive: true });
1188
- writeFileSync3(dest, bytes);
1534
+ const dest = join5(staging, entry2.path);
1535
+ mkdirSync5(dirname2(dest), { recursive: true });
1536
+ writeFileSync6(dest, bytes);
1189
1537
  }
1190
- const target = join2(dir, "payload");
1191
- mkdirSync2(dir, { recursive: true });
1192
- rmSync2(target, { force: true, recursive: true });
1193
- renameSync(staging, target);
1538
+ const target = join5(dir, "payload");
1539
+ mkdirSync5(dir, { recursive: true });
1540
+ rmSync4(target, { force: true, recursive: true });
1541
+ renameSync3(staging, target);
1194
1542
  } catch (error) {
1195
- rmSync2(staging, { force: true, recursive: true });
1543
+ rmSync4(staging, { force: true, recursive: true });
1196
1544
  throw error;
1197
1545
  }
1198
1546
  return manifest.version;
1199
1547
  }
1200
1548
 
1201
- // src/store.ts
1202
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
1203
- import { tmpdir as tmpdir3 } from "node:os";
1204
- import { join as join3 } from "node:path";
1205
- function defaultProductDir(product) {
1206
- if (process.platform === "win32") {
1207
- const base = process.env.LOCALAPPDATA ?? join3(tmpdir3(), "launcher-fallback");
1208
- return join3(base, product);
1209
- }
1210
- const home = process.env.HOME ?? tmpdir3();
1211
- return join3(home, `.${product}`);
1212
- }
1213
- function resolveProductDir(product, explicit) {
1214
- return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
1215
- }
1216
- function tokensPath(dir) {
1217
- return join3(dir, "tokens.json");
1218
- }
1219
- function statePath(dir) {
1220
- return join3(dir, "state.json");
1221
- }
1222
- function payloadDir(dir) {
1223
- return join3(dir, "payload");
1224
- }
1225
- function readTokens(dir) {
1226
- try {
1227
- const data = JSON.parse(readFileSync4(tokensPath(dir), "utf8"));
1228
- if (typeof data.accessToken !== "string" || !data.accessToken) return null;
1229
- if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
1230
- const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
1231
- if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
1232
- if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
1233
- return tokens;
1234
- } catch {
1235
- return null;
1236
- }
1237
- }
1238
- function writeTokens(dir, tokens) {
1239
- mkdirSync3(dir, { recursive: true });
1240
- try {
1241
- writeFileSync4(tokensPath(dir), `${JSON.stringify(tokens)}
1242
- `, { mode: 384 });
1243
- } catch {
1244
- writeFileSync4(tokensPath(dir), `${JSON.stringify(tokens)}
1245
- `);
1246
- }
1247
- }
1248
- function clearTokens(dir) {
1249
- rmSync3(tokensPath(dir), { force: true });
1250
- }
1251
- function readState(dir) {
1252
- try {
1253
- const data = JSON.parse(readFileSync4(statePath(dir), "utf8"));
1254
- if (typeof data.version !== "string" || !data.version) return null;
1255
- return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
1256
- } catch {
1257
- return null;
1258
- }
1259
- }
1260
- function writeState(dir, state) {
1261
- mkdirSync3(dir, { recursive: true });
1262
- writeFileSync4(statePath(dir), `${JSON.stringify(state, null, 2)}
1263
- `);
1264
- }
1265
- function wipeProductDir(dir) {
1266
- rmSync3(tokensPath(dir), { force: true });
1267
- rmSync3(statePath(dir), { force: true });
1268
- rmSync3(payloadDir(dir), { force: true, recursive: true });
1269
- }
1270
-
1271
1549
  // src/index.ts
1272
- var LAUNCHER_VERSION = true ? "0.1.11" : readVersionFromPackage();
1550
+ var LAUNCHER_VERSION = true ? "0.1.13" : readVersionFromPackage();
1273
1551
  function defaultPrint(message) {
1274
1552
  process.stdout.write(`${message}
1275
1553
  `);
@@ -1281,30 +1559,37 @@ function defaultPrintErr(message) {
1281
1559
  async function run(rawOptions = {}) {
1282
1560
  const print = rawOptions.print ?? defaultPrint;
1283
1561
  const printErr = rawOptions.printErr ?? defaultPrintErr;
1284
- const argv = rawOptions.argv ?? process.argv.slice(2);
1285
- const runAt = argv.indexOf("--run");
1286
- if (runAt >= 0) return runFile(argv[runAt + 1], argv.slice(runAt + 2), printErr);
1562
+ const argv2 = rawOptions.argv ?? process.argv.slice(2);
1563
+ const runAt = argv2.indexOf("--run");
1564
+ if (runAt >= 0) return runFile(argv2[runAt + 1], argv2.slice(runAt + 2), printErr);
1287
1565
  let config;
1288
1566
  try {
1289
- config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv, "--config") });
1567
+ config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv2, "--config") });
1290
1568
  } catch (error) {
1291
1569
  print(`cannot start: ${error.message}`);
1292
1570
  return 2;
1293
1571
  }
1294
- const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(argv, "--dir"));
1295
- const positional = argv.filter((arg) => !arg.startsWith("-"));
1296
- if (argv.includes("--version") || argv.includes("-v")) {
1572
+ const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(argv2, "--dir"));
1573
+ const positional = argv2.filter((arg) => !arg.startsWith("-"));
1574
+ if (argv2.includes("--version") || argv2.includes("-v")) {
1297
1575
  print(`${config.binName} launcher ${LAUNCHER_VERSION}`);
1298
1576
  return 0;
1299
1577
  }
1300
- if (argv.includes("--help") || argv.includes("-h") || positional.length === 0) {
1578
+ if (argv2.includes("--help") || argv2.includes("-h") || positional.length === 0) {
1301
1579
  printUsage(config, print);
1302
1580
  return positional.length === 0 ? 2 : 0;
1303
1581
  }
1304
- const command = positional[0];
1582
+ const command2 = positional[0];
1583
+ let unlock;
1305
1584
  try {
1306
- switch (command) {
1585
+ unlock = lockInstallation(dir);
1586
+ await recoverAcquisition(dir, async (command3, cwd, env) => {
1587
+ const result = (rawOptions.runEntry ?? defaultRunEntry)(command3, cwd, env);
1588
+ return result.ok && (result.code === void 0 || result.code === 0);
1589
+ }, payloadEnv(dir) ?? process.env);
1590
+ switch (command2) {
1307
1591
  case "login":
1592
+ unlock();
1308
1593
  await doLogin(config, dir, rawOptions, print);
1309
1594
  return 0;
1310
1595
  case "logout":
@@ -1316,11 +1601,11 @@ async function run(rawOptions = {}) {
1316
1601
  case "update":
1317
1602
  return await doUpdate(config, dir, rawOptions, print);
1318
1603
  case "doctor":
1319
- return doDoctor(config, dir, rawOptions, print);
1604
+ return doDoctor(config, dir, rawOptions, print, unlock);
1320
1605
  case "autoupdate":
1321
1606
  return doAutoupdate(config, rawOptions, positional.slice(1), print);
1322
1607
  default:
1323
- return doForward(config, dir, rawOptions, command, argv, print);
1608
+ return doForward(config, dir, rawOptions, command2, argv2, print, unlock);
1324
1609
  }
1325
1610
  } catch (error) {
1326
1611
  if (error instanceof NeedsLoginError) {
@@ -1329,12 +1614,14 @@ async function run(rawOptions = {}) {
1329
1614
  }
1330
1615
  print(`failed: ${error.message}`);
1331
1616
  return 1;
1617
+ } finally {
1618
+ unlock?.();
1332
1619
  }
1333
1620
  }
1334
- function flagValue(argv, flag) {
1335
- const index = argv.indexOf(flag);
1621
+ function flagValue(argv2, flag) {
1622
+ const index = argv2.indexOf(flag);
1336
1623
  if (index < 0) return void 0;
1337
- const value = argv[index + 1];
1624
+ const value = argv2[index + 1];
1338
1625
  return value && !value.startsWith("-") ? value : void 0;
1339
1626
  }
1340
1627
  function printUsage(config, print) {
@@ -1348,8 +1635,8 @@ async function runFile(file, args, printErr) {
1348
1635
  printErr("launcher --run needs a file to run.");
1349
1636
  return 1;
1350
1637
  }
1351
- const abs = resolve(process.cwd(), file);
1352
- if (!existsSync4(abs)) {
1638
+ const abs = resolve2(process.cwd(), file);
1639
+ if (!existsSync6(abs)) {
1353
1640
  printErr(`cannot run ${file}: no such file.`);
1354
1641
  return 1;
1355
1642
  }
@@ -1442,14 +1729,14 @@ async function fetchAccessTokenOrLogin(config, dir, options, print, installer) {
1442
1729
  }
1443
1730
  function readPayloadArgv(dir, key) {
1444
1731
  try {
1445
- const parsed = JSON.parse(readFileSync5(join4(payloadDir(dir), "payload.json"), "utf8"));
1446
- const entry = parsed[key];
1447
- if (typeof entry === "string") {
1448
- const parts = entry.trim().split(/\s+/).filter(Boolean);
1732
+ const parsed = JSON.parse(readFileSync7(join6(payloadDir(dir), "payload.json"), "utf8"));
1733
+ const entry2 = parsed[key];
1734
+ if (typeof entry2 === "string") {
1735
+ const parts = entry2.trim().split(/\s+/).filter(Boolean);
1449
1736
  return parts.length > 0 ? parts : null;
1450
1737
  }
1451
- if (Array.isArray(entry) && entry.every((part) => typeof part === "string" && part.length > 0)) {
1452
- return entry;
1738
+ if (Array.isArray(entry2) && entry2.every((part) => typeof part === "string" && part.length > 0)) {
1739
+ return entry2;
1453
1740
  }
1454
1741
  return null;
1455
1742
  } catch {
@@ -1464,7 +1751,7 @@ function readPayloadRun(dir) {
1464
1751
  }
1465
1752
  function readPayloadVerbs(dir) {
1466
1753
  try {
1467
- const parsed = JSON.parse(readFileSync5(join4(payloadDir(dir), "payload.json"), "utf8"));
1754
+ const parsed = JSON.parse(readFileSync7(join6(payloadDir(dir), "payload.json"), "utf8"));
1468
1755
  const verbs = parsed.verbs;
1469
1756
  if (verbs === "*") return "*";
1470
1757
  if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
@@ -1475,13 +1762,13 @@ function readPayloadVerbs(dir) {
1475
1762
  return null;
1476
1763
  }
1477
1764
  }
1478
- function resolveEntry(entry) {
1479
- return entry[0] === "$self" ? [process.execPath, ...entry.slice(1)] : entry;
1765
+ function resolveEntry(entry2) {
1766
+ return entry2[0] === "$self" ? [process.execPath, ...entry2.slice(1)] : entry2;
1480
1767
  }
1481
- function needsShell(command) {
1768
+ function needsShell(command2) {
1482
1769
  if (process.platform !== "win32") return false;
1483
- if (/\.(cmd|bat)$/i.test(command)) return true;
1484
- return !existsSync4(command);
1770
+ if (/\.(cmd|bat)$/i.test(command2)) return true;
1771
+ return !existsSync6(command2);
1485
1772
  }
1486
1773
  function quoteForShell(arg) {
1487
1774
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
@@ -1508,11 +1795,11 @@ function parseProgress(raw) {
1508
1795
  }
1509
1796
  return progress;
1510
1797
  }
1511
- function defaultRunEntry(entry, cwd, env) {
1512
- const [command, ...args] = entry;
1513
- const shell = needsShell(command);
1514
- const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
1515
- const spawnEntry = (progress2) => spawnSync2(commandLine, shell ? [] : args, {
1798
+ function defaultRunEntry(entry2, cwd, env) {
1799
+ const [command2, ...args] = entry2;
1800
+ const shell = needsShell(command2);
1801
+ const commandLine = shell ? [command2, ...args].map(quoteForShell).join(" ") : command2;
1802
+ const spawnEntry = (progress2) => spawnSync3(commandLine, shell ? [] : args, {
1516
1803
  cwd,
1517
1804
  stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
1518
1805
  shell,
@@ -1570,11 +1857,41 @@ async function installOrUpdate(config, dir, options, print, update) {
1570
1857
  version = manifest.version;
1571
1858
  installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
1572
1859
  const current = readState(dir);
1573
- const unchanged = update && current?.version === version && existsSync4(payloadDir(dir));
1860
+ const unchanged = update && current?.version === version && existsSync6(payloadDir(dir));
1861
+ if (unchanged && current.acquired) {
1862
+ installer.surface({ id: config.product, from: version, to: version, state: "current" });
1863
+ return await finishLastMile(config, dir, options, installer, version, true, acquisitionRepairCommand(dir, version));
1864
+ }
1574
1865
  if (!unchanged) {
1575
1866
  installer.phase("download", { state: "running" });
1576
1867
  started = Date.now();
1577
- await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
1868
+ const candidate = randomUUID4();
1869
+ const candidateDir = join6(dir, "candidates", candidate);
1870
+ await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl });
1871
+ const acquisition = existsSync6(join6(candidateDir, "payload", "payload.json")) ? readAcquisition(join6(candidateDir, "payload")) : null;
1872
+ if (acquisition) {
1873
+ installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
1874
+ installer.phase("activate", { state: "running" });
1875
+ let outcome;
1876
+ const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
1877
+ await installAcquisition(dir, candidate, version, acquisition, {
1878
+ fetchImpl,
1879
+ env,
1880
+ run: async (command2, cwd, childEnv) => {
1881
+ const result = options.runEntry ? options.runEntry(command2, cwd, childEnv) : await runInstallEntry(command2, cwd, childEnv, installer, readTokens(dir));
1882
+ if (result.outcome) outcome = validateInstallerOutcome(result.outcome);
1883
+ return result.ok && (result.code === void 0 || result.code === 0) && !result.outcome?.operationFailed && !result.outcome?.failed;
1884
+ }
1885
+ });
1886
+ installer.phase("activate");
1887
+ installer.surface({ id: config.product, from: current?.version, to: version, state: "updated" });
1888
+ installer.finish(outcome ?? { version, total: 1, updated: 1, failed: 0, installed: true });
1889
+ return 0;
1890
+ }
1891
+ const target = join6(dir, "payload");
1892
+ mkdirSync6(dir, { recursive: true });
1893
+ rmSync5(target, { recursive: true, force: true });
1894
+ renameSync4(join6(candidateDir, "payload"), target);
1578
1895
  writeState(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1579
1896
  installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
1580
1897
  }
@@ -1612,12 +1929,12 @@ function payloadEnv(dir) {
1612
1929
  return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
1613
1930
  }
1614
1931
  var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
1615
- function payloadFileAsCommand(entry, payload) {
1616
- const first = entry[0];
1932
+ function payloadFileAsCommand(entry2, payload) {
1933
+ const first = entry2[0];
1617
1934
  if (!first || first === "$self") return null;
1618
1935
  if (first.includes("/") || first.includes("\\")) return null;
1619
- const candidate = join4(payload, first);
1620
- if (!existsSync4(candidate)) return null;
1936
+ const candidate = join6(payload, first);
1937
+ if (!existsSync6(candidate)) return null;
1621
1938
  if (NEVER_A_PROGRAM.test(first)) return first;
1622
1939
  if (process.platform === "win32") return null;
1623
1940
  try {
@@ -1626,22 +1943,22 @@ function payloadFileAsCommand(entry, payload) {
1626
1943
  return null;
1627
1944
  }
1628
1945
  }
1629
- async function finishLastMile(config, dir, options, installer, version, unchanged) {
1630
- const entry = readPayloadEntry(dir);
1946
+ async function finishLastMile(config, dir, options, installer, version, unchanged, repairCommand) {
1947
+ const entry2 = repairCommand ?? readPayloadEntry(dir);
1631
1948
  const payload = payloadDir(dir);
1632
- if (!entry) {
1949
+ if (!entry2) {
1633
1950
  installer.finish({
1634
1951
  version,
1635
1952
  total: 1,
1636
1953
  updated: unchanged ? 0 : 1,
1637
1954
  failed: 0,
1638
1955
  installed: true,
1639
- detail: `next step: run ${join4(payload, config.binName)} to start ${config.product}.`
1956
+ detail: `next step: run ${join6(payload, config.binName)} to start ${config.product}.`
1640
1957
  });
1641
1958
  return 0;
1642
1959
  }
1643
- const command = resolveEntry(entry);
1644
- const dataFile = payloadFileAsCommand(entry, payload);
1960
+ const command2 = resolveEntry(entry2);
1961
+ const dataFile = payloadFileAsCommand(entry2, payload);
1645
1962
  if (dataFile) {
1646
1963
  installer.finish({
1647
1964
  version,
@@ -1654,8 +1971,9 @@ async function finishLastMile(config, dir, options, installer, version, unchange
1654
1971
  }
1655
1972
  installer.phase("activate", { state: "running" });
1656
1973
  const started = Date.now();
1657
- const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
1658
- const result = options.runEntry ? options.runEntry(command, payload, env) : await runInstallEntry(command, payload, env, installer, readTokens(dir));
1974
+ const inherited = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
1975
+ const env = repairCommand ? runtimeEnvironment(repairCommand[0], inherited) : inherited;
1976
+ const result = options.runEntry ? options.runEntry(command2, payload, env) : await runInstallEntry(command2, payload, env, installer, readTokens(dir));
1659
1977
  for (const progress of result.progress ?? []) installer.milestone(progress);
1660
1978
  const succeeded = result.ok && (result.code === void 0 || result.code === 0);
1661
1979
  const outcome = result.outcome ? validateInstallerOutcome(result.outcome) : void 0;
@@ -1670,18 +1988,18 @@ async function finishLastMile(config, dir, options, installer, version, unchange
1670
1988
  },
1671
1989
  ...!succeeded ? {
1672
1990
  operationFailed: true,
1673
- 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}` : ""}`,
1674
- retry: `(cd ${payload} && ${command.join(" ")})`
1991
+ 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}` : ""}`,
1992
+ retry: `(cd ${payload} && ${command2.join(" ")})`
1675
1993
  } : {}
1676
1994
  });
1677
1995
  return succeeded ? outcome?.operationFailed || outcome?.failed ? 1 : 0 : result.code || 1;
1678
1996
  }
1679
- async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1680
- const [command, ...args] = entry;
1681
- const shell = needsShell(command);
1997
+ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
1998
+ const [command2, ...args] = entry2;
1999
+ const shell = needsShell(command2);
1682
2000
  const progress = !(process.platform === "win32" && shell);
1683
- const outcomeDir = mkdtempSync(join4(tmpdir4(), "mm-installer-outcome-"));
1684
- const outcomeFile = join4(outcomeDir, "outcome.json");
2001
+ const outcomeDir = mkdtempSync2(join6(tmpdir4(), "mm-installer-outcome-"));
2002
+ const outcomeFile = join6(outcomeDir, "outcome.json");
1685
2003
  const childEnv = { ...env, MM_INSTALLER_OUTCOME_FILE: outcomeFile };
1686
2004
  delete childEnv.MM_FACE_TRANSCRIPT;
1687
2005
  delete childEnv.MM_PROGRESS_FD;
@@ -1690,8 +2008,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1690
2008
  const secrets = [tokens?.accessToken, tokens?.refreshToken].filter((value) => Boolean(value));
1691
2009
  const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
1692
2010
  try {
1693
- const result = await new Promise((resolve2) => {
1694
- const child = spawn2(shell ? [command, ...args].map(quoteForShell).join(" ") : command, shell ? [] : args, {
2011
+ const result = await new Promise((resolve3) => {
2012
+ const child = spawn2(shell ? [command2, ...args].map(quoteForShell).join(" ") : command2, shell ? [] : args, {
1695
2013
  cwd,
1696
2014
  shell,
1697
2015
  windowsHide: true,
@@ -1727,8 +2045,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1727
2045
  if (pending.length > 65536) pending = "";
1728
2046
  });
1729
2047
  }
1730
- child.on("error", (error) => resolve2({ ok: false, error: error.message }));
1731
- child.on("close", (code) => resolve2({
2048
+ child.on("error", (error) => resolve3({ ok: false, error: error.message }));
2049
+ child.on("close", (code) => resolve3({
1732
2050
  ok: code === 0,
1733
2051
  ...code !== null ? { code } : {},
1734
2052
  ...code !== 0 ? { error: `exit code ${code}` } : {}
@@ -1741,25 +2059,30 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1741
2059
  return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
1742
2060
  }
1743
2061
  } finally {
1744
- if (existsSync4(outcomeFile)) unlinkSync(outcomeFile);
2062
+ if (existsSync6(outcomeFile)) unlinkSync2(outcomeFile);
1745
2063
  rmdirSync(outcomeDir);
1746
2064
  }
1747
2065
  }
1748
- function doForward(config, dir, options, command, argv, print) {
1749
- if (command && existsSync4(command)) {
1750
- print(`${command} is a file, not a command \u2014 did you mean \`--run ${command}\`?`);
2066
+ function doForward(config, dir, options, command2, argv2, print, unlock) {
2067
+ if (command2 && existsSync6(command2)) {
2068
+ print(`${command2} is a file, not a command \u2014 did you mean \`--run ${command2}\`?`);
1751
2069
  return 2;
1752
2070
  }
1753
- const target = readPayloadRun(dir);
2071
+ const acquired = readState(dir)?.acquired;
2072
+ const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
1754
2073
  const verbs = readPayloadVerbs(dir);
1755
- const declared = verbs === "*" || Array.isArray(verbs) && command !== void 0 && verbs.includes(command);
2074
+ const declared = verbs === "*" || Array.isArray(verbs) && command2 !== void 0 && verbs.includes(command2);
1756
2075
  if (!target || !declared) {
1757
- print(`unknown command: ${command}`);
2076
+ print(`unknown command: ${command2}`);
1758
2077
  printUsage(config, print);
1759
2078
  return 2;
1760
2079
  }
1761
- const forwarded = [...resolveEntry(target), ...argv];
1762
- const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir), payloadEnv(dir));
2080
+ const forwarded = [...resolveEntry(target), ...argv2];
2081
+ const cwd = payloadDir(dir);
2082
+ const inherited = payloadEnv(dir);
2083
+ const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
2084
+ unlock();
2085
+ const result = (options.runEntry ?? defaultRunEntry)(forwarded, cwd, env);
1763
2086
  if (!result.ok && result.code === void 0) {
1764
2087
  print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
1765
2088
  return 1;
@@ -1769,10 +2092,10 @@ function doForward(config, dir, options, command, argv, print) {
1769
2092
  function doAutoupdate(config, options, args, print) {
1770
2093
  const mode = args[0] ?? "status";
1771
2094
  const scheduleOptions = options.autoupdate ?? {};
1772
- const command = options.autoupdate?.command ?? [process.execPath, "update"];
2095
+ const command2 = options.autoupdate?.command ?? [process.execPath, "update"];
1773
2096
  if (mode === "on") {
1774
2097
  try {
1775
- enableSchedule(config, command, scheduleOptions);
2098
+ enableSchedule(config, command2, scheduleOptions);
1776
2099
  } catch (error) {
1777
2100
  print(error.message);
1778
2101
  return 1;
@@ -1804,19 +2127,24 @@ function doAutoupdate(config, options, args, print) {
1804
2127
  if (state.lastRun) print(`last run: ${state.lastRun}`);
1805
2128
  return 0;
1806
2129
  }
1807
- function doDoctor(config, dir, options, print) {
2130
+ function doDoctor(config, dir, options, print, unlock) {
1808
2131
  doLauncherDoctor(config, dir, options, print);
1809
- const target = readPayloadRun(dir);
2132
+ const acquired = readState(dir)?.acquired;
2133
+ const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
1810
2134
  const verbs = readPayloadVerbs(dir);
1811
2135
  const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
1812
2136
  if (!chains) return 0;
1813
- const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir), payloadEnv(dir));
2137
+ const cwd = payloadDir(dir);
2138
+ const inherited = payloadEnv(dir);
2139
+ const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
2140
+ unlock();
2141
+ const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], cwd, env);
1814
2142
  return result.code ?? (result.ok ? 0 : 1);
1815
2143
  }
1816
2144
  function doLauncherDoctor(config, dir, options, print) {
1817
2145
  const tokens = readTokens(dir);
1818
2146
  const state = readState(dir);
1819
- const payloadPresent = existsSync4(payloadDir(dir));
2147
+ const payloadPresent = existsSync6(payloadDir(dir));
1820
2148
  print(`product: ${config.product}`);
1821
2149
  print(`host: ${config.host}`);
1822
2150
  print(`login: ${config.loginKind}`);
@@ -1828,7 +2156,7 @@ function doLauncherDoctor(config, dir, options, print) {
1828
2156
  print("token: none \u2014 run login first.");
1829
2157
  }
1830
2158
  print(`payload: ${state ? state.version : "none"}${payloadPresent ? "" : " (not downloaded)"}`);
1831
- print(`paths: tokens ${join4(dir, "tokens.json")}, state ${join4(dir, "state.json")}, payload ${payloadDir(dir)}`);
2159
+ print(`paths: tokens ${join6(dir, "tokens.json")}, state ${join6(dir, "state.json")}, payload ${payloadDir(dir)}`);
1832
2160
  const schedule = querySchedule(config, options.autoupdate ?? {});
1833
2161
  print(
1834
2162
  `auto-update: ${!schedule.supported ? "unsupported on this platform" : schedule.enabled ? `on (${schedule.cadence ?? "hourly"})` : "off"}`