@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.sea.cjs
CHANGED
|
@@ -32,14 +32,342 @@ __export(index_exports, {
|
|
|
32
32
|
runInstallEntry: () => runInstallEntry
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(index_exports);
|
|
35
|
-
var
|
|
36
|
-
|
|
35
|
+
var import_node_crypto6 = require("node:crypto");
|
|
36
|
+
|
|
37
|
+
// src/acquisition.ts
|
|
38
|
+
var import_node_crypto3 = require("node:crypto");
|
|
39
|
+
var import_node_fs3 = require("node:fs");
|
|
40
|
+
var import_node_path3 = require("node:path");
|
|
41
|
+
|
|
42
|
+
// src/runtime.ts
|
|
43
|
+
var import_node_crypto = require("node:crypto");
|
|
44
|
+
var import_node_child_process = require("node:child_process");
|
|
45
|
+
var import_node_fs = require("node:fs");
|
|
46
|
+
var import_node_path = require("node:path");
|
|
47
|
+
var NODE_VERSION = "24.20.0";
|
|
48
|
+
var NPM_VERSION = "12.0.2";
|
|
49
|
+
var ARTIFACTS = {
|
|
50
|
+
"win32-x64": ["node-v24.20.0-win-x64.zip", "6cac9ffbca8f6a47091e4b5c772e0606049c3871cb67d900c0cedde630e545ba"],
|
|
51
|
+
"win32-arm64": ["node-v24.20.0-win-arm64.zip", "31c6799744de8a54601643098040c68c3697e56c94e407d61d0e5fa5f34191d7"],
|
|
52
|
+
"darwin-arm64": ["node-v24.20.0-darwin-arm64.tar.gz", "40e5607e5ecb3db9192723776da2d75d966260fc74a7a9e731c1bd67dda96bc8"],
|
|
53
|
+
"linux-x64": ["node-v24.20.0-linux-x64.tar.gz", "855d581f8a4eb1a8117e3426de25fe02770592febcfb31369aee1ffbfee9e8ec"],
|
|
54
|
+
"linux-arm64": ["node-v24.20.0-linux-arm64.tar.gz", "3515603e2487879a39bc75716f1a2affd027500c64ba50e845cf72cb33219013"]
|
|
55
|
+
};
|
|
56
|
+
var NPM_INTEGRITY = "uIXokLlBj6FpNUTQX1PmT5pz7BlIN9QlixX+zdaSNHsd0qUXsbDLr50xzY6Sw7cJVr0uzHKDOle0swmPW/p5Qw==";
|
|
57
|
+
async function verifiedDownload(url, path, algorithm, digest, fetchImpl) {
|
|
58
|
+
const response = await fetchImpl(url);
|
|
59
|
+
if (!response.ok) throw new Error(`runtime download failed (${response.status})`);
|
|
60
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
61
|
+
if ((0, import_node_crypto.createHash)(algorithm).update(bytes).digest(algorithm === "sha512" ? "base64" : "hex") !== digest) throw new Error("runtime archive checksum mismatch");
|
|
62
|
+
(0, import_node_fs.writeFileSync)(path, bytes);
|
|
63
|
+
}
|
|
64
|
+
function command(executable, args) {
|
|
65
|
+
const result = (0, import_node_child_process.spawnSync)(executable, args, { encoding: "utf8", windowsHide: true });
|
|
66
|
+
if (result.error || result.status !== 0) throw new Error(`runtime preparation failed: ${result.error?.message ?? result.stderr.trim()}`);
|
|
67
|
+
return result.stdout.trim();
|
|
68
|
+
}
|
|
69
|
+
async function acquireRuntime(dir, fetchImpl = fetch) {
|
|
70
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
71
|
+
const artifact = ARTIFACTS[platform];
|
|
72
|
+
if (!artifact) throw new Error(`managed runtime does not support ${platform}`);
|
|
73
|
+
const runtimeDir = (0, import_node_path.join)(dir, "runtimes");
|
|
74
|
+
(0, import_node_fs.mkdirSync)(runtimeDir, { recursive: true });
|
|
75
|
+
const receipt = (0, import_node_path.join)(runtimeDir, `node-${NODE_VERSION}-npm-${NPM_VERSION}-${platform}.json`);
|
|
76
|
+
if ((0, import_node_fs.existsSync)(receipt)) {
|
|
77
|
+
const runtime2 = JSON.parse((0, import_node_fs.readFileSync)(receipt, "utf8"));
|
|
78
|
+
for (const executable of [runtime2.node, runtime2.npm]) {
|
|
79
|
+
const rel = (0, import_node_path.relative)((0, import_node_fs.realpathSync)(runtimeDir), (0, import_node_fs.realpathSync)(executable));
|
|
80
|
+
if (rel.startsWith("..") || (0, import_node_path.isAbsolute)(rel)) throw new Error("cached runtime escapes owned directory");
|
|
81
|
+
}
|
|
82
|
+
if (command(runtime2.node, ["--version"]) === `v${NODE_VERSION}` && command(runtime2.node, [runtime2.npm, "--version"]) === NPM_VERSION) return runtime2;
|
|
83
|
+
throw new Error("cached runtime validation failed");
|
|
84
|
+
}
|
|
85
|
+
const root = (0, import_node_fs.mkdtempSync)((0, import_node_path.join)(runtimeDir, "runtime-"));
|
|
86
|
+
const archive = (0, import_node_path.join)(root, artifact[0]);
|
|
87
|
+
await verifiedDownload(`https://nodejs.org/dist/v${NODE_VERSION}/${artifact[0]}`, archive, "sha256", artifact[1], fetchImpl);
|
|
88
|
+
const tar = process.platform === "win32" ? (0, import_node_path.join)(process.env.SystemRoot ?? "C:/Windows", "System32", "tar.exe") : "/usr/bin/tar";
|
|
89
|
+
command(tar, ["-xf", archive, "-C", root]);
|
|
90
|
+
const unpacked = (0, import_node_path.join)(root, artifact[0].replace(/\.(zip|tar\.gz)$/, ""));
|
|
91
|
+
const node = (0, import_node_path.join)(unpacked, process.platform === "win32" ? "node.exe" : "bin/node");
|
|
92
|
+
const npmArchive = (0, import_node_path.join)(root, "npm.tgz");
|
|
93
|
+
await verifiedDownload(`https://registry.npmjs.org/npm/-/npm-${NPM_VERSION}.tgz`, npmArchive, "sha512", NPM_INTEGRITY, fetchImpl);
|
|
94
|
+
const npmRoot = (0, import_node_path.join)(root, "npm");
|
|
95
|
+
(0, import_node_fs.mkdirSync)(npmRoot);
|
|
96
|
+
command(tar, ["-xf", npmArchive, "-C", npmRoot]);
|
|
97
|
+
const installedNpm = (0, import_node_path.join)(unpacked, process.platform === "win32" ? "node_modules/npm" : "lib/node_modules/npm");
|
|
98
|
+
if ((0, import_node_fs.existsSync)(installedNpm)) (0, import_node_fs.renameSync)(installedNpm, (0, import_node_path.join)(root, "npm-bundled"));
|
|
99
|
+
(0, import_node_fs.renameSync)((0, import_node_path.join)(npmRoot, "package"), installedNpm);
|
|
100
|
+
const npm = (0, import_node_path.join)(installedNpm, "bin/npm-cli.js");
|
|
101
|
+
if (command(node, ["--version"]) !== `v${NODE_VERSION}` || command(node, [npm, "--version"]) !== NPM_VERSION) throw new Error("downloaded runtime validation failed");
|
|
102
|
+
const runtime = { node, npm };
|
|
103
|
+
const temporary = `${receipt}.${(0, import_node_crypto.randomUUID)()}.tmp`;
|
|
104
|
+
try {
|
|
105
|
+
(0, import_node_fs.writeFileSync)(temporary, JSON.stringify(runtime), { flag: "wx", mode: 384, flush: true });
|
|
106
|
+
(0, import_node_fs.renameSync)(temporary, receipt);
|
|
107
|
+
} finally {
|
|
108
|
+
(0, import_node_fs.rmSync)(temporary, { force: true });
|
|
109
|
+
}
|
|
110
|
+
return runtime;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/store.ts
|
|
114
|
+
var import_node_fs2 = require("node:fs");
|
|
115
|
+
var import_node_crypto2 = require("node:crypto");
|
|
116
|
+
var import_node_os = require("node:os");
|
|
117
|
+
var import_node_path2 = require("node:path");
|
|
118
|
+
function defaultProductDir(product) {
|
|
119
|
+
if (process.platform === "win32") {
|
|
120
|
+
const base = process.env.LOCALAPPDATA ?? (0, import_node_path2.join)((0, import_node_os.tmpdir)(), "launcher-fallback");
|
|
121
|
+
return (0, import_node_path2.join)(base, product);
|
|
122
|
+
}
|
|
123
|
+
const home = process.env.HOME ?? (0, import_node_os.tmpdir)();
|
|
124
|
+
return (0, import_node_path2.join)(home, `.${product}`);
|
|
125
|
+
}
|
|
126
|
+
function resolveProductDir(product, explicit) {
|
|
127
|
+
return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
|
|
128
|
+
}
|
|
129
|
+
function tokensPath(dir) {
|
|
130
|
+
return (0, import_node_path2.join)(dir, "tokens.json");
|
|
131
|
+
}
|
|
132
|
+
function statePath(dir) {
|
|
133
|
+
return (0, import_node_path2.join)(dir, "state.json");
|
|
134
|
+
}
|
|
135
|
+
function payloadDir(dir) {
|
|
136
|
+
const acquired = readState(dir)?.acquired;
|
|
137
|
+
if (acquired) return (0, import_node_path2.join)(dir, "candidates", acquired.candidate, "payload");
|
|
138
|
+
return (0, import_node_path2.join)(dir, "payload");
|
|
139
|
+
}
|
|
140
|
+
function readTokens(dir) {
|
|
141
|
+
try {
|
|
142
|
+
const data = JSON.parse((0, import_node_fs2.readFileSync)(tokensPath(dir), "utf8"));
|
|
143
|
+
if (typeof data.accessToken !== "string" || !data.accessToken) return null;
|
|
144
|
+
if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
|
|
145
|
+
const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
|
|
146
|
+
if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
|
|
147
|
+
if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
|
|
148
|
+
return tokens;
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function writeTokens(dir, tokens) {
|
|
154
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
155
|
+
try {
|
|
156
|
+
(0, import_node_fs2.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
157
|
+
`, { mode: 384 });
|
|
158
|
+
} catch {
|
|
159
|
+
(0, import_node_fs2.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function clearTokens(dir) {
|
|
164
|
+
(0, import_node_fs2.rmSync)(tokensPath(dir), { force: true });
|
|
165
|
+
}
|
|
166
|
+
function readState(dir) {
|
|
167
|
+
try {
|
|
168
|
+
const data = JSON.parse((0, import_node_fs2.readFileSync)(statePath(dir), "utf8"));
|
|
169
|
+
if (typeof data.version !== "string" || !data.version) return null;
|
|
170
|
+
const state = { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
|
|
171
|
+
if (data.acquired) {
|
|
172
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(data.acquired.candidate) || typeof data.acquired.node !== "string" || typeof data.acquired.entry !== "string") return null;
|
|
173
|
+
state.acquired = data.acquired;
|
|
174
|
+
}
|
|
175
|
+
return state;
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function writeState(dir, state) {
|
|
181
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
182
|
+
const temporary = `${statePath(dir)}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
|
|
183
|
+
try {
|
|
184
|
+
(0, import_node_fs2.writeFileSync)(temporary, `${JSON.stringify(state, null, 2)}
|
|
185
|
+
`, { flag: "wx", mode: 384, flush: true });
|
|
186
|
+
(0, import_node_fs2.renameSync)(temporary, statePath(dir));
|
|
187
|
+
} finally {
|
|
188
|
+
(0, import_node_fs2.rmSync)(temporary, { force: true });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function wipeProductDir(dir) {
|
|
192
|
+
const payload = payloadDir(dir);
|
|
193
|
+
(0, import_node_fs2.rmSync)(tokensPath(dir), { force: true });
|
|
194
|
+
(0, import_node_fs2.rmSync)(statePath(dir), { force: true });
|
|
195
|
+
(0, import_node_fs2.rmSync)(payload, { force: true, recursive: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/acquisition.ts
|
|
199
|
+
function safePath(value) {
|
|
200
|
+
return typeof value === "string" && value.length > 0 && !/[\\:\0]/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
201
|
+
}
|
|
202
|
+
function argumentsValid(value) {
|
|
203
|
+
return Array.isArray(value) && value.every((arg) => typeof arg === "string" && !arg.includes("\0") && (!arg.includes("$") || arg === "$prefix" || arg === "$version"));
|
|
204
|
+
}
|
|
205
|
+
function readAcquisition(payload) {
|
|
206
|
+
const metadata = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(payload, "payload.json"), "utf8"));
|
|
207
|
+
if (metadata.acquisition === void 0) return null;
|
|
208
|
+
const a = metadata.acquisition;
|
|
209
|
+
if (!a || a.schema !== 1 || !/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(a.package) || !safePath(a.archive) || !a.archive.endsWith(".tgz") || !safePath(a.convergeEntry) || !safePath(a.rollbackEntry) || !safePath(a.runEntry) || !argumentsValid(a.convergeArgs) || !argumentsValid(a.rollbackArgs) || !argumentsValid(a.repairArgs) || !Array.isArray(a.platforms) || !a.platforms.length || a.platforms.some((p) => !["win32-x64", "win32-arm64", "darwin-arm64", "linux-x64", "linux-arm64"].includes(p))) {
|
|
210
|
+
throw new Error("invalid signed acquisition declaration");
|
|
211
|
+
}
|
|
212
|
+
return a;
|
|
213
|
+
}
|
|
214
|
+
function runtimeEnvironment(node, inherited, platform = process.platform) {
|
|
215
|
+
const env = { ...inherited };
|
|
216
|
+
let current = env.PATH ?? "";
|
|
217
|
+
if (platform === "win32") {
|
|
218
|
+
const keys = Object.keys(env).filter((key) => key.toLowerCase() === "path").sort();
|
|
219
|
+
current = (keys.length ? env[keys[0]] : "") ?? "";
|
|
220
|
+
for (const key of keys) delete env[key];
|
|
221
|
+
}
|
|
222
|
+
env.PATH = `${(0, import_node_path3.dirname)(node)}${platform === "win32" ? ";" : ":"}${current}`;
|
|
223
|
+
return env;
|
|
224
|
+
}
|
|
225
|
+
function lockInstallation(dir) {
|
|
226
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
227
|
+
const path = (0, import_node_path3.join)(dir, "installation.lock");
|
|
228
|
+
const owner = `${process.pid}:${(0, import_node_crypto3.randomUUID)()}`;
|
|
229
|
+
try {
|
|
230
|
+
(0, import_node_fs3.writeFileSync)(path, owner, { flag: "wx", mode: 384 });
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error.code !== "EEXIST") throw error;
|
|
233
|
+
const recovery = `${path}.recovery`;
|
|
234
|
+
try {
|
|
235
|
+
(0, import_node_fs3.writeFileSync)(recovery, owner, { flag: "wx", mode: 384, flush: true });
|
|
236
|
+
} catch {
|
|
237
|
+
throw new Error("installation lock recovery is already pending");
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const previous = (0, import_node_fs3.readFileSync)(path, "utf8");
|
|
241
|
+
const pid = Number(previous.split(":")[0]);
|
|
242
|
+
if (!Number.isInteger(pid) || pid <= 0) throw new Error("installation lock is malformed; recovery required");
|
|
243
|
+
try {
|
|
244
|
+
process.kill(pid, 0);
|
|
245
|
+
throw new Error("another installer is running");
|
|
246
|
+
} catch (probe) {
|
|
247
|
+
if (probe.code !== "ESRCH") throw probe;
|
|
248
|
+
}
|
|
249
|
+
(0, import_node_fs3.unlinkSync)(path);
|
|
250
|
+
(0, import_node_fs3.writeFileSync)(path, owner, { flag: "wx", mode: 384, flush: true });
|
|
251
|
+
} finally {
|
|
252
|
+
(0, import_node_fs3.unlinkSync)(recovery);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
let released = false;
|
|
256
|
+
return () => {
|
|
257
|
+
if (!released && (0, import_node_fs3.readFileSync)(path, "utf8") === owner) (0, import_node_fs3.unlinkSync)(path);
|
|
258
|
+
released = true;
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function pendingPath(dir) {
|
|
262
|
+
return (0, import_node_path3.join)(dir, "acquisition-pending.json");
|
|
263
|
+
}
|
|
264
|
+
function candidateRoot(dir, candidate) {
|
|
265
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(candidate)) throw new Error("invalid acquisition candidate");
|
|
266
|
+
return (0, import_node_path3.join)(dir, "candidates", candidate);
|
|
267
|
+
}
|
|
268
|
+
function packageRoot(prefix, acquisition) {
|
|
269
|
+
return (0, import_node_path3.join)(prefix, "node_modules", acquisition.package);
|
|
270
|
+
}
|
|
271
|
+
function entry(root, path) {
|
|
272
|
+
const actual = (0, import_node_fs3.realpathSync)((0, import_node_path3.join)(root, path));
|
|
273
|
+
const rel = (0, import_node_path3.relative)((0, import_node_fs3.realpathSync)(root), actual);
|
|
274
|
+
if (rel.startsWith("..") || (0, import_node_path3.isAbsolute)(rel)) throw new Error("acquired entry escapes package");
|
|
275
|
+
return actual;
|
|
276
|
+
}
|
|
277
|
+
function argv(node, root, file, args, prefix, version) {
|
|
278
|
+
return [node, entry(root, file), ...args.map((arg) => arg === "$prefix" ? prefix : arg === "$version" ? version : arg)];
|
|
279
|
+
}
|
|
280
|
+
function acquisitionRepairCommand(dir, version) {
|
|
281
|
+
const state = readState(dir);
|
|
282
|
+
if (!state?.acquired || state.version !== version) throw new Error("installed acquisition selection is missing");
|
|
283
|
+
const root = candidateRoot(dir, state.acquired.candidate);
|
|
284
|
+
const acquisition = readAcquisition((0, import_node_path3.join)(root, "payload"));
|
|
285
|
+
if (!acquisition) throw new Error("installed acquisition declaration is missing");
|
|
286
|
+
const prefix = (0, import_node_path3.join)(root, "prefix");
|
|
287
|
+
const productRoot = packageRoot(prefix, acquisition);
|
|
288
|
+
const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(productRoot, "package.json"), "utf8"));
|
|
289
|
+
if (pkg.name !== acquisition.package || pkg.version !== version) throw new Error("installed package identity does not match signed release");
|
|
290
|
+
return argv((0, import_node_fs3.realpathSync)(state.acquired.node), productRoot, acquisition.runEntry, acquisition.repairArgs, prefix, version);
|
|
291
|
+
}
|
|
292
|
+
async function recoverAcquisition(dir, run2, env) {
|
|
293
|
+
if (!(0, import_node_fs3.existsSync)(pendingPath(dir))) return;
|
|
294
|
+
const pending = JSON.parse((0, import_node_fs3.readFileSync)(pendingPath(dir), "utf8"));
|
|
295
|
+
if (pending.schema !== 1 || typeof pending.version !== "string" || typeof pending.node !== "string") throw new Error("invalid pending acquisition receipt");
|
|
296
|
+
const root = candidateRoot(dir, pending.candidate);
|
|
297
|
+
if (readState(dir)?.acquired?.candidate === pending.candidate) {
|
|
298
|
+
(0, import_node_fs3.unlinkSync)(pendingPath(dir));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const acquisition = readAcquisition((0, import_node_path3.join)(root, "payload"));
|
|
302
|
+
if (!acquisition) throw new Error("pending acquisition lost its signed declaration");
|
|
303
|
+
const prefix = (0, import_node_path3.join)(root, "prefix");
|
|
304
|
+
const command2 = argv(pending.node, packageRoot(prefix, acquisition), acquisition.rollbackEntry, acquisition.rollbackArgs, prefix, pending.version);
|
|
305
|
+
if (!await run2(command2, root, runtimeEnvironment(pending.node, env))) throw new Error("installation recovery is pending; product rollback did not finish");
|
|
306
|
+
if (pending.previous) writeState(dir, pending.previous);
|
|
307
|
+
else if ((0, import_node_fs3.existsSync)(statePath(dir))) (0, import_node_fs3.unlinkSync)(statePath(dir));
|
|
308
|
+
(0, import_node_fs3.unlinkSync)(pendingPath(dir));
|
|
309
|
+
}
|
|
310
|
+
async function installAcquisition(dir, candidate, version, acquisition, options) {
|
|
311
|
+
if (!acquisition.platforms.includes(`${process.platform}-${process.arch}`)) throw new Error("this product does not support this platform");
|
|
312
|
+
const root = candidateRoot(dir, candidate);
|
|
313
|
+
const payload = (0, import_node_path3.join)(root, "payload");
|
|
314
|
+
const prefix = (0, import_node_path3.join)(root, "prefix");
|
|
315
|
+
(0, import_node_fs3.mkdirSync)(prefix);
|
|
316
|
+
const runtime = await (options.runtime ?? acquireRuntime)(dir, options.fetchImpl);
|
|
317
|
+
const env = runtimeEnvironment(runtime.node, options.env);
|
|
318
|
+
const npmEnv = { ...env };
|
|
319
|
+
for (const key of Object.keys(npmEnv)) {
|
|
320
|
+
if (/^npm_config_/i.test(key) || /^(NPM_TOKEN|NODE_AUTH_TOKEN|MM_INSTALLER_TOKEN)$/i.test(key)) delete npmEnv[key];
|
|
321
|
+
}
|
|
322
|
+
const npmrc = (0, import_node_path3.join)(root, "public.npmrc");
|
|
323
|
+
const globalrc = (0, import_node_path3.join)(root, "global.npmrc");
|
|
324
|
+
(0, import_node_fs3.writeFileSync)(npmrc, "registry=https://registry.npmjs.org/\n");
|
|
325
|
+
(0, import_node_fs3.writeFileSync)(globalrc, "");
|
|
326
|
+
const install = [
|
|
327
|
+
runtime.node,
|
|
328
|
+
runtime.npm,
|
|
329
|
+
"install",
|
|
330
|
+
"--prefix",
|
|
331
|
+
prefix,
|
|
332
|
+
"--ignore-scripts",
|
|
333
|
+
"--no-audit",
|
|
334
|
+
"--no-fund",
|
|
335
|
+
"--userconfig",
|
|
336
|
+
npmrc,
|
|
337
|
+
"--globalconfig",
|
|
338
|
+
globalrc,
|
|
339
|
+
"--registry",
|
|
340
|
+
"https://registry.npmjs.org/",
|
|
341
|
+
(0, import_node_path3.resolve)(payload, acquisition.archive)
|
|
342
|
+
];
|
|
343
|
+
if (!await options.run(install, prefix, npmEnv)) throw new Error("local product archive installation failed");
|
|
344
|
+
const productRoot = packageRoot(prefix, acquisition);
|
|
345
|
+
const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(productRoot, "package.json"), "utf8"));
|
|
346
|
+
if (pkg.name !== acquisition.package || pkg.version !== version) throw new Error("acquired package identity does not match signed release");
|
|
347
|
+
const runEntry = entry(productRoot, acquisition.runEntry);
|
|
348
|
+
const convergence = argv(runtime.node, productRoot, acquisition.convergeEntry, acquisition.convergeArgs, prefix, version);
|
|
349
|
+
entry(productRoot, acquisition.rollbackEntry);
|
|
350
|
+
const pending = { schema: 1, candidate, version, node: runtime.node, previous: readState(dir) };
|
|
351
|
+
(0, import_node_fs3.writeFileSync)(pendingPath(dir), JSON.stringify(pending), { flag: "wx", mode: 384, flush: true });
|
|
352
|
+
try {
|
|
353
|
+
if (!await options.run(convergence, root, env)) throw new Error("product convergence did not finish");
|
|
354
|
+
(options.commit ?? writeState)(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), acquired: { candidate, node: runtime.node, entry: runEntry } });
|
|
355
|
+
} catch (error) {
|
|
356
|
+
await recoverAcquisition(dir, options.run, env);
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
(0, import_node_fs3.unlinkSync)(pendingPath(dir));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/index.ts
|
|
363
|
+
var import_node_child_process4 = require("node:child_process");
|
|
364
|
+
var import_node_fs11 = require("node:fs");
|
|
37
365
|
var import_node_os4 = require("node:os");
|
|
38
|
-
var
|
|
366
|
+
var import_node_path6 = require("node:path");
|
|
39
367
|
var import_node_url2 = require("node:url");
|
|
40
368
|
|
|
41
369
|
// ../face/src/face.ts
|
|
42
|
-
var
|
|
370
|
+
var import_node_fs4 = require("node:fs");
|
|
43
371
|
|
|
44
372
|
// ../face/src/products.ts
|
|
45
373
|
var PRODUCTS = Object.freeze({
|
|
@@ -158,7 +486,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
158
486
|
const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
|
|
159
487
|
if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
|
|
160
488
|
try {
|
|
161
|
-
(0,
|
|
489
|
+
(0, import_node_fs4.writeSync)(progressFd, `${JSON.stringify(record)}
|
|
162
490
|
`);
|
|
163
491
|
return true;
|
|
164
492
|
} catch {
|
|
@@ -225,7 +553,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
225
553
|
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
226
554
|
|
|
227
555
|
// ../face/src/spinner.ts
|
|
228
|
-
var
|
|
556
|
+
var import_node_fs5 = require("node:fs");
|
|
229
557
|
var import_node_worker_threads = require("node:worker_threads");
|
|
230
558
|
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
231
559
|
var WORKER_SOURCE = `
|
|
@@ -263,8 +591,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
263
591
|
const write = (text) => {
|
|
264
592
|
if (stream) stream.write(text);
|
|
265
593
|
else {
|
|
266
|
-
(0,
|
|
267
|
-
if (transcriptPath) (0,
|
|
594
|
+
(0, import_node_fs5.writeSync)(2, text);
|
|
595
|
+
if (transcriptPath) (0, import_node_fs5.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
|
|
268
596
|
`);
|
|
269
597
|
}
|
|
270
598
|
};
|
|
@@ -337,10 +665,10 @@ var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
|
337
665
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
338
666
|
|
|
339
667
|
// ../face/src/run.ts
|
|
340
|
-
var
|
|
668
|
+
var import_node_fs7 = require("node:fs");
|
|
341
669
|
|
|
342
670
|
// ../face/src/outcome.ts
|
|
343
|
-
var
|
|
671
|
+
var import_node_fs6 = require("node:fs");
|
|
344
672
|
var counts = ["total", "updated", "failed"];
|
|
345
673
|
var strings = ["version", "retry", "detail", "logPath"];
|
|
346
674
|
var flags = ["dryRun", "installed", "deferred", "operationFailed"];
|
|
@@ -362,12 +690,12 @@ function validateInstallerOutcome(value) {
|
|
|
362
690
|
return { ...facts };
|
|
363
691
|
}
|
|
364
692
|
function writeInstallerOutcome(path, value) {
|
|
365
|
-
(0,
|
|
693
|
+
(0, import_node_fs6.writeFileSync)(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
|
|
366
694
|
}
|
|
367
695
|
function readInstallerOutcome(path) {
|
|
368
696
|
let text;
|
|
369
697
|
try {
|
|
370
|
-
text = (0,
|
|
698
|
+
text = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
371
699
|
} catch (error) {
|
|
372
700
|
if (error.code === "ENOENT") return void 0;
|
|
373
701
|
throw error;
|
|
@@ -459,7 +787,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
459
787
|
if (!text) return;
|
|
460
788
|
write(text, channel);
|
|
461
789
|
if (env.MM_FACE_TRANSCRIPT) {
|
|
462
|
-
(0,
|
|
790
|
+
(0, import_node_fs7.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
|
|
463
791
|
`, "utf8");
|
|
464
792
|
}
|
|
465
793
|
};
|
|
@@ -594,17 +922,17 @@ function createInstallerRun(value, options = {}) {
|
|
|
594
922
|
}
|
|
595
923
|
|
|
596
924
|
// src/autoupdate.ts
|
|
597
|
-
var
|
|
598
|
-
var
|
|
599
|
-
var
|
|
600
|
-
var
|
|
925
|
+
var import_node_child_process2 = require("node:child_process");
|
|
926
|
+
var import_node_fs8 = require("node:fs");
|
|
927
|
+
var import_node_os2 = require("node:os");
|
|
928
|
+
var import_node_path4 = require("node:path");
|
|
601
929
|
function schedulePlatform(override) {
|
|
602
930
|
const platform = override ?? process.platform;
|
|
603
931
|
if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
|
|
604
932
|
return null;
|
|
605
933
|
}
|
|
606
|
-
function defaultExec(
|
|
607
|
-
const result = (0,
|
|
934
|
+
function defaultExec(command2, args) {
|
|
935
|
+
const result = (0, import_node_child_process2.spawnSync)(command2, args, { encoding: "utf8", windowsHide: true });
|
|
608
936
|
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
609
937
|
return {
|
|
610
938
|
code: typeof result.status === "number" ? result.status : 1,
|
|
@@ -614,8 +942,8 @@ function defaultExec(command, args) {
|
|
|
614
942
|
}
|
|
615
943
|
function homeOf(options) {
|
|
616
944
|
if (options.homeDir) return options.homeDir;
|
|
617
|
-
if (process.platform === "win32") return process.env.USERPROFILE ?? (0,
|
|
618
|
-
return process.env.HOME ?? (0,
|
|
945
|
+
if (process.platform === "win32") return process.env.USERPROFILE ?? (0, import_node_os2.tmpdir)();
|
|
946
|
+
return process.env.HOME ?? (0, import_node_os2.tmpdir)();
|
|
619
947
|
}
|
|
620
948
|
function scheduleName(config) {
|
|
621
949
|
return `${config.binName} autoupdate`;
|
|
@@ -626,13 +954,13 @@ function scheduleLabel(config) {
|
|
|
626
954
|
function quoteWindows(arg) {
|
|
627
955
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
628
956
|
}
|
|
629
|
-
function enableSchedule(config,
|
|
957
|
+
function enableSchedule(config, command2, options = {}) {
|
|
630
958
|
const platform = schedulePlatform(options.platform);
|
|
631
959
|
if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
|
|
632
960
|
const exec = options.exec ?? defaultExec;
|
|
633
961
|
const home = homeOf(options);
|
|
634
962
|
if (platform === "win32") {
|
|
635
|
-
const taskLine =
|
|
963
|
+
const taskLine = command2.map(quoteWindows).join(" ");
|
|
636
964
|
const result2 = exec("schtasks", ["/Create", "/TN", scheduleName(config), "/TR", taskLine, "/SC", "HOURLY", "/IT", "/F"]);
|
|
637
965
|
if (result2.code !== 0) {
|
|
638
966
|
throw new Error(`could not turn autoupdate on: ${firstLine(result2.stdout || result2.stderr) || `exit ${result2.code}`}`);
|
|
@@ -641,10 +969,10 @@ function enableSchedule(config, command, options = {}) {
|
|
|
641
969
|
}
|
|
642
970
|
if (platform === "darwin") {
|
|
643
971
|
const label2 = scheduleLabel(config);
|
|
644
|
-
const dir2 = (0,
|
|
645
|
-
(0,
|
|
646
|
-
const plist = (0,
|
|
647
|
-
(0,
|
|
972
|
+
const dir2 = (0, import_node_path4.join)(home, "Library", "LaunchAgents");
|
|
973
|
+
(0, import_node_fs8.mkdirSync)(dir2, { recursive: true });
|
|
974
|
+
const plist = (0, import_node_path4.join)(dir2, `${label2}.plist`);
|
|
975
|
+
(0, import_node_fs8.writeFileSync)(plist, darwinPlist(label2, command2));
|
|
648
976
|
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
649
977
|
const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
|
|
650
978
|
if (result2.code !== 0) {
|
|
@@ -653,10 +981,10 @@ function enableSchedule(config, command, options = {}) {
|
|
|
653
981
|
return;
|
|
654
982
|
}
|
|
655
983
|
const label = scheduleLabel(config);
|
|
656
|
-
const dir = (0,
|
|
657
|
-
(0,
|
|
658
|
-
(0,
|
|
659
|
-
(0,
|
|
984
|
+
const dir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
|
|
985
|
+
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
986
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path4.join)(dir, `${label}.service`), linuxService(command2));
|
|
987
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path4.join)(dir, `${label}.timer`), linuxTimer(label));
|
|
660
988
|
const reload = exec("systemctl", ["--user", "daemon-reload"]);
|
|
661
989
|
if (reload.code !== 0) {
|
|
662
990
|
throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
|
|
@@ -681,14 +1009,14 @@ function disableSchedule(config, options = {}) {
|
|
|
681
1009
|
if (platform === "darwin") {
|
|
682
1010
|
const label2 = scheduleLabel(config);
|
|
683
1011
|
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
684
|
-
(0,
|
|
1012
|
+
(0, import_node_fs8.rmSync)((0, import_node_path4.join)(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
|
|
685
1013
|
return;
|
|
686
1014
|
}
|
|
687
1015
|
const label = scheduleLabel(config);
|
|
688
|
-
const dir = (0,
|
|
1016
|
+
const dir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
|
|
689
1017
|
exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
|
|
690
|
-
(0,
|
|
691
|
-
(0,
|
|
1018
|
+
(0, import_node_fs8.rmSync)((0, import_node_path4.join)(dir, `${label}.service`), { force: true });
|
|
1019
|
+
(0, import_node_fs8.rmSync)((0, import_node_path4.join)(dir, `${label}.timer`), { force: true });
|
|
692
1020
|
}
|
|
693
1021
|
function querySchedule(config, options = {}) {
|
|
694
1022
|
const platform = schedulePlatform(options.platform);
|
|
@@ -706,20 +1034,20 @@ function querySchedule(config, options = {}) {
|
|
|
706
1034
|
return state2;
|
|
707
1035
|
}
|
|
708
1036
|
if (platform === "darwin") {
|
|
709
|
-
const plist = (0,
|
|
710
|
-
if (!(0,
|
|
1037
|
+
const plist = (0, import_node_path4.join)(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
|
|
1038
|
+
if (!(0, import_node_fs8.existsSync)(plist)) return { supported: true, enabled: false };
|
|
711
1039
|
return { supported: true, enabled: true, cadence: "hourly" };
|
|
712
1040
|
}
|
|
713
|
-
const timer = (0,
|
|
714
|
-
if (!(0,
|
|
1041
|
+
const timer = (0, import_node_path4.join)(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
|
|
1042
|
+
if (!(0, import_node_fs8.existsSync)(timer)) return { supported: true, enabled: false };
|
|
715
1043
|
const state = { supported: true, enabled: true, cadence: "hourly" };
|
|
716
1044
|
const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
|
|
717
1045
|
const stamp = (shown.stdout ?? "").trim();
|
|
718
1046
|
if (shown.code === 0 && stamp && stamp !== "n/a") state.lastRun = stamp;
|
|
719
1047
|
return state;
|
|
720
1048
|
}
|
|
721
|
-
function darwinPlist(label,
|
|
722
|
-
const args =
|
|
1049
|
+
function darwinPlist(label, command2) {
|
|
1050
|
+
const args = command2.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
|
|
723
1051
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
724
1052
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
725
1053
|
<plist version="1.0">
|
|
@@ -736,8 +1064,8 @@ ${args}
|
|
|
736
1064
|
</plist>
|
|
737
1065
|
`;
|
|
738
1066
|
}
|
|
739
|
-
function linuxService(
|
|
740
|
-
const line =
|
|
1067
|
+
function linuxService(command2) {
|
|
1068
|
+
const line = command2.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
|
|
741
1069
|
return `[Unit]
|
|
742
1070
|
Description=${"Hourly update check"}
|
|
743
1071
|
[Service]
|
|
@@ -769,7 +1097,7 @@ function firstLine(text) {
|
|
|
769
1097
|
}
|
|
770
1098
|
|
|
771
1099
|
// src/config.ts
|
|
772
|
-
var
|
|
1100
|
+
var import_node_fs9 = require("node:fs");
|
|
773
1101
|
var import_node_sea = require("node:sea");
|
|
774
1102
|
|
|
775
1103
|
// src/module-url.ts
|
|
@@ -792,10 +1120,10 @@ function loadProductConfig(options = {}) {
|
|
|
792
1120
|
const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
|
|
793
1121
|
const devFallback = new URL("../config/product.template.json", moduleUrl());
|
|
794
1122
|
if (explicit) {
|
|
795
|
-
return parseProductConfig((0,
|
|
1123
|
+
return parseProductConfig((0, import_node_fs9.readFileSync)(explicit, "utf8"));
|
|
796
1124
|
}
|
|
797
1125
|
try {
|
|
798
|
-
return parseProductConfig((0,
|
|
1126
|
+
return parseProductConfig((0, import_node_fs9.readFileSync)(devFallback, "utf8"));
|
|
799
1127
|
} catch {
|
|
800
1128
|
}
|
|
801
1129
|
try {
|
|
@@ -850,14 +1178,14 @@ function field(record, key) {
|
|
|
850
1178
|
}
|
|
851
1179
|
|
|
852
1180
|
// src/login-github.ts
|
|
853
|
-
var
|
|
854
|
-
var realSleep = (ms) => new Promise((
|
|
1181
|
+
var import_node_child_process3 = require("node:child_process");
|
|
1182
|
+
var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
855
1183
|
function openBrowser(url) {
|
|
856
1184
|
if (process.env.LAUNCHER_NO_OPEN === "1") return;
|
|
857
1185
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
|
|
858
1186
|
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
859
1187
|
try {
|
|
860
|
-
const child = (0,
|
|
1188
|
+
const child = (0, import_node_child_process3.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
861
1189
|
child.on("error", () => {
|
|
862
1190
|
});
|
|
863
1191
|
child.unref();
|
|
@@ -971,7 +1299,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
|
|
|
971
1299
|
}
|
|
972
1300
|
|
|
973
1301
|
// src/login-google.ts
|
|
974
|
-
var
|
|
1302
|
+
var import_node_crypto4 = require("node:crypto");
|
|
975
1303
|
var import_node_http = require("node:http");
|
|
976
1304
|
var b64url = (bytes) => bytes.toString("base64url");
|
|
977
1305
|
async function loginGoogle(options) {
|
|
@@ -979,7 +1307,7 @@ async function loginGoogle(options) {
|
|
|
979
1307
|
const server = options.host.replace(/\/+$/, "");
|
|
980
1308
|
const timeoutMs = options.timeoutMs ?? 5 * 6e4;
|
|
981
1309
|
const listener = (0, import_node_http.createServer)();
|
|
982
|
-
await new Promise((
|
|
1310
|
+
await new Promise((resolve3) => listener.listen(0, "127.0.0.1", resolve3));
|
|
983
1311
|
const port = listener.address().port;
|
|
984
1312
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
985
1313
|
try {
|
|
@@ -993,9 +1321,9 @@ async function loginGoogle(options) {
|
|
|
993
1321
|
if (typeof clientId !== "string" || !clientId) {
|
|
994
1322
|
throw new Error("the sign-in server returned a malformed registration");
|
|
995
1323
|
}
|
|
996
|
-
const verifier = b64url((0,
|
|
997
|
-
const challenge = b64url((0,
|
|
998
|
-
const state = b64url((0,
|
|
1324
|
+
const verifier = b64url((0, import_node_crypto4.randomBytes)(32));
|
|
1325
|
+
const challenge = b64url((0, import_node_crypto4.createHash)("sha256").update(verifier).digest());
|
|
1326
|
+
const state = b64url((0, import_node_crypto4.randomBytes)(16));
|
|
999
1327
|
const authorize = new URL(`${server}/oauth/authorize`);
|
|
1000
1328
|
authorize.search = new URLSearchParams({
|
|
1001
1329
|
response_type: "code",
|
|
@@ -1006,7 +1334,7 @@ async function loginGoogle(options) {
|
|
|
1006
1334
|
code_challenge: challenge,
|
|
1007
1335
|
code_challenge_method: "S256"
|
|
1008
1336
|
}).toString();
|
|
1009
|
-
const code = await new Promise((
|
|
1337
|
+
const code = await new Promise((resolve3, reject) => {
|
|
1010
1338
|
const timer = setTimeout(() => reject(new Error("sign-in timed out \u2014 run login again.")), timeoutMs);
|
|
1011
1339
|
listener.on("request", (req, res) => {
|
|
1012
1340
|
const url = new URL(req.url ?? "/", redirectUri);
|
|
@@ -1024,7 +1352,7 @@ async function loginGoogle(options) {
|
|
|
1024
1352
|
}
|
|
1025
1353
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("Signed in.", "You can close this tab and go back to the terminal."));
|
|
1026
1354
|
clearTimeout(timer);
|
|
1027
|
-
|
|
1355
|
+
resolve3(received);
|
|
1028
1356
|
});
|
|
1029
1357
|
const print = options.print ?? ((line) => process.stderr.write(`${line}
|
|
1030
1358
|
`));
|
|
@@ -1084,10 +1412,10 @@ function page(title, body) {
|
|
|
1084
1412
|
}
|
|
1085
1413
|
|
|
1086
1414
|
// src/payload.ts
|
|
1087
|
-
var
|
|
1088
|
-
var
|
|
1089
|
-
var
|
|
1090
|
-
var
|
|
1415
|
+
var import_node_crypto5 = require("node:crypto");
|
|
1416
|
+
var import_node_fs10 = require("node:fs");
|
|
1417
|
+
var import_node_os3 = require("node:os");
|
|
1418
|
+
var import_node_path5 = require("node:path");
|
|
1091
1419
|
|
|
1092
1420
|
// src/canonical.ts
|
|
1093
1421
|
function canonicalJson(value) {
|
|
@@ -1104,7 +1432,7 @@ function encode(value) {
|
|
|
1104
1432
|
return JSON.stringify(value);
|
|
1105
1433
|
}
|
|
1106
1434
|
if (Array.isArray(value)) {
|
|
1107
|
-
return `[${value.map((
|
|
1435
|
+
return `[${value.map((entry2) => encode(entry2)).join(",")}]`;
|
|
1108
1436
|
}
|
|
1109
1437
|
if (typeof value === "object") {
|
|
1110
1438
|
const record = value;
|
|
@@ -1139,7 +1467,7 @@ function canonicalManifestBytes(manifest) {
|
|
|
1139
1467
|
}
|
|
1140
1468
|
function ed25519PublicKey(config) {
|
|
1141
1469
|
const raw = publicKeyBytes(config);
|
|
1142
|
-
return (0,
|
|
1470
|
+
return (0, import_node_crypto5.createPublicKey)({
|
|
1143
1471
|
key: { kty: "OKP", crv: "Ed25519", x: raw.toString("base64url") },
|
|
1144
1472
|
format: "jwk"
|
|
1145
1473
|
});
|
|
@@ -1148,14 +1476,14 @@ function verifyManifest(manifest, signature, config) {
|
|
|
1148
1476
|
try {
|
|
1149
1477
|
const signatureBytes = Buffer.from(signature, "base64");
|
|
1150
1478
|
if (signatureBytes.length === 0) return false;
|
|
1151
|
-
return (0,
|
|
1479
|
+
return (0, import_node_crypto5.verify)(null, canonicalManifestBytes(manifest), ed25519PublicKey(config), signatureBytes);
|
|
1152
1480
|
} catch {
|
|
1153
1481
|
return false;
|
|
1154
1482
|
}
|
|
1155
1483
|
}
|
|
1156
|
-
function verifyFileBytes(
|
|
1157
|
-
if (
|
|
1158
|
-
return (0,
|
|
1484
|
+
function verifyFileBytes(entry2, bytes) {
|
|
1485
|
+
if (entry2.size !== bytes.length) return false;
|
|
1486
|
+
return (0, import_node_crypto5.createHash)("sha256").update(bytes).digest("hex") === entry2.sha256.toLowerCase();
|
|
1159
1487
|
}
|
|
1160
1488
|
async function readErrorCode(response) {
|
|
1161
1489
|
try {
|
|
@@ -1186,8 +1514,8 @@ function parseManifest(json) {
|
|
|
1186
1514
|
if (typeof record.signature !== "string" || !record.signature) {
|
|
1187
1515
|
throw new Error("the release manifest is unsigned");
|
|
1188
1516
|
}
|
|
1189
|
-
const files = record.files.map((
|
|
1190
|
-
const file =
|
|
1517
|
+
const files = record.files.map((entry2) => {
|
|
1518
|
+
const file = entry2;
|
|
1191
1519
|
if (typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.size !== "number") {
|
|
1192
1520
|
throw new Error("the release manifest lists a malformed file");
|
|
1193
1521
|
}
|
|
@@ -1227,101 +1555,31 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
|
|
|
1227
1555
|
}
|
|
1228
1556
|
async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
|
|
1229
1557
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1230
|
-
const staging = (0,
|
|
1231
|
-
(0,
|
|
1558
|
+
const staging = (0, import_node_path5.join)((0, import_node_os3.tmpdir)(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
|
|
1559
|
+
(0, import_node_fs10.mkdirSync)(staging, { recursive: true });
|
|
1232
1560
|
try {
|
|
1233
|
-
for (const
|
|
1234
|
-
const bytes = await fetchFileBytes(config, accessToken,
|
|
1235
|
-
if (!verifyFileBytes(
|
|
1236
|
-
throw new Error(`file ${
|
|
1561
|
+
for (const entry2 of manifest.files) {
|
|
1562
|
+
const bytes = await fetchFileBytes(config, accessToken, entry2.path, fetchImpl);
|
|
1563
|
+
if (!verifyFileBytes(entry2, bytes)) {
|
|
1564
|
+
throw new Error(`file ${entry2.path} failed its checksum \u2014 refusing to install anything.`);
|
|
1237
1565
|
}
|
|
1238
|
-
const dest = (0,
|
|
1239
|
-
(0,
|
|
1240
|
-
(0,
|
|
1566
|
+
const dest = (0, import_node_path5.join)(staging, entry2.path);
|
|
1567
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path5.dirname)(dest), { recursive: true });
|
|
1568
|
+
(0, import_node_fs10.writeFileSync)(dest, bytes);
|
|
1241
1569
|
}
|
|
1242
|
-
const target = (0,
|
|
1243
|
-
(0,
|
|
1244
|
-
(0,
|
|
1245
|
-
(0,
|
|
1570
|
+
const target = (0, import_node_path5.join)(dir, "payload");
|
|
1571
|
+
(0, import_node_fs10.mkdirSync)(dir, { recursive: true });
|
|
1572
|
+
(0, import_node_fs10.rmSync)(target, { force: true, recursive: true });
|
|
1573
|
+
(0, import_node_fs10.renameSync)(staging, target);
|
|
1246
1574
|
} catch (error) {
|
|
1247
|
-
(0,
|
|
1575
|
+
(0, import_node_fs10.rmSync)(staging, { force: true, recursive: true });
|
|
1248
1576
|
throw error;
|
|
1249
1577
|
}
|
|
1250
1578
|
return manifest.version;
|
|
1251
1579
|
}
|
|
1252
1580
|
|
|
1253
|
-
// src/store.ts
|
|
1254
|
-
var import_node_fs8 = require("node:fs");
|
|
1255
|
-
var import_node_os3 = require("node:os");
|
|
1256
|
-
var import_node_path3 = require("node:path");
|
|
1257
|
-
function defaultProductDir(product) {
|
|
1258
|
-
if (process.platform === "win32") {
|
|
1259
|
-
const base = process.env.LOCALAPPDATA ?? (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "launcher-fallback");
|
|
1260
|
-
return (0, import_node_path3.join)(base, product);
|
|
1261
|
-
}
|
|
1262
|
-
const home = process.env.HOME ?? (0, import_node_os3.tmpdir)();
|
|
1263
|
-
return (0, import_node_path3.join)(home, `.${product}`);
|
|
1264
|
-
}
|
|
1265
|
-
function resolveProductDir(product, explicit) {
|
|
1266
|
-
return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
|
|
1267
|
-
}
|
|
1268
|
-
function tokensPath(dir) {
|
|
1269
|
-
return (0, import_node_path3.join)(dir, "tokens.json");
|
|
1270
|
-
}
|
|
1271
|
-
function statePath(dir) {
|
|
1272
|
-
return (0, import_node_path3.join)(dir, "state.json");
|
|
1273
|
-
}
|
|
1274
|
-
function payloadDir(dir) {
|
|
1275
|
-
return (0, import_node_path3.join)(dir, "payload");
|
|
1276
|
-
}
|
|
1277
|
-
function readTokens(dir) {
|
|
1278
|
-
try {
|
|
1279
|
-
const data = JSON.parse((0, import_node_fs8.readFileSync)(tokensPath(dir), "utf8"));
|
|
1280
|
-
if (typeof data.accessToken !== "string" || !data.accessToken) return null;
|
|
1281
|
-
if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
|
|
1282
|
-
const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
|
|
1283
|
-
if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
|
|
1284
|
-
if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
|
|
1285
|
-
return tokens;
|
|
1286
|
-
} catch {
|
|
1287
|
-
return null;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
function writeTokens(dir, tokens) {
|
|
1291
|
-
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
1292
|
-
try {
|
|
1293
|
-
(0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
1294
|
-
`, { mode: 384 });
|
|
1295
|
-
} catch {
|
|
1296
|
-
(0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
1297
|
-
`);
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
function clearTokens(dir) {
|
|
1301
|
-
(0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
|
|
1302
|
-
}
|
|
1303
|
-
function readState(dir) {
|
|
1304
|
-
try {
|
|
1305
|
-
const data = JSON.parse((0, import_node_fs8.readFileSync)(statePath(dir), "utf8"));
|
|
1306
|
-
if (typeof data.version !== "string" || !data.version) return null;
|
|
1307
|
-
return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
|
|
1308
|
-
} catch {
|
|
1309
|
-
return null;
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
function writeState(dir, state) {
|
|
1313
|
-
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
1314
|
-
(0, import_node_fs8.writeFileSync)(statePath(dir), `${JSON.stringify(state, null, 2)}
|
|
1315
|
-
`);
|
|
1316
|
-
}
|
|
1317
|
-
function wipeProductDir(dir) {
|
|
1318
|
-
(0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
|
|
1319
|
-
(0, import_node_fs8.rmSync)(statePath(dir), { force: true });
|
|
1320
|
-
(0, import_node_fs8.rmSync)(payloadDir(dir), { force: true, recursive: true });
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
1581
|
// src/index.ts
|
|
1324
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1582
|
+
var LAUNCHER_VERSION = true ? "0.1.13" : readVersionFromPackage();
|
|
1325
1583
|
function defaultPrint(message) {
|
|
1326
1584
|
process.stdout.write(`${message}
|
|
1327
1585
|
`);
|
|
@@ -1333,30 +1591,37 @@ function defaultPrintErr(message) {
|
|
|
1333
1591
|
async function run(rawOptions = {}) {
|
|
1334
1592
|
const print = rawOptions.print ?? defaultPrint;
|
|
1335
1593
|
const printErr = rawOptions.printErr ?? defaultPrintErr;
|
|
1336
|
-
const
|
|
1337
|
-
const runAt =
|
|
1338
|
-
if (runAt >= 0) return runFile(
|
|
1594
|
+
const argv2 = rawOptions.argv ?? process.argv.slice(2);
|
|
1595
|
+
const runAt = argv2.indexOf("--run");
|
|
1596
|
+
if (runAt >= 0) return runFile(argv2[runAt + 1], argv2.slice(runAt + 2), printErr);
|
|
1339
1597
|
let config;
|
|
1340
1598
|
try {
|
|
1341
|
-
config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(
|
|
1599
|
+
config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv2, "--config") });
|
|
1342
1600
|
} catch (error) {
|
|
1343
1601
|
print(`cannot start: ${error.message}`);
|
|
1344
1602
|
return 2;
|
|
1345
1603
|
}
|
|
1346
|
-
const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(
|
|
1347
|
-
const positional =
|
|
1348
|
-
if (
|
|
1604
|
+
const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(argv2, "--dir"));
|
|
1605
|
+
const positional = argv2.filter((arg) => !arg.startsWith("-"));
|
|
1606
|
+
if (argv2.includes("--version") || argv2.includes("-v")) {
|
|
1349
1607
|
print(`${config.binName} launcher ${LAUNCHER_VERSION}`);
|
|
1350
1608
|
return 0;
|
|
1351
1609
|
}
|
|
1352
|
-
if (
|
|
1610
|
+
if (argv2.includes("--help") || argv2.includes("-h") || positional.length === 0) {
|
|
1353
1611
|
printUsage(config, print);
|
|
1354
1612
|
return positional.length === 0 ? 2 : 0;
|
|
1355
1613
|
}
|
|
1356
|
-
const
|
|
1614
|
+
const command2 = positional[0];
|
|
1615
|
+
let unlock;
|
|
1357
1616
|
try {
|
|
1358
|
-
|
|
1617
|
+
unlock = lockInstallation(dir);
|
|
1618
|
+
await recoverAcquisition(dir, async (command3, cwd, env) => {
|
|
1619
|
+
const result = (rawOptions.runEntry ?? defaultRunEntry)(command3, cwd, env);
|
|
1620
|
+
return result.ok && (result.code === void 0 || result.code === 0);
|
|
1621
|
+
}, payloadEnv(dir) ?? process.env);
|
|
1622
|
+
switch (command2) {
|
|
1359
1623
|
case "login":
|
|
1624
|
+
unlock();
|
|
1360
1625
|
await doLogin(config, dir, rawOptions, print);
|
|
1361
1626
|
return 0;
|
|
1362
1627
|
case "logout":
|
|
@@ -1368,11 +1633,11 @@ async function run(rawOptions = {}) {
|
|
|
1368
1633
|
case "update":
|
|
1369
1634
|
return await doUpdate(config, dir, rawOptions, print);
|
|
1370
1635
|
case "doctor":
|
|
1371
|
-
return doDoctor(config, dir, rawOptions, print);
|
|
1636
|
+
return doDoctor(config, dir, rawOptions, print, unlock);
|
|
1372
1637
|
case "autoupdate":
|
|
1373
1638
|
return doAutoupdate(config, rawOptions, positional.slice(1), print);
|
|
1374
1639
|
default:
|
|
1375
|
-
return doForward(config, dir, rawOptions,
|
|
1640
|
+
return doForward(config, dir, rawOptions, command2, argv2, print, unlock);
|
|
1376
1641
|
}
|
|
1377
1642
|
} catch (error) {
|
|
1378
1643
|
if (error instanceof NeedsLoginError) {
|
|
@@ -1381,12 +1646,14 @@ async function run(rawOptions = {}) {
|
|
|
1381
1646
|
}
|
|
1382
1647
|
print(`failed: ${error.message}`);
|
|
1383
1648
|
return 1;
|
|
1649
|
+
} finally {
|
|
1650
|
+
unlock?.();
|
|
1384
1651
|
}
|
|
1385
1652
|
}
|
|
1386
|
-
function flagValue(
|
|
1387
|
-
const index =
|
|
1653
|
+
function flagValue(argv2, flag) {
|
|
1654
|
+
const index = argv2.indexOf(flag);
|
|
1388
1655
|
if (index < 0) return void 0;
|
|
1389
|
-
const value =
|
|
1656
|
+
const value = argv2[index + 1];
|
|
1390
1657
|
return value && !value.startsWith("-") ? value : void 0;
|
|
1391
1658
|
}
|
|
1392
1659
|
function printUsage(config, print) {
|
|
@@ -1400,8 +1667,8 @@ async function runFile(file, args, printErr) {
|
|
|
1400
1667
|
printErr("launcher --run needs a file to run.");
|
|
1401
1668
|
return 1;
|
|
1402
1669
|
}
|
|
1403
|
-
const abs = (0,
|
|
1404
|
-
if (!(0,
|
|
1670
|
+
const abs = (0, import_node_path6.resolve)(process.cwd(), file);
|
|
1671
|
+
if (!(0, import_node_fs11.existsSync)(abs)) {
|
|
1405
1672
|
printErr(`cannot run ${file}: no such file.`);
|
|
1406
1673
|
return 1;
|
|
1407
1674
|
}
|
|
@@ -1494,14 +1761,14 @@ async function fetchAccessTokenOrLogin(config, dir, options, print, installer) {
|
|
|
1494
1761
|
}
|
|
1495
1762
|
function readPayloadArgv(dir, key) {
|
|
1496
1763
|
try {
|
|
1497
|
-
const parsed = JSON.parse((0,
|
|
1498
|
-
const
|
|
1499
|
-
if (typeof
|
|
1500
|
-
const parts =
|
|
1764
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(payloadDir(dir), "payload.json"), "utf8"));
|
|
1765
|
+
const entry2 = parsed[key];
|
|
1766
|
+
if (typeof entry2 === "string") {
|
|
1767
|
+
const parts = entry2.trim().split(/\s+/).filter(Boolean);
|
|
1501
1768
|
return parts.length > 0 ? parts : null;
|
|
1502
1769
|
}
|
|
1503
|
-
if (Array.isArray(
|
|
1504
|
-
return
|
|
1770
|
+
if (Array.isArray(entry2) && entry2.every((part) => typeof part === "string" && part.length > 0)) {
|
|
1771
|
+
return entry2;
|
|
1505
1772
|
}
|
|
1506
1773
|
return null;
|
|
1507
1774
|
} catch {
|
|
@@ -1516,7 +1783,7 @@ function readPayloadRun(dir) {
|
|
|
1516
1783
|
}
|
|
1517
1784
|
function readPayloadVerbs(dir) {
|
|
1518
1785
|
try {
|
|
1519
|
-
const parsed = JSON.parse((0,
|
|
1786
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(payloadDir(dir), "payload.json"), "utf8"));
|
|
1520
1787
|
const verbs = parsed.verbs;
|
|
1521
1788
|
if (verbs === "*") return "*";
|
|
1522
1789
|
if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
|
|
@@ -1527,13 +1794,13 @@ function readPayloadVerbs(dir) {
|
|
|
1527
1794
|
return null;
|
|
1528
1795
|
}
|
|
1529
1796
|
}
|
|
1530
|
-
function resolveEntry(
|
|
1531
|
-
return
|
|
1797
|
+
function resolveEntry(entry2) {
|
|
1798
|
+
return entry2[0] === "$self" ? [process.execPath, ...entry2.slice(1)] : entry2;
|
|
1532
1799
|
}
|
|
1533
|
-
function needsShell(
|
|
1800
|
+
function needsShell(command2) {
|
|
1534
1801
|
if (process.platform !== "win32") return false;
|
|
1535
|
-
if (/\.(cmd|bat)$/i.test(
|
|
1536
|
-
return !(0,
|
|
1802
|
+
if (/\.(cmd|bat)$/i.test(command2)) return true;
|
|
1803
|
+
return !(0, import_node_fs11.existsSync)(command2);
|
|
1537
1804
|
}
|
|
1538
1805
|
function quoteForShell(arg) {
|
|
1539
1806
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
@@ -1560,11 +1827,11 @@ function parseProgress(raw) {
|
|
|
1560
1827
|
}
|
|
1561
1828
|
return progress;
|
|
1562
1829
|
}
|
|
1563
|
-
function defaultRunEntry(
|
|
1564
|
-
const [
|
|
1565
|
-
const shell = needsShell(
|
|
1566
|
-
const commandLine = shell ? [
|
|
1567
|
-
const spawnEntry = (progress2) => (0,
|
|
1830
|
+
function defaultRunEntry(entry2, cwd, env) {
|
|
1831
|
+
const [command2, ...args] = entry2;
|
|
1832
|
+
const shell = needsShell(command2);
|
|
1833
|
+
const commandLine = shell ? [command2, ...args].map(quoteForShell).join(" ") : command2;
|
|
1834
|
+
const spawnEntry = (progress2) => (0, import_node_child_process4.spawnSync)(commandLine, shell ? [] : args, {
|
|
1568
1835
|
cwd,
|
|
1569
1836
|
stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
|
|
1570
1837
|
shell,
|
|
@@ -1622,11 +1889,41 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
1622
1889
|
version = manifest.version;
|
|
1623
1890
|
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
1624
1891
|
const current = readState(dir);
|
|
1625
|
-
const unchanged = update && current?.version === version && (0,
|
|
1892
|
+
const unchanged = update && current?.version === version && (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
1893
|
+
if (unchanged && current.acquired) {
|
|
1894
|
+
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
1895
|
+
return await finishLastMile(config, dir, options, installer, version, true, acquisitionRepairCommand(dir, version));
|
|
1896
|
+
}
|
|
1626
1897
|
if (!unchanged) {
|
|
1627
1898
|
installer.phase("download", { state: "running" });
|
|
1628
1899
|
started = Date.now();
|
|
1629
|
-
|
|
1900
|
+
const candidate = (0, import_node_crypto6.randomUUID)();
|
|
1901
|
+
const candidateDir = (0, import_node_path6.join)(dir, "candidates", candidate);
|
|
1902
|
+
await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl });
|
|
1903
|
+
const acquisition = (0, import_node_fs11.existsSync)((0, import_node_path6.join)(candidateDir, "payload", "payload.json")) ? readAcquisition((0, import_node_path6.join)(candidateDir, "payload")) : null;
|
|
1904
|
+
if (acquisition) {
|
|
1905
|
+
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
1906
|
+
installer.phase("activate", { state: "running" });
|
|
1907
|
+
let outcome;
|
|
1908
|
+
const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
|
|
1909
|
+
await installAcquisition(dir, candidate, version, acquisition, {
|
|
1910
|
+
fetchImpl,
|
|
1911
|
+
env,
|
|
1912
|
+
run: async (command2, cwd, childEnv) => {
|
|
1913
|
+
const result = options.runEntry ? options.runEntry(command2, cwd, childEnv) : await runInstallEntry(command2, cwd, childEnv, installer, readTokens(dir));
|
|
1914
|
+
if (result.outcome) outcome = validateInstallerOutcome(result.outcome);
|
|
1915
|
+
return result.ok && (result.code === void 0 || result.code === 0) && !result.outcome?.operationFailed && !result.outcome?.failed;
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
installer.phase("activate");
|
|
1919
|
+
installer.surface({ id: config.product, from: current?.version, to: version, state: "updated" });
|
|
1920
|
+
installer.finish(outcome ?? { version, total: 1, updated: 1, failed: 0, installed: true });
|
|
1921
|
+
return 0;
|
|
1922
|
+
}
|
|
1923
|
+
const target = (0, import_node_path6.join)(dir, "payload");
|
|
1924
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
1925
|
+
(0, import_node_fs11.rmSync)(target, { recursive: true, force: true });
|
|
1926
|
+
(0, import_node_fs11.renameSync)((0, import_node_path6.join)(candidateDir, "payload"), target);
|
|
1630
1927
|
writeState(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1631
1928
|
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
1632
1929
|
}
|
|
@@ -1664,36 +1961,36 @@ function payloadEnv(dir) {
|
|
|
1664
1961
|
return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
|
|
1665
1962
|
}
|
|
1666
1963
|
var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
|
|
1667
|
-
function payloadFileAsCommand(
|
|
1668
|
-
const first =
|
|
1964
|
+
function payloadFileAsCommand(entry2, payload) {
|
|
1965
|
+
const first = entry2[0];
|
|
1669
1966
|
if (!first || first === "$self") return null;
|
|
1670
1967
|
if (first.includes("/") || first.includes("\\")) return null;
|
|
1671
|
-
const candidate = (0,
|
|
1672
|
-
if (!(0,
|
|
1968
|
+
const candidate = (0, import_node_path6.join)(payload, first);
|
|
1969
|
+
if (!(0, import_node_fs11.existsSync)(candidate)) return null;
|
|
1673
1970
|
if (NEVER_A_PROGRAM.test(first)) return first;
|
|
1674
1971
|
if (process.platform === "win32") return null;
|
|
1675
1972
|
try {
|
|
1676
|
-
return ((0,
|
|
1973
|
+
return ((0, import_node_fs11.statSync)(candidate).mode & 73) === 0 ? first : null;
|
|
1677
1974
|
} catch {
|
|
1678
1975
|
return null;
|
|
1679
1976
|
}
|
|
1680
1977
|
}
|
|
1681
|
-
async function finishLastMile(config, dir, options, installer, version, unchanged) {
|
|
1682
|
-
const
|
|
1978
|
+
async function finishLastMile(config, dir, options, installer, version, unchanged, repairCommand) {
|
|
1979
|
+
const entry2 = repairCommand ?? readPayloadEntry(dir);
|
|
1683
1980
|
const payload = payloadDir(dir);
|
|
1684
|
-
if (!
|
|
1981
|
+
if (!entry2) {
|
|
1685
1982
|
installer.finish({
|
|
1686
1983
|
version,
|
|
1687
1984
|
total: 1,
|
|
1688
1985
|
updated: unchanged ? 0 : 1,
|
|
1689
1986
|
failed: 0,
|
|
1690
1987
|
installed: true,
|
|
1691
|
-
detail: `next step: run ${(0,
|
|
1988
|
+
detail: `next step: run ${(0, import_node_path6.join)(payload, config.binName)} to start ${config.product}.`
|
|
1692
1989
|
});
|
|
1693
1990
|
return 0;
|
|
1694
1991
|
}
|
|
1695
|
-
const
|
|
1696
|
-
const dataFile = payloadFileAsCommand(
|
|
1992
|
+
const command2 = resolveEntry(entry2);
|
|
1993
|
+
const dataFile = payloadFileAsCommand(entry2, payload);
|
|
1697
1994
|
if (dataFile) {
|
|
1698
1995
|
installer.finish({
|
|
1699
1996
|
version,
|
|
@@ -1706,8 +2003,9 @@ async function finishLastMile(config, dir, options, installer, version, unchange
|
|
|
1706
2003
|
}
|
|
1707
2004
|
installer.phase("activate", { state: "running" });
|
|
1708
2005
|
const started = Date.now();
|
|
1709
|
-
const
|
|
1710
|
-
const
|
|
2006
|
+
const inherited = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
|
|
2007
|
+
const env = repairCommand ? runtimeEnvironment(repairCommand[0], inherited) : inherited;
|
|
2008
|
+
const result = options.runEntry ? options.runEntry(command2, payload, env) : await runInstallEntry(command2, payload, env, installer, readTokens(dir));
|
|
1711
2009
|
for (const progress of result.progress ?? []) installer.milestone(progress);
|
|
1712
2010
|
const succeeded = result.ok && (result.code === void 0 || result.code === 0);
|
|
1713
2011
|
const outcome = result.outcome ? validateInstallerOutcome(result.outcome) : void 0;
|
|
@@ -1722,18 +2020,18 @@ async function finishLastMile(config, dir, options, installer, version, unchange
|
|
|
1722
2020
|
},
|
|
1723
2021
|
...!succeeded ? {
|
|
1724
2022
|
operationFailed: true,
|
|
1725
|
-
detail: result.code === void 0 ? `Arming this machine could not start: ${result.error ??
|
|
1726
|
-
retry: `(cd ${payload} && ${
|
|
2023
|
+
detail: result.code === void 0 ? `Arming this machine could not start: ${result.error ?? command2[0]}` : `Arming this machine did not finish (exit code ${result.code})${result.error?.startsWith("installer outcome:") ? `: ${result.error}` : ""}`,
|
|
2024
|
+
retry: `(cd ${payload} && ${command2.join(" ")})`
|
|
1727
2025
|
} : {}
|
|
1728
2026
|
});
|
|
1729
2027
|
return succeeded ? outcome?.operationFailed || outcome?.failed ? 1 : 0 : result.code || 1;
|
|
1730
2028
|
}
|
|
1731
|
-
async function runInstallEntry(
|
|
1732
|
-
const [
|
|
1733
|
-
const shell = needsShell(
|
|
2029
|
+
async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
2030
|
+
const [command2, ...args] = entry2;
|
|
2031
|
+
const shell = needsShell(command2);
|
|
1734
2032
|
const progress = !(process.platform === "win32" && shell);
|
|
1735
|
-
const outcomeDir = (0,
|
|
1736
|
-
const outcomeFile = (0,
|
|
2033
|
+
const outcomeDir = (0, import_node_fs11.mkdtempSync)((0, import_node_path6.join)((0, import_node_os4.tmpdir)(), "mm-installer-outcome-"));
|
|
2034
|
+
const outcomeFile = (0, import_node_path6.join)(outcomeDir, "outcome.json");
|
|
1737
2035
|
const childEnv = { ...env, MM_INSTALLER_OUTCOME_FILE: outcomeFile };
|
|
1738
2036
|
delete childEnv.MM_FACE_TRANSCRIPT;
|
|
1739
2037
|
delete childEnv.MM_PROGRESS_FD;
|
|
@@ -1742,8 +2040,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
|
1742
2040
|
const secrets = [tokens?.accessToken, tokens?.refreshToken].filter((value) => Boolean(value));
|
|
1743
2041
|
const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
|
|
1744
2042
|
try {
|
|
1745
|
-
const result = await new Promise((
|
|
1746
|
-
const child = (0,
|
|
2043
|
+
const result = await new Promise((resolve3) => {
|
|
2044
|
+
const child = (0, import_node_child_process4.spawn)(shell ? [command2, ...args].map(quoteForShell).join(" ") : command2, shell ? [] : args, {
|
|
1747
2045
|
cwd,
|
|
1748
2046
|
shell,
|
|
1749
2047
|
windowsHide: true,
|
|
@@ -1779,8 +2077,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
|
1779
2077
|
if (pending.length > 65536) pending = "";
|
|
1780
2078
|
});
|
|
1781
2079
|
}
|
|
1782
|
-
child.on("error", (error) =>
|
|
1783
|
-
child.on("close", (code) =>
|
|
2080
|
+
child.on("error", (error) => resolve3({ ok: false, error: error.message }));
|
|
2081
|
+
child.on("close", (code) => resolve3({
|
|
1784
2082
|
ok: code === 0,
|
|
1785
2083
|
...code !== null ? { code } : {},
|
|
1786
2084
|
...code !== 0 ? { error: `exit code ${code}` } : {}
|
|
@@ -1793,25 +2091,30 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
|
1793
2091
|
return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
|
|
1794
2092
|
}
|
|
1795
2093
|
} finally {
|
|
1796
|
-
if ((0,
|
|
1797
|
-
(0,
|
|
2094
|
+
if ((0, import_node_fs11.existsSync)(outcomeFile)) (0, import_node_fs11.unlinkSync)(outcomeFile);
|
|
2095
|
+
(0, import_node_fs11.rmdirSync)(outcomeDir);
|
|
1798
2096
|
}
|
|
1799
2097
|
}
|
|
1800
|
-
function doForward(config, dir, options,
|
|
1801
|
-
if (
|
|
1802
|
-
print(`${
|
|
2098
|
+
function doForward(config, dir, options, command2, argv2, print, unlock) {
|
|
2099
|
+
if (command2 && (0, import_node_fs11.existsSync)(command2)) {
|
|
2100
|
+
print(`${command2} is a file, not a command \u2014 did you mean \`--run ${command2}\`?`);
|
|
1803
2101
|
return 2;
|
|
1804
2102
|
}
|
|
1805
|
-
const
|
|
2103
|
+
const acquired = readState(dir)?.acquired;
|
|
2104
|
+
const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
|
|
1806
2105
|
const verbs = readPayloadVerbs(dir);
|
|
1807
|
-
const declared = verbs === "*" || Array.isArray(verbs) &&
|
|
2106
|
+
const declared = verbs === "*" || Array.isArray(verbs) && command2 !== void 0 && verbs.includes(command2);
|
|
1808
2107
|
if (!target || !declared) {
|
|
1809
|
-
print(`unknown command: ${
|
|
2108
|
+
print(`unknown command: ${command2}`);
|
|
1810
2109
|
printUsage(config, print);
|
|
1811
2110
|
return 2;
|
|
1812
2111
|
}
|
|
1813
|
-
const forwarded = [...resolveEntry(target), ...
|
|
1814
|
-
const
|
|
2112
|
+
const forwarded = [...resolveEntry(target), ...argv2];
|
|
2113
|
+
const cwd = payloadDir(dir);
|
|
2114
|
+
const inherited = payloadEnv(dir);
|
|
2115
|
+
const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
|
|
2116
|
+
unlock();
|
|
2117
|
+
const result = (options.runEntry ?? defaultRunEntry)(forwarded, cwd, env);
|
|
1815
2118
|
if (!result.ok && result.code === void 0) {
|
|
1816
2119
|
print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
|
|
1817
2120
|
return 1;
|
|
@@ -1821,10 +2124,10 @@ function doForward(config, dir, options, command, argv, print) {
|
|
|
1821
2124
|
function doAutoupdate(config, options, args, print) {
|
|
1822
2125
|
const mode = args[0] ?? "status";
|
|
1823
2126
|
const scheduleOptions = options.autoupdate ?? {};
|
|
1824
|
-
const
|
|
2127
|
+
const command2 = options.autoupdate?.command ?? [process.execPath, "update"];
|
|
1825
2128
|
if (mode === "on") {
|
|
1826
2129
|
try {
|
|
1827
|
-
enableSchedule(config,
|
|
2130
|
+
enableSchedule(config, command2, scheduleOptions);
|
|
1828
2131
|
} catch (error) {
|
|
1829
2132
|
print(error.message);
|
|
1830
2133
|
return 1;
|
|
@@ -1856,19 +2159,24 @@ function doAutoupdate(config, options, args, print) {
|
|
|
1856
2159
|
if (state.lastRun) print(`last run: ${state.lastRun}`);
|
|
1857
2160
|
return 0;
|
|
1858
2161
|
}
|
|
1859
|
-
function doDoctor(config, dir, options, print) {
|
|
2162
|
+
function doDoctor(config, dir, options, print, unlock) {
|
|
1860
2163
|
doLauncherDoctor(config, dir, options, print);
|
|
1861
|
-
const
|
|
2164
|
+
const acquired = readState(dir)?.acquired;
|
|
2165
|
+
const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
|
|
1862
2166
|
const verbs = readPayloadVerbs(dir);
|
|
1863
2167
|
const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
|
|
1864
2168
|
if (!chains) return 0;
|
|
1865
|
-
const
|
|
2169
|
+
const cwd = payloadDir(dir);
|
|
2170
|
+
const inherited = payloadEnv(dir);
|
|
2171
|
+
const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
|
|
2172
|
+
unlock();
|
|
2173
|
+
const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], cwd, env);
|
|
1866
2174
|
return result.code ?? (result.ok ? 0 : 1);
|
|
1867
2175
|
}
|
|
1868
2176
|
function doLauncherDoctor(config, dir, options, print) {
|
|
1869
2177
|
const tokens = readTokens(dir);
|
|
1870
2178
|
const state = readState(dir);
|
|
1871
|
-
const payloadPresent = (0,
|
|
2179
|
+
const payloadPresent = (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
1872
2180
|
print(`product: ${config.product}`);
|
|
1873
2181
|
print(`host: ${config.host}`);
|
|
1874
2182
|
print(`login: ${config.loginKind}`);
|
|
@@ -1880,7 +2188,7 @@ function doLauncherDoctor(config, dir, options, print) {
|
|
|
1880
2188
|
print("token: none \u2014 run login first.");
|
|
1881
2189
|
}
|
|
1882
2190
|
print(`payload: ${state ? state.version : "none"}${payloadPresent ? "" : " (not downloaded)"}`);
|
|
1883
|
-
print(`paths: tokens ${(0,
|
|
2191
|
+
print(`paths: tokens ${(0, import_node_path6.join)(dir, "tokens.json")}, state ${(0, import_node_path6.join)(dir, "state.json")}, payload ${payloadDir(dir)}`);
|
|
1884
2192
|
const schedule = querySchedule(config, options.autoupdate ?? {});
|
|
1885
2193
|
print(
|
|
1886
2194
|
`auto-update: ${!schedule.supported ? "unsupported on this platform" : schedule.enabled ? `on (${schedule.cadence ?? "hourly"})` : "off"}`
|