@mutmutco/installer-launcher 0.1.14 → 0.1.15
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 +130 -30
- package/dist/launcher.sea.cjs +110 -10
- package/package.json +1 -1
package/dist/launcher.js
CHANGED
|
@@ -6,7 +6,7 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
6
6
|
|
|
7
7
|
// src/acquisition.ts
|
|
8
8
|
import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
|
|
9
|
-
import { existsSync as
|
|
9
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
10
10
|
import { join as join4, resolve, relative as relative2, isAbsolute as isAbsolute2, dirname as dirname2 } from "node:path";
|
|
11
11
|
|
|
12
12
|
// src/runtime.ts
|
|
@@ -194,7 +194,7 @@ function wipeProductDir(dir) {
|
|
|
194
194
|
|
|
195
195
|
// src/payload.ts
|
|
196
196
|
import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
|
|
197
|
-
import { mkdirSync as mkdirSync3, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
197
|
+
import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
198
198
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
199
199
|
import { dirname, join as join3 } from "node:path";
|
|
200
200
|
|
|
@@ -403,6 +403,17 @@ async function fetchVerifiedManifest(config, accessToken, fetchImpl = fetch) {
|
|
|
403
403
|
}
|
|
404
404
|
return manifest;
|
|
405
405
|
}
|
|
406
|
+
async function fetchReleaseVersion(config, fetchImpl = fetch) {
|
|
407
|
+
try {
|
|
408
|
+
const response = await fetchImpl(`${config.host}/release/version`);
|
|
409
|
+
if (!response.ok) return null;
|
|
410
|
+
const json = await response.json();
|
|
411
|
+
const version = typeof json?.version === "string" ? json.version.trim() : "";
|
|
412
|
+
return version || null;
|
|
413
|
+
} catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
406
417
|
async function fetchFileBytes(config, accessToken, path, fetchImpl, onProgress, expectedSize) {
|
|
407
418
|
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
408
419
|
const response = await fetchImpl(`${config.host}/release/${encoded}`, {
|
|
@@ -414,7 +425,36 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl, onProgress,
|
|
|
414
425
|
}
|
|
415
426
|
return readDownload(response, onProgress, expectedSize);
|
|
416
427
|
}
|
|
428
|
+
var VERSION_CACHE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
429
|
+
function versionCacheRoot(dir, version) {
|
|
430
|
+
return VERSION_CACHE_RE.test(version) ? join3(dir, "payload-cache", version) : "";
|
|
431
|
+
}
|
|
432
|
+
function cachedPayloadForVersion(dir, version) {
|
|
433
|
+
const root = versionCacheRoot(dir, version);
|
|
434
|
+
if (!root) return null;
|
|
435
|
+
const payload = join3(root, "payload");
|
|
436
|
+
return existsSync4(payload) ? payload : null;
|
|
437
|
+
}
|
|
438
|
+
function rememberPayloadVersion(dir, version, payload) {
|
|
439
|
+
try {
|
|
440
|
+
const root = versionCacheRoot(dir, version);
|
|
441
|
+
if (!root) return;
|
|
442
|
+
const dest = join3(root, "payload");
|
|
443
|
+
if (existsSync4(dest)) return;
|
|
444
|
+
mkdirSync3(root, { recursive: true });
|
|
445
|
+
cpSync(payload, dest, { recursive: true });
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
}
|
|
417
449
|
async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
|
|
450
|
+
const target = join3(dir, "payload");
|
|
451
|
+
const cached = options.cacheDir ? cachedPayloadForVersion(options.cacheDir, manifest.version) : null;
|
|
452
|
+
if (cached) {
|
|
453
|
+
mkdirSync3(dir, { recursive: true });
|
|
454
|
+
rmSync3(target, { force: true, recursive: true });
|
|
455
|
+
cpSync(cached, target, { recursive: true });
|
|
456
|
+
return manifest.version;
|
|
457
|
+
}
|
|
418
458
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
419
459
|
const staging = join3(tmpdir2(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
|
|
420
460
|
mkdirSync3(staging, { recursive: true });
|
|
@@ -431,7 +471,6 @@ async function downloadAndUnpack(config, dir, manifest, accessToken, options = {
|
|
|
431
471
|
mkdirSync3(dirname(dest), { recursive: true });
|
|
432
472
|
writeFileSync3(dest, bytes);
|
|
433
473
|
}
|
|
434
|
-
const target = join3(dir, "payload");
|
|
435
474
|
mkdirSync3(dir, { recursive: true });
|
|
436
475
|
rmSync3(target, { force: true, recursive: true });
|
|
437
476
|
renameSync3(staging, target);
|
|
@@ -439,6 +478,7 @@ async function downloadAndUnpack(config, dir, manifest, accessToken, options = {
|
|
|
439
478
|
rmSync3(staging, { force: true, recursive: true });
|
|
440
479
|
throw error;
|
|
441
480
|
}
|
|
481
|
+
if (options.cacheDir) rememberPayloadVersion(options.cacheDir, manifest.version, target);
|
|
442
482
|
return manifest.version;
|
|
443
483
|
}
|
|
444
484
|
|
|
@@ -448,7 +488,7 @@ function reusableCandidate(dir, manifest) {
|
|
|
448
488
|
const saved = JSON.parse(readFileSync4(join4(dir, "acquisition-cache.json"), "utf8"));
|
|
449
489
|
if (saved.signature !== manifest.signature) return null;
|
|
450
490
|
const root = candidateRoot(dir, saved.candidate);
|
|
451
|
-
if (!
|
|
491
|
+
if (!existsSync5(join4(root, "acquired.json"))) return null;
|
|
452
492
|
for (const file of manifest.files) {
|
|
453
493
|
if (!safePath(file.path) || !verifyFileBytes(file, readFileSync4(join4(root, "payload", file.path)))) return null;
|
|
454
494
|
}
|
|
@@ -555,7 +595,7 @@ function acquisitionRepairCommand(dir, version) {
|
|
|
555
595
|
return argv(realpathSync2(state.acquired.node), productRoot, acquisition.runEntry, acquisition.repairArgs, prefix, version);
|
|
556
596
|
}
|
|
557
597
|
async function recoverAcquisition(dir, run2, env) {
|
|
558
|
-
if (!
|
|
598
|
+
if (!existsSync5(pendingPath(dir))) return;
|
|
559
599
|
const pending = JSON.parse(readFileSync4(pendingPath(dir), "utf8"));
|
|
560
600
|
if (pending.schema !== 1 || typeof pending.version !== "string" || typeof pending.node !== "string") throw new Error("invalid pending acquisition receipt");
|
|
561
601
|
const root = candidateRoot(dir, pending.candidate);
|
|
@@ -569,7 +609,7 @@ async function recoverAcquisition(dir, run2, env) {
|
|
|
569
609
|
const command2 = argv(pending.node, packageRoot(prefix, acquisition), acquisition.rollbackEntry, acquisition.rollbackArgs, prefix, pending.version);
|
|
570
610
|
if (!await run2(command2, root, runtimeEnvironment(pending.node, env))) throw new Error("installation recovery is pending; product rollback did not finish");
|
|
571
611
|
if (pending.previous) writeState(dir, pending.previous);
|
|
572
|
-
else if (
|
|
612
|
+
else if (existsSync5(statePath(dir))) unlinkSync(statePath(dir));
|
|
573
613
|
unlinkSync(pendingPath(dir));
|
|
574
614
|
}
|
|
575
615
|
async function installAcquisition(dir, candidate, version, acquisition, options) {
|
|
@@ -579,7 +619,7 @@ async function installAcquisition(dir, candidate, version, acquisition, options)
|
|
|
579
619
|
const prefix = join4(root, "prefix");
|
|
580
620
|
const prepared = join4(root, "acquired.json");
|
|
581
621
|
const archiveHash = createHash3("sha256").update(readFileSync4(join4(payload, acquisition.archive))).digest("hex");
|
|
582
|
-
const reused =
|
|
622
|
+
const reused = existsSync5(prepared);
|
|
583
623
|
if (reused && JSON.parse(readFileSync4(prepared, "utf8")).archiveHash !== archiveHash) throw new Error("cached acquisition archive changed");
|
|
584
624
|
if (!reused) mkdirSync4(prefix);
|
|
585
625
|
const runtime = await (options.runtime ?? acquireRuntime)(dir, options.fetchImpl, options.onProgress);
|
|
@@ -632,7 +672,7 @@ async function installAcquisition(dir, candidate, version, acquisition, options)
|
|
|
632
672
|
|
|
633
673
|
// src/index.ts
|
|
634
674
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
635
|
-
import { existsSync as
|
|
675
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync, mkdtempSync as mkdtempSync2, unlinkSync as unlinkSync2, rmdirSync, mkdirSync as mkdirSync6, renameSync as renameSync4, rmSync as rmSync5 } from "node:fs";
|
|
636
676
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
637
677
|
import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
|
|
638
678
|
import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -831,7 +871,26 @@ var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
|
831
871
|
// ../face/src/spinner.ts
|
|
832
872
|
import { appendFileSync as appendFileSync2, writeSync as writeSync2 } from "node:fs";
|
|
833
873
|
import { Worker } from "node:worker_threads";
|
|
834
|
-
|
|
874
|
+
|
|
875
|
+
// ../face/src/encoding.ts
|
|
876
|
+
var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
|
|
877
|
+
var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
|
|
878
|
+
function consoleRendersUnicode(env = process.env, platform = process.platform) {
|
|
879
|
+
if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
|
|
880
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
881
|
+
if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
|
|
882
|
+
if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
|
|
883
|
+
if (/utf-?8/iu.test(locale)) return true;
|
|
884
|
+
if (platform === "win32") {
|
|
885
|
+
return Boolean(env.WT_SESSION) || env.TERM_PROGRAM === "vscode" || env.CHCP === "65001" || env.ConEmuANSI === "ON";
|
|
886
|
+
}
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
890
|
+
return consoleRendersUnicode(env, platform) ? UNICODE_SPINNER_FRAMES : ASCII_SPINNER_FRAMES;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// ../face/src/spinner.ts
|
|
835
894
|
var WORKER_SOURCE = `
|
|
836
895
|
const { parentPort, workerData } = require('node:worker_threads');
|
|
837
896
|
const { writeSync, appendFileSync } = require('node:fs');
|
|
@@ -856,8 +915,10 @@ parentPort.on('message', (next) => {
|
|
|
856
915
|
});
|
|
857
916
|
setInterval(draw, workerData.intervalMs);
|
|
858
917
|
`;
|
|
859
|
-
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
918
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath, env = process.env, platform = process.platform }) {
|
|
860
919
|
animate = animate && !face.nested && !face.emitsProgress;
|
|
920
|
+
const glyphs = spinnerFramesFor(env, platform);
|
|
921
|
+
const ascii = glyphs[0] === ASCII_SPINNER_FRAMES[0];
|
|
861
922
|
let timer = null;
|
|
862
923
|
let worker = null;
|
|
863
924
|
let control = null;
|
|
@@ -875,7 +936,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
875
936
|
};
|
|
876
937
|
const render = (text, measure) => {
|
|
877
938
|
const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
|
|
878
|
-
|
|
939
|
+
const prepared = line && ascii ? line.replaceAll(GLYPH.bar, "|") : line;
|
|
940
|
+
frames = prepared ? glyphs.map((glyph) => `\r\x1B[2K${prepared.replace(GLYPH.dot, glyph)}`) : [];
|
|
879
941
|
};
|
|
880
942
|
const draw = () => {
|
|
881
943
|
if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
|
|
@@ -952,7 +1014,6 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
952
1014
|
}
|
|
953
1015
|
};
|
|
954
1016
|
}
|
|
955
|
-
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
956
1017
|
|
|
957
1018
|
// ../face/src/conformance.ts
|
|
958
1019
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
@@ -1056,6 +1117,14 @@ var PHASES = {
|
|
|
1056
1117
|
doctor: ["Checking health", "Checked health"],
|
|
1057
1118
|
rollback: ["Restoring the previous version", "Restored the previous version"]
|
|
1058
1119
|
};
|
|
1120
|
+
var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
|
|
1121
|
+
"preflight",
|
|
1122
|
+
"resolve",
|
|
1123
|
+
"verify-release",
|
|
1124
|
+
"download",
|
|
1125
|
+
"sign-in",
|
|
1126
|
+
"check"
|
|
1127
|
+
]);
|
|
1059
1128
|
function createInstallerRun(value, options = {}) {
|
|
1060
1129
|
const declaration = validateInstallerProduct(value);
|
|
1061
1130
|
const env = options.env ?? process.env;
|
|
@@ -1091,6 +1160,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1091
1160
|
};
|
|
1092
1161
|
const spinner = createSpinner(face, {
|
|
1093
1162
|
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
1163
|
+
env,
|
|
1094
1164
|
...options.write ? { stream: { write: (text) => {
|
|
1095
1165
|
emit(String(text), "spinner");
|
|
1096
1166
|
return true;
|
|
@@ -1109,6 +1179,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1109
1179
|
};
|
|
1110
1180
|
let started = false;
|
|
1111
1181
|
let finished = false;
|
|
1182
|
+
let operation = options.operation;
|
|
1112
1183
|
const start = () => {
|
|
1113
1184
|
if (started || finished) return;
|
|
1114
1185
|
started = true;
|
|
@@ -1116,7 +1187,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1116
1187
|
const welcome = face.welcome();
|
|
1117
1188
|
if (tty) lines(welcome);
|
|
1118
1189
|
else if (welcome.length) {
|
|
1119
|
-
const warm =
|
|
1190
|
+
const warm = operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
1120
1191
|
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
1121
1192
|
}
|
|
1122
1193
|
};
|
|
@@ -1132,13 +1203,20 @@ function createInstallerRun(value, options = {}) {
|
|
|
1132
1203
|
get errors() {
|
|
1133
1204
|
return [...errors];
|
|
1134
1205
|
},
|
|
1206
|
+
get operation() {
|
|
1207
|
+
return operation;
|
|
1208
|
+
},
|
|
1209
|
+
set operation(value2) {
|
|
1210
|
+
operation = value2;
|
|
1211
|
+
},
|
|
1135
1212
|
start,
|
|
1136
1213
|
phase(id, facts = {}) {
|
|
1137
1214
|
if (finished) throw new Error("installer run already finished");
|
|
1138
1215
|
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
1139
1216
|
start();
|
|
1140
1217
|
const state = facts.state ?? "ok";
|
|
1141
|
-
const
|
|
1218
|
+
const fold = operation === "update" && UPDATE_CHECK_PHASES.has(id) && state !== "fail";
|
|
1219
|
+
const title = PHASES[fold ? "check" : id][state === "ok" ? 1 : 0];
|
|
1142
1220
|
if (state === "running") {
|
|
1143
1221
|
if (face.progress(title, facts.measure ?? null)) return;
|
|
1144
1222
|
if (activeTitle === title) spinner.say(title, facts.measure ?? null);
|
|
@@ -1148,6 +1226,10 @@ function createInstallerRun(value, options = {}) {
|
|
|
1148
1226
|
}
|
|
1149
1227
|
return;
|
|
1150
1228
|
}
|
|
1229
|
+
if (fold && id !== "check") {
|
|
1230
|
+
if (facts.detail) run2.relay(facts.detail);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1151
1233
|
if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
1152
1234
|
if (facts.detail) run2.relay(facts.detail);
|
|
1153
1235
|
},
|
|
@@ -1286,7 +1368,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1286
1368
|
|
|
1287
1369
|
// src/autoupdate.ts
|
|
1288
1370
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1289
|
-
import { existsSync as
|
|
1371
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1290
1372
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
1291
1373
|
import { join as join5 } from "node:path";
|
|
1292
1374
|
function schedulePlatform(override) {
|
|
@@ -1398,11 +1480,11 @@ function querySchedule(config, options = {}) {
|
|
|
1398
1480
|
}
|
|
1399
1481
|
if (platform === "darwin") {
|
|
1400
1482
|
const plist = join5(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
|
|
1401
|
-
if (!
|
|
1483
|
+
if (!existsSync6(plist)) return { supported: true, enabled: false };
|
|
1402
1484
|
return { supported: true, enabled: true, cadence: "hourly" };
|
|
1403
1485
|
}
|
|
1404
1486
|
const timer = join5(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
|
|
1405
|
-
if (!
|
|
1487
|
+
if (!existsSync6(timer)) return { supported: true, enabled: false };
|
|
1406
1488
|
const state = { supported: true, enabled: true, cadence: "hourly" };
|
|
1407
1489
|
const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
|
|
1408
1490
|
const stamp = (shown.stdout ?? "").trim();
|
|
@@ -1694,7 +1776,7 @@ function page(title, body) {
|
|
|
1694
1776
|
}
|
|
1695
1777
|
|
|
1696
1778
|
// src/index.ts
|
|
1697
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1779
|
+
var LAUNCHER_VERSION = true ? "0.1.15" : readVersionFromPackage();
|
|
1698
1780
|
function defaultPrint(message) {
|
|
1699
1781
|
process.stdout.write(`${message}
|
|
1700
1782
|
`);
|
|
@@ -1783,7 +1865,7 @@ async function runFile(file, args, printErr) {
|
|
|
1783
1865
|
return 1;
|
|
1784
1866
|
}
|
|
1785
1867
|
const abs = resolve2(process.cwd(), file);
|
|
1786
|
-
if (!
|
|
1868
|
+
if (!existsSync7(abs)) {
|
|
1787
1869
|
printErr(`cannot run ${file}: no such file.`);
|
|
1788
1870
|
return 1;
|
|
1789
1871
|
}
|
|
@@ -1915,7 +1997,7 @@ function resolveEntry(entry2) {
|
|
|
1915
1997
|
function needsShell(command2) {
|
|
1916
1998
|
if (process.platform !== "win32") return false;
|
|
1917
1999
|
if (/\.(cmd|bat)$/i.test(command2)) return true;
|
|
1918
|
-
return !
|
|
2000
|
+
return !existsSync7(command2);
|
|
1919
2001
|
}
|
|
1920
2002
|
function quoteForShell(arg) {
|
|
1921
2003
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
@@ -1995,8 +2077,27 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
1995
2077
|
let version = readState(dir)?.version ?? "unknown";
|
|
1996
2078
|
installer.start();
|
|
1997
2079
|
try {
|
|
2080
|
+
const current = readState(dir);
|
|
2081
|
+
const payloadPresent = existsSync7(payloadDir(dir));
|
|
1998
2082
|
let started = Date.now();
|
|
2083
|
+
installer.phase("resolve", { state: "running" });
|
|
2084
|
+
const pointer = await fetchReleaseVersion(config, fetchImpl);
|
|
2085
|
+
if (pointer && current?.version === pointer && payloadPresent) {
|
|
2086
|
+
version = pointer;
|
|
2087
|
+
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2088
|
+
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
2089
|
+
return await finishLastMile(
|
|
2090
|
+
config,
|
|
2091
|
+
dir,
|
|
2092
|
+
options,
|
|
2093
|
+
installer,
|
|
2094
|
+
version,
|
|
2095
|
+
true,
|
|
2096
|
+
current.acquired ? acquisitionRepairCommand(dir, version) : void 0
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
1999
2099
|
installer.phase("sign-in", { state: "running" });
|
|
2100
|
+
started = Date.now();
|
|
2000
2101
|
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print, installer);
|
|
2001
2102
|
installer.phase("sign-in", { seconds: (Date.now() - started) / 1e3 });
|
|
2002
2103
|
installer.phase("resolve", { state: "running" });
|
|
@@ -2004,8 +2105,7 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2004
2105
|
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
2005
2106
|
version = manifest.version;
|
|
2006
2107
|
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2007
|
-
const
|
|
2008
|
-
const unchanged = current?.version === version && existsSync6(payloadDir(dir));
|
|
2108
|
+
const unchanged = current?.version === version && existsSync7(payloadDir(dir));
|
|
2009
2109
|
if (unchanged && current.acquired) {
|
|
2010
2110
|
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
2011
2111
|
return await finishLastMile(config, dir, options, installer, version, true, acquisitionRepairCommand(dir, version));
|
|
@@ -2020,8 +2120,8 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2020
2120
|
state: "running",
|
|
2021
2121
|
measure: total ? `${Math.floor(done / total * 100)}% \xB7 ${done}/${total} B` : `${done} B`
|
|
2022
2122
|
});
|
|
2023
|
-
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress });
|
|
2024
|
-
const acquisition =
|
|
2123
|
+
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress, cacheDir: dir });
|
|
2124
|
+
const acquisition = existsSync7(join6(candidateDir, "payload", "payload.json")) ? readAcquisition(join6(candidateDir, "payload")) : null;
|
|
2025
2125
|
if (acquisition) {
|
|
2026
2126
|
rememberCandidate(dir, candidate, manifest);
|
|
2027
2127
|
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
@@ -2090,7 +2190,7 @@ function payloadFileAsCommand(entry2, payload) {
|
|
|
2090
2190
|
if (!first || first === "$self") return null;
|
|
2091
2191
|
if (first.includes("/") || first.includes("\\")) return null;
|
|
2092
2192
|
const candidate = join6(payload, first);
|
|
2093
|
-
if (!
|
|
2193
|
+
if (!existsSync7(candidate)) return null;
|
|
2094
2194
|
if (NEVER_A_PROGRAM.test(first)) return first;
|
|
2095
2195
|
if (process.platform === "win32") return null;
|
|
2096
2196
|
try {
|
|
@@ -2207,7 +2307,7 @@ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
|
2207
2307
|
const pollProgress = () => {
|
|
2208
2308
|
if (progressError) return;
|
|
2209
2309
|
try {
|
|
2210
|
-
if (!
|
|
2310
|
+
if (!existsSync7(progressFile)) return;
|
|
2211
2311
|
const bytes = readFileSync7(progressFile);
|
|
2212
2312
|
receive(progressDecoder.write(bytes.subarray(progressOffset)));
|
|
2213
2313
|
progressOffset = bytes.length;
|
|
@@ -2241,13 +2341,13 @@ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
|
2241
2341
|
return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
|
|
2242
2342
|
}
|
|
2243
2343
|
} finally {
|
|
2244
|
-
if (
|
|
2245
|
-
if (
|
|
2344
|
+
if (existsSync7(progressFile)) unlinkSync2(progressFile);
|
|
2345
|
+
if (existsSync7(outcomeFile)) unlinkSync2(outcomeFile);
|
|
2246
2346
|
rmdirSync(outcomeDir);
|
|
2247
2347
|
}
|
|
2248
2348
|
}
|
|
2249
2349
|
function doForward(config, dir, options, command2, argv2, print, unlock) {
|
|
2250
|
-
if (command2 &&
|
|
2350
|
+
if (command2 && existsSync7(command2)) {
|
|
2251
2351
|
print(`${command2} is a file, not a command \u2014 did you mean \`--run ${command2}\`?`);
|
|
2252
2352
|
return 2;
|
|
2253
2353
|
}
|
|
@@ -2327,7 +2427,7 @@ function doDoctor(config, dir, options, print, unlock) {
|
|
|
2327
2427
|
function doLauncherDoctor(config, dir, options, print) {
|
|
2328
2428
|
const tokens = readTokens(dir);
|
|
2329
2429
|
const state = readState(dir);
|
|
2330
|
-
const payloadPresent =
|
|
2430
|
+
const payloadPresent = existsSync7(payloadDir(dir));
|
|
2331
2431
|
print(`product: ${config.product}`);
|
|
2332
2432
|
print(`host: ${config.host}`);
|
|
2333
2433
|
print(`login: ${config.loginKind}`);
|
package/dist/launcher.sea.cjs
CHANGED
|
@@ -435,6 +435,17 @@ async function fetchVerifiedManifest(config, accessToken, fetchImpl = fetch) {
|
|
|
435
435
|
}
|
|
436
436
|
return manifest;
|
|
437
437
|
}
|
|
438
|
+
async function fetchReleaseVersion(config, fetchImpl = fetch) {
|
|
439
|
+
try {
|
|
440
|
+
const response = await fetchImpl(`${config.host}/release/version`);
|
|
441
|
+
if (!response.ok) return null;
|
|
442
|
+
const json = await response.json();
|
|
443
|
+
const version = typeof json?.version === "string" ? json.version.trim() : "";
|
|
444
|
+
return version || null;
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
438
449
|
async function fetchFileBytes(config, accessToken, path, fetchImpl, onProgress, expectedSize) {
|
|
439
450
|
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
440
451
|
const response = await fetchImpl(`${config.host}/release/${encoded}`, {
|
|
@@ -446,7 +457,36 @@ async function fetchFileBytes(config, accessToken, path, fetchImpl, onProgress,
|
|
|
446
457
|
}
|
|
447
458
|
return readDownload(response, onProgress, expectedSize);
|
|
448
459
|
}
|
|
460
|
+
var VERSION_CACHE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
461
|
+
function versionCacheRoot(dir, version) {
|
|
462
|
+
return VERSION_CACHE_RE.test(version) ? (0, import_node_path3.join)(dir, "payload-cache", version) : "";
|
|
463
|
+
}
|
|
464
|
+
function cachedPayloadForVersion(dir, version) {
|
|
465
|
+
const root = versionCacheRoot(dir, version);
|
|
466
|
+
if (!root) return null;
|
|
467
|
+
const payload = (0, import_node_path3.join)(root, "payload");
|
|
468
|
+
return (0, import_node_fs4.existsSync)(payload) ? payload : null;
|
|
469
|
+
}
|
|
470
|
+
function rememberPayloadVersion(dir, version, payload) {
|
|
471
|
+
try {
|
|
472
|
+
const root = versionCacheRoot(dir, version);
|
|
473
|
+
if (!root) return;
|
|
474
|
+
const dest = (0, import_node_path3.join)(root, "payload");
|
|
475
|
+
if ((0, import_node_fs4.existsSync)(dest)) return;
|
|
476
|
+
(0, import_node_fs4.mkdirSync)(root, { recursive: true });
|
|
477
|
+
(0, import_node_fs4.cpSync)(payload, dest, { recursive: true });
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
449
481
|
async function downloadAndUnpack(config, dir, manifest, accessToken, options = {}) {
|
|
482
|
+
const target = (0, import_node_path3.join)(dir, "payload");
|
|
483
|
+
const cached = options.cacheDir ? cachedPayloadForVersion(options.cacheDir, manifest.version) : null;
|
|
484
|
+
if (cached) {
|
|
485
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
486
|
+
(0, import_node_fs4.rmSync)(target, { force: true, recursive: true });
|
|
487
|
+
(0, import_node_fs4.cpSync)(cached, target, { recursive: true });
|
|
488
|
+
return manifest.version;
|
|
489
|
+
}
|
|
450
490
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
451
491
|
const staging = (0, import_node_path3.join)((0, import_node_os2.tmpdir)(), `launcher-${config.product}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
|
|
452
492
|
(0, import_node_fs4.mkdirSync)(staging, { recursive: true });
|
|
@@ -463,7 +503,6 @@ async function downloadAndUnpack(config, dir, manifest, accessToken, options = {
|
|
|
463
503
|
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(dest), { recursive: true });
|
|
464
504
|
(0, import_node_fs4.writeFileSync)(dest, bytes);
|
|
465
505
|
}
|
|
466
|
-
const target = (0, import_node_path3.join)(dir, "payload");
|
|
467
506
|
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
468
507
|
(0, import_node_fs4.rmSync)(target, { force: true, recursive: true });
|
|
469
508
|
(0, import_node_fs4.renameSync)(staging, target);
|
|
@@ -471,6 +510,7 @@ async function downloadAndUnpack(config, dir, manifest, accessToken, options = {
|
|
|
471
510
|
(0, import_node_fs4.rmSync)(staging, { force: true, recursive: true });
|
|
472
511
|
throw error;
|
|
473
512
|
}
|
|
513
|
+
if (options.cacheDir) rememberPayloadVersion(options.cacheDir, manifest.version, target);
|
|
474
514
|
return manifest.version;
|
|
475
515
|
}
|
|
476
516
|
|
|
@@ -863,7 +903,26 @@ var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
|
863
903
|
// ../face/src/spinner.ts
|
|
864
904
|
var import_node_fs7 = require("node:fs");
|
|
865
905
|
var import_node_worker_threads = require("node:worker_threads");
|
|
866
|
-
|
|
906
|
+
|
|
907
|
+
// ../face/src/encoding.ts
|
|
908
|
+
var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
|
|
909
|
+
var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
|
|
910
|
+
function consoleRendersUnicode(env = process.env, platform = process.platform) {
|
|
911
|
+
if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
|
|
912
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
913
|
+
if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
|
|
914
|
+
if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
|
|
915
|
+
if (/utf-?8/iu.test(locale)) return true;
|
|
916
|
+
if (platform === "win32") {
|
|
917
|
+
return Boolean(env.WT_SESSION) || env.TERM_PROGRAM === "vscode" || env.CHCP === "65001" || env.ConEmuANSI === "ON";
|
|
918
|
+
}
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
922
|
+
return consoleRendersUnicode(env, platform) ? UNICODE_SPINNER_FRAMES : ASCII_SPINNER_FRAMES;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ../face/src/spinner.ts
|
|
867
926
|
var WORKER_SOURCE = `
|
|
868
927
|
const { parentPort, workerData } = require('node:worker_threads');
|
|
869
928
|
const { writeSync, appendFileSync } = require('node:fs');
|
|
@@ -888,8 +947,10 @@ parentPort.on('message', (next) => {
|
|
|
888
947
|
});
|
|
889
948
|
setInterval(draw, workerData.intervalMs);
|
|
890
949
|
`;
|
|
891
|
-
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
950
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath, env = process.env, platform = process.platform }) {
|
|
892
951
|
animate = animate && !face.nested && !face.emitsProgress;
|
|
952
|
+
const glyphs = spinnerFramesFor(env, platform);
|
|
953
|
+
const ascii = glyphs[0] === ASCII_SPINNER_FRAMES[0];
|
|
893
954
|
let timer = null;
|
|
894
955
|
let worker = null;
|
|
895
956
|
let control = null;
|
|
@@ -907,7 +968,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
907
968
|
};
|
|
908
969
|
const render = (text, measure) => {
|
|
909
970
|
const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
|
|
910
|
-
|
|
971
|
+
const prepared = line && ascii ? line.replaceAll(GLYPH.bar, "|") : line;
|
|
972
|
+
frames = prepared ? glyphs.map((glyph) => `\r\x1B[2K${prepared.replace(GLYPH.dot, glyph)}`) : [];
|
|
911
973
|
};
|
|
912
974
|
const draw = () => {
|
|
913
975
|
if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
|
|
@@ -984,7 +1046,6 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
984
1046
|
}
|
|
985
1047
|
};
|
|
986
1048
|
}
|
|
987
|
-
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
988
1049
|
|
|
989
1050
|
// ../face/src/conformance.ts
|
|
990
1051
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
@@ -1088,6 +1149,14 @@ var PHASES = {
|
|
|
1088
1149
|
doctor: ["Checking health", "Checked health"],
|
|
1089
1150
|
rollback: ["Restoring the previous version", "Restored the previous version"]
|
|
1090
1151
|
};
|
|
1152
|
+
var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
|
|
1153
|
+
"preflight",
|
|
1154
|
+
"resolve",
|
|
1155
|
+
"verify-release",
|
|
1156
|
+
"download",
|
|
1157
|
+
"sign-in",
|
|
1158
|
+
"check"
|
|
1159
|
+
]);
|
|
1091
1160
|
function createInstallerRun(value, options = {}) {
|
|
1092
1161
|
const declaration = validateInstallerProduct(value);
|
|
1093
1162
|
const env = options.env ?? process.env;
|
|
@@ -1123,6 +1192,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1123
1192
|
};
|
|
1124
1193
|
const spinner = createSpinner(face, {
|
|
1125
1194
|
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
1195
|
+
env,
|
|
1126
1196
|
...options.write ? { stream: { write: (text) => {
|
|
1127
1197
|
emit(String(text), "spinner");
|
|
1128
1198
|
return true;
|
|
@@ -1141,6 +1211,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1141
1211
|
};
|
|
1142
1212
|
let started = false;
|
|
1143
1213
|
let finished = false;
|
|
1214
|
+
let operation = options.operation;
|
|
1144
1215
|
const start = () => {
|
|
1145
1216
|
if (started || finished) return;
|
|
1146
1217
|
started = true;
|
|
@@ -1148,7 +1219,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1148
1219
|
const welcome = face.welcome();
|
|
1149
1220
|
if (tty) lines(welcome);
|
|
1150
1221
|
else if (welcome.length) {
|
|
1151
|
-
const warm =
|
|
1222
|
+
const warm = operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
1152
1223
|
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
1153
1224
|
}
|
|
1154
1225
|
};
|
|
@@ -1164,13 +1235,20 @@ function createInstallerRun(value, options = {}) {
|
|
|
1164
1235
|
get errors() {
|
|
1165
1236
|
return [...errors];
|
|
1166
1237
|
},
|
|
1238
|
+
get operation() {
|
|
1239
|
+
return operation;
|
|
1240
|
+
},
|
|
1241
|
+
set operation(value2) {
|
|
1242
|
+
operation = value2;
|
|
1243
|
+
},
|
|
1167
1244
|
start,
|
|
1168
1245
|
phase(id, facts = {}) {
|
|
1169
1246
|
if (finished) throw new Error("installer run already finished");
|
|
1170
1247
|
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
1171
1248
|
start();
|
|
1172
1249
|
const state = facts.state ?? "ok";
|
|
1173
|
-
const
|
|
1250
|
+
const fold = operation === "update" && UPDATE_CHECK_PHASES.has(id) && state !== "fail";
|
|
1251
|
+
const title = PHASES[fold ? "check" : id][state === "ok" ? 1 : 0];
|
|
1174
1252
|
if (state === "running") {
|
|
1175
1253
|
if (face.progress(title, facts.measure ?? null)) return;
|
|
1176
1254
|
if (activeTitle === title) spinner.say(title, facts.measure ?? null);
|
|
@@ -1180,6 +1258,10 @@ function createInstallerRun(value, options = {}) {
|
|
|
1180
1258
|
}
|
|
1181
1259
|
return;
|
|
1182
1260
|
}
|
|
1261
|
+
if (fold && id !== "check") {
|
|
1262
|
+
if (facts.detail) run2.relay(facts.detail);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1183
1265
|
if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
1184
1266
|
if (facts.detail) run2.relay(facts.detail);
|
|
1185
1267
|
},
|
|
@@ -1726,7 +1808,7 @@ function page(title, body) {
|
|
|
1726
1808
|
}
|
|
1727
1809
|
|
|
1728
1810
|
// src/index.ts
|
|
1729
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1811
|
+
var LAUNCHER_VERSION = true ? "0.1.15" : readVersionFromPackage();
|
|
1730
1812
|
function defaultPrint(message) {
|
|
1731
1813
|
process.stdout.write(`${message}
|
|
1732
1814
|
`);
|
|
@@ -2027,8 +2109,27 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2027
2109
|
let version = readState(dir)?.version ?? "unknown";
|
|
2028
2110
|
installer.start();
|
|
2029
2111
|
try {
|
|
2112
|
+
const current = readState(dir);
|
|
2113
|
+
const payloadPresent = (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
2030
2114
|
let started = Date.now();
|
|
2115
|
+
installer.phase("resolve", { state: "running" });
|
|
2116
|
+
const pointer = await fetchReleaseVersion(config, fetchImpl);
|
|
2117
|
+
if (pointer && current?.version === pointer && payloadPresent) {
|
|
2118
|
+
version = pointer;
|
|
2119
|
+
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2120
|
+
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
2121
|
+
return await finishLastMile(
|
|
2122
|
+
config,
|
|
2123
|
+
dir,
|
|
2124
|
+
options,
|
|
2125
|
+
installer,
|
|
2126
|
+
version,
|
|
2127
|
+
true,
|
|
2128
|
+
current.acquired ? acquisitionRepairCommand(dir, version) : void 0
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2031
2131
|
installer.phase("sign-in", { state: "running" });
|
|
2132
|
+
started = Date.now();
|
|
2032
2133
|
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print, installer);
|
|
2033
2134
|
installer.phase("sign-in", { seconds: (Date.now() - started) / 1e3 });
|
|
2034
2135
|
installer.phase("resolve", { state: "running" });
|
|
@@ -2036,7 +2137,6 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2036
2137
|
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
2037
2138
|
version = manifest.version;
|
|
2038
2139
|
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2039
|
-
const current = readState(dir);
|
|
2040
2140
|
const unchanged = current?.version === version && (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
2041
2141
|
if (unchanged && current.acquired) {
|
|
2042
2142
|
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
@@ -2052,7 +2152,7 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2052
2152
|
state: "running",
|
|
2053
2153
|
measure: total ? `${Math.floor(done / total * 100)}% \xB7 ${done}/${total} B` : `${done} B`
|
|
2054
2154
|
});
|
|
2055
|
-
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress });
|
|
2155
|
+
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress, cacheDir: dir });
|
|
2056
2156
|
const acquisition = (0, import_node_fs11.existsSync)((0, import_node_path6.join)(candidateDir, "payload", "payload.json")) ? readAcquisition((0, import_node_path6.join)(candidateDir, "payload")) : null;
|
|
2057
2157
|
if (acquisition) {
|
|
2058
2158
|
rememberCandidate(dir, candidate, manifest);
|
package/package.json
CHANGED