@mutmutco/installer-launcher 0.1.2 → 0.1.4
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 +573 -77
- package/dist/launcher.sea.cjs +579 -81
- package/package.json +4 -1
package/dist/launcher.js
CHANGED
|
@@ -1,13 +1,319 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { dirname as dirname2, join as
|
|
4
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
5
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
6
|
+
import { dirname as dirname2, join as join4, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
8
8
|
|
|
9
|
+
// node_modules/@mutmutco/installer-face/dist/index.js
|
|
10
|
+
var PRODUCTS = Object.freeze({
|
|
11
|
+
"mm-strategy": Object.freeze({
|
|
12
|
+
name: "MM Strategy",
|
|
13
|
+
accent: "38;2;249;115;22",
|
|
14
|
+
warm: "Welcome. Let's set up MM Strategy \u2014 about a minute."
|
|
15
|
+
}),
|
|
16
|
+
"mmi-hub": Object.freeze({
|
|
17
|
+
name: "mmi-hub",
|
|
18
|
+
accent: "38;2;125;211;252",
|
|
19
|
+
warm: "Welcome back. Checking your surfaces\u2026"
|
|
20
|
+
}),
|
|
21
|
+
"jerv-hub": Object.freeze({
|
|
22
|
+
name: "jerv-hub",
|
|
23
|
+
accent: "38;2;248;113;113",
|
|
24
|
+
warm: "Welcome back. Checking your surfaces\u2026"
|
|
25
|
+
}),
|
|
26
|
+
jervcode: Object.freeze({
|
|
27
|
+
name: "JervCode",
|
|
28
|
+
accent: "38;2;192;132;252",
|
|
29
|
+
warm: "Welcome back. Keeping JervCode current\u2026"
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
function identityFor(product) {
|
|
33
|
+
const identity = PRODUCTS[product];
|
|
34
|
+
if (!identity) {
|
|
35
|
+
throw new Error(`unknown product ${JSON.stringify(product)} \u2014 add it to installer/face/src/products.ts rather than composing a greeting locally (known: ${Object.keys(PRODUCTS).join(", ")})`);
|
|
36
|
+
}
|
|
37
|
+
return identity;
|
|
38
|
+
}
|
|
39
|
+
var GLYPH = Object.freeze({
|
|
40
|
+
diamond: "\u25C6",
|
|
41
|
+
hollow: "\u25C7",
|
|
42
|
+
bar: "\u2502",
|
|
43
|
+
check: "\u2714",
|
|
44
|
+
cross: "\u2716",
|
|
45
|
+
dot: "\u25CF",
|
|
46
|
+
boxTop: "\u256D",
|
|
47
|
+
boxTopEnd: "\u256E",
|
|
48
|
+
boxBottom: "\u2570",
|
|
49
|
+
boxBottomEnd: "\u256F",
|
|
50
|
+
rule: "\u2500"
|
|
51
|
+
});
|
|
52
|
+
var PALETTE = Object.freeze({
|
|
53
|
+
green: "38;2;31;209;138",
|
|
54
|
+
ink: "38;2;237;237;237",
|
|
55
|
+
muted: "38;2;150;150;150",
|
|
56
|
+
line: "38;2;42;42;42"
|
|
57
|
+
});
|
|
58
|
+
var TITLE_COLUMN = 6;
|
|
59
|
+
var ANSI = /\u001b\[[0-9;]*m/g;
|
|
60
|
+
function visibleWidth(text) {
|
|
61
|
+
return [...String(text).replace(ANSI, "")].length;
|
|
62
|
+
}
|
|
63
|
+
function faceWidth(columns) {
|
|
64
|
+
const raw = Number(columns);
|
|
65
|
+
return Math.max(40, Math.min(100, Number.isFinite(raw) && raw > 0 ? raw : 100));
|
|
66
|
+
}
|
|
67
|
+
function wrapWords(text, width) {
|
|
68
|
+
const limit = Math.max(8, Math.floor(width));
|
|
69
|
+
const lines = [];
|
|
70
|
+
let line = "";
|
|
71
|
+
for (const word of String(text).split(/\s+/u).filter(Boolean)) {
|
|
72
|
+
let token = word;
|
|
73
|
+
while (visibleWidth(token) > limit) {
|
|
74
|
+
if (line) {
|
|
75
|
+
lines.push(line);
|
|
76
|
+
line = "";
|
|
77
|
+
}
|
|
78
|
+
lines.push([...token].slice(0, limit).join(""));
|
|
79
|
+
token = [...token].slice(limit).join("");
|
|
80
|
+
}
|
|
81
|
+
if (!line) line = token;
|
|
82
|
+
else if (visibleWidth(line) + 1 + visibleWidth(token) <= limit) line += ` ${token}`;
|
|
83
|
+
else {
|
|
84
|
+
lines.push(line);
|
|
85
|
+
line = token;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (line) lines.push(line);
|
|
89
|
+
return lines.length ? lines : [""];
|
|
90
|
+
}
|
|
91
|
+
function createFace({ product, color = false, columns }) {
|
|
92
|
+
const identity = identityFor(product);
|
|
93
|
+
const width = faceWidth(columns);
|
|
94
|
+
const paint = (sgr, text) => color ? `\x1B[${sgr}m${text}\x1B[0m` : String(text);
|
|
95
|
+
const bar = () => paint(PALETTE.muted, GLYPH.bar);
|
|
96
|
+
const indent = " ".repeat(TITLE_COLUMN - 1);
|
|
97
|
+
const welcome = () => [
|
|
98
|
+
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
99
|
+
bar(),
|
|
100
|
+
`${bar()} ${identity.warm}`,
|
|
101
|
+
bar()
|
|
102
|
+
];
|
|
103
|
+
const step = (title, seconds = null, kind = "ok") => {
|
|
104
|
+
const glyph = kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.hollow) : paint(PALETTE.green, GLYPH.check);
|
|
105
|
+
const time = seconds === null || seconds === void 0 ? "" : paint(PALETTE.muted, `${Math.max(0, Math.round(seconds))}s`);
|
|
106
|
+
const column = Math.min(44, Math.max(0, width - 8));
|
|
107
|
+
const reserved = time ? 6 : 0;
|
|
108
|
+
const [first, ...rest] = wrapWords(title, width - TITLE_COLUMN - reserved);
|
|
109
|
+
const head = `${bar()} ${glyph} ${first}`;
|
|
110
|
+
const pad = Math.max(1, column - visibleWidth(head));
|
|
111
|
+
return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
|
|
112
|
+
};
|
|
113
|
+
const relay = (text) => String(text).split("\n").map((line) => line.trim() === "" ? bar() : `${bar()}${indent}${line}`).join("\n");
|
|
114
|
+
const receipt = (lines) => {
|
|
115
|
+
const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
|
|
116
|
+
const line = String(raw);
|
|
117
|
+
if (visibleWidth(line) <= width - 6) return [line];
|
|
118
|
+
const lead = /^\s*/u.exec(line)?.[0] ?? "";
|
|
119
|
+
return wrapWords(line, width - 6 - lead.length).map((part) => `${lead}${part}`);
|
|
120
|
+
});
|
|
121
|
+
const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
|
|
122
|
+
const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
|
|
123
|
+
const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
|
|
124
|
+
const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(PALETTE.green, GLYPH.check)}${line.slice(1)}` : line;
|
|
125
|
+
return [
|
|
126
|
+
frame(GLYPH.boxTop, GLYPH.boxTopEnd),
|
|
127
|
+
...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
|
|
128
|
+
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
129
|
+
];
|
|
130
|
+
};
|
|
131
|
+
const signOff = () => `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
132
|
+
const refusal = (message) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message}`;
|
|
133
|
+
return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
|
|
134
|
+
}
|
|
135
|
+
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
136
|
+
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
137
|
+
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
138
|
+
var ALLOWED = new Set(Object.values(GLYPH));
|
|
139
|
+
|
|
140
|
+
// src/autoupdate.ts
|
|
141
|
+
import { spawnSync } from "node:child_process";
|
|
142
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
143
|
+
import { tmpdir } from "node:os";
|
|
144
|
+
import { join } from "node:path";
|
|
145
|
+
function schedulePlatform(override) {
|
|
146
|
+
const platform = override ?? process.platform;
|
|
147
|
+
if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function defaultExec(command, args) {
|
|
151
|
+
const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
|
|
152
|
+
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
153
|
+
return {
|
|
154
|
+
code: typeof result.status === "number" ? result.status : 1,
|
|
155
|
+
stdout: String(result.stdout ?? ""),
|
|
156
|
+
stderr: String(result.stderr ?? "")
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function homeOf(options) {
|
|
160
|
+
if (options.homeDir) return options.homeDir;
|
|
161
|
+
if (process.platform === "win32") return process.env.USERPROFILE ?? tmpdir();
|
|
162
|
+
return process.env.HOME ?? tmpdir();
|
|
163
|
+
}
|
|
164
|
+
function scheduleName(config) {
|
|
165
|
+
return `${config.binName} autoupdate`;
|
|
166
|
+
}
|
|
167
|
+
function scheduleLabel(config) {
|
|
168
|
+
return `${config.binName}-autoupdate`.replace(/[^A-Za-z0-9_-]+/g, "-");
|
|
169
|
+
}
|
|
170
|
+
function quoteWindows(arg) {
|
|
171
|
+
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
172
|
+
}
|
|
173
|
+
function enableSchedule(config, command, options = {}) {
|
|
174
|
+
const platform = schedulePlatform(options.platform);
|
|
175
|
+
if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
|
|
176
|
+
const exec = options.exec ?? defaultExec;
|
|
177
|
+
const home = homeOf(options);
|
|
178
|
+
if (platform === "win32") {
|
|
179
|
+
const taskLine = command.map(quoteWindows).join(" ");
|
|
180
|
+
const result2 = exec("schtasks", ["/Create", "/TN", scheduleName(config), "/TR", taskLine, "/SC", "HOURLY", "/IT", "/F"]);
|
|
181
|
+
if (result2.code !== 0) {
|
|
182
|
+
throw new Error(`could not turn autoupdate on: ${firstLine(result2.stdout || result2.stderr) || `exit ${result2.code}`}`);
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (platform === "darwin") {
|
|
187
|
+
const label2 = scheduleLabel(config);
|
|
188
|
+
const dir2 = join(home, "Library", "LaunchAgents");
|
|
189
|
+
mkdirSync(dir2, { recursive: true });
|
|
190
|
+
const plist = join(dir2, `${label2}.plist`);
|
|
191
|
+
writeFileSync(plist, darwinPlist(label2, command));
|
|
192
|
+
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
193
|
+
const result2 = exec("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plist]);
|
|
194
|
+
if (result2.code !== 0) {
|
|
195
|
+
throw new Error(`could not turn autoupdate on: ${firstLine(result2.stderr || result2.stdout) || `exit ${result2.code}`}`);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const label = scheduleLabel(config);
|
|
200
|
+
const dir = join(home, ".config", "systemd", "user");
|
|
201
|
+
mkdirSync(dir, { recursive: true });
|
|
202
|
+
writeFileSync(join(dir, `${label}.service`), linuxService(command));
|
|
203
|
+
writeFileSync(join(dir, `${label}.timer`), linuxTimer(label));
|
|
204
|
+
const reload = exec("systemctl", ["--user", "daemon-reload"]);
|
|
205
|
+
if (reload.code !== 0) {
|
|
206
|
+
throw new Error(`could not turn autoupdate on: ${firstLine(reload.stderr || reload.stdout) || `exit ${reload.code}`}`);
|
|
207
|
+
}
|
|
208
|
+
const result = exec("systemctl", ["--user", "enable", "--now", `${label}.timer`]);
|
|
209
|
+
if (result.code !== 0) {
|
|
210
|
+
throw new Error(`could not turn autoupdate on: ${firstLine(result.stderr || result.stdout) || `exit ${result.code}`}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function disableSchedule(config, options = {}) {
|
|
214
|
+
const platform = schedulePlatform(options.platform);
|
|
215
|
+
if (!platform) throw new Error(`autoupdate is not supported on ${options.platform ?? process.platform}.`);
|
|
216
|
+
const exec = options.exec ?? defaultExec;
|
|
217
|
+
const home = homeOf(options);
|
|
218
|
+
if (platform === "win32") {
|
|
219
|
+
const result = exec("schtasks", ["/Delete", "/TN", scheduleName(config), "/F"]);
|
|
220
|
+
if (result.code !== 0 && !/cannot find|does not exist/i.test(`${result.stdout} ${result.stderr}`)) {
|
|
221
|
+
throw new Error(`could not turn autoupdate off: ${firstLine(result.stdout || result.stderr) || `exit ${result.code}`}`);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (platform === "darwin") {
|
|
226
|
+
const label2 = scheduleLabel(config);
|
|
227
|
+
exec("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${label2}`]);
|
|
228
|
+
rmSync(join(home, "Library", "LaunchAgents", `${label2}.plist`), { force: true });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const label = scheduleLabel(config);
|
|
232
|
+
const dir = join(home, ".config", "systemd", "user");
|
|
233
|
+
exec("systemctl", ["--user", "disable", "--now", `${label}.timer`]);
|
|
234
|
+
rmSync(join(dir, `${label}.service`), { force: true });
|
|
235
|
+
rmSync(join(dir, `${label}.timer`), { force: true });
|
|
236
|
+
}
|
|
237
|
+
function querySchedule(config, options = {}) {
|
|
238
|
+
const platform = schedulePlatform(options.platform);
|
|
239
|
+
if (!platform) return { supported: false, enabled: false };
|
|
240
|
+
const exec = options.exec ?? defaultExec;
|
|
241
|
+
const home = homeOf(options);
|
|
242
|
+
if (platform === "win32") {
|
|
243
|
+
const result = exec("schtasks", ["/Query", "/TN", scheduleName(config), "/FO", "LIST", "/V"]);
|
|
244
|
+
if (result.code !== 0) return { supported: true, enabled: false };
|
|
245
|
+
const lastRun = valueOf(result.stdout, "Last Run Time:");
|
|
246
|
+
const nextRun = valueOf(result.stdout, "Next Run Time:");
|
|
247
|
+
const state2 = { supported: true, enabled: true, cadence: "hourly" };
|
|
248
|
+
if (lastRun && !/disabled|never|not run/i.test(lastRun)) state2.lastRun = lastRun;
|
|
249
|
+
else if (nextRun && !/disabled|never/i.test(nextRun)) state2.lastRun = `next run ${nextRun}`;
|
|
250
|
+
return state2;
|
|
251
|
+
}
|
|
252
|
+
if (platform === "darwin") {
|
|
253
|
+
const plist = join(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
|
|
254
|
+
if (!existsSync(plist)) return { supported: true, enabled: false };
|
|
255
|
+
return { supported: true, enabled: true, cadence: "hourly" };
|
|
256
|
+
}
|
|
257
|
+
const timer = join(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
|
|
258
|
+
if (!existsSync(timer)) return { supported: true, enabled: false };
|
|
259
|
+
const state = { supported: true, enabled: true, cadence: "hourly" };
|
|
260
|
+
const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
|
|
261
|
+
const stamp = (shown.stdout ?? "").trim();
|
|
262
|
+
if (shown.code === 0 && stamp && stamp !== "n/a") state.lastRun = stamp;
|
|
263
|
+
return state;
|
|
264
|
+
}
|
|
265
|
+
function darwinPlist(label, command) {
|
|
266
|
+
const args = command.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
|
|
267
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
268
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
269
|
+
<plist version="1.0">
|
|
270
|
+
<dict>
|
|
271
|
+
<key>Label</key>
|
|
272
|
+
<string>${xmlEscape(label)}</string>
|
|
273
|
+
<key>ProgramArguments</key>
|
|
274
|
+
<array>
|
|
275
|
+
${args}
|
|
276
|
+
</array>
|
|
277
|
+
<key>StartInterval</key>
|
|
278
|
+
<integer>3600</integer>
|
|
279
|
+
</dict>
|
|
280
|
+
</plist>
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
function linuxService(command) {
|
|
284
|
+
const line = command.map((arg) => /[\s"\\]/.test(arg) ? `"${arg.replace(/(["\\])/g, "\\$1")}"` : arg).join(" ");
|
|
285
|
+
return `[Unit]
|
|
286
|
+
Description=${"Hourly update check"}
|
|
287
|
+
[Service]
|
|
288
|
+
Type=oneshot
|
|
289
|
+
ExecStart=${line}
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
function linuxTimer(label) {
|
|
293
|
+
return `[Unit]
|
|
294
|
+
Description=Hourly update check for ${label}
|
|
295
|
+
[Timer]
|
|
296
|
+
OnCalendar=hourly
|
|
297
|
+
Persistent=true
|
|
298
|
+
[Install]
|
|
299
|
+
WantedBy=timers.target
|
|
300
|
+
`;
|
|
301
|
+
}
|
|
302
|
+
function xmlEscape(text) {
|
|
303
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
304
|
+
}
|
|
305
|
+
function valueOf(output, key) {
|
|
306
|
+
const line = output.split(/\r?\n/).find((candidate) => candidate.trimStart().startsWith(key));
|
|
307
|
+
if (!line) return null;
|
|
308
|
+
const value = line.slice(line.indexOf(key) + key.length).trim();
|
|
309
|
+
return value ? value : null;
|
|
310
|
+
}
|
|
311
|
+
function firstLine(text) {
|
|
312
|
+
return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
313
|
+
}
|
|
314
|
+
|
|
9
315
|
// src/config.ts
|
|
10
|
-
import { readFileSync } from "node:fs";
|
|
316
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
11
317
|
import { getAsset } from "node:sea";
|
|
12
318
|
|
|
13
319
|
// src/module-url.ts
|
|
@@ -29,10 +335,10 @@ function loadProductConfig(options = {}) {
|
|
|
29
335
|
const explicit = options.configPath ?? process.env.LAUNCHER_CONFIG;
|
|
30
336
|
const devFallback = new URL("../config/product.template.json", moduleUrl());
|
|
31
337
|
if (explicit) {
|
|
32
|
-
return parseProductConfig(
|
|
338
|
+
return parseProductConfig(readFileSync2(explicit, "utf8"));
|
|
33
339
|
}
|
|
34
340
|
try {
|
|
35
|
-
return parseProductConfig(
|
|
341
|
+
return parseProductConfig(readFileSync2(devFallback, "utf8"));
|
|
36
342
|
} catch {
|
|
37
343
|
}
|
|
38
344
|
try {
|
|
@@ -321,9 +627,9 @@ function page(title, body) {
|
|
|
321
627
|
|
|
322
628
|
// src/payload.ts
|
|
323
629
|
import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
|
|
324
|
-
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
325
|
-
import { tmpdir } from "node:os";
|
|
326
|
-
import { dirname, join } from "node:path";
|
|
630
|
+
import { mkdirSync as mkdirSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
631
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
632
|
+
import { dirname, join as join2 } from "node:path";
|
|
327
633
|
|
|
328
634
|
// src/canonical.ts
|
|
329
635
|
function canonicalJson(value) {
|
|
@@ -463,56 +769,56 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl) {
|
|
|
463
769
|
}
|
|
464
770
|
async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
|
|
465
771
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
466
|
-
const staging =
|
|
467
|
-
|
|
772
|
+
const staging = join2(tmpdir2(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
|
|
773
|
+
mkdirSync2(staging, { recursive: true });
|
|
468
774
|
try {
|
|
469
775
|
for (const entry of manifest.files) {
|
|
470
776
|
const bytes = await fetchFileBytes(config, accessToken, entry.path, fetchImpl);
|
|
471
777
|
if (!verifyFileBytes(entry, bytes)) {
|
|
472
778
|
throw new Error(`file ${entry.path} failed its checksum \u2014 refusing to install anything.`);
|
|
473
779
|
}
|
|
474
|
-
const dest =
|
|
475
|
-
|
|
476
|
-
|
|
780
|
+
const dest = join2(staging, entry.path);
|
|
781
|
+
mkdirSync2(dirname(dest), { recursive: true });
|
|
782
|
+
writeFileSync2(dest, bytes);
|
|
477
783
|
}
|
|
478
|
-
const target =
|
|
479
|
-
|
|
480
|
-
|
|
784
|
+
const target = join2(dir, "payload");
|
|
785
|
+
mkdirSync2(dir, { recursive: true });
|
|
786
|
+
rmSync2(target, { force: true, recursive: true });
|
|
481
787
|
renameSync(staging, target);
|
|
482
788
|
} catch (error) {
|
|
483
|
-
|
|
789
|
+
rmSync2(staging, { force: true, recursive: true });
|
|
484
790
|
throw error;
|
|
485
791
|
}
|
|
486
792
|
return manifest.version;
|
|
487
793
|
}
|
|
488
794
|
|
|
489
795
|
// src/store.ts
|
|
490
|
-
import { existsSync as
|
|
491
|
-
import { tmpdir as
|
|
492
|
-
import { join as
|
|
796
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
797
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
798
|
+
import { join as join3 } from "node:path";
|
|
493
799
|
function defaultProductDir(product) {
|
|
494
800
|
if (process.platform === "win32") {
|
|
495
|
-
const base = process.env.LOCALAPPDATA ??
|
|
496
|
-
return
|
|
801
|
+
const base = process.env.LOCALAPPDATA ?? join3(tmpdir3(), "launcher-fallback");
|
|
802
|
+
return join3(base, product);
|
|
497
803
|
}
|
|
498
|
-
const home = process.env.HOME ??
|
|
499
|
-
return
|
|
804
|
+
const home = process.env.HOME ?? tmpdir3();
|
|
805
|
+
return join3(home, `.${product}`);
|
|
500
806
|
}
|
|
501
807
|
function resolveProductDir(product, explicit) {
|
|
502
808
|
return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
|
|
503
809
|
}
|
|
504
810
|
function tokensPath(dir) {
|
|
505
|
-
return
|
|
811
|
+
return join3(dir, "tokens.json");
|
|
506
812
|
}
|
|
507
813
|
function statePath(dir) {
|
|
508
|
-
return
|
|
814
|
+
return join3(dir, "state.json");
|
|
509
815
|
}
|
|
510
816
|
function payloadDir(dir) {
|
|
511
|
-
return
|
|
817
|
+
return join3(dir, "payload");
|
|
512
818
|
}
|
|
513
819
|
function readTokens(dir) {
|
|
514
820
|
try {
|
|
515
|
-
const data = JSON.parse(
|
|
821
|
+
const data = JSON.parse(readFileSync3(tokensPath(dir), "utf8"));
|
|
516
822
|
if (typeof data.accessToken !== "string" || !data.accessToken) return null;
|
|
517
823
|
if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
|
|
518
824
|
const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
|
|
@@ -524,21 +830,21 @@ function readTokens(dir) {
|
|
|
524
830
|
}
|
|
525
831
|
}
|
|
526
832
|
function writeTokens(dir, tokens) {
|
|
527
|
-
|
|
833
|
+
mkdirSync3(dir, { recursive: true });
|
|
528
834
|
try {
|
|
529
|
-
|
|
835
|
+
writeFileSync3(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
530
836
|
`, { mode: 384 });
|
|
531
837
|
} catch {
|
|
532
|
-
|
|
838
|
+
writeFileSync3(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
533
839
|
`);
|
|
534
840
|
}
|
|
535
841
|
}
|
|
536
842
|
function clearTokens(dir) {
|
|
537
|
-
|
|
843
|
+
rmSync3(tokensPath(dir), { force: true });
|
|
538
844
|
}
|
|
539
845
|
function readState(dir) {
|
|
540
846
|
try {
|
|
541
|
-
const data = JSON.parse(
|
|
847
|
+
const data = JSON.parse(readFileSync3(statePath(dir), "utf8"));
|
|
542
848
|
if (typeof data.version !== "string" || !data.version) return null;
|
|
543
849
|
return { version: data.version, updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "" };
|
|
544
850
|
} catch {
|
|
@@ -546,18 +852,18 @@ function readState(dir) {
|
|
|
546
852
|
}
|
|
547
853
|
}
|
|
548
854
|
function writeState(dir, state) {
|
|
549
|
-
|
|
550
|
-
|
|
855
|
+
mkdirSync3(dir, { recursive: true });
|
|
856
|
+
writeFileSync3(statePath(dir), `${JSON.stringify(state, null, 2)}
|
|
551
857
|
`);
|
|
552
858
|
}
|
|
553
859
|
function wipeProductDir(dir) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
860
|
+
rmSync3(tokensPath(dir), { force: true });
|
|
861
|
+
rmSync3(statePath(dir), { force: true });
|
|
862
|
+
rmSync3(payloadDir(dir), { force: true, recursive: true });
|
|
557
863
|
}
|
|
558
864
|
|
|
559
865
|
// src/index.ts
|
|
560
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
866
|
+
var LAUNCHER_VERSION = true ? "0.1.4" : readVersionFromPackage();
|
|
561
867
|
function defaultPrint(message) {
|
|
562
868
|
process.stdout.write(`${message}
|
|
563
869
|
`);
|
|
@@ -604,12 +910,11 @@ async function run(rawOptions = {}) {
|
|
|
604
910
|
case "update":
|
|
605
911
|
return await doUpdate(config, dir, rawOptions, print);
|
|
606
912
|
case "doctor":
|
|
607
|
-
doDoctor(config, dir, print);
|
|
608
|
-
|
|
913
|
+
return doDoctor(config, dir, rawOptions, print);
|
|
914
|
+
case "autoupdate":
|
|
915
|
+
return doAutoupdate(config, rawOptions, positional.slice(1), print);
|
|
609
916
|
default:
|
|
610
|
-
|
|
611
|
-
printUsage(config, print);
|
|
612
|
-
return 2;
|
|
917
|
+
return doForward(config, dir, rawOptions, command, argv, print);
|
|
613
918
|
}
|
|
614
919
|
} catch (error) {
|
|
615
920
|
if (error instanceof NeedsLoginError) {
|
|
@@ -628,8 +933,9 @@ function flagValue(argv, flag) {
|
|
|
628
933
|
}
|
|
629
934
|
function printUsage(config, print) {
|
|
630
935
|
print(`${config.binName} launcher ${LAUNCHER_VERSION} \u2014 sign in and install ${config.product}.`);
|
|
631
|
-
print("usage: launcher <login|logout|install|update|doctor> [--config <path>] [--dir <path>]");
|
|
936
|
+
print("usage: launcher <login|logout|install|update|doctor|autoupdate on|off|status> [--config <path>] [--dir <path>]");
|
|
632
937
|
print(" launcher --run <file> [args\u2026] (run a payload file with the embedded runtime)");
|
|
938
|
+
print(" launcher <verb> [args\u2026] (forwarded to the payload when it declares run + verbs)");
|
|
633
939
|
}
|
|
634
940
|
async function runFile(file, args, printErr) {
|
|
635
941
|
if (!file) {
|
|
@@ -637,7 +943,7 @@ async function runFile(file, args, printErr) {
|
|
|
637
943
|
return 1;
|
|
638
944
|
}
|
|
639
945
|
const abs = resolve(process.cwd(), file);
|
|
640
|
-
if (!
|
|
946
|
+
if (!existsSync4(abs)) {
|
|
641
947
|
printErr(`cannot run ${file}: no such file.`);
|
|
642
948
|
return 1;
|
|
643
949
|
}
|
|
@@ -723,10 +1029,10 @@ async function fetchAccessTokenOrLogin(config, dir, options, print) {
|
|
|
723
1029
|
if (!after) throw new Error("sign-in did not produce a token");
|
|
724
1030
|
return after.accessToken;
|
|
725
1031
|
}
|
|
726
|
-
function
|
|
1032
|
+
function readPayloadArgv(dir, key) {
|
|
727
1033
|
try {
|
|
728
|
-
const parsed = JSON.parse(
|
|
729
|
-
const entry = parsed
|
|
1034
|
+
const parsed = JSON.parse(readFileSync4(join4(payloadDir(dir), "payload.json"), "utf8"));
|
|
1035
|
+
const entry = parsed[key];
|
|
730
1036
|
if (typeof entry === "string") {
|
|
731
1037
|
const parts = entry.trim().split(/\s+/).filter(Boolean);
|
|
732
1038
|
return parts.length > 0 ? parts : null;
|
|
@@ -739,67 +1045,251 @@ function readPayloadEntry(dir) {
|
|
|
739
1045
|
return null;
|
|
740
1046
|
}
|
|
741
1047
|
}
|
|
1048
|
+
function readPayloadEntry(dir) {
|
|
1049
|
+
return readPayloadArgv(dir, "entry");
|
|
1050
|
+
}
|
|
1051
|
+
function readPayloadRun(dir) {
|
|
1052
|
+
return readPayloadArgv(dir, "run");
|
|
1053
|
+
}
|
|
1054
|
+
function readPayloadVerbs(dir) {
|
|
1055
|
+
try {
|
|
1056
|
+
const parsed = JSON.parse(readFileSync4(join4(payloadDir(dir), "payload.json"), "utf8"));
|
|
1057
|
+
const verbs = parsed.verbs;
|
|
1058
|
+
if (verbs === "*") return "*";
|
|
1059
|
+
if (Array.isArray(verbs) && verbs.every((v) => typeof v === "string" && v.trim().length > 0)) {
|
|
1060
|
+
return verbs;
|
|
1061
|
+
}
|
|
1062
|
+
return null;
|
|
1063
|
+
} catch {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
742
1067
|
function resolveEntry(entry) {
|
|
743
1068
|
return entry[0] === "$self" ? [process.execPath, ...entry.slice(1)] : entry;
|
|
744
1069
|
}
|
|
745
1070
|
function needsShell(command) {
|
|
746
1071
|
if (process.platform !== "win32") return false;
|
|
747
1072
|
if (/\.(cmd|bat)$/i.test(command)) return true;
|
|
748
|
-
return !
|
|
1073
|
+
return !existsSync4(command);
|
|
1074
|
+
}
|
|
1075
|
+
function quoteForShell(arg) {
|
|
1076
|
+
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
749
1077
|
}
|
|
750
|
-
function defaultRunEntry(entry, cwd) {
|
|
1078
|
+
function defaultRunEntry(entry, cwd, env) {
|
|
751
1079
|
const [command, ...args] = entry;
|
|
752
|
-
const
|
|
1080
|
+
const shell = needsShell(command);
|
|
1081
|
+
const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
|
|
1082
|
+
const result = spawnSync2(commandLine, shell ? [] : args, {
|
|
753
1083
|
cwd,
|
|
754
1084
|
stdio: "inherit",
|
|
755
|
-
shell
|
|
1085
|
+
shell,
|
|
1086
|
+
env: env ?? process.env,
|
|
756
1087
|
windowsHide: true
|
|
757
1088
|
});
|
|
758
1089
|
if (result.error) return { ok: false, error: result.error.message };
|
|
759
|
-
|
|
1090
|
+
const code = typeof result.status === "number" ? result.status : void 0;
|
|
1091
|
+
return { ok: result.status === 0, error: result.status === 0 ? void 0 : `exit code ${result.status}`, code };
|
|
1092
|
+
}
|
|
1093
|
+
function faceProduct(config) {
|
|
1094
|
+
const known = { mmi: "mmi-hub", jerv: "jerv-hub" };
|
|
1095
|
+
return known[config.product] ?? config.product;
|
|
1096
|
+
}
|
|
1097
|
+
function faceFor(config, options) {
|
|
1098
|
+
const tty = options.tty ?? Boolean(process.stdout.isTTY);
|
|
1099
|
+
if (!tty) return null;
|
|
1100
|
+
try {
|
|
1101
|
+
return createFace({
|
|
1102
|
+
product: faceProduct(config),
|
|
1103
|
+
color: !process.env.NO_COLOR && process.env.TERM !== "dumb",
|
|
1104
|
+
columns: process.stdout.columns
|
|
1105
|
+
});
|
|
1106
|
+
} catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function since(start) {
|
|
1111
|
+
return (Date.now() - start) / 1e3;
|
|
1112
|
+
}
|
|
1113
|
+
function printReceipt(face, config, print, ready, lines) {
|
|
1114
|
+
const name = face ? face.identity.name : config.product;
|
|
1115
|
+
const headline = ready ? `${name} is ready.` : `${name} is installed, but not ready yet.`;
|
|
1116
|
+
if (!face) {
|
|
1117
|
+
print(headline);
|
|
1118
|
+
for (const line of lines) print(line.trim());
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const glyph = ready ? "\u2714" : "\u2716";
|
|
1122
|
+
for (const line of face.receipt([`${glyph} ${headline}`, ...lines])) print(line);
|
|
1123
|
+
print(face.signOff());
|
|
1124
|
+
}
|
|
1125
|
+
function printStep(face, print, title, seconds, kind = "ok") {
|
|
1126
|
+
print(face ? face.step(title, seconds, kind) : title);
|
|
760
1127
|
}
|
|
761
1128
|
async function doInstall(config, dir, options, print) {
|
|
762
1129
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1130
|
+
const face = faceFor(config, options);
|
|
763
1131
|
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print);
|
|
1132
|
+
const started = Date.now();
|
|
764
1133
|
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
765
1134
|
await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
|
|
766
1135
|
writeState(dir, { version: manifest.version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
767
|
-
print
|
|
1136
|
+
printStep(face, print, `installed ${config.product} ${manifest.version}`, since(started));
|
|
1137
|
+
return finishLastMile(config, dir, options, print, face, manifest.version);
|
|
1138
|
+
}
|
|
1139
|
+
async function doUpdate(config, dir, options, print) {
|
|
1140
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1141
|
+
const face = faceFor(config, options);
|
|
1142
|
+
if (face) for (const line of face.welcome()) print(line);
|
|
1143
|
+
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print);
|
|
1144
|
+
const started = Date.now();
|
|
1145
|
+
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
1146
|
+
const current = readState(dir);
|
|
1147
|
+
if (current && current.version === manifest.version && existsSync4(payloadDir(dir))) {
|
|
1148
|
+
printStep(face, print, `${config.product} is already up to date at ${manifest.version}`, since(started));
|
|
1149
|
+
return finishLastMile(config, dir, options, print, face, manifest.version);
|
|
1150
|
+
}
|
|
1151
|
+
await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
|
|
1152
|
+
writeState(dir, { version: manifest.version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1153
|
+
printStep(face, print, `updated ${config.product} to ${manifest.version}`, since(started));
|
|
1154
|
+
return finishLastMile(config, dir, options, print, face, manifest.version);
|
|
1155
|
+
}
|
|
1156
|
+
function payloadEnv(dir) {
|
|
1157
|
+
const stored = readTokens(dir);
|
|
1158
|
+
if (!stored?.accessToken) return void 0;
|
|
1159
|
+
return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
|
|
1160
|
+
}
|
|
1161
|
+
var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
|
|
1162
|
+
function payloadFileAsCommand(entry, payload) {
|
|
1163
|
+
const first = entry[0];
|
|
1164
|
+
if (!first || first === "$self") return null;
|
|
1165
|
+
if (first.includes("/") || first.includes("\\")) return null;
|
|
1166
|
+
const candidate = join4(payload, first);
|
|
1167
|
+
if (!existsSync4(candidate)) return null;
|
|
1168
|
+
if (NEVER_A_PROGRAM.test(first)) return first;
|
|
1169
|
+
if (process.platform === "win32") return null;
|
|
1170
|
+
try {
|
|
1171
|
+
return (statSync(candidate).mode & 73) === 0 ? first : null;
|
|
1172
|
+
} catch {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function finishLastMile(config, dir, options, print, face, version) {
|
|
768
1177
|
const entry = readPayloadEntry(dir);
|
|
1178
|
+
const payload = payloadDir(dir);
|
|
769
1179
|
if (!entry) {
|
|
770
|
-
|
|
1180
|
+
printReceipt(face, config, print, true, [
|
|
1181
|
+
` Installed ${version} into ${dir}`,
|
|
1182
|
+
` next step: run ${join4(payload, config.binName)} to start ${config.product}.`
|
|
1183
|
+
]);
|
|
771
1184
|
return 0;
|
|
772
1185
|
}
|
|
773
|
-
const payload = payloadDir(dir);
|
|
774
1186
|
const command = resolveEntry(entry);
|
|
775
|
-
const
|
|
776
|
-
|
|
1187
|
+
const dataFile = payloadFileAsCommand(entry, payload);
|
|
1188
|
+
if (dataFile) {
|
|
1189
|
+
printStep(face, print, `The payload names ${dataFile} as its command, but that is a file, not a program`, null, "fail");
|
|
1190
|
+
printReceipt(face, config, print, false, [
|
|
1191
|
+
` Downloaded ${version} into ${dir}`,
|
|
1192
|
+
` This payload was built wrong: its entry must be a command, not one of its own files.`,
|
|
1193
|
+
` Nothing on this machine can finish it \u2014 report it to the ${config.product} maintainers.`
|
|
1194
|
+
]);
|
|
1195
|
+
return 2;
|
|
1196
|
+
}
|
|
1197
|
+
const started = Date.now();
|
|
1198
|
+
const result = (options.runEntry ?? defaultRunEntry)(command, payload, payloadEnv(dir));
|
|
777
1199
|
if (result.ok) {
|
|
778
|
-
|
|
1200
|
+
printStep(face, print, "Armed this machine", since(started));
|
|
1201
|
+
printReceipt(face, config, print, true, [
|
|
1202
|
+
` Installed ${version} into ${dir}`,
|
|
1203
|
+
` Check health any time: ${config.binName} doctor`
|
|
1204
|
+
]);
|
|
779
1205
|
return 0;
|
|
780
1206
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1207
|
+
const code = result.code ?? 1;
|
|
1208
|
+
printStep(
|
|
1209
|
+
face,
|
|
1210
|
+
print,
|
|
1211
|
+
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})`,
|
|
1212
|
+
null,
|
|
1213
|
+
"fail"
|
|
1214
|
+
);
|
|
1215
|
+
printReceipt(face, config, print, false, [
|
|
1216
|
+
` Downloaded ${version} into ${dir}`,
|
|
1217
|
+
` Finish it with: (cd ${payload} && ${command.join(" ")})`
|
|
1218
|
+
]);
|
|
1219
|
+
return code;
|
|
784
1220
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1221
|
+
function doForward(config, dir, options, command, argv, print) {
|
|
1222
|
+
if (command && existsSync4(command)) {
|
|
1223
|
+
print(`${command} is a file, not a command \u2014 did you mean \`--run ${command}\`?`);
|
|
1224
|
+
return 2;
|
|
1225
|
+
}
|
|
1226
|
+
const target = readPayloadRun(dir);
|
|
1227
|
+
const verbs = readPayloadVerbs(dir);
|
|
1228
|
+
const declared = verbs === "*" || Array.isArray(verbs) && command !== void 0 && verbs.includes(command);
|
|
1229
|
+
if (!target || !declared) {
|
|
1230
|
+
print(`unknown command: ${command}`);
|
|
1231
|
+
printUsage(config, print);
|
|
1232
|
+
return 2;
|
|
1233
|
+
}
|
|
1234
|
+
const forwarded = [...resolveEntry(target), ...argv];
|
|
1235
|
+
const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir), payloadEnv(dir));
|
|
1236
|
+
if (!result.ok && result.code === void 0) {
|
|
1237
|
+
print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
|
|
1238
|
+
return 1;
|
|
1239
|
+
}
|
|
1240
|
+
return result.code ?? 0;
|
|
1241
|
+
}
|
|
1242
|
+
function doAutoupdate(config, options, args, print) {
|
|
1243
|
+
const mode = args[0] ?? "status";
|
|
1244
|
+
const scheduleOptions = options.autoupdate ?? {};
|
|
1245
|
+
const command = options.autoupdate?.command ?? [process.execPath, "update"];
|
|
1246
|
+
if (mode === "on") {
|
|
1247
|
+
try {
|
|
1248
|
+
enableSchedule(config, command, scheduleOptions);
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
print(error.message);
|
|
1251
|
+
return 1;
|
|
1252
|
+
}
|
|
1253
|
+
const state2 = querySchedule(config, scheduleOptions);
|
|
1254
|
+
print(`auto-update: on (${state2.cadence ?? "hourly"})`);
|
|
792
1255
|
return 0;
|
|
793
1256
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1257
|
+
if (mode === "off") {
|
|
1258
|
+
try {
|
|
1259
|
+
disableSchedule(config, scheduleOptions);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
print(error.message);
|
|
1262
|
+
return 1;
|
|
1263
|
+
}
|
|
1264
|
+
print("auto-update: off");
|
|
1265
|
+
return 0;
|
|
1266
|
+
}
|
|
1267
|
+
if (mode !== "status") {
|
|
1268
|
+
print(`unknown autoupdate mode: ${mode} \u2014 use on, off or status.`);
|
|
1269
|
+
return 1;
|
|
1270
|
+
}
|
|
1271
|
+
const state = querySchedule(config, scheduleOptions);
|
|
1272
|
+
if (!state.supported) {
|
|
1273
|
+
print("auto-update: unsupported on this platform");
|
|
1274
|
+
return 1;
|
|
1275
|
+
}
|
|
1276
|
+
print(`auto-update: ${state.enabled ? `on (${state.cadence ?? "hourly"})` : "off"}`);
|
|
1277
|
+
if (state.lastRun) print(`last run: ${state.lastRun}`);
|
|
797
1278
|
return 0;
|
|
798
1279
|
}
|
|
799
|
-
function doDoctor(config, dir, print) {
|
|
1280
|
+
function doDoctor(config, dir, options, print) {
|
|
1281
|
+
doLauncherDoctor(config, dir, options, print);
|
|
1282
|
+
const target = readPayloadRun(dir);
|
|
1283
|
+
const verbs = readPayloadVerbs(dir);
|
|
1284
|
+
const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
|
|
1285
|
+
if (!chains) return 0;
|
|
1286
|
+
const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir), payloadEnv(dir));
|
|
1287
|
+
return result.code ?? (result.ok ? 0 : 1);
|
|
1288
|
+
}
|
|
1289
|
+
function doLauncherDoctor(config, dir, options, print) {
|
|
800
1290
|
const tokens = readTokens(dir);
|
|
801
1291
|
const state = readState(dir);
|
|
802
|
-
const payloadPresent =
|
|
1292
|
+
const payloadPresent = existsSync4(payloadDir(dir));
|
|
803
1293
|
print(`product: ${config.product}`);
|
|
804
1294
|
print(`host: ${config.host}`);
|
|
805
1295
|
print(`login: ${config.loginKind}`);
|
|
@@ -811,7 +1301,11 @@ function doDoctor(config, dir, print) {
|
|
|
811
1301
|
print("token: none \u2014 run login first.");
|
|
812
1302
|
}
|
|
813
1303
|
print(`payload: ${state ? state.version : "none"}${payloadPresent ? "" : " (not downloaded)"}`);
|
|
814
|
-
print(`paths: tokens ${
|
|
1304
|
+
print(`paths: tokens ${join4(dir, "tokens.json")}, state ${join4(dir, "state.json")}, payload ${payloadDir(dir)}`);
|
|
1305
|
+
const schedule = querySchedule(config, options.autoupdate ?? {});
|
|
1306
|
+
print(
|
|
1307
|
+
`auto-update: ${!schedule.supported ? "unsupported on this platform" : schedule.enabled ? `on (${schedule.cadence ?? "hourly"})` : "off"}`
|
|
1308
|
+
);
|
|
815
1309
|
}
|
|
816
1310
|
var invokedAsMain = typeof process.argv[1] === "string" && (() => {
|
|
817
1311
|
try {
|
|
@@ -841,6 +1335,8 @@ export {
|
|
|
841
1335
|
defaultRunEntry,
|
|
842
1336
|
ensureFreshToken,
|
|
843
1337
|
readPayloadEntry,
|
|
1338
|
+
readPayloadRun,
|
|
1339
|
+
readPayloadVerbs,
|
|
844
1340
|
resolveEntry,
|
|
845
1341
|
run,
|
|
846
1342
|
runFile
|