@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/README.md +52 -0
- package/dist/acquisition-required.mjs +3 -0
- package/dist/launcher.js +523 -215
- package/dist/launcher.sea.cjs +538 -230
- package/package.json +3 -2
package/dist/launcher.js
CHANGED
|
@@ -1,10 +1,338 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import {
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
576
|
-
const result =
|
|
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 ??
|
|
587
|
-
return process.env.HOME ??
|
|
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,
|
|
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 =
|
|
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 =
|
|
614
|
-
|
|
615
|
-
const plist =
|
|
616
|
-
|
|
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 =
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
981
|
+
rmSync3(join4(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
|
|
654
982
|
return;
|
|
655
983
|
}
|
|
656
984
|
const label = scheduleLabel(config);
|
|
657
|
-
const dir =
|
|
985
|
+
const dir = join4(home, ".config", "systemd", "user");
|
|
658
986
|
exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
|
|
659
|
-
|
|
660
|
-
|
|
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 =
|
|
679
|
-
if (!
|
|
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 =
|
|
683
|
-
if (!
|
|
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,
|
|
691
|
-
const args =
|
|
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(
|
|
709
|
-
const line =
|
|
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
|
|
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(
|
|
1091
|
+
return parseProductConfig(readFileSync6(explicit, "utf8"));
|
|
764
1092
|
}
|
|
765
1093
|
try {
|
|
766
|
-
return parseProductConfig(
|
|
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((
|
|
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((
|
|
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(
|
|
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((
|
|
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
|
-
|
|
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
|
|
1056
|
-
import { mkdirSync as
|
|
1057
|
-
import { tmpdir as
|
|
1058
|
-
import { dirname, join as
|
|
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((
|
|
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(
|
|
1125
|
-
if (
|
|
1126
|
-
return
|
|
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((
|
|
1158
|
-
const file =
|
|
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 =
|
|
1199
|
-
|
|
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
|
|
1202
|
-
const bytes = await fetchFileBytes(config, accessToken,
|
|
1203
|
-
if (!verifyFileBytes(
|
|
1204
|
-
throw new Error(`file ${
|
|
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 =
|
|
1207
|
-
|
|
1208
|
-
|
|
1534
|
+
const dest = join5(staging, entry2.path);
|
|
1535
|
+
mkdirSync5(dirname2(dest), { recursive: true });
|
|
1536
|
+
writeFileSync6(dest, bytes);
|
|
1209
1537
|
}
|
|
1210
|
-
const target =
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
1305
|
-
const runAt =
|
|
1306
|
-
if (runAt >= 0) return runFile(
|
|
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(
|
|
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(
|
|
1315
|
-
const positional =
|
|
1316
|
-
if (
|
|
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 (
|
|
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
|
|
1582
|
+
const command2 = positional[0];
|
|
1583
|
+
let unlock;
|
|
1325
1584
|
try {
|
|
1326
|
-
|
|
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,
|
|
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(
|
|
1355
|
-
const index =
|
|
1621
|
+
function flagValue(argv2, flag) {
|
|
1622
|
+
const index = argv2.indexOf(flag);
|
|
1356
1623
|
if (index < 0) return void 0;
|
|
1357
|
-
const value =
|
|
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 =
|
|
1372
|
-
if (!
|
|
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(
|
|
1466
|
-
const
|
|
1467
|
-
if (typeof
|
|
1468
|
-
const parts =
|
|
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(
|
|
1472
|
-
return
|
|
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(
|
|
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(
|
|
1499
|
-
return
|
|
1765
|
+
function resolveEntry(entry2) {
|
|
1766
|
+
return entry2[0] === "$self" ? [process.execPath, ...entry2.slice(1)] : entry2;
|
|
1500
1767
|
}
|
|
1501
|
-
function needsShell(
|
|
1768
|
+
function needsShell(command2) {
|
|
1502
1769
|
if (process.platform !== "win32") return false;
|
|
1503
|
-
if (/\.(cmd|bat)$/i.test(
|
|
1504
|
-
return !
|
|
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(
|
|
1532
|
-
const [
|
|
1533
|
-
const shell = needsShell(
|
|
1534
|
-
const commandLine = shell ? [
|
|
1535
|
-
const spawnEntry = (progress2) =>
|
|
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 &&
|
|
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
|
-
|
|
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(
|
|
1636
|
-
const first =
|
|
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 =
|
|
1640
|
-
if (!
|
|
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
|
|
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 (!
|
|
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 ${
|
|
1956
|
+
detail: `next step: run ${join6(payload, config.binName)} to start ${config.product}.`
|
|
1660
1957
|
});
|
|
1661
1958
|
return 0;
|
|
1662
1959
|
}
|
|
1663
|
-
const
|
|
1664
|
-
const dataFile = payloadFileAsCommand(
|
|
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
|
|
1678
|
-
const
|
|
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 ??
|
|
1694
|
-
retry: `(cd ${payload} && ${
|
|
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(
|
|
1700
|
-
const [
|
|
1701
|
-
const shell = needsShell(
|
|
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 =
|
|
1704
|
-
const outcomeFile =
|
|
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((
|
|
1714
|
-
const child = spawn2(shell ? [
|
|
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) =>
|
|
1751
|
-
child.on("close", (code) =>
|
|
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 (
|
|
2062
|
+
if (existsSync6(outcomeFile)) unlinkSync2(outcomeFile);
|
|
1765
2063
|
rmdirSync(outcomeDir);
|
|
1766
2064
|
}
|
|
1767
2065
|
}
|
|
1768
|
-
function doForward(config, dir, options,
|
|
1769
|
-
if (
|
|
1770
|
-
print(`${
|
|
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
|
|
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) &&
|
|
2074
|
+
const declared = verbs === "*" || Array.isArray(verbs) && command2 !== void 0 && verbs.includes(command2);
|
|
1776
2075
|
if (!target || !declared) {
|
|
1777
|
-
print(`unknown command: ${
|
|
2076
|
+
print(`unknown command: ${command2}`);
|
|
1778
2077
|
printUsage(config, print);
|
|
1779
2078
|
return 2;
|
|
1780
2079
|
}
|
|
1781
|
-
const forwarded = [...resolveEntry(target), ...
|
|
1782
|
-
const
|
|
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
|
|
2095
|
+
const command2 = options.autoupdate?.command ?? [process.execPath, "update"];
|
|
1793
2096
|
if (mode === "on") {
|
|
1794
2097
|
try {
|
|
1795
|
-
enableSchedule(config,
|
|
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
|
|
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
|
|
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 =
|
|
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 ${
|
|
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"}`
|