@mutmutco/installer-launcher 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/acquisition-required.mjs +3 -0
- package/dist/launcher.js +548 -220
- package/dist/launcher.sea.cjs +563 -235
- package/package.json +4 -3
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;
|
|
@@ -443,7 +771,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
443
771
|
product: declaration.product,
|
|
444
772
|
columns: options.columns,
|
|
445
773
|
env,
|
|
446
|
-
color: tty && options.color !== false && env.NO_COLOR === void 0
|
|
774
|
+
color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
|
|
447
775
|
});
|
|
448
776
|
const errors = [];
|
|
449
777
|
const write = options.write ? (text, channel) => {
|
|
@@ -459,7 +787,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
459
787
|
if (!text) return;
|
|
460
788
|
write(text, channel);
|
|
461
789
|
if (env.MM_FACE_TRANSCRIPT) {
|
|
462
|
-
(0,
|
|
790
|
+
(0, import_node_fs7.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
|
|
463
791
|
`, "utf8");
|
|
464
792
|
}
|
|
465
793
|
};
|
|
@@ -479,9 +807,13 @@ function createInstallerRun(value, options = {}) {
|
|
|
479
807
|
const start = () => {
|
|
480
808
|
if (started || finished) return;
|
|
481
809
|
started = true;
|
|
810
|
+
if (options.quiet) return;
|
|
482
811
|
const welcome = face.welcome();
|
|
483
812
|
if (tty) lines(welcome);
|
|
484
|
-
else if (welcome.length)
|
|
813
|
+
else if (welcome.length) {
|
|
814
|
+
const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
815
|
+
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
816
|
+
}
|
|
485
817
|
};
|
|
486
818
|
const durable = (title, measure, kind) => {
|
|
487
819
|
spinner.stop();
|
|
@@ -511,12 +843,13 @@ function createInstallerRun(value, options = {}) {
|
|
|
511
843
|
const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
|
|
512
844
|
if (!surface) throw new Error("installer run: undeclared surface");
|
|
513
845
|
start();
|
|
514
|
-
const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} \u2192 ${facts.to}` : ` ${facts.to}` : "";
|
|
846
|
+
const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
|
|
515
847
|
const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
|
|
516
848
|
if (!status) throw new Error("installer run: unknown surface state");
|
|
517
|
-
const
|
|
849
|
+
const separator = tty ? "\xB7" : "-";
|
|
850
|
+
const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
|
|
518
851
|
const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
|
|
519
|
-
durable(`${facts.id}${versions}
|
|
852
|
+
durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
|
|
520
853
|
if (facts.detail) run2.relay(facts.detail);
|
|
521
854
|
},
|
|
522
855
|
milestone({ step, state, ms }) {
|
|
@@ -543,6 +876,20 @@ function createInstallerRun(value, options = {}) {
|
|
|
543
876
|
`);
|
|
544
877
|
}
|
|
545
878
|
},
|
|
879
|
+
cancel() {
|
|
880
|
+
if (finished) return;
|
|
881
|
+
if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
|
|
882
|
+
env.MM_INSTALLER_OUTCOME_FILE,
|
|
883
|
+
{ total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
|
|
884
|
+
);
|
|
885
|
+
start();
|
|
886
|
+
spinner.stop();
|
|
887
|
+
finished = true;
|
|
888
|
+
if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
|
|
889
|
+
lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
|
|
890
|
+
}
|
|
891
|
+
if (tty) lines([face.signOff()]);
|
|
892
|
+
},
|
|
546
893
|
finish(facts) {
|
|
547
894
|
if (finished) return;
|
|
548
895
|
validateInstallerOutcome(facts);
|
|
@@ -550,6 +897,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
550
897
|
start();
|
|
551
898
|
spinner.stop();
|
|
552
899
|
finished = true;
|
|
900
|
+
if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
|
|
553
901
|
const changed = !facts.version ? "No release target is available." : facts.dryRun ? `Would update ${facts.updated} of ${facts.total} surfaces to ${facts.version}.` : facts.installed ? `Installed ${facts.version} across ${facts.total} surfaces.` : `Updated ${facts.updated} of ${facts.total} surfaces to ${facts.version}.`;
|
|
554
902
|
const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
|
|
555
903
|
const body = [
|
|
@@ -574,17 +922,17 @@ function createInstallerRun(value, options = {}) {
|
|
|
574
922
|
}
|
|
575
923
|
|
|
576
924
|
// src/autoupdate.ts
|
|
577
|
-
var
|
|
578
|
-
var
|
|
579
|
-
var
|
|
580
|
-
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");
|
|
581
929
|
function schedulePlatform(override) {
|
|
582
930
|
const platform = override ?? process.platform;
|
|
583
931
|
if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
|
|
584
932
|
return null;
|
|
585
933
|
}
|
|
586
|
-
function defaultExec(
|
|
587
|
-
const result = (0,
|
|
934
|
+
function defaultExec(command2, args) {
|
|
935
|
+
const result = (0, import_node_child_process2.spawnSync)(command2, args, { encoding: "utf8", windowsHide: true });
|
|
588
936
|
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
589
937
|
return {
|
|
590
938
|
code: typeof result.status === "number" ? result.status : 1,
|
|
@@ -594,8 +942,8 @@ function defaultExec(command, args) {
|
|
|
594
942
|
}
|
|
595
943
|
function homeOf(options) {
|
|
596
944
|
if (options.homeDir) return options.homeDir;
|
|
597
|
-
if (process.platform === "win32") return process.env.USERPROFILE ?? (0,
|
|
598
|
-
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)();
|
|
599
947
|
}
|
|
600
948
|
function scheduleName(config) {
|
|
601
949
|
return `${config.binName} autoupdate`;
|
|
@@ -606,13 +954,13 @@ function scheduleLabel(config) {
|
|
|
606
954
|
function quoteWindows(arg) {
|
|
607
955
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
608
956
|
}
|
|
609
|
-
function enableSchedule(config,
|
|
957
|
+
function enableSchedule(config, command2, options = {}) {
|
|
610
958
|
const platform = schedulePlatform(options.platform);
|
|
611
959
|
if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
|
|
612
960
|
const exec = options.exec ?? defaultExec;
|
|
613
961
|
const home = homeOf(options);
|
|
614
962
|
if (platform === "win32") {
|
|
615
|
-
const taskLine =
|
|
963
|
+
const taskLine = command2.map(quoteWindows).join(" ");
|
|
616
964
|
const result2 = exec("schtasks", ["/Create", "/TN", scheduleName(config), "/TR", taskLine, "/SC", "HOURLY", "/IT", "/F"]);
|
|
617
965
|
if (result2.code !== 0) {
|
|
618
966
|
throw new Error(`could not turn autoupdate on: ${firstLine(result2.stdout || result2.stderr) || `exit ${result2.code}`}`);
|
|
@@ -621,10 +969,10 @@ function enableSchedule(config, command, options = {}) {
|
|
|
621
969
|
}
|
|
622
970
|
if (platform === "darwin") {
|
|
623
971
|
const label2 = scheduleLabel(config);
|
|
624
|
-
const dir2 = (0,
|
|
625
|
-
(0,
|
|
626
|
-
const plist = (0,
|
|
627
|
-
(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));
|
|
628
976
|
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
629
977
|
const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
|
|
630
978
|
if (result2.code !== 0) {
|
|
@@ -633,10 +981,10 @@ function enableSchedule(config, command, options = {}) {
|
|
|
633
981
|
return;
|
|
634
982
|
}
|
|
635
983
|
const label = scheduleLabel(config);
|
|
636
|
-
const dir = (0,
|
|
637
|
-
(0,
|
|
638
|
-
(0,
|
|
639
|
-
(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));
|
|
640
988
|
const reload = exec("systemctl", ["--user", "daemon-reload"]);
|
|
641
989
|
if (reload.code !== 0) {
|
|
642
990
|
throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
|
|
@@ -661,14 +1009,14 @@ function disableSchedule(config, options = {}) {
|
|
|
661
1009
|
if (platform === "darwin") {
|
|
662
1010
|
const label2 = scheduleLabel(config);
|
|
663
1011
|
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
664
|
-
(0,
|
|
1012
|
+
(0, import_node_fs8.rmSync)((0, import_node_path4.join)(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
|
|
665
1013
|
return;
|
|
666
1014
|
}
|
|
667
1015
|
const label = scheduleLabel(config);
|
|
668
|
-
const dir = (0,
|
|
1016
|
+
const dir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
|
|
669
1017
|
exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
|
|
670
|
-
(0,
|
|
671
|
-
(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 });
|
|
672
1020
|
}
|
|
673
1021
|
function querySchedule(config, options = {}) {
|
|
674
1022
|
const platform = schedulePlatform(options.platform);
|
|
@@ -686,20 +1034,20 @@ function querySchedule(config, options = {}) {
|
|
|
686
1034
|
return state2;
|
|
687
1035
|
}
|
|
688
1036
|
if (platform === "darwin") {
|
|
689
|
-
const plist = (0,
|
|
690
|
-
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 };
|
|
691
1039
|
return { supported: true, enabled: true, cadence: "hourly" };
|
|
692
1040
|
}
|
|
693
|
-
const timer = (0,
|
|
694
|
-
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 };
|
|
695
1043
|
const state = { supported: true, enabled: true, cadence: "hourly" };
|
|
696
1044
|
const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
|
|
697
1045
|
const stamp = (shown.stdout ?? "").trim();
|
|
698
1046
|
if (shown.code === 0 && stamp && stamp !== "n/a") state.lastRun = stamp;
|
|
699
1047
|
return state;
|
|
700
1048
|
}
|
|
701
|
-
function darwinPlist(label,
|
|
702
|
-
const args =
|
|
1049
|
+
function darwinPlist(label, command2) {
|
|
1050
|
+
const args = command2.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
|
|
703
1051
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
704
1052
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
705
1053
|
<plist version="1.0">
|
|
@@ -716,8 +1064,8 @@ ${args}
|
|
|
716
1064
|
</plist>
|
|
717
1065
|
`;
|
|
718
1066
|
}
|
|
719
|
-
function linuxService(
|
|
720
|
-
const line =
|
|
1067
|
+
function linuxService(command2) {
|
|
1068
|
+
const line = command2.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
|
|
721
1069
|
return `[Unit]
|
|
722
1070
|
Description=${"Hourly update check"}
|
|
723
1071
|
[Service]
|
|
@@ -749,7 +1097,7 @@ function firstLine(text) {
|
|
|
749
1097
|
}
|
|
750
1098
|
|
|
751
1099
|
// src/config.ts
|
|
752
|
-
var
|
|
1100
|
+
var import_node_fs9 = require("node:fs");
|
|
753
1101
|
var import_node_sea = require("node:sea");
|
|
754
1102
|
|
|
755
1103
|
// src/module-url.ts
|
|
@@ -772,10 +1120,10 @@ function loadProductConfig(options = {}) {
|
|
|
772
1120
|
const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
|
|
773
1121
|
const devFallback = new URL("../config/product.template.json", moduleUrl());
|
|
774
1122
|
if (explicit) {
|
|
775
|
-
return parseProductConfig((0,
|
|
1123
|
+
return parseProductConfig((0, import_node_fs9.readFileSync)(explicit, "utf8"));
|
|
776
1124
|
}
|
|
777
1125
|
try {
|
|
778
|
-
return parseProductConfig((0,
|
|
1126
|
+
return parseProductConfig((0, import_node_fs9.readFileSync)(devFallback, "utf8"));
|
|
779
1127
|
} catch {
|
|
780
1128
|
}
|
|
781
1129
|
try {
|
|
@@ -830,14 +1178,14 @@ function field(record, key) {
|
|
|
830
1178
|
}
|
|
831
1179
|
|
|
832
1180
|
// src/login-github.ts
|
|
833
|
-
var
|
|
834
|
-
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));
|
|
835
1183
|
function openBrowser(url) {
|
|
836
1184
|
if (process.env.LAUNCHER_NO_OPEN === "1") return;
|
|
837
1185
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
|
|
838
1186
|
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
839
1187
|
try {
|
|
840
|
-
const child = (0,
|
|
1188
|
+
const child = (0, import_node_child_process3.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
841
1189
|
child.on("error", () => {
|
|
842
1190
|
});
|
|
843
1191
|
child.unref();
|
|
@@ -951,7 +1299,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
|
|
|
951
1299
|
}
|
|
952
1300
|
|
|
953
1301
|
// src/login-google.ts
|
|
954
|
-
var
|
|
1302
|
+
var import_node_crypto4 = require("node:crypto");
|
|
955
1303
|
var import_node_http = require("node:http");
|
|
956
1304
|
var b64url = (bytes) => bytes.toString("base64url");
|
|
957
1305
|
async function loginGoogle(options) {
|
|
@@ -959,7 +1307,7 @@ async function loginGoogle(options) {
|
|
|
959
1307
|
const server = options.host.replace(/\/+$/, "");
|
|
960
1308
|
const timeoutMs = options.timeoutMs ?? 5 * 6e4;
|
|
961
1309
|
const listener = (0, import_node_http.createServer)();
|
|
962
|
-
await new Promise((
|
|
1310
|
+
await new Promise((resolve3) => listener.listen(0, "127.0.0.1", resolve3));
|
|
963
1311
|
const port = listener.address().port;
|
|
964
1312
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
965
1313
|
try {
|
|
@@ -973,9 +1321,9 @@ async function loginGoogle(options) {
|
|
|
973
1321
|
if (typeof clientId !== "string" || !clientId) {
|
|
974
1322
|
throw new Error("the sign-in server returned a malformed registration");
|
|
975
1323
|
}
|
|
976
|
-
const verifier = b64url((0,
|
|
977
|
-
const challenge = b64url((0,
|
|
978
|
-
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));
|
|
979
1327
|
const authorize = new URL(`${server}/oauth/authorize`);
|
|
980
1328
|
authorize.search = new URLSearchParams({
|
|
981
1329
|
response_type: "code",
|
|
@@ -986,7 +1334,7 @@ async function loginGoogle(options) {
|
|
|
986
1334
|
code_challenge: challenge,
|
|
987
1335
|
code_challenge_method: "S256"
|
|
988
1336
|
}).toString();
|
|
989
|
-
const code = await new Promise((
|
|
1337
|
+
const code = await new Promise((resolve3, reject) => {
|
|
990
1338
|
const timer = setTimeout(() => reject(new Error("sign-in timed out \u2014 run login again.")), timeoutMs);
|
|
991
1339
|
listener.on("request", (req, res) => {
|
|
992
1340
|
const url = new URL(req.url ?? "/", redirectUri);
|
|
@@ -1004,7 +1352,7 @@ async function loginGoogle(options) {
|
|
|
1004
1352
|
}
|
|
1005
1353
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("Signed in.", "You can close this tab and go back to the terminal."));
|
|
1006
1354
|
clearTimeout(timer);
|
|
1007
|
-
|
|
1355
|
+
resolve3(received);
|
|
1008
1356
|
});
|
|
1009
1357
|
const print = options.print ?? ((line) => process.stderr.write(`${line}
|
|
1010
1358
|
`));
|
|
@@ -1064,10 +1412,10 @@ function page(title, body) {
|
|
|
1064
1412
|
}
|
|
1065
1413
|
|
|
1066
1414
|
// src/payload.ts
|
|
1067
|
-
var
|
|
1068
|
-
var
|
|
1069
|
-
var
|
|
1070
|
-
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");
|
|
1071
1419
|
|
|
1072
1420
|
// src/canonical.ts
|
|
1073
1421
|
function canonicalJson(value) {
|
|
@@ -1084,7 +1432,7 @@ function encode(value) {
|
|
|
1084
1432
|
return JSON.stringify(value);
|
|
1085
1433
|
}
|
|
1086
1434
|
if (Array.isArray(value)) {
|
|
1087
|
-
return `[${value.map((
|
|
1435
|
+
return `[${value.map((entry2) => encode(entry2)).join(",")}]`;
|
|
1088
1436
|
}
|
|
1089
1437
|
if (typeof value === "object") {
|
|
1090
1438
|
const record = value;
|
|
@@ -1119,7 +1467,7 @@ function canonicalManifestBytes(manifest) {
|
|
|
1119
1467
|
}
|
|
1120
1468
|
function ed25519PublicKey(config) {
|
|
1121
1469
|
const raw = publicKeyBytes(config);
|
|
1122
|
-
return (0,
|
|
1470
|
+
return (0, import_node_crypto5.createPublicKey)({
|
|
1123
1471
|
key: { kty: "OKP", crv: "Ed25519", x: raw.toString("base64url") },
|
|
1124
1472
|
format: "jwk"
|
|
1125
1473
|
});
|
|
@@ -1128,14 +1476,14 @@ function verifyManifest(manifest, signature, config) {
|
|
|
1128
1476
|
try {
|
|
1129
1477
|
const signatureBytes = Buffer.from(signature, "base64");
|
|
1130
1478
|
if (signatureBytes.length === 0) return false;
|
|
1131
|
-
return (0,
|
|
1479
|
+
return (0, import_node_crypto5.verify)(null, canonicalManifestBytes(manifest), ed25519PublicKey(config), signatureBytes);
|
|
1132
1480
|
} catch {
|
|
1133
1481
|
return false;
|
|
1134
1482
|
}
|
|
1135
1483
|
}
|
|
1136
|
-
function verifyFileBytes(
|
|
1137
|
-
if (
|
|
1138
|
-
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();
|
|
1139
1487
|
}
|
|
1140
1488
|
async function readErrorCode(response) {
|
|
1141
1489
|
try {
|
|
@@ -1166,8 +1514,8 @@ function parseManifest(json) {
|
|
|
1166
1514
|
if (typeof record.signature !== "string" || !record.signature) {
|
|
1167
1515
|
throw new Error("the release manifest is unsigned");
|
|
1168
1516
|
}
|
|
1169
|
-
const files = record.files.map((
|
|
1170
|
-
const file =
|
|
1517
|
+
const files = record.files.map((entry2) => {
|
|
1518
|
+
const file = entry2;
|
|
1171
1519
|
if (typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.size !== "number") {
|
|
1172
1520
|
throw new Error("the release manifest lists a malformed file");
|
|
1173
1521
|
}
|
|
@@ -1207,101 +1555,31 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
|
|
|
1207
1555
|
}
|
|
1208
1556
|
async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
|
|
1209
1557
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1210
|
-
const staging = (0,
|
|
1211
|
-
(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 });
|
|
1212
1560
|
try {
|
|
1213
|
-
for (const
|
|
1214
|
-
const bytes = await fetchFileBytes(config, accessToken,
|
|
1215
|
-
if (!verifyFileBytes(
|
|
1216
|
-
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.`);
|
|
1217
1565
|
}
|
|
1218
|
-
const dest = (0,
|
|
1219
|
-
(0,
|
|
1220
|
-
(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);
|
|
1221
1569
|
}
|
|
1222
|
-
const target = (0,
|
|
1223
|
-
(0,
|
|
1224
|
-
(0,
|
|
1225
|
-
(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);
|
|
1226
1574
|
} catch (error) {
|
|
1227
|
-
(0,
|
|
1575
|
+
(0, import_node_fs10.rmSync)(staging, { force: true, recursive: true });
|
|
1228
1576
|
throw error;
|
|
1229
1577
|
}
|
|
1230
1578
|
return manifest.version;
|
|
1231
1579
|
}
|
|
1232
1580
|
|
|
1233
|
-
// src/store.ts
|
|
1234
|
-
var import_node_fs8 = require("node:fs");
|
|
1235
|
-
var import_node_os3 = require("node:os");
|
|
1236
|
-
var import_node_path3 = require("node:path");
|
|
1237
|
-
function defaultProductDir(product) {
|
|
1238
|
-
if (process.platform === "win32") {
|
|
1239
|
-
const base = process.env.LOCALAPPDATA ?? (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "launcher-fallback");
|
|
1240
|
-
return (0, import_node_path3.join)(base, product);
|
|
1241
|
-
}
|
|
1242
|
-
const home = process.env.HOME ?? (0, import_node_os3.tmpdir)();
|
|
1243
|
-
return (0, import_node_path3.join)(home, `.${product}`);
|
|
1244
|
-
}
|
|
1245
|
-
function resolveProductDir(product, explicit) {
|
|
1246
|
-
return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
|
|
1247
|
-
}
|
|
1248
|
-
function tokensPath(dir) {
|
|
1249
|
-
return (0, import_node_path3.join)(dir, "tokens.json");
|
|
1250
|
-
}
|
|
1251
|
-
function statePath(dir) {
|
|
1252
|
-
return (0, import_node_path3.join)(dir, "state.json");
|
|
1253
|
-
}
|
|
1254
|
-
function payloadDir(dir) {
|
|
1255
|
-
return (0, import_node_path3.join)(dir, "payload");
|
|
1256
|
-
}
|
|
1257
|
-
function readTokens(dir) {
|
|
1258
|
-
try {
|
|
1259
|
-
const data = JSON.parse((0, import_node_fs8.readFileSync)(tokensPath(dir), "utf8"));
|
|
1260
|
-
if (typeof data.accessToken !== "string" || !data.accessToken) return null;
|
|
1261
|
-
if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
|
|
1262
|
-
const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
|
|
1263
|
-
if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
|
|
1264
|
-
if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
|
|
1265
|
-
return tokens;
|
|
1266
|
-
} catch {
|
|
1267
|
-
return null;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
function writeTokens(dir, tokens) {
|
|
1271
|
-
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
1272
|
-
try {
|
|
1273
|
-
(0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
1274
|
-
`, { mode: 384 });
|
|
1275
|
-
} catch {
|
|
1276
|
-
(0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
1277
|
-
`);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
function clearTokens(dir) {
|
|
1281
|
-
(0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
|
|
1282
|
-
}
|
|
1283
|
-
function readState(dir) {
|
|
1284
|
-
try {
|
|
1285
|
-
const data = JSON.parse((0, import_node_fs8.readFileSync)(statePath(dir), "utf8"));
|
|
1286
|
-
if (typeof data.version !== "string" || !data.version) return null;
|
|
1287
|
-
return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
|
|
1288
|
-
} catch {
|
|
1289
|
-
return null;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
function writeState(dir, state) {
|
|
1293
|
-
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
1294
|
-
(0, import_node_fs8.writeFileSync)(statePath(dir), `${JSON.stringify(state, null, 2)}
|
|
1295
|
-
`);
|
|
1296
|
-
}
|
|
1297
|
-
function wipeProductDir(dir) {
|
|
1298
|
-
(0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
|
|
1299
|
-
(0, import_node_fs8.rmSync)(statePath(dir), { force: true });
|
|
1300
|
-
(0, import_node_fs8.rmSync)(payloadDir(dir), { force: true, recursive: true });
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
1581
|
// src/index.ts
|
|
1304
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1582
|
+
var LAUNCHER_VERSION = true ? "0.1.13" : readVersionFromPackage();
|
|
1305
1583
|
function defaultPrint(message) {
|
|
1306
1584
|
process.stdout.write(`${message}
|
|
1307
1585
|
`);
|
|
@@ -1313,30 +1591,37 @@ function defaultPrintErr(message) {
|
|
|
1313
1591
|
async function run(rawOptions = {}) {
|
|
1314
1592
|
const print = rawOptions.print ?? defaultPrint;
|
|
1315
1593
|
const printErr = rawOptions.printErr ?? defaultPrintErr;
|
|
1316
|
-
const
|
|
1317
|
-
const runAt =
|
|
1318
|
-
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);
|
|
1319
1597
|
let config;
|
|
1320
1598
|
try {
|
|
1321
|
-
config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(
|
|
1599
|
+
config = loadProductConfig({ configPath: rawOptions.configPath ?? flagValue(argv2, "--config") });
|
|
1322
1600
|
} catch (error) {
|
|
1323
1601
|
print(`cannot start: ${error.message}`);
|
|
1324
1602
|
return 2;
|
|
1325
1603
|
}
|
|
1326
|
-
const dir = resolveProductDir(config.product, rawOptions.dir ?? flagValue(
|
|
1327
|
-
const positional =
|
|
1328
|
-
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")) {
|
|
1329
1607
|
print(`${config.binName} launcher ${LAUNCHER_VERSION}`);
|
|
1330
1608
|
return 0;
|
|
1331
1609
|
}
|
|
1332
|
-
if (
|
|
1610
|
+
if (argv2.includes("--help") || argv2.includes("-h") || positional.length === 0) {
|
|
1333
1611
|
printUsage(config, print);
|
|
1334
1612
|
return positional.length === 0 ? 2 : 0;
|
|
1335
1613
|
}
|
|
1336
|
-
const
|
|
1614
|
+
const command2 = positional[0];
|
|
1615
|
+
let unlock;
|
|
1337
1616
|
try {
|
|
1338
|
-
|
|
1617
|
+
unlock = lockInstallation(dir);
|
|
1618
|
+
await recoverAcquisition(dir, async (command3, cwd, env) => {
|
|
1619
|
+
const result = (rawOptions.runEntry ?? defaultRunEntry)(command3, cwd, env);
|
|
1620
|
+
return result.ok && (result.code === void 0 || result.code === 0);
|
|
1621
|
+
}, payloadEnv(dir) ?? process.env);
|
|
1622
|
+
switch (command2) {
|
|
1339
1623
|
case "login":
|
|
1624
|
+
unlock();
|
|
1340
1625
|
await doLogin(config, dir, rawOptions, print);
|
|
1341
1626
|
return 0;
|
|
1342
1627
|
case "logout":
|
|
@@ -1348,11 +1633,11 @@ async function run(rawOptions = {}) {
|
|
|
1348
1633
|
case "update":
|
|
1349
1634
|
return await doUpdate(config, dir, rawOptions, print);
|
|
1350
1635
|
case "doctor":
|
|
1351
|
-
return doDoctor(config, dir, rawOptions, print);
|
|
1636
|
+
return doDoctor(config, dir, rawOptions, print, unlock);
|
|
1352
1637
|
case "autoupdate":
|
|
1353
1638
|
return doAutoupdate(config, rawOptions, positional.slice(1), print);
|
|
1354
1639
|
default:
|
|
1355
|
-
return doForward(config, dir, rawOptions,
|
|
1640
|
+
return doForward(config, dir, rawOptions, command2, argv2, print, unlock);
|
|
1356
1641
|
}
|
|
1357
1642
|
} catch (error) {
|
|
1358
1643
|
if (error instanceof NeedsLoginError) {
|
|
@@ -1361,12 +1646,14 @@ async function run(rawOptions = {}) {
|
|
|
1361
1646
|
}
|
|
1362
1647
|
print(`failed: ${error.message}`);
|
|
1363
1648
|
return 1;
|
|
1649
|
+
} finally {
|
|
1650
|
+
unlock?.();
|
|
1364
1651
|
}
|
|
1365
1652
|
}
|
|
1366
|
-
function flagValue(
|
|
1367
|
-
const index =
|
|
1653
|
+
function flagValue(argv2, flag) {
|
|
1654
|
+
const index = argv2.indexOf(flag);
|
|
1368
1655
|
if (index < 0) return void 0;
|
|
1369
|
-
const value =
|
|
1656
|
+
const value = argv2[index + 1];
|
|
1370
1657
|
return value && !value.startsWith("-") ? value : void 0;
|
|
1371
1658
|
}
|
|
1372
1659
|
function printUsage(config, print) {
|
|
@@ -1380,8 +1667,8 @@ async function runFile(file, args, printErr) {
|
|
|
1380
1667
|
printErr("launcher --run needs a file to run.");
|
|
1381
1668
|
return 1;
|
|
1382
1669
|
}
|
|
1383
|
-
const abs = (0,
|
|
1384
|
-
if (!(0,
|
|
1670
|
+
const abs = (0, import_node_path6.resolve)(process.cwd(), file);
|
|
1671
|
+
if (!(0, import_node_fs11.existsSync)(abs)) {
|
|
1385
1672
|
printErr(`cannot run ${file}: no such file.`);
|
|
1386
1673
|
return 1;
|
|
1387
1674
|
}
|
|
@@ -1474,14 +1761,14 @@ async function fetchAccessTokenOrLogin(config, dir, options, print, installer) {
|
|
|
1474
1761
|
}
|
|
1475
1762
|
function readPayloadArgv(dir, key) {
|
|
1476
1763
|
try {
|
|
1477
|
-
const parsed = JSON.parse((0,
|
|
1478
|
-
const
|
|
1479
|
-
if (typeof
|
|
1480
|
-
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);
|
|
1481
1768
|
return parts.length > 0 ? parts : null;
|
|
1482
1769
|
}
|
|
1483
|
-
if (Array.isArray(
|
|
1484
|
-
return
|
|
1770
|
+
if (Array.isArray(entry2) && entry2.every((part) => typeof part === "string" && part.length > 0)) {
|
|
1771
|
+
return entry2;
|
|
1485
1772
|
}
|
|
1486
1773
|
return null;
|
|
1487
1774
|
} catch {
|
|
@@ -1496,7 +1783,7 @@ function readPayloadRun(dir) {
|
|
|
1496
1783
|
}
|
|
1497
1784
|
function readPayloadVerbs(dir) {
|
|
1498
1785
|
try {
|
|
1499
|
-
const parsed = JSON.parse((0,
|
|
1786
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(payloadDir(dir), "payload.json"), "utf8"));
|
|
1500
1787
|
const verbs = parsed.verbs;
|
|
1501
1788
|
if (verbs === "*") return "*";
|
|
1502
1789
|
if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
|
|
@@ -1507,13 +1794,13 @@ function readPayloadVerbs(dir) {
|
|
|
1507
1794
|
return null;
|
|
1508
1795
|
}
|
|
1509
1796
|
}
|
|
1510
|
-
function resolveEntry(
|
|
1511
|
-
return
|
|
1797
|
+
function resolveEntry(entry2) {
|
|
1798
|
+
return entry2[0] === "$self" ? [process.execPath, ...entry2.slice(1)] : entry2;
|
|
1512
1799
|
}
|
|
1513
|
-
function needsShell(
|
|
1800
|
+
function needsShell(command2) {
|
|
1514
1801
|
if (process.platform !== "win32") return false;
|
|
1515
|
-
if (/\.(cmd|bat)$/i.test(
|
|
1516
|
-
return !(0,
|
|
1802
|
+
if (/\.(cmd|bat)$/i.test(command2)) return true;
|
|
1803
|
+
return !(0, import_node_fs11.existsSync)(command2);
|
|
1517
1804
|
}
|
|
1518
1805
|
function quoteForShell(arg) {
|
|
1519
1806
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
@@ -1540,11 +1827,11 @@ function parseProgress(raw) {
|
|
|
1540
1827
|
}
|
|
1541
1828
|
return progress;
|
|
1542
1829
|
}
|
|
1543
|
-
function defaultRunEntry(
|
|
1544
|
-
const [
|
|
1545
|
-
const shell = needsShell(
|
|
1546
|
-
const commandLine = shell ? [
|
|
1547
|
-
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, {
|
|
1548
1835
|
cwd,
|
|
1549
1836
|
stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
|
|
1550
1837
|
shell,
|
|
@@ -1602,11 +1889,41 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
1602
1889
|
version = manifest.version;
|
|
1603
1890
|
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
1604
1891
|
const current = readState(dir);
|
|
1605
|
-
const unchanged = update && current?.version === version && (0,
|
|
1892
|
+
const unchanged = update && current?.version === version && (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
1893
|
+
if (unchanged && current.acquired) {
|
|
1894
|
+
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
1895
|
+
return await finishLastMile(config, dir, options, installer, version, true, acquisitionRepairCommand(dir, version));
|
|
1896
|
+
}
|
|
1606
1897
|
if (!unchanged) {
|
|
1607
1898
|
installer.phase("download", { state: "running" });
|
|
1608
1899
|
started = Date.now();
|
|
1609
|
-
|
|
1900
|
+
const candidate = (0, import_node_crypto6.randomUUID)();
|
|
1901
|
+
const candidateDir = (0, import_node_path6.join)(dir, "candidates", candidate);
|
|
1902
|
+
await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl });
|
|
1903
|
+
const acquisition = (0, import_node_fs11.existsSync)((0, import_node_path6.join)(candidateDir, "payload", "payload.json")) ? readAcquisition((0, import_node_path6.join)(candidateDir, "payload")) : null;
|
|
1904
|
+
if (acquisition) {
|
|
1905
|
+
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
1906
|
+
installer.phase("activate", { state: "running" });
|
|
1907
|
+
let outcome;
|
|
1908
|
+
const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
|
|
1909
|
+
await installAcquisition(dir, candidate, version, acquisition, {
|
|
1910
|
+
fetchImpl,
|
|
1911
|
+
env,
|
|
1912
|
+
run: async (command2, cwd, childEnv) => {
|
|
1913
|
+
const result = options.runEntry ? options.runEntry(command2, cwd, childEnv) : await runInstallEntry(command2, cwd, childEnv, installer, readTokens(dir));
|
|
1914
|
+
if (result.outcome) outcome = validateInstallerOutcome(result.outcome);
|
|
1915
|
+
return result.ok && (result.code === void 0 || result.code === 0) && !result.outcome?.operationFailed && !result.outcome?.failed;
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
installer.phase("activate");
|
|
1919
|
+
installer.surface({ id: config.product, from: current?.version, to: version, state: "updated" });
|
|
1920
|
+
installer.finish(outcome ?? { version, total: 1, updated: 1, failed: 0, installed: true });
|
|
1921
|
+
return 0;
|
|
1922
|
+
}
|
|
1923
|
+
const target = (0, import_node_path6.join)(dir, "payload");
|
|
1924
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
1925
|
+
(0, import_node_fs11.rmSync)(target, { recursive: true, force: true });
|
|
1926
|
+
(0, import_node_fs11.renameSync)((0, import_node_path6.join)(candidateDir, "payload"), target);
|
|
1610
1927
|
writeState(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1611
1928
|
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
1612
1929
|
}
|
|
@@ -1644,36 +1961,36 @@ function payloadEnv(dir) {
|
|
|
1644
1961
|
return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
|
|
1645
1962
|
}
|
|
1646
1963
|
var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
|
|
1647
|
-
function payloadFileAsCommand(
|
|
1648
|
-
const first =
|
|
1964
|
+
function payloadFileAsCommand(entry2, payload) {
|
|
1965
|
+
const first = entry2[0];
|
|
1649
1966
|
if (!first || first === "$self") return null;
|
|
1650
1967
|
if (first.includes("/") || first.includes("\\")) return null;
|
|
1651
|
-
const candidate = (0,
|
|
1652
|
-
if (!(0,
|
|
1968
|
+
const candidate = (0, import_node_path6.join)(payload, first);
|
|
1969
|
+
if (!(0, import_node_fs11.existsSync)(candidate)) return null;
|
|
1653
1970
|
if (NEVER_A_PROGRAM.test(first)) return first;
|
|
1654
1971
|
if (process.platform === "win32") return null;
|
|
1655
1972
|
try {
|
|
1656
|
-
return ((0,
|
|
1973
|
+
return ((0, import_node_fs11.statSync)(candidate).mode & 73) === 0 ? first : null;
|
|
1657
1974
|
} catch {
|
|
1658
1975
|
return null;
|
|
1659
1976
|
}
|
|
1660
1977
|
}
|
|
1661
|
-
async function finishLastMile(config, dir, options, installer, version, unchanged) {
|
|
1662
|
-
const
|
|
1978
|
+
async function finishLastMile(config, dir, options, installer, version, unchanged, repairCommand) {
|
|
1979
|
+
const entry2 = repairCommand ?? readPayloadEntry(dir);
|
|
1663
1980
|
const payload = payloadDir(dir);
|
|
1664
|
-
if (!
|
|
1981
|
+
if (!entry2) {
|
|
1665
1982
|
installer.finish({
|
|
1666
1983
|
version,
|
|
1667
1984
|
total: 1,
|
|
1668
1985
|
updated: unchanged ? 0 : 1,
|
|
1669
1986
|
failed: 0,
|
|
1670
1987
|
installed: true,
|
|
1671
|
-
detail: `next step: run ${(0,
|
|
1988
|
+
detail: `next step: run ${(0, import_node_path6.join)(payload, config.binName)} to start ${config.product}.`
|
|
1672
1989
|
});
|
|
1673
1990
|
return 0;
|
|
1674
1991
|
}
|
|
1675
|
-
const
|
|
1676
|
-
const dataFile = payloadFileAsCommand(
|
|
1992
|
+
const command2 = resolveEntry(entry2);
|
|
1993
|
+
const dataFile = payloadFileAsCommand(entry2, payload);
|
|
1677
1994
|
if (dataFile) {
|
|
1678
1995
|
installer.finish({
|
|
1679
1996
|
version,
|
|
@@ -1686,8 +2003,9 @@ async function finishLastMile(config, dir, options, installer, version, unchange
|
|
|
1686
2003
|
}
|
|
1687
2004
|
installer.phase("activate", { state: "running" });
|
|
1688
2005
|
const started = Date.now();
|
|
1689
|
-
const
|
|
1690
|
-
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));
|
|
1691
2009
|
for (const progress of result.progress ?? []) installer.milestone(progress);
|
|
1692
2010
|
const succeeded = result.ok && (result.code === void 0 || result.code === 0);
|
|
1693
2011
|
const outcome = result.outcome ? validateInstallerOutcome(result.outcome) : void 0;
|
|
@@ -1702,18 +2020,18 @@ async function finishLastMile(config, dir, options, installer, version, unchange
|
|
|
1702
2020
|
},
|
|
1703
2021
|
...!succeeded ? {
|
|
1704
2022
|
operationFailed: true,
|
|
1705
|
-
detail: result.code === void 0 ? `Arming this machine could not start: ${result.error ??
|
|
1706
|
-
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(" ")})`
|
|
1707
2025
|
} : {}
|
|
1708
2026
|
});
|
|
1709
2027
|
return succeeded ? outcome?.operationFailed || outcome?.failed ? 1 : 0 : result.code || 1;
|
|
1710
2028
|
}
|
|
1711
|
-
async function runInstallEntry(
|
|
1712
|
-
const [
|
|
1713
|
-
const shell = needsShell(
|
|
2029
|
+
async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
2030
|
+
const [command2, ...args] = entry2;
|
|
2031
|
+
const shell = needsShell(command2);
|
|
1714
2032
|
const progress = !(process.platform === "win32" && shell);
|
|
1715
|
-
const outcomeDir = (0,
|
|
1716
|
-
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");
|
|
1717
2035
|
const childEnv = { ...env, MM_INSTALLER_OUTCOME_FILE: outcomeFile };
|
|
1718
2036
|
delete childEnv.MM_FACE_TRANSCRIPT;
|
|
1719
2037
|
delete childEnv.MM_PROGRESS_FD;
|
|
@@ -1722,8 +2040,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
|
1722
2040
|
const secrets = [tokens?.accessToken, tokens?.refreshToken].filter((value) => Boolean(value));
|
|
1723
2041
|
const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
|
|
1724
2042
|
try {
|
|
1725
|
-
const result = await new Promise((
|
|
1726
|
-
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, {
|
|
1727
2045
|
cwd,
|
|
1728
2046
|
shell,
|
|
1729
2047
|
windowsHide: true,
|
|
@@ -1759,8 +2077,8 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
|
1759
2077
|
if (pending.length > 65536) pending = "";
|
|
1760
2078
|
});
|
|
1761
2079
|
}
|
|
1762
|
-
child.on("error", (error) =>
|
|
1763
|
-
child.on("close", (code) =>
|
|
2080
|
+
child.on("error", (error) => resolve3({ ok: false, error: error.message }));
|
|
2081
|
+
child.on("close", (code) => resolve3({
|
|
1764
2082
|
ok: code === 0,
|
|
1765
2083
|
...code !== null ? { code } : {},
|
|
1766
2084
|
...code !== 0 ? { error: `exit code ${code}` } : {}
|
|
@@ -1773,25 +2091,30 @@ async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
|
1773
2091
|
return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
|
|
1774
2092
|
}
|
|
1775
2093
|
} finally {
|
|
1776
|
-
if ((0,
|
|
1777
|
-
(0,
|
|
2094
|
+
if ((0, import_node_fs11.existsSync)(outcomeFile)) (0, import_node_fs11.unlinkSync)(outcomeFile);
|
|
2095
|
+
(0, import_node_fs11.rmdirSync)(outcomeDir);
|
|
1778
2096
|
}
|
|
1779
2097
|
}
|
|
1780
|
-
function doForward(config, dir, options,
|
|
1781
|
-
if (
|
|
1782
|
-
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}\`?`);
|
|
1783
2101
|
return 2;
|
|
1784
2102
|
}
|
|
1785
|
-
const
|
|
2103
|
+
const acquired = readState(dir)?.acquired;
|
|
2104
|
+
const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
|
|
1786
2105
|
const verbs = readPayloadVerbs(dir);
|
|
1787
|
-
const declared = verbs === "*" || Array.isArray(verbs) &&
|
|
2106
|
+
const declared = verbs === "*" || Array.isArray(verbs) && command2 !== void 0 && verbs.includes(command2);
|
|
1788
2107
|
if (!target || !declared) {
|
|
1789
|
-
print(`unknown command: ${
|
|
2108
|
+
print(`unknown command: ${command2}`);
|
|
1790
2109
|
printUsage(config, print);
|
|
1791
2110
|
return 2;
|
|
1792
2111
|
}
|
|
1793
|
-
const forwarded = [...resolveEntry(target), ...
|
|
1794
|
-
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);
|
|
1795
2118
|
if (!result.ok && result.code === void 0) {
|
|
1796
2119
|
print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
|
|
1797
2120
|
return 1;
|
|
@@ -1801,10 +2124,10 @@ function doForward(config, dir, options, command, argv, print) {
|
|
|
1801
2124
|
function doAutoupdate(config, options, args, print) {
|
|
1802
2125
|
const mode = args[0] ?? "status";
|
|
1803
2126
|
const scheduleOptions = options.autoupdate ?? {};
|
|
1804
|
-
const
|
|
2127
|
+
const command2 = options.autoupdate?.command ?? [process.execPath, "update"];
|
|
1805
2128
|
if (mode === "on") {
|
|
1806
2129
|
try {
|
|
1807
|
-
enableSchedule(config,
|
|
2130
|
+
enableSchedule(config, command2, scheduleOptions);
|
|
1808
2131
|
} catch (error) {
|
|
1809
2132
|
print(error.message);
|
|
1810
2133
|
return 1;
|
|
@@ -1836,19 +2159,24 @@ function doAutoupdate(config, options, args, print) {
|
|
|
1836
2159
|
if (state.lastRun) print(`last run: ${state.lastRun}`);
|
|
1837
2160
|
return 0;
|
|
1838
2161
|
}
|
|
1839
|
-
function doDoctor(config, dir, options, print) {
|
|
2162
|
+
function doDoctor(config, dir, options, print, unlock) {
|
|
1840
2163
|
doLauncherDoctor(config, dir, options, print);
|
|
1841
|
-
const
|
|
2164
|
+
const acquired = readState(dir)?.acquired;
|
|
2165
|
+
const target = acquired ? [acquired.node, acquired.entry] : readPayloadRun(dir);
|
|
1842
2166
|
const verbs = readPayloadVerbs(dir);
|
|
1843
2167
|
const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
|
|
1844
2168
|
if (!chains) return 0;
|
|
1845
|
-
const
|
|
2169
|
+
const cwd = payloadDir(dir);
|
|
2170
|
+
const inherited = payloadEnv(dir);
|
|
2171
|
+
const env = acquired ? runtimeEnvironment(acquired.node, inherited ?? process.env) : inherited;
|
|
2172
|
+
unlock();
|
|
2173
|
+
const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], cwd, env);
|
|
1846
2174
|
return result.code ?? (result.ok ? 0 : 1);
|
|
1847
2175
|
}
|
|
1848
2176
|
function doLauncherDoctor(config, dir, options, print) {
|
|
1849
2177
|
const tokens = readTokens(dir);
|
|
1850
2178
|
const state = readState(dir);
|
|
1851
|
-
const payloadPresent = (0,
|
|
2179
|
+
const payloadPresent = (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
1852
2180
|
print(`product: ${config.product}`);
|
|
1853
2181
|
print(`host: ${config.host}`);
|
|
1854
2182
|
print(`login: ${config.loginKind}`);
|
|
@@ -1860,7 +2188,7 @@ function doLauncherDoctor(config, dir, options, print) {
|
|
|
1860
2188
|
print("token: none \u2014 run login first.");
|
|
1861
2189
|
}
|
|
1862
2190
|
print(`payload: ${state ? state.version : "none"}${payloadPresent ? "" : " (not downloaded)"}`);
|
|
1863
|
-
print(`paths: tokens ${(0,
|
|
2191
|
+
print(`paths: tokens ${(0, import_node_path6.join)(dir, "tokens.json")}, state ${(0, import_node_path6.join)(dir, "state.json")}, payload ${payloadDir(dir)}`);
|
|
1864
2192
|
const schedule = querySchedule(config, options.autoupdate ?? {});
|
|
1865
2193
|
print(
|
|
1866
2194
|
`auto-update: ${!schedule.supported ? "unsupported on this platform" : schedule.enabled ? `on (${schedule.cadence ?? "hourly"})` : "off"}`
|