@mutmutco/installer-launcher 0.1.12 → 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;
@@ -563,17 +891,17 @@ function createInstallerRun(value, options = {}) {
563
891
  }
564
892
 
565
893
  // src/autoupdate.ts
566
- import { spawnSync } from "node:child_process";
567
- import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
568
- import { tmpdir } from "node:os";
569
- 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";
570
898
  function schedulePlatform(override) {
571
899
  const platform = override ?? process.platform;
572
900
  if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
573
901
  return null;
574
902
  }
575
- function defaultExec(command, args) {
576
- const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
903
+ function defaultExec(command2, args) {
904
+ const result = spawnSync2(command2, args, { encoding: "utf8", windowsHide: true });
577
905
  if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
578
906
  return {
579
907
  code: typeof result.status === "number" ? result.status : 1,
@@ -583,8 +911,8 @@ function defaultExec(command, args) {
583
911
  }
584
912
  function homeOf(options) {
585
913
  if (options.homeDir) return options.homeDir;
586
- if (process.platform === "win32") return process.env.USERPROFILE ?? tmpdir();
587
- return process.env.HOME ?? tmpdir();
914
+ if (process.platform === "win32") return process.env.USERPROFILE ?? tmpdir2();
915
+ return process.env.HOME ?? tmpdir2();
588
916
  }
589
917
  function scheduleName(config) {
590
918
  return `${config.binName} autoupdate`;
@@ -595,13 +923,13 @@ function scheduleLabel(config) {
595
923
  function quoteWindows(arg) {
596
924
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
597
925
  }
598
- function enableSchedule(config, command, options = {}) {
926
+ function enableSchedule(config, command2, options = {}) {
599
927
  const platform = schedulePlatform(options.platform);
600
928
  if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
601
929
  const exec = options.exec ?? defaultExec;
602
930
  const home = homeOf(options);
603
931
  if (platform === "win32") {
604
- const taskLine = command.map(quoteWindows).join(" ");
932
+ const taskLine = command2.map(quoteWindows).join(" ");
605
933
  const result2 = exec("schtasks", ["/Create", "/TN", scheduleName(config), "/TR", taskLine, "/SC", "HOURLY", "/IT", "/F"]);
606
934
  if (result2.code !== 0) {
607
935
  throw new Error(`could not turn autoupdate on: ${firstLine(result2.stdout || result2.stderr) || `exit ${result2.code}`}`);
@@ -610,10 +938,10 @@ function enableSchedule(config, command, options = {}) {
610
938
  }
611
939
  if (platform === "darwin") {
612
940
  const label2 = scheduleLabel(config);
613
- const dir2 = join(home, "Library", "LaunchAgents");
614
- mkdirSync(dir2, { recursive: true });
615
- const plist = join(dir2, `${label2}.plist`);
616
- 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));
617
945
  exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
618
946
  const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
619
947
  if (result2.code !== 0) {
@@ -622,10 +950,10 @@ function enableSchedule(config, command, options = {}) {
622
950
  return;
623
951
  }
624
952
  const label = scheduleLabel(config);
625
- const dir = join(home, ".config", "systemd", "user");
626
- mkdirSync(dir, { recursive: true });
627
- writeFileSync2(join(dir, `${label}.service`), linuxService(command));
628
- 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));
629
957
  const reload = exec("systemctl", ["--user", "daemon-reload"]);
630
958
  if (reload.code !== 0) {
631
959
  throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
@@ -650,14 +978,14 @@ function disableSchedule(config, options = {}) {
650
978
  if (platform === "darwin") {
651
979
  const label2 = scheduleLabel(config);
652
980
  exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
653
- rmSync(join(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
981
+ rmSync3(join4(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
654
982
  return;
655
983
  }
656
984
  const label = scheduleLabel(config);
657
- const dir = join(home, ".config", "systemd", "user");
985
+ const dir = join4(home, ".config", "systemd", "user");
658
986
  exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
659
- rmSync(join(dir, `${label}.service`), { force: true });
660
- rmSync(join(dir, `${label}.timer`), { force: true });
987
+ rmSync3(join4(dir, `${label}.service`), { force: true });
988
+ rmSync3(join4(dir, `${label}.timer`), { force: true });
661
989
  }
662
990
  function querySchedule(config, options = {}) {
663
991
  const platform = schedulePlatform(options.platform);
@@ -675,20 +1003,20 @@ function querySchedule(config, options = {}) {
675
1003
  return state2;
676
1004
  }
677
1005
  if (platform === "darwin") {
678
- const plist = join(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
679
- 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 };
680
1008
  return { supported: true, enabled: true, cadence: "hourly" };
681
1009
  }
682
- const timer = join(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
683
- 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 };
684
1012
  const state = { supported: true, enabled: true, cadence: "hourly" };
685
1013
  const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
686
1014
  const stamp = (shown.stdout ?? "").trim();
687
1015
  if (shown.code === 0 && stamp && stamp !== "n/a") state.lastRun = stamp;
688
1016
  return state;
689
1017
  }
690
- function darwinPlist(label, command) {
691
- 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");
692
1020
  return `<?xml version="1.0" encoding="UTF-8"?>
693
1021
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
694
1022
  <plist version="1.0">
@@ -705,8 +1033,8 @@ ${args}
705
1033
  </plist>
706
1034
  `;
707
1035
  }
708
- function linuxService(command) {
709
- 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(" ");
710
1038
  return `[Unit]
711
1039
  Description=${"Hourly update check"}
712
1040
  [Service]
@@ -738,7 +1066,7 @@ function firstLine(text) {
738
1066
  }
739
1067
 
740
1068
  // src/config.ts
741
- import { readFileSync as readFileSync3 } from "node:fs";
1069
+ import { readFileSync as readFileSync6 } from "node:fs";
742
1070
  import { getAsset } from "node:sea";
743
1071
 
744
1072
  // src/module-url.ts
@@ -760,10 +1088,10 @@ function loadProductConfig(options = {}) {
760
1088
  const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
761
1089
  const devFallback = new URL("../config/product.template.json", moduleUrl());
762
1090
  if (explicit) {
763
- return parseProductConfig(readFileSync3(explicit, "utf8"));
1091
+ return parseProductConfig(readFileSync6(explicit, "utf8"));
764
1092
  }
765
1093
  try {
766
- return parseProductConfig(readFileSync3(devFallback, "utf8"));
1094
+ return parseProductConfig(readFileSync6(devFallback, "utf8"));
767
1095
  } catch {
768
1096
  }
769
1097
  try {
@@ -819,7 +1147,7 @@ function field(record, key) {
819
1147
 
820
1148
  // src/login-github.ts
821
1149
  import { spawn } from "node:child_process";
822
- var realSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1150
+ var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
823
1151
  function openBrowser(url) {
824
1152
  if (process.env.LAUNCHER_NO_OPEN === "1") return;
825
1153
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
@@ -939,7 +1267,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
939
1267
  }
940
1268
 
941
1269
  // src/login-google.ts
942
- import { createHash, randomBytes } from "node:crypto";
1270
+ import { createHash as createHash2, randomBytes } from "node:crypto";
943
1271
  import { createServer } from "node:http";
944
1272
  var b64url = (bytes) => bytes.toString("base64url");
945
1273
  async function loginGoogle(options) {
@@ -947,7 +1275,7 @@ async function loginGoogle(options) {
947
1275
  const server = options.host.replace(/\/+$/, "");
948
1276
  const timeoutMs = options.timeoutMs ?? 5 * 6e4;
949
1277
  const listener = createServer();
950
- 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));
951
1279
  const port = listener.address().port;
952
1280
  const redirectUri = `http://127.0.0.1:${port}/callback`;
953
1281
  try {
@@ -962,7 +1290,7 @@ async function loginGoogle(options) {
962
1290
  throw new Error("the sign-in server returned a malformed registration");
963
1291
  }
964
1292
  const verifier = b64url(randomBytes(32));
965
- const challenge = b64url(createHash("sha256").update(verifier).digest());
1293
+ const challenge = b64url(createHash2("sha256").update(verifier).digest());
966
1294
  const state = b64url(randomBytes(16));
967
1295
  const authorize = new URL(`${server}/oauth/authorize`);
968
1296
  authorize.search = new URLSearchParams({
@@ -974,7 +1302,7 @@ async function loginGoogle(options) {
974
1302
  code_challenge: challenge,
975
1303
  code_challenge_method: "S256"
976
1304
  }).toString();
977
- const code = await new Promise((resolve2, reject) => {
1305
+ const code = await new Promise((resolve3, reject) => {
978
1306
  const timer = setTimeout(() => reject(new Error("sign-in timed out \u2014 run login again.")), timeoutMs);
979
1307
  listener.on("request", (req, res) => {
980
1308
  const url = new URL(req.url ?? "/", redirectUri);
@@ -992,7 +1320,7 @@ async function loginGoogle(options) {
992
1320
  }
993
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."));
994
1322
  clearTimeout(timer);
995
- resolve2(received);
1323
+ resolve3(received);
996
1324
  });
997
1325
  const print = options.print ?? ((line) => process.stderr.write(`${line}
998
1326
  `));
@@ -1052,10 +1380,10 @@ function page(title, body) {
1052
1380
  }
1053
1381
 
1054
1382
  // src/payload.ts
1055
- import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
1056
- import { mkdirSync as mkdirSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
1057
- import { tmpdir as tmpdir2 } from "node:os";
1058
- 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";
1059
1387
 
1060
1388
  // src/canonical.ts
1061
1389
  function canonicalJson(value) {
@@ -1072,7 +1400,7 @@ function encode(value) {
1072
1400
  return JSON.stringify(value);
1073
1401
  }
1074
1402
  if (Array.isArray(value)) {
1075
- return `[${value.map((entry) => encode(entry)).join(",")}]`;
1403
+ return `[${value.map((entry2) => encode(entry2)).join(",")}]`;
1076
1404
  }
1077
1405
  if (typeof value === "object") {
1078
1406
  const record = value;
@@ -1121,9 +1449,9 @@ function verifyManifest(manifest, signature, config) {
1121
1449
  return false;
1122
1450
  }
1123
1451
  }
1124
- function verifyFileBytes(entry, bytes) {
1125
- if (entry.size !== bytes.length) return false;
1126
- 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();
1127
1455
  }
1128
1456
  async function readErrorCode(response) {
1129
1457
  try {
@@ -1154,8 +1482,8 @@ function parseManifest(json) {
1154
1482
  if (typeof record.signature !== "string" || !record.signature) {
1155
1483
  throw new Error("the release manifest is unsigned");
1156
1484
  }
1157
- const files = record.files.map((entry) => {
1158
- const file = entry;
1485
+ const files = record.files.map((entry2) => {
1486
+ const file = entry2;
1159
1487
  if (typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.size !== "number") {
1160
1488
  throw new Error("the release manifest lists a malformed file");
1161
1489
  }
@@ -1195,101 +1523,31 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
1195
1523
  }
1196
1524
  async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
1197
1525
  const fetchImpl = options.fetchImpl ?? fetch;
1198
- const staging = join2(tmpdir2(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
1199
- mkdirSync2(staging, { recursive: true });
1526
+ const staging = join5(tmpdir3(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
1527
+ mkdirSync5(staging, { recursive: true });
1200
1528
  try {
1201
- for (const entry of manifest.files) {
1202
- const bytes = await fetchFileBytes(config, accessToken, entry.path, fetchImpl);
1203
- if (!verifyFileBytes(entry, bytes)) {
1204
- 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.`);
1205
1533
  }
1206
- const dest = join2(staging, entry.path);
1207
- mkdirSync2(dirname(dest), { recursive: true });
1208
- writeFileSync3(dest, bytes);
1534
+ const dest = join5(staging, entry2.path);
1535
+ mkdirSync5(dirname2(dest), { recursive: true });
1536
+ writeFileSync6(dest, bytes);
1209
1537
  }
1210
- const target = join2(dir, "payload");
1211
- mkdirSync2(dir, { recursive: true });
1212
- rmSync2(target, { force: true, recursive: true });
1213
- 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);
1214
1542
  } catch (error) {
1215
- rmSync2(staging, { force: true, recursive: true });
1543
+ rmSync4(staging, { force: true, recursive: true });
1216
1544
  throw error;
1217
1545
  }
1218
1546
  return manifest.version;
1219
1547
  }
1220
1548
 
1221
- // src/store.ts
1222
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
1223
- import { tmpdir as tmpdir3 } from "node:os";
1224
- import { join as join3 } from "node:path";
1225
- function defaultProductDir(product) {
1226
- if (process.platform === "win32") {
1227
- const base = process.env.LOCALAPPDATA ?? join3(tmpdir3(), "launcher-fallback");
1228
- return join3(base, product);
1229
- }
1230
- const home = process.env.HOME ?? tmpdir3();
1231
- return join3(home, `.${product}`);
1232
- }
1233
- function resolveProductDir(product, explicit) {
1234
- return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
1235
- }
1236
- function tokensPath(dir) {
1237
- return join3(dir, "tokens.json");
1238
- }
1239
- function statePath(dir) {
1240
- return join3(dir, "state.json");
1241
- }
1242
- function payloadDir(dir) {
1243
- return join3(dir, "payload");
1244
- }
1245
- function readTokens(dir) {
1246
- try {
1247
- const data = JSON.parse(readFileSync4(tokensPath(dir), "utf8"));
1248
- if (typeof data.accessToken !== "string" || !data.accessToken) return null;
1249
- if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
1250
- const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
1251
- if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
1252
- if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
1253
- return tokens;
1254
- } catch {
1255
- return null;
1256
- }
1257
- }
1258
- function writeTokens(dir, tokens) {
1259
- mkdirSync3(dir, { recursive: true });
1260
- try {
1261
- writeFileSync4(tokensPath(dir), `${JSON.stringify(tokens)}
1262
- `, { mode: 384 });
1263
- } catch {
1264
- writeFileSync4(tokensPath(dir), `${JSON.stringify(tokens)}
1265
- `);
1266
- }
1267
- }
1268
- function clearTokens(dir) {
1269
- rmSync3(tokensPath(dir), { force: true });
1270
- }
1271
- function readState(dir) {
1272
- try {
1273
- const data = JSON.parse(readFileSync4(statePath(dir), "utf8"));
1274
- if (typeof data.version !== "string" || !data.version) return null;
1275
- return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
1276
- } catch {
1277
- return null;
1278
- }
1279
- }
1280
- function writeState(dir, state) {
1281
- mkdirSync3(dir, { recursive: true });
1282
- writeFileSync4(statePath(dir), `${JSON.stringify(state, null, 2)}
1283
- `);
1284
- }
1285
- function wipeProductDir(dir) {
1286
- rmSync3(tokensPath(dir), { force: true });
1287
- rmSync3(statePath(dir), { force: true });
1288
- rmSync3(payloadDir(dir), { force: true, recursive: true });
1289
- }
1290
-
1291
1549
  // src/index.ts
1292
- var LAUNCHER_VERSION = true ? "0.1.12" : readVersionFromPackage();
1550
+ var LAUNCHER_VERSION = true ? "0.1.13" : readVersionFromPackage();
1293
1551
  function defaultPrint(message) {
1294
1552
  process.stdout.write(`${message}
1295
1553
  `);
@@ -1301,30 +1559,37 @@ function defaultPrintErr(message) {
1301
1559
  async function run(rawOptions = {}) {
1302
1560
  const print = rawOptions.print ?? defaultPrint;
1303
1561
  const printErr = rawOptions.printErr ?? defaultPrintErr;
1304
- const argv = rawOptions.argv ?? process.argv.slice(2);
1305
- const runAt = argv.indexOf("--run");
1306
- 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);
1307
1565
  let config;
1308
1566
  try {
1309
- config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv, "--config") });
1567
+ config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv2, "--config") });
1310
1568
  } catch (error) {
1311
1569
  print(`cannot start: ${error.message}`);
1312
1570
  return 2;
1313
1571
  }
1314
- const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(argv, "--dir"));
1315
- const positional = argv.filter((arg) => !arg.startsWith("-"));
1316
- 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")) {
1317
1575
  print(`${config.binName} launcher ${LAUNCHER_VERSION}`);
1318
1576
  return 0;
1319
1577
  }
1320
- if (argv.includes("--help") || argv.includes("-h") || positional.length === 0) {
1578
+ if (argv2.includes("--help") || argv2.includes("-h") || positional.length === 0) {
1321
1579
  printUsage(config, print);
1322
1580
  return positional.length === 0 ? 2 : 0;
1323
1581
  }
1324
- const command = positional[0];
1582
+ const command2 = positional[0];
1583
+ let unlock;
1325
1584
  try {
1326
- 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) {
1327
1591
  case "login":
1592
+ unlock();
1328
1593
  await doLogin(config, dir, rawOptions, print);
1329
1594
  return 0;
1330
1595
  case "logout":
@@ -1336,11 +1601,11 @@ async function run(rawOptions = {}) {
1336
1601
  case "update":
1337
1602
  return await doUpdate(config, dir, rawOptions, print);
1338
1603
  case "doctor":
1339
- return doDoctor(config, dir, rawOptions, print);
1604
+ return doDoctor(config, dir, rawOptions, print, unlock);
1340
1605
  case "autoupdate":
1341
1606
  return doAutoupdate(config, rawOptions, positional.slice(1), print);
1342
1607
  default:
1343
- return doForward(config, dir, rawOptions, command, argv, print);
1608
+ return doForward(config, dir, rawOptions, command2, argv2, print, unlock);
1344
1609
  }
1345
1610
  } catch (error) {
1346
1611
  if (error instanceof NeedsLoginError) {
@@ -1349,12 +1614,14 @@ async function run(rawOptions = {}) {
1349
1614
  }
1350
1615
  print(`failed: ${error.message}`);
1351
1616
  return 1;
1617
+ } finally {
1618
+ unlock?.();
1352
1619
  }
1353
1620
  }
1354
- function flagValue(argv, flag) {
1355
- const index = argv.indexOf(flag);
1621
+ function flagValue(argv2, flag) {
1622
+ const index = argv2.indexOf(flag);
1356
1623
  if (index < 0) return void 0;
1357
- const value = argv[index + 1];
1624
+ const value = argv2[index + 1];
1358
1625
  return value && !value.startsWith("-") ? value : void 0;
1359
1626
  }
1360
1627
  function printUsage(config, print) {
@@ -1368,8 +1635,8 @@ async function runFile(file, args, printErr) {
1368
1635
  printErr("launcher --run needs a file to run.");
1369
1636
  return 1;
1370
1637
  }
1371
- const abs = resolve(process.cwd(), file);
1372
- if (!existsSync4(abs)) {
1638
+ const abs = resolve2(process.cwd(), file);
1639
+ if (!existsSync6(abs)) {
1373
1640
  printErr(`cannot run ${file}: no such file.`);
1374
1641
  return 1;
1375
1642
  }
@@ -1462,14 +1729,14 @@ async function fetchAccessTokenOrLogin(config, dir, options, print, installer) {
1462
1729
  }
1463
1730
  function readPayloadArgv(dir, key) {
1464
1731
  try {
1465
- const parsed = JSON.parse(readFileSync5(join4(payloadDir(dir), "payload.json"), "utf8"));
1466
- const entry = parsed[key];
1467
- if (typeof entry === "string") {
1468
- 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);
1469
1736
  return parts.length > 0 ? parts : null;
1470
1737
  }
1471
- if (Array.isArray(entry) && entry.every((part) => typeof part === "string" && part.length > 0)) {
1472
- return entry;
1738
+ if (Array.isArray(entry2) && entry2.every((part) => typeof part === "string" && part.length > 0)) {
1739
+ return entry2;
1473
1740
  }
1474
1741
  return null;
1475
1742
  } catch {
@@ -1484,7 +1751,7 @@ function readPayloadRun(dir) {
1484
1751
  }
1485
1752
  function readPayloadVerbs(dir) {
1486
1753
  try {
1487
- const parsed = JSON.parse(readFileSync5(join4(payloadDir(dir), "payload.json"), "utf8"));
1754
+ const parsed = JSON.parse(readFileSync7(join6(payloadDir(dir), "payload.json"), "utf8"));
1488
1755
  const verbs = parsed.verbs;
1489
1756
  if (verbs === "*") return "*";
1490
1757
  if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
@@ -1495,13 +1762,13 @@ function readPayloadVerbs(dir) {
1495
1762
  return null;
1496
1763
  }
1497
1764
  }
1498
- function resolveEntry(entry) {
1499
- 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;
1500
1767
  }
1501
- function needsShell(command) {
1768
+ function needsShell(command2) {
1502
1769
  if (process.platform !== "win32") return false;
1503
- if (/\.(cmd|bat)$/i.test(command)) return true;
1504
- return !existsSync4(command);
1770
+ if (/\.(cmd|bat)$/i.test(command2)) return true;
1771
+ return !existsSync6(command2);
1505
1772
  }
1506
1773
  function quoteForShell(arg) {
1507
1774
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
@@ -1528,11 +1795,11 @@ function parseProgress(raw) {
1528
1795
  }
1529
1796
  return progress;
1530
1797
  }
1531
- function defaultRunEntry(entry, cwd, env) {
1532
- const [command, ...args] = entry;
1533
- const shell = needsShell(command);
1534
- const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
1535
- 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, {
1536
1803
  cwd,
1537
1804
  stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
1538
1805
  shell,
@@ -1590,11 +1857,41 @@ async function installOrUpdate(config, dir, options, print, update) {
1590
1857
  version = manifest.version;
1591
1858
  installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
1592
1859
  const current = readState(dir);
1593
- 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
+ }
1594
1865
  if (!unchanged) {
1595
1866
  installer.phase("download", { state: "running" });
1596
1867
  started = Date.now();
1597
- 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);
1598
1895
  writeState(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1599
1896
  installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
1600
1897
  }
@@ -1632,12 +1929,12 @@ function payloadEnv(dir) {
1632
1929
  return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
1633
1930
  }
1634
1931
  var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
1635
- function payloadFileAsCommand(entry, payload) {
1636
- const first = entry[0];
1932
+ function payloadFileAsCommand(entry2, payload) {
1933
+ const first = entry2[0];
1637
1934
  if (!first || first === "$self") return null;
1638
1935
  if (first.includes("/") || first.includes("\\")) return null;
1639
- const candidate = join4(payload, first);
1640
- if (!existsSync4(candidate)) return null;
1936
+ const candidate = join6(payload, first);
1937
+ if (!existsSync6(candidate)) return null;
1641
1938
  if (NEVER_A_PROGRAM.test(first)) return first;
1642
1939
  if (process.platform === "win32") return null;
1643
1940
  try {
@@ -1646,22 +1943,22 @@ function payloadFileAsCommand(entry, payload) {
1646
1943
  return null;
1647
1944
  }
1648
1945
  }
1649
- async function finishLastMile(config, dir, options, installer, version, unchanged) {
1650
- const entry = readPayloadEntry(dir);
1946
+ async function finishLastMile(config, dir, options, installer, version, unchanged, repairCommand) {
1947
+ const entry2 = repairCommand ?? readPayloadEntry(dir);
1651
1948
  const payload = payloadDir(dir);
1652
- if (!entry) {
1949
+ if (!entry2) {
1653
1950
  installer.finish({
1654
1951
  version,
1655
1952
  total: 1,
1656
1953
  updated: unchanged ? 0 : 1,
1657
1954
  failed: 0,
1658
1955
  installed: true,
1659
- 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}.`
1660
1957
  });
1661
1958
  return 0;
1662
1959
  }
1663
- const command = resolveEntry(entry);
1664
- const dataFile = payloadFileAsCommand(entry, payload);
1960
+ const command2 = resolveEntry(entry2);
1961
+ const dataFile = payloadFileAsCommand(entry2, payload);
1665
1962
  if (dataFile) {
1666
1963
  installer.finish({
1667
1964
  version,
@@ -1674,8 +1971,9 @@ async function finishLastMile(config, dir, options, installer, version, unchange
1674
1971
  }
1675
1972
  installer.phase("activate", { state: "running" });
1676
1973
  const started = Date.now();
1677
- const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
1678
- 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));
1679
1977
  for (const progress of result.progress ?? []) installer.milestone(progress);
1680
1978
  const succeeded = result.ok && (result.code === void 0 || result.code === 0);
1681
1979
  const outcome = result.outcome ? validateInstallerOutcome(result.outcome) : void 0;
@@ -1690,18 +1988,18 @@ async function finishLastMile(config, dir, options, installer, version, unchange
1690
1988
  },
1691
1989
  ...!succeeded ? {
1692
1990
  operationFailed: true,
1693
- 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}` : ""}`,
1694
- 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(" ")})`
1695
1993
  } : {}
1696
1994
  });
1697
1995
  return succeeded ? outcome?.operationFailed || outcome?.failed ? 1 : 0 : result.code || 1;
1698
1996
  }
1699
- async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1700
- const [command, ...args] = entry;
1701
- const shell = needsShell(command);
1997
+ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
1998
+ const [command2, ...args] = entry2;
1999
+ const shell = needsShell(command2);
1702
2000
  const progress = !(process.platform === "win32" && shell);
1703
- const outcomeDir = mkdtempSync(join4(tmpdir4(), "mm-installer-outcome-"));
1704
- const outcomeFile = join4(outcomeDir, "outcome.json");
2001
+ const outcomeDir = mkdtempSync2(join6(tmpdir4(), "mm-installer-outcome-"));
2002
+ const outcomeFile = join6(outcomeDir, "outcome.json");
1705
2003
  const childEnv = { ...env, MM_INSTALLER_OUTCOME_FILE: outcomeFile };
1706
2004
  delete childEnv.MM_FACE_TRANSCRIPT;
1707
2005
  delete childEnv.MM_PROGRESS_FD;
@@ -1710,8 +2008,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1710
2008
  const secrets = [tokens?.accessToken, tokens?.refreshToken].filter((value) => Boolean(value));
1711
2009
  const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
1712
2010
  try {
1713
- const result = await new Promise((resolve2) => {
1714
- 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, {
1715
2013
  cwd,
1716
2014
  shell,
1717
2015
  windowsHide: true,
@@ -1747,8 +2045,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1747
2045
  if (pending.length > 65536) pending = "";
1748
2046
  });
1749
2047
  }
1750
- child.on("error", (error) => resolve2({ ok: false, error: error.message }));
1751
- child.on("close", (code) => resolve2({
2048
+ child.on("error", (error) => resolve3({ ok: false, error: error.message }));
2049
+ child.on("close", (code) => resolve3({
1752
2050
  ok: code === 0,
1753
2051
  ...code !== null ? { code } : {},
1754
2052
  ...code !== 0 ? { error: `exit code ${code}` } : {}
@@ -1761,25 +2059,30 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
1761
2059
  return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
1762
2060
  }
1763
2061
  } finally {
1764
- if (existsSync4(outcomeFile)) unlinkSync(outcomeFile);
2062
+ if (existsSync6(outcomeFile)) unlinkSync2(outcomeFile);
1765
2063
  rmdirSync(outcomeDir);
1766
2064
  }
1767
2065
  }
1768
- function doForward(config, dir, options, command, argv, print) {
1769
- if (command && existsSync4(command)) {
1770
- 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}\`?`);
1771
2069
  return 2;
1772
2070
  }
1773
- const target = readPayloadRun(dir);
2071
+ const acquired = readState(dir)?.acquired;
2072
+ const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
1774
2073
  const verbs = readPayloadVerbs(dir);
1775
- 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);
1776
2075
  if (!target || !declared) {
1777
- print(`unknown command: ${command}`);
2076
+ print(`unknown command: ${command2}`);
1778
2077
  printUsage(config, print);
1779
2078
  return 2;
1780
2079
  }
1781
- const forwarded = [...resolveEntry(target), ...argv];
1782
- 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);
1783
2086
  if (!result.ok && result.code === void 0) {
1784
2087
  print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
1785
2088
  return 1;
@@ -1789,10 +2092,10 @@ function doForward(config, dir, options, command, argv, print) {
1789
2092
  function doAutoupdate(config, options, args, print) {
1790
2093
  const mode = args[0] ?? "status";
1791
2094
  const scheduleOptions = options.autoupdate ?? {};
1792
- const command = options.autoupdate?.command ?? [process.execPath, "update"];
2095
+ const command2 = options.autoupdate?.command ?? [process.execPath, "update"];
1793
2096
  if (mode === "on") {
1794
2097
  try {
1795
- enableSchedule(config, command, scheduleOptions);
2098
+ enableSchedule(config, command2, scheduleOptions);
1796
2099
  } catch (error) {
1797
2100
  print(error.message);
1798
2101
  return 1;
@@ -1824,19 +2127,24 @@ function doAutoupdate(config, options, args, print) {
1824
2127
  if (state.lastRun) print(`last run: ${state.lastRun}`);
1825
2128
  return 0;
1826
2129
  }
1827
- function doDoctor(config, dir, options, print) {
2130
+ function doDoctor(config, dir, options, print, unlock) {
1828
2131
  doLauncherDoctor(config, dir, options, print);
1829
- const target = readPayloadRun(dir);
2132
+ const acquired = readState(dir)?.acquired;
2133
+ const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
1830
2134
  const verbs = readPayloadVerbs(dir);
1831
2135
  const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
1832
2136
  if (!chains) return 0;
1833
- 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);
1834
2142
  return result.code ?? (result.ok ? 0 : 1);
1835
2143
  }
1836
2144
  function doLauncherDoctor(config, dir, options, print) {
1837
2145
  const tokens = readTokens(dir);
1838
2146
  const state = readState(dir);
1839
- const payloadPresent = existsSync4(payloadDir(dir));
2147
+ const payloadPresent = existsSync6(payloadDir(dir));
1840
2148
  print(`product: ${config.product}`);
1841
2149
  print(`host: ${config.host}`);
1842
2150
  print(`login: ${config.loginKind}`);
@@ -1848,7 +2156,7 @@ function doLauncherDoctor(config, dir, options, print) {
1848
2156
  print("token: none \u2014 run login first.");
1849
2157
  }
1850
2158
  print(`payload: ${state ? state.version : "none"}${payloadPresent ? "" : " (not downloaded)"}`);
1851
- 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)}`);
1852
2160
  const schedule = querySchedule(config, options.autoupdate ?? {});
1853
2161
  print(
1854
2162
  `auto-update: ${!schedule.supported ? "unsupported on this platform" : schedule.enabled ? `on (${schedule.cadence ?? "hourly"})` : "off"}`