@mutmutco/installer-launcher 0.1.7 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/launcher.js +611 -144
- package/dist/launcher.sea.cjs +637 -169
- package/package.json +1 -4
package/dist/launcher.sea.cjs
CHANGED
|
@@ -28,35 +28,48 @@ __export(index_exports, {
|
|
|
28
28
|
readPayloadVerbs: () => readPayloadVerbs,
|
|
29
29
|
resolveEntry: () => resolveEntry,
|
|
30
30
|
run: () => run,
|
|
31
|
-
runFile: () => runFile
|
|
31
|
+
runFile: () => runFile,
|
|
32
|
+
runInstallEntry: () => runInstallEntry
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(index_exports);
|
|
34
35
|
var import_node_child_process3 = require("node:child_process");
|
|
35
|
-
var
|
|
36
|
+
var import_node_fs9 = require("node:fs");
|
|
37
|
+
var import_node_os4 = require("node:os");
|
|
36
38
|
var import_node_path4 = require("node:path");
|
|
37
39
|
var import_node_url2 = require("node:url");
|
|
38
40
|
|
|
39
|
-
//
|
|
41
|
+
// ../face/src/face.ts
|
|
42
|
+
var import_node_fs = require("node:fs");
|
|
43
|
+
|
|
44
|
+
// ../face/src/products.ts
|
|
40
45
|
var PRODUCTS = Object.freeze({
|
|
41
46
|
"mm-strategy": Object.freeze({
|
|
42
47
|
name: "MM Strategy",
|
|
48
|
+
installWarm: "Welcome. Let's set up MM Strategy \u2014 about a minute.",
|
|
43
49
|
accent: "38;2;249;115;22",
|
|
44
|
-
warm: "Welcome. Let's set up MM Strategy \u2014 about a minute."
|
|
50
|
+
warm: "Welcome. Let's set up MM Strategy \u2014 about a minute.",
|
|
51
|
+
doctor: "mm-strategy doctor"
|
|
45
52
|
}),
|
|
46
53
|
"mmi-hub": Object.freeze({
|
|
47
54
|
name: "mmi-hub",
|
|
55
|
+
installWarm: "Welcome. Setting up mmi-hub \u2014 about a minute.",
|
|
48
56
|
accent: "38;2;125;211;252",
|
|
49
|
-
warm: "Welcome back. Checking your surfaces\u2026"
|
|
57
|
+
warm: "Welcome back. Checking your surfaces\u2026",
|
|
58
|
+
doctor: "mmi doctor"
|
|
50
59
|
}),
|
|
51
60
|
"jerv-hub": Object.freeze({
|
|
52
61
|
name: "jerv-hub",
|
|
62
|
+
installWarm: "Welcome. Setting up jerv-hub \u2014 about a minute.",
|
|
53
63
|
accent: "38;2;248;113;113",
|
|
54
|
-
warm: "Welcome back. Checking your surfaces\u2026"
|
|
64
|
+
warm: "Welcome back. Checking your surfaces\u2026",
|
|
65
|
+
doctor: "jerv doctor"
|
|
55
66
|
}),
|
|
56
67
|
jervcode: Object.freeze({
|
|
57
68
|
name: "JervCode",
|
|
69
|
+
installWarm: "Welcome. Setting up JervCode \u2014 about a minute.",
|
|
58
70
|
accent: "38;2;192;132;252",
|
|
59
|
-
warm: "Welcome back. Keeping JervCode current\u2026"
|
|
71
|
+
warm: "Welcome back. Keeping JervCode current\u2026",
|
|
72
|
+
doctor: "jervcode doctor"
|
|
60
73
|
})
|
|
61
74
|
});
|
|
62
75
|
function identityFor(product) {
|
|
@@ -66,6 +79,8 @@ function identityFor(product) {
|
|
|
66
79
|
}
|
|
67
80
|
return identity;
|
|
68
81
|
}
|
|
82
|
+
|
|
83
|
+
// ../face/src/face.ts
|
|
69
84
|
var GLYPH = Object.freeze({
|
|
70
85
|
diamond: "\u25C6",
|
|
71
86
|
hollow: "\u25C7",
|
|
@@ -90,6 +105,9 @@ var ANSI = /\u001b\[[0-9;]*m/g;
|
|
|
90
105
|
function visibleWidth(text) {
|
|
91
106
|
return [...String(text).replace(ANSI, "")].length;
|
|
92
107
|
}
|
|
108
|
+
function stripColor(text) {
|
|
109
|
+
return String(text).replace(ANSI, "");
|
|
110
|
+
}
|
|
93
111
|
function faceWidth(columns) {
|
|
94
112
|
const raw = Number(columns);
|
|
95
113
|
return Math.max(40, Math.min(100, Number.isFinite(raw) && raw > 0 ? raw : 100));
|
|
@@ -118,35 +136,64 @@ function wrapWords(text, width) {
|
|
|
118
136
|
if (line) lines.push(line);
|
|
119
137
|
return lines.length ? lines : [""];
|
|
120
138
|
}
|
|
121
|
-
|
|
139
|
+
var PROGRESS_PROTOCOL = 1;
|
|
140
|
+
function readProgressFd(env) {
|
|
141
|
+
const fd = Number.parseInt(String(env.MM_PROGRESS_FD ?? ""), 10);
|
|
142
|
+
if (!Number.isInteger(fd) || fd <= 0) return null;
|
|
143
|
+
const protocol = Number.parseInt(String(env.MM_PROGRESS_PROTOCOL ?? PROGRESS_PROTOCOL), 10);
|
|
144
|
+
return protocol === PROGRESS_PROTOCOL ? fd : null;
|
|
145
|
+
}
|
|
146
|
+
function createFace({ product, color = false, columns, env = process.env, operation }) {
|
|
122
147
|
const identity = identityFor(product);
|
|
123
148
|
const width = faceWidth(columns);
|
|
124
149
|
const paint = (sgr, text) => color ? `\x1B[${sgr}m${text}\x1B[0m` : String(text);
|
|
125
150
|
const bar = () => paint(PALETTE.muted, GLYPH.bar);
|
|
126
151
|
const indent = " ".repeat(TITLE_COLUMN - 1);
|
|
127
|
-
const
|
|
152
|
+
const continuedPhases = new Set((env.MM_FACE_CONTINUES ?? "").split(",").map((phase) => phase.trim()).filter(Boolean));
|
|
153
|
+
const continuesFace = continuedPhases.size > 0;
|
|
154
|
+
const nested = env.MM_OUTER_CONSOLE === "1";
|
|
155
|
+
const progressFd = readProgressFd(env);
|
|
156
|
+
const emitMilestone = (title, measure, kind) => {
|
|
157
|
+
if (progressFd === null) return false;
|
|
158
|
+
const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
|
|
159
|
+
if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
|
|
160
|
+
try {
|
|
161
|
+
(0, import_node_fs.writeSync)(progressFd, `${JSON.stringify(record)}
|
|
162
|
+
`);
|
|
163
|
+
return true;
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const welcome = () => continuesFace || nested ? [] : [
|
|
128
169
|
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
129
170
|
bar(),
|
|
130
|
-
`${bar()} ${identity.warm}`,
|
|
171
|
+
`${bar()} ${operation === "install" ? identity.installWarm : identity.warm}`,
|
|
131
172
|
bar()
|
|
132
173
|
];
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
174
|
+
const continues = (phase, kind = "ok") => {
|
|
175
|
+
const inherited = continuedPhases.delete(String(phase).trim());
|
|
176
|
+
return kind === "fail" ? false : inherited;
|
|
177
|
+
};
|
|
178
|
+
const step = (title, measure = null, kind = "ok") => {
|
|
179
|
+
if (emitMilestone(title, measure, kind)) return "";
|
|
180
|
+
const glyph = kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(PALETTE.green, GLYPH.check);
|
|
181
|
+
const measured = measure === null || measure === void 0 ? "" : paint(PALETTE.muted, typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : String(measure).trim());
|
|
182
|
+
const time = measured;
|
|
136
183
|
const column = Math.min(44, Math.max(0, width - 8));
|
|
137
|
-
const reserved = time ? 6 : 0;
|
|
184
|
+
const reserved = time ? Math.max(6, visibleWidth(time) + 2) : 0;
|
|
138
185
|
const [first, ...rest] = wrapWords(title, width - TITLE_COLUMN - reserved);
|
|
139
186
|
const head = `${bar()} ${glyph} ${first}`;
|
|
140
187
|
const pad = Math.max(1, column - visibleWidth(head));
|
|
141
188
|
return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
|
|
142
189
|
};
|
|
143
|
-
const relay = (text) => String(text).split("\n").
|
|
144
|
-
const receipt = (lines) => {
|
|
190
|
+
const relay = (text) => String(text).split("\n").flatMap((line) => line.trim() === "" ? [bar()] : (visibleWidth(line) <= width - TITLE_COLUMN ? [line] : wrapWords(line, width - TITLE_COLUMN)).map((part) => `${bar()}${indent}${part}`)).join("\n");
|
|
191
|
+
const receipt = (lines, { ready = true } = {}) => {
|
|
192
|
+
if (ready && nested) return [];
|
|
145
193
|
const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
|
|
146
194
|
const line = String(raw);
|
|
147
195
|
if (visibleWidth(line) <= width - 6) return [line];
|
|
148
|
-
|
|
149
|
-
return wrapWords(line, width - 6 - lead.length).map((part) => `${lead}${part}`);
|
|
196
|
+
return wrapWords(line, width - 6);
|
|
150
197
|
});
|
|
151
198
|
const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
|
|
152
199
|
const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
|
|
@@ -158,18 +205,361 @@ function createFace({ product, color = false, columns }) {
|
|
|
158
205
|
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
159
206
|
];
|
|
160
207
|
};
|
|
161
|
-
const
|
|
208
|
+
const outcome = (changed, options = {}) => {
|
|
209
|
+
const fact = String(changed ?? "").trim();
|
|
210
|
+
if (!fact) {
|
|
211
|
+
throw new Error(`installer face: the receipt must say what changed \u2014 pass the one measured fact this run produced, such as "Updated 2 of 7 surfaces to 4.4.10." (${identity.name})`);
|
|
212
|
+
}
|
|
213
|
+
return receipt([
|
|
214
|
+
`${GLYPH.check} ${identity.name} is ready.`,
|
|
215
|
+
fact,
|
|
216
|
+
`Check health any time: ${identity.doctor}`
|
|
217
|
+
], options);
|
|
218
|
+
};
|
|
219
|
+
const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
162
220
|
const refusal = (message) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message}`;
|
|
163
|
-
return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
|
|
221
|
+
return { identity, width, nested, emitsProgress: progressFd !== null, welcome, continues, step, relay, receipt, outcome, signOff, refusal, paint };
|
|
164
222
|
}
|
|
223
|
+
|
|
224
|
+
// ../face/src/shell.ts
|
|
165
225
|
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
226
|
+
|
|
227
|
+
// ../face/src/spinner.ts
|
|
228
|
+
var import_node_fs2 = require("node:fs");
|
|
229
|
+
var import_node_worker_threads = require("node:worker_threads");
|
|
166
230
|
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
231
|
+
var WORKER_SOURCE = `
|
|
232
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
233
|
+
const { writeSync, appendFileSync } = require('node:fs');
|
|
234
|
+
const control = new Int32Array(workerData.control);
|
|
235
|
+
let frames = workerData.frames, frame = workerData.frame;
|
|
236
|
+
function draw() {
|
|
237
|
+
if (Atomics.compareExchange(control, 1, 0, 1) !== 0) return;
|
|
238
|
+
try {
|
|
239
|
+
if (Atomics.load(control, 0) || Atomics.load(control, 2) || !frames.length) return;
|
|
240
|
+
const text = frames[frame++ % frames.length];
|
|
241
|
+
writeSync(2, text);
|
|
242
|
+
if (workerData.transcriptPath) appendFileSync(workerData.transcriptPath, JSON.stringify({ channel: 'spinner', text }) + '\\n');
|
|
243
|
+
} finally {
|
|
244
|
+
Atomics.store(control, 1, 0);
|
|
245
|
+
Atomics.notify(control, 1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
parentPort.on('message', (next) => {
|
|
249
|
+
if (Atomics.load(control, 2)) return;
|
|
250
|
+
frames = next.frames;
|
|
251
|
+
frame = next.frame;
|
|
252
|
+
Atomics.store(control, 0, 0);
|
|
253
|
+
});
|
|
254
|
+
setInterval(draw, workerData.intervalMs);
|
|
255
|
+
`;
|
|
256
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
257
|
+
animate = animate && !face.nested && !face.emitsProgress;
|
|
258
|
+
let timer = null;
|
|
259
|
+
let worker = null;
|
|
260
|
+
let control = null;
|
|
261
|
+
let frames = [];
|
|
262
|
+
let frame = 0;
|
|
263
|
+
const write = (text) => {
|
|
264
|
+
if (stream) stream.write(text);
|
|
265
|
+
else {
|
|
266
|
+
(0, import_node_fs2.writeSync)(2, text);
|
|
267
|
+
if (transcriptPath) (0, import_node_fs2.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
|
|
268
|
+
`);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const render = (text, measure) => {
|
|
272
|
+
const line = face.step(text, measure, "note").split("\n")[0];
|
|
273
|
+
frames = line ? FRAMES.map((glyph) => `\r\x1B[2K${line.replace(GLYPH.dot, glyph)}`) : [];
|
|
274
|
+
};
|
|
275
|
+
const draw = () => {
|
|
276
|
+
if (animate && frames.length) write(frames[frame++ % frames.length]);
|
|
277
|
+
};
|
|
278
|
+
const pause = () => {
|
|
279
|
+
if (!control) return;
|
|
280
|
+
Atomics.store(control, 0, 1);
|
|
281
|
+
while (Atomics.load(control, 1)) Atomics.wait(control, 1, 1);
|
|
282
|
+
};
|
|
283
|
+
const halt = () => {
|
|
284
|
+
if (timer) clearInterval(timer);
|
|
285
|
+
timer = null;
|
|
286
|
+
if (control) Atomics.store(control, 2, 1);
|
|
287
|
+
pause();
|
|
288
|
+
if (worker) void worker.terminate();
|
|
289
|
+
worker = null;
|
|
290
|
+
control = null;
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
start(text, measure = null) {
|
|
294
|
+
if (!animate) return;
|
|
295
|
+
halt();
|
|
296
|
+
render(text, measure);
|
|
297
|
+
frame = 0;
|
|
298
|
+
draw();
|
|
299
|
+
if (!frames.length) return;
|
|
300
|
+
if (stream) timer = setInterval(draw, intervalMs).unref();
|
|
301
|
+
else {
|
|
302
|
+
control = new Int32Array(new SharedArrayBuffer(12));
|
|
303
|
+
try {
|
|
304
|
+
worker = new import_node_worker_threads.Worker(WORKER_SOURCE, { eval: true, workerData: {
|
|
305
|
+
control: control.buffer,
|
|
306
|
+
frames,
|
|
307
|
+
frame,
|
|
308
|
+
intervalMs,
|
|
309
|
+
transcriptPath
|
|
310
|
+
} });
|
|
311
|
+
const active = worker;
|
|
312
|
+
worker.on("error", () => {
|
|
313
|
+
if (worker === active) halt();
|
|
314
|
+
});
|
|
315
|
+
worker.unref();
|
|
316
|
+
} catch {
|
|
317
|
+
halt();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
say(text, measure = null) {
|
|
322
|
+
if (!animate) return;
|
|
323
|
+
pause();
|
|
324
|
+
render(text, measure);
|
|
325
|
+
draw();
|
|
326
|
+
worker?.postMessage({ frames, frame });
|
|
327
|
+
},
|
|
328
|
+
stop() {
|
|
329
|
+
halt();
|
|
330
|
+
if (animate) write("\r\x1B[2K");
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
167
334
|
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
335
|
+
|
|
336
|
+
// ../face/src/conformance.ts
|
|
168
337
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
169
338
|
|
|
339
|
+
// ../face/src/run.ts
|
|
340
|
+
var import_node_fs4 = require("node:fs");
|
|
341
|
+
|
|
342
|
+
// ../face/src/outcome.ts
|
|
343
|
+
var import_node_fs3 = require("node:fs");
|
|
344
|
+
var counts = ["total", "updated", "failed"];
|
|
345
|
+
var strings = ["version", "retry", "detail"];
|
|
346
|
+
var flags = ["dryRun", "installed", "deferred", "operationFailed"];
|
|
347
|
+
function validateInstallerOutcome(value) {
|
|
348
|
+
const invalid = () => {
|
|
349
|
+
throw new Error("installer outcome: invalid child result");
|
|
350
|
+
};
|
|
351
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalid();
|
|
352
|
+
const facts = value;
|
|
353
|
+
const allowed = [...counts, ...strings, ...flags];
|
|
354
|
+
if (Object.keys(facts).some((key) => !allowed.includes(key))) return invalid();
|
|
355
|
+
for (const key of counts) if (!Number.isSafeInteger(facts[key]) || facts[key] < 0) return invalid();
|
|
356
|
+
if (facts.updated + facts.failed > facts.total) return invalid();
|
|
357
|
+
for (const key of strings) if (facts[key] !== void 0 && (typeof facts[key] !== "string" || facts[key].length > 4096)) return invalid();
|
|
358
|
+
for (const key of flags) if (facts[key] !== void 0 && typeof facts[key] !== "boolean") return invalid();
|
|
359
|
+
return { ...facts };
|
|
360
|
+
}
|
|
361
|
+
function writeInstallerOutcome(path, value) {
|
|
362
|
+
(0, import_node_fs3.writeFileSync)(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
|
|
363
|
+
}
|
|
364
|
+
function readInstallerOutcome(path) {
|
|
365
|
+
let text;
|
|
366
|
+
try {
|
|
367
|
+
text = (0, import_node_fs3.readFileSync)(path, "utf8");
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (error.code === "ENOENT") return void 0;
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
if (text.length > 16384) throw new Error("installer outcome: child result is too large");
|
|
373
|
+
try {
|
|
374
|
+
return validateInstallerOutcome(JSON.parse(text));
|
|
375
|
+
} catch {
|
|
376
|
+
throw new Error("installer outcome: invalid child result");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ../face/src/run.ts
|
|
381
|
+
function validateInstallerProduct(value) {
|
|
382
|
+
const fail = (field2) => {
|
|
383
|
+
throw new Error(`installer product: invalid ${field2}`);
|
|
384
|
+
};
|
|
385
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail("declaration");
|
|
386
|
+
const input = value;
|
|
387
|
+
const text = (value2, field2) => typeof value2 === "string" && value2.trim() && !/[\r\n\x00-\x1f]/u.test(value2) ? value2 : fail(field2);
|
|
388
|
+
const key = text(input.product, "product");
|
|
389
|
+
const product = { mmi: "mmi-hub", jerv: "jerv-hub" }[key] ?? key;
|
|
390
|
+
const identity = identityFor(product);
|
|
391
|
+
const gate = text(input.gate, "gate");
|
|
392
|
+
let gateUrl;
|
|
393
|
+
try {
|
|
394
|
+
gateUrl = new URL(gate);
|
|
395
|
+
} catch {
|
|
396
|
+
return fail("gate");
|
|
397
|
+
}
|
|
398
|
+
if (!["https:", "http:"].includes(gateUrl.protocol) || gateUrl.username || gateUrl.password) return fail("gate");
|
|
399
|
+
const doctor = text(input.doctor, "doctor");
|
|
400
|
+
if (doctor !== identity.doctor) return fail("doctor");
|
|
401
|
+
if (!Array.isArray(input.surfaces) || input.surfaces.length === 0) return fail("surfaces");
|
|
402
|
+
const ids = /* @__PURE__ */ new Set();
|
|
403
|
+
const surfaces = input.surfaces.map((raw) => {
|
|
404
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fail("surface");
|
|
405
|
+
const source = raw;
|
|
406
|
+
const id = text(source.id, "surface.id");
|
|
407
|
+
if (ids.has(id)) return fail("duplicate surface.id");
|
|
408
|
+
ids.add(id);
|
|
409
|
+
const surface = { id };
|
|
410
|
+
for (const field2 of ["npm", "bin", "kind", "activation"]) {
|
|
411
|
+
if (source[field2] !== void 0) surface[field2] = text(source[field2], `surface.${field2}`);
|
|
412
|
+
}
|
|
413
|
+
if (Boolean(surface.npm) !== Boolean(surface.bin)) return fail("surface npm/bin pair");
|
|
414
|
+
if (surface.kind !== void 0 && !["agent-home", "payload"].includes(surface.kind)) return fail("surface.kind");
|
|
415
|
+
if (!surface.npm && !surface.kind) return fail("surface implementation");
|
|
416
|
+
return surface;
|
|
417
|
+
});
|
|
418
|
+
return { product, gate, doctor, surfaces };
|
|
419
|
+
}
|
|
420
|
+
var PHASES = {
|
|
421
|
+
preflight: ["Checking prerequisites", "Checked prerequisites"],
|
|
422
|
+
resolve: ["Resolving the release", "Resolved the release"],
|
|
423
|
+
download: ["Downloading the payload", "Downloaded the payload"],
|
|
424
|
+
"sign-in": ["Signing in", "Signed in"],
|
|
425
|
+
check: ["Checking surfaces", "Checked surfaces"],
|
|
426
|
+
arm: ["Scheduling updates", "Armed hourly updates"],
|
|
427
|
+
"verify-release": ["Checking the release version", "Verified the release version"],
|
|
428
|
+
verify: ["Verifying the payload", "Verified the payload"],
|
|
429
|
+
install: ["Installing the product", "Installed the product"],
|
|
430
|
+
activate: ["Activating surfaces", "Activated surfaces"],
|
|
431
|
+
doctor: ["Checking health", "Checked health"]
|
|
432
|
+
};
|
|
433
|
+
function createInstallerRun(value, options = {}) {
|
|
434
|
+
const declaration = validateInstallerProduct(value);
|
|
435
|
+
const env = options.env ?? process.env;
|
|
436
|
+
const tty = options.tty ?? Boolean(process.stdout.isTTY);
|
|
437
|
+
const face = createFace({
|
|
438
|
+
operation: options.operation,
|
|
439
|
+
product: declaration.product,
|
|
440
|
+
columns: options.columns,
|
|
441
|
+
env,
|
|
442
|
+
color: tty && options.color !== false && env.NO_COLOR === void 0
|
|
443
|
+
});
|
|
444
|
+
const write = options.write ?? ((text, channel) => {
|
|
445
|
+
(channel === "stdout" ? process.stdout : process.stderr).write(text);
|
|
446
|
+
});
|
|
447
|
+
const emit = (text, channel = "stdout", recorded = text) => {
|
|
448
|
+
if (!text) return;
|
|
449
|
+
write(text, channel);
|
|
450
|
+
if (env.MM_FACE_TRANSCRIPT) {
|
|
451
|
+
(0, import_node_fs4.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
|
|
452
|
+
`, "utf8");
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
const lines = (rows, channel = "stdout") => {
|
|
456
|
+
for (const row of rows.flatMap((row2) => row2.split("\n"))) if (row) emit(`${row}
|
|
457
|
+
`, channel);
|
|
458
|
+
};
|
|
459
|
+
const spinner = createSpinner(face, {
|
|
460
|
+
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
461
|
+
...options.write ? { stream: { write: (text) => {
|
|
462
|
+
emit(String(text), "spinner");
|
|
463
|
+
return true;
|
|
464
|
+
} } } : { transcriptPath: env.MM_FACE_TRANSCRIPT }
|
|
465
|
+
});
|
|
466
|
+
let started = false;
|
|
467
|
+
let finished = false;
|
|
468
|
+
const start = () => {
|
|
469
|
+
if (started || finished) return;
|
|
470
|
+
started = true;
|
|
471
|
+
const welcome = face.welcome();
|
|
472
|
+
if (tty) lines(welcome);
|
|
473
|
+
else if (welcome.length) lines([`${face.identity.name} \u2014 Mutatis Mutandis`, options.operation === "install" ? face.identity.installWarm : face.identity.warm]);
|
|
474
|
+
};
|
|
475
|
+
const durable = (title, measure, kind) => {
|
|
476
|
+
spinner.stop();
|
|
477
|
+
const rendered = face.step(title, measure, kind);
|
|
478
|
+
if (rendered) lines([tty ? rendered : `${kind === "fail" ? "Failed: " : ""}${title}${measure === null ? "" : ` (${typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : measure})`}`]);
|
|
479
|
+
};
|
|
480
|
+
const run2 = {
|
|
481
|
+
start,
|
|
482
|
+
phase(id, facts = {}) {
|
|
483
|
+
if (finished) throw new Error("installer run already finished");
|
|
484
|
+
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
485
|
+
start();
|
|
486
|
+
const state = facts.state ?? "ok";
|
|
487
|
+
const title = PHASES[id][state === "ok" ? 1 : 0];
|
|
488
|
+
if (state === "running") {
|
|
489
|
+
spinner.start(title, facts.measure ?? null);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
493
|
+
if (facts.detail) run2.relay(facts.detail);
|
|
494
|
+
},
|
|
495
|
+
surface(facts) {
|
|
496
|
+
if (finished) throw new Error("installer run already finished");
|
|
497
|
+
const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
|
|
498
|
+
if (!surface) throw new Error("installer run: undeclared surface");
|
|
499
|
+
start();
|
|
500
|
+
const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} \u2192 ${facts.to}` : ` ${facts.to}` : "";
|
|
501
|
+
const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
|
|
502
|
+
if (!status) throw new Error("installer run: unknown surface state");
|
|
503
|
+
const activation = facts.state === "updated" && surface.activation ? ` \xB7 ${surface.activation}` : "";
|
|
504
|
+
const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
|
|
505
|
+
durable(`${facts.id}${versions} \xB7 ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
|
|
506
|
+
if (facts.detail) run2.relay(facts.detail);
|
|
507
|
+
},
|
|
508
|
+
milestone({ step, state, ms }) {
|
|
509
|
+
start();
|
|
510
|
+
durable(step, ms === void 0 ? null : ms / 1e3, state);
|
|
511
|
+
},
|
|
512
|
+
signIn({ url, code }) {
|
|
513
|
+
start();
|
|
514
|
+
spinner.stop();
|
|
515
|
+
for (const text of [`Open ${url}`, `Enter code: ${code}`]) {
|
|
516
|
+
const rendered = `${tty ? face.relay(text) : text}
|
|
517
|
+
`;
|
|
518
|
+
emit(rendered, "stdout", rendered.replace(code, "[redacted]"));
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
// Only pass safe diagnostic text, never authentication output or credentials.
|
|
522
|
+
relay(text, channel = "stdout", record = true) {
|
|
523
|
+
spinner.stop();
|
|
524
|
+
const rendered = tty ? face.relay(text) : stripColor(text);
|
|
525
|
+
for (const row of rendered.split("\n")) if (row) {
|
|
526
|
+
emit(`${row}
|
|
527
|
+
`, channel, record ? `${row}
|
|
528
|
+
` : `${tty ? face.relay("[external output omitted]") : "[external output omitted]"}
|
|
529
|
+
`);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
finish(facts) {
|
|
533
|
+
if (finished) return;
|
|
534
|
+
validateInstallerOutcome(facts);
|
|
535
|
+
if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(env.MM_INSTALLER_OUTCOME_FILE, facts);
|
|
536
|
+
start();
|
|
537
|
+
spinner.stop();
|
|
538
|
+
finished = true;
|
|
539
|
+
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}.`;
|
|
540
|
+
const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
|
|
541
|
+
const body = [
|
|
542
|
+
`${ready ? GLYPH.check : facts.dryRun ? GLYPH.dot : GLYPH.cross} ${face.identity.name}${ready ? " is ready." : facts.dryRun ? " preview complete." : facts.deferred ? " update deferred." : " is not ready yet."}`,
|
|
543
|
+
changed,
|
|
544
|
+
...facts.failed ? [`Failed ${facts.failed} of ${facts.total} surfaces.`] : [],
|
|
545
|
+
...facts.detail ? [facts.detail] : [],
|
|
546
|
+
facts.retry && facts.failed ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
|
|
547
|
+
];
|
|
548
|
+
if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE && (facts.failed > 0 || facts.operationFailed)) {
|
|
549
|
+
lines(tty ? face.receipt(body, { ready }) : body.map((row) => row.replace(/^[✔✖●] /u, "")));
|
|
550
|
+
}
|
|
551
|
+
if (tty) lines([face.signOff()]);
|
|
552
|
+
},
|
|
553
|
+
stop() {
|
|
554
|
+
spinner.stop();
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
return run2;
|
|
558
|
+
}
|
|
559
|
+
|
|
170
560
|
// src/autoupdate.ts
|
|
171
561
|
var import_node_child_process = require("node:child_process");
|
|
172
|
-
var
|
|
562
|
+
var import_node_fs5 = require("node:fs");
|
|
173
563
|
var import_node_os = require("node:os");
|
|
174
564
|
var import_node_path = require("node:path");
|
|
175
565
|
function schedulePlatform(override) {
|
|
@@ -216,9 +606,9 @@ function enableSchedule(config, command, options = {}) {
|
|
|
216
606
|
if (platform === "darwin") {
|
|
217
607
|
const label2 = scheduleLabel(config);
|
|
218
608
|
const dir2 = (0, import_node_path.join)(home, "Library", "LaunchAgents");
|
|
219
|
-
(0,
|
|
609
|
+
(0, import_node_fs5.mkdirSync)(dir2, { recursive: true });
|
|
220
610
|
const plist = (0, import_node_path.join)(dir2, `${label2}.plist`);
|
|
221
|
-
(0,
|
|
611
|
+
(0, import_node_fs5.writeFileSync)(plist, darwinPlist(label2, command));
|
|
222
612
|
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
223
613
|
const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
|
|
224
614
|
if (result2.code !== 0) {
|
|
@@ -228,9 +618,9 @@ function enableSchedule(config, command, options = {}) {
|
|
|
228
618
|
}
|
|
229
619
|
const label = scheduleLabel(config);
|
|
230
620
|
const dir = (0, import_node_path.join)(home, ".config", "systemd", "user");
|
|
231
|
-
(0,
|
|
232
|
-
(0,
|
|
233
|
-
(0,
|
|
621
|
+
(0, import_node_fs5.mkdirSync)(dir, { recursive: true });
|
|
622
|
+
(0, import_node_fs5.writeFileSync)((0, import_node_path.join)(dir, `${label}.service`), linuxService(command));
|
|
623
|
+
(0, import_node_fs5.writeFileSync)((0, import_node_path.join)(dir, `${label}.timer`), linuxTimer(label));
|
|
234
624
|
const reload = exec("systemctl", ["--user", "daemon-reload"]);
|
|
235
625
|
if (reload.code !== 0) {
|
|
236
626
|
throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
|
|
@@ -255,14 +645,14 @@ function disableSchedule(config, options = {}) {
|
|
|
255
645
|
if (platform === "darwin") {
|
|
256
646
|
const label2 = scheduleLabel(config);
|
|
257
647
|
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
258
|
-
(0,
|
|
648
|
+
(0, import_node_fs5.rmSync)((0, import_node_path.join)(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
|
|
259
649
|
return;
|
|
260
650
|
}
|
|
261
651
|
const label = scheduleLabel(config);
|
|
262
652
|
const dir = (0, import_node_path.join)(home, ".config", "systemd", "user");
|
|
263
653
|
exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
|
|
264
|
-
(0,
|
|
265
|
-
(0,
|
|
654
|
+
(0, import_node_fs5.rmSync)((0, import_node_path.join)(dir, `${label}.service`), { force: true });
|
|
655
|
+
(0, import_node_fs5.rmSync)((0, import_node_path.join)(dir, `${label}.timer`), { force: true });
|
|
266
656
|
}
|
|
267
657
|
function querySchedule(config, options = {}) {
|
|
268
658
|
const platform = schedulePlatform(options.platform);
|
|
@@ -281,11 +671,11 @@ function querySchedule(config, options = {}) {
|
|
|
281
671
|
}
|
|
282
672
|
if (platform === "darwin") {
|
|
283
673
|
const plist = (0, import_node_path.join)(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
|
|
284
|
-
if (!(0,
|
|
674
|
+
if (!(0, import_node_fs5.existsSync)(plist)) return { supported: true, enabled: false };
|
|
285
675
|
return { supported: true, enabled: true, cadence: "hourly" };
|
|
286
676
|
}
|
|
287
677
|
const timer = (0, import_node_path.join)(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
|
|
288
|
-
if (!(0,
|
|
678
|
+
if (!(0, import_node_fs5.existsSync)(timer)) return { supported: true, enabled: false };
|
|
289
679
|
const state = { supported: true, enabled: true, cadence: "hourly" };
|
|
290
680
|
const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
|
|
291
681
|
const stamp = (shown.stdout ?? "").trim();
|
|
@@ -343,7 +733,7 @@ function firstLine(text) {
|
|
|
343
733
|
}
|
|
344
734
|
|
|
345
735
|
// src/config.ts
|
|
346
|
-
var
|
|
736
|
+
var import_node_fs6 = require("node:fs");
|
|
347
737
|
var import_node_sea = require("node:sea");
|
|
348
738
|
|
|
349
739
|
// src/module-url.ts
|
|
@@ -366,10 +756,10 @@ function loadProductConfig(options = {}) {
|
|
|
366
756
|
const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
|
|
367
757
|
const devFallback = new URL("../config/product.template.json", moduleUrl());
|
|
368
758
|
if (explicit) {
|
|
369
|
-
return parseProductConfig((0,
|
|
759
|
+
return parseProductConfig((0, import_node_fs6.readFileSync)(explicit, "utf8"));
|
|
370
760
|
}
|
|
371
761
|
try {
|
|
372
|
-
return parseProductConfig((0,
|
|
762
|
+
return parseProductConfig((0, import_node_fs6.readFileSync)(devFallback, "utf8"));
|
|
373
763
|
} catch {
|
|
374
764
|
}
|
|
375
765
|
try {
|
|
@@ -488,7 +878,8 @@ async function loginGithub(config, options = {}) {
|
|
|
488
878
|
const sleep = options.sleep ?? realSleep;
|
|
489
879
|
const issued = await requestDeviceCode(config, fetchImpl);
|
|
490
880
|
const openUrl = issued.verification_uri_complete ?? issued.verification_uri;
|
|
491
|
-
|
|
881
|
+
if (options.onDeviceCode) options.onDeviceCode({ url: issued.verification_uri, code: issued.user_code });
|
|
882
|
+
else print(`To sign in, open ${issued.verification_uri} and enter the code ${issued.user_code}.`);
|
|
492
883
|
open(openUrl);
|
|
493
884
|
const deadline = now() + issued.expires_in * 1e3;
|
|
494
885
|
let intervalMs = Math.max(1, issued.interval) * 1e3;
|
|
@@ -658,7 +1049,7 @@ function page(title, body) {
|
|
|
658
1049
|
|
|
659
1050
|
// src/payload.ts
|
|
660
1051
|
var import_node_crypto2 = require("node:crypto");
|
|
661
|
-
var
|
|
1052
|
+
var import_node_fs7 = require("node:fs");
|
|
662
1053
|
var import_node_os2 = require("node:os");
|
|
663
1054
|
var import_node_path2 = require("node:path");
|
|
664
1055
|
|
|
@@ -801,7 +1192,7 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
|
|
|
801
1192
|
async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
|
|
802
1193
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
803
1194
|
const staging = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
|
|
804
|
-
(0,
|
|
1195
|
+
(0, import_node_fs7.mkdirSync)(staging, { recursive: true });
|
|
805
1196
|
try {
|
|
806
1197
|
for (const entry of manifest.files) {
|
|
807
1198
|
const bytes = await fetchFileBytes(config, accessToken, entry.path, fetchImpl);
|
|
@@ -809,22 +1200,22 @@ async function downloadAndUnpack(config, dir, manifest, accessToken, options = {
|
|
|
809
1200
|
throw new Error(`file ${entry.path} failed its checksum \u2014 refusing to install anything.`);
|
|
810
1201
|
}
|
|
811
1202
|
const dest = (0, import_node_path2.join)(staging, entry.path);
|
|
812
|
-
(0,
|
|
813
|
-
(0,
|
|
1203
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path2.dirname)(dest), { recursive: true });
|
|
1204
|
+
(0, import_node_fs7.writeFileSync)(dest, bytes);
|
|
814
1205
|
}
|
|
815
1206
|
const target = (0, import_node_path2.join)(dir, "payload");
|
|
816
|
-
(0,
|
|
817
|
-
(0,
|
|
818
|
-
(0,
|
|
1207
|
+
(0, import_node_fs7.mkdirSync)(dir, { recursive: true });
|
|
1208
|
+
(0, import_node_fs7.rmSync)(target, { force: true, recursive: true });
|
|
1209
|
+
(0, import_node_fs7.renameSync)(staging, target);
|
|
819
1210
|
} catch (error) {
|
|
820
|
-
(0,
|
|
1211
|
+
(0, import_node_fs7.rmSync)(staging, { force: true, recursive: true });
|
|
821
1212
|
throw error;
|
|
822
1213
|
}
|
|
823
1214
|
return manifest.version;
|
|
824
1215
|
}
|
|
825
1216
|
|
|
826
1217
|
// src/store.ts
|
|
827
|
-
var
|
|
1218
|
+
var import_node_fs8 = require("node:fs");
|
|
828
1219
|
var import_node_os3 = require("node:os");
|
|
829
1220
|
var import_node_path3 = require("node:path");
|
|
830
1221
|
function defaultProductDir(product) {
|
|
@@ -849,7 +1240,7 @@ function payloadDir(dir) {
|
|
|
849
1240
|
}
|
|
850
1241
|
function readTokens(dir) {
|
|
851
1242
|
try {
|
|
852
|
-
const data = JSON.parse((0,
|
|
1243
|
+
const data = JSON.parse((0, import_node_fs8.readFileSync)(tokensPath(dir), "utf8"));
|
|
853
1244
|
if (typeof data.accessToken !== "string" || !data.accessToken) return null;
|
|
854
1245
|
if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
|
|
855
1246
|
const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
|
|
@@ -861,21 +1252,21 @@ function readTokens(dir) {
|
|
|
861
1252
|
}
|
|
862
1253
|
}
|
|
863
1254
|
function writeTokens(dir, tokens) {
|
|
864
|
-
(0,
|
|
1255
|
+
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
865
1256
|
try {
|
|
866
|
-
(0,
|
|
1257
|
+
(0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
867
1258
|
`, { mode: 384 });
|
|
868
1259
|
} catch {
|
|
869
|
-
(0,
|
|
1260
|
+
(0, import_node_fs8.writeFileSync)(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
870
1261
|
`);
|
|
871
1262
|
}
|
|
872
1263
|
}
|
|
873
1264
|
function clearTokens(dir) {
|
|
874
|
-
(0,
|
|
1265
|
+
(0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
|
|
875
1266
|
}
|
|
876
1267
|
function readState(dir) {
|
|
877
1268
|
try {
|
|
878
|
-
const data = JSON.parse((0,
|
|
1269
|
+
const data = JSON.parse((0, import_node_fs8.readFileSync)(statePath(dir), "utf8"));
|
|
879
1270
|
if (typeof data.version !== "string" || !data.version) return null;
|
|
880
1271
|
return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
|
|
881
1272
|
} catch {
|
|
@@ -883,18 +1274,18 @@ function readState(dir) {
|
|
|
883
1274
|
}
|
|
884
1275
|
}
|
|
885
1276
|
function writeState(dir, state) {
|
|
886
|
-
(0,
|
|
887
|
-
(0,
|
|
1277
|
+
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
1278
|
+
(0, import_node_fs8.writeFileSync)(statePath(dir), `${JSON.stringify(state, null, 2)}
|
|
888
1279
|
`);
|
|
889
1280
|
}
|
|
890
1281
|
function wipeProductDir(dir) {
|
|
891
|
-
(0,
|
|
892
|
-
(0,
|
|
893
|
-
(0,
|
|
1282
|
+
(0, import_node_fs8.rmSync)(tokensPath(dir), { force: true });
|
|
1283
|
+
(0, import_node_fs8.rmSync)(statePath(dir), { force: true });
|
|
1284
|
+
(0, import_node_fs8.rmSync)(payloadDir(dir), { force: true, recursive: true });
|
|
894
1285
|
}
|
|
895
1286
|
|
|
896
1287
|
// src/index.ts
|
|
897
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1288
|
+
var LAUNCHER_VERSION = true ? "0.1.10" : readVersionFromPackage();
|
|
898
1289
|
function defaultPrint(message) {
|
|
899
1290
|
process.stdout.write(`${message}
|
|
900
1291
|
`);
|
|
@@ -974,7 +1365,7 @@ async function runFile(file, args, printErr) {
|
|
|
974
1365
|
return 1;
|
|
975
1366
|
}
|
|
976
1367
|
const abs = (0, import_node_path4.resolve)(process.cwd(), file);
|
|
977
|
-
if (!(0,
|
|
1368
|
+
if (!(0, import_node_fs9.existsSync)(abs)) {
|
|
978
1369
|
printErr(`cannot run ${file}: no such file.`);
|
|
979
1370
|
return 1;
|
|
980
1371
|
}
|
|
@@ -1030,11 +1421,16 @@ async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
|
1030
1421
|
});
|
|
1031
1422
|
return refreshed.accessToken;
|
|
1032
1423
|
}
|
|
1033
|
-
async function doLogin(config, dir, options, print) {
|
|
1424
|
+
async function doLogin(config, dir, options, print, installer) {
|
|
1034
1425
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1035
1426
|
const open = options.open ?? openBrowser;
|
|
1036
1427
|
if (config.loginKind === "github") {
|
|
1037
|
-
const tokens = await loginGithub(config, {
|
|
1428
|
+
const tokens = await loginGithub(config, {
|
|
1429
|
+
fetchImpl,
|
|
1430
|
+
open,
|
|
1431
|
+
print,
|
|
1432
|
+
...installer ? { onDeviceCode: (prompt) => installer.signIn(prompt) } : {}
|
|
1433
|
+
});
|
|
1038
1434
|
writeTokens(dir, {
|
|
1039
1435
|
accessToken: tokens.accessToken,
|
|
1040
1436
|
refreshToken: tokens.refreshToken,
|
|
@@ -1049,20 +1445,20 @@ async function doLogin(config, dir, options, print) {
|
|
|
1049
1445
|
clientId: tokens.clientId
|
|
1050
1446
|
});
|
|
1051
1447
|
}
|
|
1052
|
-
print(`signed in to ${config.product}.`);
|
|
1448
|
+
if (!installer) print(`signed in to ${config.product}.`);
|
|
1053
1449
|
}
|
|
1054
|
-
async function fetchAccessTokenOrLogin(config, dir, options, print) {
|
|
1450
|
+
async function fetchAccessTokenOrLogin(config, dir, options, print, installer) {
|
|
1055
1451
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1056
1452
|
const fresh = await ensureFreshToken(config, dir, fetchImpl);
|
|
1057
1453
|
if (fresh) return fresh;
|
|
1058
|
-
await doLogin(config, dir, options, print);
|
|
1454
|
+
await doLogin(config, dir, options, print, installer);
|
|
1059
1455
|
const after = readTokens(dir);
|
|
1060
1456
|
if (!after) throw new Error("sign-in did not produce a token");
|
|
1061
1457
|
return after.accessToken;
|
|
1062
1458
|
}
|
|
1063
1459
|
function readPayloadArgv(dir, key) {
|
|
1064
1460
|
try {
|
|
1065
|
-
const parsed = JSON.parse((0,
|
|
1461
|
+
const parsed = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(payloadDir(dir), "payload.json"), "utf8"));
|
|
1066
1462
|
const entry = parsed[key];
|
|
1067
1463
|
if (typeof entry === "string") {
|
|
1068
1464
|
const parts = entry.trim().split(/\s+/).filter(Boolean);
|
|
@@ -1084,7 +1480,7 @@ function readPayloadRun(dir) {
|
|
|
1084
1480
|
}
|
|
1085
1481
|
function readPayloadVerbs(dir) {
|
|
1086
1482
|
try {
|
|
1087
|
-
const parsed = JSON.parse((0,
|
|
1483
|
+
const parsed = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(payloadDir(dir), "payload.json"), "utf8"));
|
|
1088
1484
|
const verbs = parsed.verbs;
|
|
1089
1485
|
if (verbs === "*") return "*";
|
|
1090
1486
|
if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
|
|
@@ -1101,7 +1497,7 @@ function resolveEntry(entry) {
|
|
|
1101
1497
|
function needsShell(command) {
|
|
1102
1498
|
if (process.platform !== "win32") return false;
|
|
1103
1499
|
if (/\.(cmd|bat)$/i.test(command)) return true;
|
|
1104
|
-
return !(0,
|
|
1500
|
+
return !(0, import_node_fs9.existsSync)(command);
|
|
1105
1501
|
}
|
|
1106
1502
|
function quoteForShell(arg) {
|
|
1107
1503
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
@@ -1132,7 +1528,7 @@ function defaultRunEntry(entry, cwd, env) {
|
|
|
1132
1528
|
const [command, ...args] = entry;
|
|
1133
1529
|
const shell = needsShell(command);
|
|
1134
1530
|
const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
|
|
1135
|
-
const
|
|
1531
|
+
const spawnEntry = (progress2) => (0, import_node_child_process3.spawnSync)(commandLine, shell ? [] : args, {
|
|
1136
1532
|
cwd,
|
|
1137
1533
|
stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
|
|
1138
1534
|
shell,
|
|
@@ -1141,12 +1537,12 @@ function defaultRunEntry(entry, cwd, env) {
|
|
|
1141
1537
|
});
|
|
1142
1538
|
let result;
|
|
1143
1539
|
if (process.platform === "win32" && shell) {
|
|
1144
|
-
result =
|
|
1540
|
+
result = spawnEntry(false);
|
|
1145
1541
|
} else {
|
|
1146
1542
|
try {
|
|
1147
|
-
result =
|
|
1543
|
+
result = spawnEntry(true);
|
|
1148
1544
|
} catch {
|
|
1149
|
-
result =
|
|
1545
|
+
result = spawnEntry(false);
|
|
1150
1546
|
}
|
|
1151
1547
|
}
|
|
1152
1548
|
if (result.error) return { ok: false, error: result.error.message };
|
|
@@ -1163,73 +1559,71 @@ function faceProduct(config) {
|
|
|
1163
1559
|
const known = { mmi: "mmi-hub", jerv: "jerv-hub" };
|
|
1164
1560
|
return known[config.product] ?? config.product;
|
|
1165
1561
|
}
|
|
1166
|
-
function
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1562
|
+
async function installOrUpdate(config, dir, options, print, update) {
|
|
1563
|
+
const product = faceProduct(config);
|
|
1564
|
+
const installer = createInstallerRun({
|
|
1565
|
+
product,
|
|
1566
|
+
gate: config.host,
|
|
1567
|
+
doctor: identityFor(product).doctor,
|
|
1568
|
+
surfaces: [{ id: config.product, kind: "payload" }]
|
|
1569
|
+
}, {
|
|
1570
|
+
operation: update ? "update" : "install",
|
|
1571
|
+
tty: options.tty,
|
|
1572
|
+
animate: !options.print && Boolean(process.stderr.isTTY),
|
|
1573
|
+
write: (text, channel) => {
|
|
1574
|
+
if (options.print) print(text.replace(/\n$/, ""));
|
|
1575
|
+
else (channel === "stdout" ? process.stdout : process.stderr).write(text);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1579
|
+
let version = readState(dir)?.version ?? "unknown";
|
|
1580
|
+
installer.start();
|
|
1169
1581
|
try {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1582
|
+
let started = Date.now();
|
|
1583
|
+
installer.phase("sign-in", { state: "running" });
|
|
1584
|
+
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print, installer);
|
|
1585
|
+
installer.phase("sign-in", { seconds: (Date.now() - started) / 1e3 });
|
|
1586
|
+
installer.phase("resolve", { state: "running" });
|
|
1587
|
+
started = Date.now();
|
|
1588
|
+
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
1589
|
+
version = manifest.version;
|
|
1590
|
+
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
1591
|
+
const current = readState(dir);
|
|
1592
|
+
const unchanged = update && current?.version === version && (0, import_node_fs9.existsSync)(payloadDir(dir));
|
|
1593
|
+
if (!unchanged) {
|
|
1594
|
+
installer.phase("download", { state: "running" });
|
|
1595
|
+
started = Date.now();
|
|
1596
|
+
await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
|
|
1597
|
+
writeState(dir, { version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1598
|
+
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
1599
|
+
}
|
|
1600
|
+
installer.surface({
|
|
1601
|
+
id: config.product,
|
|
1602
|
+
from: current?.version,
|
|
1603
|
+
to: version,
|
|
1604
|
+
state: unchanged ? "current" : "updated"
|
|
1174
1605
|
});
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (!face) {
|
|
1190
|
-
print(headline);
|
|
1191
|
-
for (const line of lines) print(line.trim());
|
|
1192
|
-
return;
|
|
1606
|
+
return await finishLastMile(config, dir, options, installer, version, unchanged);
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
const code = error instanceof NeedsLoginError ? 3 : 1;
|
|
1609
|
+
installer.finish({
|
|
1610
|
+
version,
|
|
1611
|
+
total: 1,
|
|
1612
|
+
updated: 0,
|
|
1613
|
+
failed: 1,
|
|
1614
|
+
detail: error.message,
|
|
1615
|
+
retry: `${config.binName} ${update ? "update" : "install"}`
|
|
1616
|
+
});
|
|
1617
|
+
return code;
|
|
1618
|
+
} finally {
|
|
1619
|
+
installer.stop();
|
|
1193
1620
|
}
|
|
1194
|
-
const glyph = ready ? "\u2714" : "\u2716";
|
|
1195
|
-
for (const line of face.receipt([`${glyph} ${headline}`, ...lines])) print(line);
|
|
1196
|
-
print(face.signOff());
|
|
1197
1621
|
}
|
|
1198
|
-
function
|
|
1199
|
-
|
|
1622
|
+
function doInstall(config, dir, options, print) {
|
|
1623
|
+
return installOrUpdate(config, dir, options, print, false);
|
|
1200
1624
|
}
|
|
1201
|
-
function
|
|
1202
|
-
|
|
1203
|
-
printStep(face, print, message.step, message.ms === void 0 ? null : message.ms / 1e3, message.state);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
async function doInstall(config, dir, options, print) {
|
|
1207
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1208
|
-
const face = faceFor(config, options);
|
|
1209
|
-
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print);
|
|
1210
|
-
const started = Date.now();
|
|
1211
|
-
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
1212
|
-
await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
|
|
1213
|
-
writeState(dir, { version: manifest.version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1214
|
-
printStep(face, print, `installed ${config.product} ${manifest.version}`, since(started));
|
|
1215
|
-
return finishLastMile(config, dir, options, print, face, manifest.version);
|
|
1216
|
-
}
|
|
1217
|
-
async function doUpdate(config, dir, options, print) {
|
|
1218
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1219
|
-
const face = faceFor(config, options);
|
|
1220
|
-
if (face && !outerConsoleOwnsOutcome()) for (const line of face.welcome()) print(line);
|
|
1221
|
-
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print);
|
|
1222
|
-
const started = Date.now();
|
|
1223
|
-
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
1224
|
-
const current = readState(dir);
|
|
1225
|
-
if (current && current.version === manifest.version && (0, import_node_fs5.existsSync)(payloadDir(dir))) {
|
|
1226
|
-
printStep(face, print, `${config.product} is already up to date at ${manifest.version}`, since(started));
|
|
1227
|
-
return finishLastMile(config, dir, options, print, face, manifest.version);
|
|
1228
|
-
}
|
|
1229
|
-
await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
|
|
1230
|
-
writeState(dir, { version: manifest.version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1231
|
-
printStep(face, print, `updated ${config.product} to ${manifest.version}`, since(started));
|
|
1232
|
-
return finishLastMile(config, dir, options, print, face, manifest.version);
|
|
1625
|
+
function doUpdate(config, dir, options, print) {
|
|
1626
|
+
return installOrUpdate(config, dir, options, print, true);
|
|
1233
1627
|
}
|
|
1234
1628
|
function payloadEnv(dir) {
|
|
1235
1629
|
const stored = readTokens(dir);
|
|
@@ -1242,63 +1636,136 @@ function payloadFileAsCommand(entry, payload) {
|
|
|
1242
1636
|
if (!first || first === "$self") return null;
|
|
1243
1637
|
if (first.includes("/") || first.includes("\\")) return null;
|
|
1244
1638
|
const candidate = (0, import_node_path4.join)(payload, first);
|
|
1245
|
-
if (!(0,
|
|
1639
|
+
if (!(0, import_node_fs9.existsSync)(candidate)) return null;
|
|
1246
1640
|
if (NEVER_A_PROGRAM.test(first)) return first;
|
|
1247
1641
|
if (process.platform === "win32") return null;
|
|
1248
1642
|
try {
|
|
1249
|
-
return ((0,
|
|
1643
|
+
return ((0, import_node_fs9.statSync)(candidate).mode & 73) === 0 ? first : null;
|
|
1250
1644
|
} catch {
|
|
1251
1645
|
return null;
|
|
1252
1646
|
}
|
|
1253
1647
|
}
|
|
1254
|
-
function finishLastMile(config, dir, options,
|
|
1648
|
+
async function finishLastMile(config, dir, options, installer, version, unchanged) {
|
|
1255
1649
|
const entry = readPayloadEntry(dir);
|
|
1256
1650
|
const payload = payloadDir(dir);
|
|
1257
1651
|
if (!entry) {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1652
|
+
installer.finish({
|
|
1653
|
+
version,
|
|
1654
|
+
total: 1,
|
|
1655
|
+
updated: unchanged ? 0 : 1,
|
|
1656
|
+
failed: 0,
|
|
1657
|
+
installed: true,
|
|
1658
|
+
detail: `next step: run ${(0, import_node_path4.join)(payload, config.binName)} to start ${config.product}.`
|
|
1659
|
+
});
|
|
1262
1660
|
return 0;
|
|
1263
1661
|
}
|
|
1264
1662
|
const command = resolveEntry(entry);
|
|
1265
1663
|
const dataFile = payloadFileAsCommand(entry, payload);
|
|
1266
1664
|
if (dataFile) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1665
|
+
installer.finish({
|
|
1666
|
+
version,
|
|
1667
|
+
total: 1,
|
|
1668
|
+
updated: 0,
|
|
1669
|
+
failed: 1,
|
|
1670
|
+
detail: `The payload names ${dataFile} as its command, but that is a file, not a program. This payload was built wrong: its entry must be a command.`
|
|
1671
|
+
});
|
|
1273
1672
|
return 2;
|
|
1274
1673
|
}
|
|
1674
|
+
installer.phase("activate", { state: "running" });
|
|
1275
1675
|
const started = Date.now();
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1676
|
+
const env = { ...payloadEnv(dir) ?? process.env, MM_OUTER_CONSOLE: "1" };
|
|
1677
|
+
const result = options.runEntry ? options.runEntry(command, payload, env) : await runInstallEntry(command, payload, env, installer, readTokens(dir));
|
|
1678
|
+
for (const progress of result.progress ?? []) installer.milestone(progress);
|
|
1679
|
+
const succeeded = result.ok && (result.code === void 0 || result.code === 0);
|
|
1680
|
+
const outcome = result.outcome ? validateInstallerOutcome(result.outcome) : void 0;
|
|
1681
|
+
installer.phase("activate", { state: succeeded ? "ok" : "fail", seconds: (Date.now() - started) / 1e3 });
|
|
1682
|
+
installer.finish({
|
|
1683
|
+
...outcome ?? {
|
|
1684
|
+
version,
|
|
1685
|
+
total: 1,
|
|
1686
|
+
updated: succeeded && !unchanged ? 1 : 0,
|
|
1687
|
+
failed: succeeded ? 0 : 1,
|
|
1688
|
+
installed: true
|
|
1689
|
+
},
|
|
1690
|
+
...!succeeded ? {
|
|
1691
|
+
operationFailed: true,
|
|
1692
|
+
detail: result.code === void 0 ? `Arming this machine could not start: ${result.error ?? command[0]}` : `Arming this machine did not finish (exit code ${result.code})${result.error?.startsWith("installer outcome:") ? `: ${result.error}` : ""}`,
|
|
1693
|
+
retry: `(cd ${payload} && ${command.join(" ")})`
|
|
1694
|
+
} : {}
|
|
1695
|
+
});
|
|
1696
|
+
return succeeded ? outcome?.operationFailed || outcome?.failed ? 1 : 0 : result.code || 1;
|
|
1697
|
+
}
|
|
1698
|
+
async function runInstallEntry(entry, cwd, env, installer, tokens = null) {
|
|
1699
|
+
const [command, ...args] = entry;
|
|
1700
|
+
const shell = needsShell(command);
|
|
1701
|
+
const progress = !(process.platform === "win32" && shell);
|
|
1702
|
+
const outcomeDir = (0, import_node_fs9.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "mm-installer-outcome-"));
|
|
1703
|
+
const outcomeFile = (0, import_node_path4.join)(outcomeDir, "outcome.json");
|
|
1704
|
+
const childEnv = { ...env, MM_INSTALLER_OUTCOME_FILE: outcomeFile };
|
|
1705
|
+
delete childEnv.MM_FACE_TRANSCRIPT;
|
|
1706
|
+
delete childEnv.MM_PROGRESS_FD;
|
|
1707
|
+
delete childEnv.MM_PROGRESS_PROTOCOL;
|
|
1708
|
+
if (progress) Object.assign(childEnv, { MM_PROGRESS_FD: "3", MM_PROGRESS_PROTOCOL: "1" });
|
|
1709
|
+
const secrets = [tokens?.accessToken, tokens?.refreshToken].filter((value) => Boolean(value));
|
|
1710
|
+
const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
|
|
1711
|
+
try {
|
|
1712
|
+
const result = await new Promise((resolve2) => {
|
|
1713
|
+
const child = (0, import_node_child_process3.spawn)(shell ? [command, ...args].map(quoteForShell).join(" ") : command, shell ? [] : args, {
|
|
1714
|
+
cwd,
|
|
1715
|
+
shell,
|
|
1716
|
+
windowsHide: true,
|
|
1717
|
+
env: childEnv,
|
|
1718
|
+
stdio: progress ? ["inherit", "pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
1719
|
+
});
|
|
1720
|
+
for (const [stream, channel2] of [[child.stdout, "stdout"], [child.stderr, "stderr"]]) {
|
|
1721
|
+
let partial = "";
|
|
1722
|
+
stream?.setEncoding("utf8").on("data", (text) => {
|
|
1723
|
+
const safe = redact(partial + text);
|
|
1724
|
+
let held = 0;
|
|
1725
|
+
for (const secret of secrets) for (let length = 1; length < secret.length; length++) {
|
|
1726
|
+
if (safe.endsWith(secret.slice(0, length))) held = Math.max(held, length);
|
|
1727
|
+
}
|
|
1728
|
+
partial = held ? safe.slice(-held) : "";
|
|
1729
|
+
const visible = held ? safe.slice(0, -held) : safe;
|
|
1730
|
+
if (visible) installer.relay(visible, channel2, false);
|
|
1731
|
+
}).on("end", () => {
|
|
1732
|
+
if (partial) installer.relay("[redacted]", channel2, false);
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
let pending = "";
|
|
1736
|
+
const channel = child.stdio[3];
|
|
1737
|
+
if (channel && "setEncoding" in channel) {
|
|
1738
|
+
channel.setEncoding("utf8");
|
|
1739
|
+
channel.on("data", (text) => {
|
|
1740
|
+
pending += text;
|
|
1741
|
+
const end = pending.lastIndexOf("\n");
|
|
1742
|
+
if (end >= 0) {
|
|
1743
|
+
for (const record of parseProgress(pending.slice(0, end))) installer.milestone({ ...record, step: redact(record.step) });
|
|
1744
|
+
pending = pending.slice(end + 1);
|
|
1745
|
+
}
|
|
1746
|
+
if (pending.length > 65536) pending = "";
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
child.on("error", (error) => resolve2({ ok: false, error: error.message }));
|
|
1750
|
+
child.on("close", (code) => resolve2({
|
|
1751
|
+
ok: code === 0,
|
|
1752
|
+
...code !== null ? { code } : {},
|
|
1753
|
+
...code !== 0 ? { error: `exit code ${code}` } : {}
|
|
1754
|
+
}));
|
|
1755
|
+
});
|
|
1756
|
+
try {
|
|
1757
|
+
const outcome = readInstallerOutcome(outcomeFile);
|
|
1758
|
+
return { ...result, ...outcome ? { outcome } : {} };
|
|
1759
|
+
} catch {
|
|
1760
|
+
return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
|
|
1761
|
+
}
|
|
1762
|
+
} finally {
|
|
1763
|
+
if ((0, import_node_fs9.existsSync)(outcomeFile)) (0, import_node_fs9.unlinkSync)(outcomeFile);
|
|
1764
|
+
(0, import_node_fs9.rmdirSync)(outcomeDir);
|
|
1285
1765
|
}
|
|
1286
|
-
const code = result.code ?? 1;
|
|
1287
|
-
printStep(
|
|
1288
|
-
face,
|
|
1289
|
-
print,
|
|
1290
|
-
result.code === void 0 ? `Arming this machine could not start: ${result.error ?? `could not run ${command[0]}`}` : `Arming this machine did not finish (exit code ${code})`,
|
|
1291
|
-
null,
|
|
1292
|
-
"fail"
|
|
1293
|
-
);
|
|
1294
|
-
printReceipt(face, config, print, false, [
|
|
1295
|
-
`Downloaded ${version} into ${dir}`,
|
|
1296
|
-
`Finish it with: (cd ${payload} && ${command.join(" ")})`
|
|
1297
|
-
]);
|
|
1298
|
-
return code;
|
|
1299
1766
|
}
|
|
1300
1767
|
function doForward(config, dir, options, command, argv, print) {
|
|
1301
|
-
if (command && (0,
|
|
1768
|
+
if (command && (0, import_node_fs9.existsSync)(command)) {
|
|
1302
1769
|
print(`${command} is a file, not a command \u2014 did you mean \`--run ${command}\`?`);
|
|
1303
1770
|
return 2;
|
|
1304
1771
|
}
|
|
@@ -1368,7 +1835,7 @@ function doDoctor(config, dir, options, print) {
|
|
|
1368
1835
|
function doLauncherDoctor(config, dir, options, print) {
|
|
1369
1836
|
const tokens = readTokens(dir);
|
|
1370
1837
|
const state = readState(dir);
|
|
1371
|
-
const payloadPresent = (0,
|
|
1838
|
+
const payloadPresent = (0, import_node_fs9.existsSync)(payloadDir(dir));
|
|
1372
1839
|
print(`product: ${config.product}`);
|
|
1373
1840
|
print(`host: ${config.host}`);
|
|
1374
1841
|
print(`login: ${config.loginKind}`);
|
|
@@ -1419,5 +1886,6 @@ if (invokedAsMain) {
|
|
|
1419
1886
|
readPayloadVerbs,
|
|
1420
1887
|
resolveEntry,
|
|
1421
1888
|
run,
|
|
1422
|
-
runFile
|
|
1889
|
+
runFile,
|
|
1890
|
+
runInstallEntry
|
|
1423
1891
|
});
|