@mutmutco/installer-launcher 0.1.14 → 0.1.17
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 +171 -41
- package/dist/launcher.sea.cjs +158 -28
- 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";
|
|
@@ -759,15 +799,24 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
759
799
|
const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
|
|
760
800
|
if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
|
|
761
801
|
if (kind === "running" && typeof measure === "string") record.measure = measure;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
802
|
+
const line = `${JSON.stringify(record)}
|
|
803
|
+
`;
|
|
804
|
+
if (progressFd !== null) {
|
|
805
|
+
try {
|
|
806
|
+
writeSync(progressFd, line);
|
|
807
|
+
return true;
|
|
808
|
+
} catch {
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (progressFile) {
|
|
812
|
+
try {
|
|
813
|
+
appendFileSync(progressFile, line);
|
|
814
|
+
return true;
|
|
815
|
+
} catch {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
770
818
|
}
|
|
819
|
+
return false;
|
|
771
820
|
};
|
|
772
821
|
const welcome = () => continuesFace || nested ? [] : [
|
|
773
822
|
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
@@ -781,7 +830,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
781
830
|
};
|
|
782
831
|
const step = (title, measure = null, kind = "ok") => {
|
|
783
832
|
if (emitMilestone(title, measure, kind)) return "";
|
|
784
|
-
const glyph = title === "Armed hourly updates" && kind === "ok" ? GLYPH.clock : kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(
|
|
833
|
+
const glyph = title === "Armed hourly updates" && kind === "ok" ? GLYPH.clock : kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(identity.accent, GLYPH.check);
|
|
785
834
|
const measured = measure === null || measure === void 0 ? "" : paint(PALETTE.muted, typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : String(measure).trim());
|
|
786
835
|
const time = measured;
|
|
787
836
|
const column = Math.min(44, Math.max(0, width - 8));
|
|
@@ -802,7 +851,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
802
851
|
const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
|
|
803
852
|
const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
|
|
804
853
|
const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
|
|
805
|
-
const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(
|
|
854
|
+
const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(identity.accent, GLYPH.check)}${line.slice(1)}` : line;
|
|
806
855
|
return [
|
|
807
856
|
frame(GLYPH.boxTop, GLYPH.boxTopEnd),
|
|
808
857
|
...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
|
|
@@ -831,7 +880,41 @@ var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
|
831
880
|
// ../face/src/spinner.ts
|
|
832
881
|
import { appendFileSync as appendFileSync2, writeSync as writeSync2 } from "node:fs";
|
|
833
882
|
import { Worker } from "node:worker_threads";
|
|
834
|
-
|
|
883
|
+
|
|
884
|
+
// ../face/src/encoding.ts
|
|
885
|
+
import { execFileSync } from "node:child_process";
|
|
886
|
+
var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
|
|
887
|
+
var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
|
|
888
|
+
var cachedCodePage;
|
|
889
|
+
function consoleOutputCodePage() {
|
|
890
|
+
if (cachedCodePage !== void 0) return cachedCodePage;
|
|
891
|
+
cachedCodePage = null;
|
|
892
|
+
try {
|
|
893
|
+
const out = execFileSync("chcp.com", { windowsHide: true, encoding: "utf8", timeout: 5e3 });
|
|
894
|
+
const pages = [...String(out).matchAll(/(\d{3,5})/gu)].map((m) => Number(m[1]));
|
|
895
|
+
cachedCodePage = pages.length ? pages[pages.length - 1] : null;
|
|
896
|
+
} catch {
|
|
897
|
+
cachedCodePage = null;
|
|
898
|
+
}
|
|
899
|
+
return cachedCodePage;
|
|
900
|
+
}
|
|
901
|
+
function consoleRendersUnicode(env = process.env, platform = process.platform, codePage = platform === "win32" ? consoleOutputCodePage() : null) {
|
|
902
|
+
if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
|
|
903
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
904
|
+
if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
|
|
905
|
+
if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
|
|
906
|
+
if (/utf-?8/iu.test(locale)) return true;
|
|
907
|
+
if (platform === "win32") {
|
|
908
|
+
if (env.WT_SESSION || env.ConEmuANSI === "ON") return true;
|
|
909
|
+
return codePage === 65001;
|
|
910
|
+
}
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
914
|
+
return consoleRendersUnicode(env, platform) ? UNICODE_SPINNER_FRAMES : ASCII_SPINNER_FRAMES;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// ../face/src/spinner.ts
|
|
835
918
|
var WORKER_SOURCE = `
|
|
836
919
|
const { parentPort, workerData } = require('node:worker_threads');
|
|
837
920
|
const { writeSync, appendFileSync } = require('node:fs');
|
|
@@ -856,8 +939,10 @@ parentPort.on('message', (next) => {
|
|
|
856
939
|
});
|
|
857
940
|
setInterval(draw, workerData.intervalMs);
|
|
858
941
|
`;
|
|
859
|
-
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
942
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath, env = process.env, platform = process.platform }) {
|
|
860
943
|
animate = animate && !face.nested && !face.emitsProgress;
|
|
944
|
+
const glyphs = spinnerFramesFor(env, platform);
|
|
945
|
+
const ascii = glyphs[0] === ASCII_SPINNER_FRAMES[0];
|
|
861
946
|
let timer = null;
|
|
862
947
|
let worker = null;
|
|
863
948
|
let control = null;
|
|
@@ -875,7 +960,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
875
960
|
};
|
|
876
961
|
const render = (text, measure) => {
|
|
877
962
|
const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
|
|
878
|
-
|
|
963
|
+
const prepared = line && ascii ? line.replaceAll(GLYPH.bar, "|") : line;
|
|
964
|
+
frames = prepared ? glyphs.map((glyph) => `\r\x1B[2K${prepared.replace(GLYPH.dot, glyph)}`) : [];
|
|
879
965
|
};
|
|
880
966
|
const draw = () => {
|
|
881
967
|
if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
|
|
@@ -952,7 +1038,6 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
952
1038
|
}
|
|
953
1039
|
};
|
|
954
1040
|
}
|
|
955
|
-
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
956
1041
|
|
|
957
1042
|
// ../face/src/conformance.ts
|
|
958
1043
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
@@ -1056,6 +1141,14 @@ var PHASES = {
|
|
|
1056
1141
|
doctor: ["Checking health", "Checked health"],
|
|
1057
1142
|
rollback: ["Restoring the previous version", "Restored the previous version"]
|
|
1058
1143
|
};
|
|
1144
|
+
var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
|
|
1145
|
+
"preflight",
|
|
1146
|
+
"resolve",
|
|
1147
|
+
"verify-release",
|
|
1148
|
+
"download",
|
|
1149
|
+
"sign-in",
|
|
1150
|
+
"check"
|
|
1151
|
+
]);
|
|
1059
1152
|
function createInstallerRun(value, options = {}) {
|
|
1060
1153
|
const declaration = validateInstallerProduct(value);
|
|
1061
1154
|
const env = options.env ?? process.env;
|
|
@@ -1091,6 +1184,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1091
1184
|
};
|
|
1092
1185
|
const spinner = createSpinner(face, {
|
|
1093
1186
|
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
1187
|
+
env,
|
|
1094
1188
|
...options.write ? { stream: { write: (text) => {
|
|
1095
1189
|
emit(String(text), "spinner");
|
|
1096
1190
|
return true;
|
|
@@ -1102,6 +1196,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1102
1196
|
let chunkColumn = 0;
|
|
1103
1197
|
const trailingCR = /* @__PURE__ */ new Set();
|
|
1104
1198
|
let deferredActivity;
|
|
1199
|
+
const drawnPhaseCompletions = /* @__PURE__ */ new Set();
|
|
1105
1200
|
const closeChunk = () => {
|
|
1106
1201
|
if (chunkOpen) emit("\n", chunkChannel);
|
|
1107
1202
|
chunkOpen = false;
|
|
@@ -1109,6 +1204,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1109
1204
|
};
|
|
1110
1205
|
let started = false;
|
|
1111
1206
|
let finished = false;
|
|
1207
|
+
let operation = options.operation;
|
|
1112
1208
|
const start = () => {
|
|
1113
1209
|
if (started || finished) return;
|
|
1114
1210
|
started = true;
|
|
@@ -1116,7 +1212,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1116
1212
|
const welcome = face.welcome();
|
|
1117
1213
|
if (tty) lines(welcome);
|
|
1118
1214
|
else if (welcome.length) {
|
|
1119
|
-
const warm =
|
|
1215
|
+
const warm = operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
1120
1216
|
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
1121
1217
|
}
|
|
1122
1218
|
};
|
|
@@ -1132,13 +1228,20 @@ function createInstallerRun(value, options = {}) {
|
|
|
1132
1228
|
get errors() {
|
|
1133
1229
|
return [...errors];
|
|
1134
1230
|
},
|
|
1231
|
+
get operation() {
|
|
1232
|
+
return operation;
|
|
1233
|
+
},
|
|
1234
|
+
set operation(value2) {
|
|
1235
|
+
operation = value2;
|
|
1236
|
+
},
|
|
1135
1237
|
start,
|
|
1136
1238
|
phase(id, facts = {}) {
|
|
1137
1239
|
if (finished) throw new Error("installer run already finished");
|
|
1138
1240
|
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
1139
1241
|
start();
|
|
1140
1242
|
const state = facts.state ?? "ok";
|
|
1141
|
-
const
|
|
1243
|
+
const fold = operation === "update" && UPDATE_CHECK_PHASES.has(id) && state !== "fail";
|
|
1244
|
+
const title = PHASES[fold ? "check" : id][state === "ok" ? 1 : 0];
|
|
1142
1245
|
if (state === "running") {
|
|
1143
1246
|
if (face.progress(title, facts.measure ?? null)) return;
|
|
1144
1247
|
if (activeTitle === title) spinner.say(title, facts.measure ?? null);
|
|
@@ -1148,7 +1251,14 @@ function createInstallerRun(value, options = {}) {
|
|
|
1148
1251
|
}
|
|
1149
1252
|
return;
|
|
1150
1253
|
}
|
|
1151
|
-
if (
|
|
1254
|
+
if (fold && id !== "check") {
|
|
1255
|
+
if (facts.detail) run2.relay(facts.detail);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (!face.continues(id, state)) {
|
|
1259
|
+
durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
1260
|
+
if (state === "ok") drawnPhaseCompletions.add(title);
|
|
1261
|
+
}
|
|
1152
1262
|
if (facts.detail) run2.relay(facts.detail);
|
|
1153
1263
|
},
|
|
1154
1264
|
surface(facts) {
|
|
@@ -1179,7 +1289,9 @@ function createInstallerRun(value, options = {}) {
|
|
|
1179
1289
|
}
|
|
1180
1290
|
return;
|
|
1181
1291
|
}
|
|
1292
|
+
if (state !== "fail" && drawnPhaseCompletions.has(step)) return;
|
|
1182
1293
|
durable(step, ms === void 0 ? null : ms / 1e3, state);
|
|
1294
|
+
if (state === "ok") drawnPhaseCompletions.add(step);
|
|
1183
1295
|
},
|
|
1184
1296
|
signIn({ url, code }) {
|
|
1185
1297
|
start();
|
|
@@ -1286,7 +1398,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1286
1398
|
|
|
1287
1399
|
// src/autoupdate.ts
|
|
1288
1400
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1289
|
-
import { existsSync as
|
|
1401
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1290
1402
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
1291
1403
|
import { join as join5 } from "node:path";
|
|
1292
1404
|
function schedulePlatform(override) {
|
|
@@ -1398,11 +1510,11 @@ function querySchedule(config, options = {}) {
|
|
|
1398
1510
|
}
|
|
1399
1511
|
if (platform === "darwin") {
|
|
1400
1512
|
const plist = join5(home, "Library", "LaunchAgents", `${scheduleLabel(config)}.plist`);
|
|
1401
|
-
if (!
|
|
1513
|
+
if (!existsSync6(plist)) return { supported: true, enabled: false };
|
|
1402
1514
|
return { supported: true, enabled: true, cadence: "hourly" };
|
|
1403
1515
|
}
|
|
1404
1516
|
const timer = join5(home, ".config", "systemd", "user", `${scheduleLabel(config)}.timer`);
|
|
1405
|
-
if (!
|
|
1517
|
+
if (!existsSync6(timer)) return { supported: true, enabled: false };
|
|
1406
1518
|
const state = { supported: true, enabled: true, cadence: "hourly" };
|
|
1407
1519
|
const shown = exec("systemctl", ["--user", "show", `${scheduleLabel(config)}.service`, "-p", "ExecMainStartTimestamp", "--value"]);
|
|
1408
1520
|
const stamp = (shown.stdout ?? "").trim();
|
|
@@ -1694,7 +1806,7 @@ function page(title, body) {
|
|
|
1694
1806
|
}
|
|
1695
1807
|
|
|
1696
1808
|
// src/index.ts
|
|
1697
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1809
|
+
var LAUNCHER_VERSION = true ? "0.1.17" : readVersionFromPackage();
|
|
1698
1810
|
function defaultPrint(message) {
|
|
1699
1811
|
process.stdout.write(`${message}
|
|
1700
1812
|
`);
|
|
@@ -1783,7 +1895,7 @@ async function runFile(file, args, printErr) {
|
|
|
1783
1895
|
return 1;
|
|
1784
1896
|
}
|
|
1785
1897
|
const abs = resolve2(process.cwd(), file);
|
|
1786
|
-
if (!
|
|
1898
|
+
if (!existsSync7(abs)) {
|
|
1787
1899
|
printErr(`cannot run ${file}: no such file.`);
|
|
1788
1900
|
return 1;
|
|
1789
1901
|
}
|
|
@@ -1915,7 +2027,7 @@ function resolveEntry(entry2) {
|
|
|
1915
2027
|
function needsShell(command2) {
|
|
1916
2028
|
if (process.platform !== "win32") return false;
|
|
1917
2029
|
if (/\.(cmd|bat)$/i.test(command2)) return true;
|
|
1918
|
-
return !
|
|
2030
|
+
return !existsSync7(command2);
|
|
1919
2031
|
}
|
|
1920
2032
|
function quoteForShell(arg) {
|
|
1921
2033
|
return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
@@ -1995,8 +2107,27 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
1995
2107
|
let version = readState(dir)?.version ?? "unknown";
|
|
1996
2108
|
installer.start();
|
|
1997
2109
|
try {
|
|
2110
|
+
const current = readState(dir);
|
|
2111
|
+
const payloadPresent = existsSync7(payloadDir(dir));
|
|
1998
2112
|
let started = Date.now();
|
|
2113
|
+
installer.phase("resolve", { state: "running" });
|
|
2114
|
+
const pointer = await fetchReleaseVersion(config, fetchImpl);
|
|
2115
|
+
if (pointer && current?.version === pointer && payloadPresent) {
|
|
2116
|
+
version = pointer;
|
|
2117
|
+
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2118
|
+
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
2119
|
+
return await finishLastMile(
|
|
2120
|
+
config,
|
|
2121
|
+
dir,
|
|
2122
|
+
options,
|
|
2123
|
+
installer,
|
|
2124
|
+
version,
|
|
2125
|
+
true,
|
|
2126
|
+
current.acquired ? acquisitionRepairCommand(dir, version) : void 0
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
1999
2129
|
installer.phase("sign-in", { state: "running" });
|
|
2130
|
+
started = Date.now();
|
|
2000
2131
|
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print, installer);
|
|
2001
2132
|
installer.phase("sign-in", { seconds: (Date.now() - started) / 1e3 });
|
|
2002
2133
|
installer.phase("resolve", { state: "running" });
|
|
@@ -2004,8 +2135,7 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2004
2135
|
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
2005
2136
|
version = manifest.version;
|
|
2006
2137
|
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2007
|
-
const
|
|
2008
|
-
const unchanged = current?.version === version && existsSync6(payloadDir(dir));
|
|
2138
|
+
const unchanged = current?.version === version && existsSync7(payloadDir(dir));
|
|
2009
2139
|
if (unchanged && current.acquired) {
|
|
2010
2140
|
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
2011
2141
|
return await finishLastMile(config, dir, options, installer, version, true, acquisitionRepairCommand(dir, version));
|
|
@@ -2020,8 +2150,8 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2020
2150
|
state: "running",
|
|
2021
2151
|
measure: total ? `${Math.floor(done / total * 100)}% \xB7 ${done}/${total} B` : `${done} B`
|
|
2022
2152
|
});
|
|
2023
|
-
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress });
|
|
2024
|
-
const acquisition =
|
|
2153
|
+
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress, cacheDir: dir });
|
|
2154
|
+
const acquisition = existsSync7(join6(candidateDir, "payload", "payload.json")) ? readAcquisition(join6(candidateDir, "payload")) : null;
|
|
2025
2155
|
if (acquisition) {
|
|
2026
2156
|
rememberCandidate(dir, candidate, manifest);
|
|
2027
2157
|
installer.phase("download", { seconds: (Date.now() - started) / 1e3 });
|
|
@@ -2090,7 +2220,7 @@ function payloadFileAsCommand(entry2, payload) {
|
|
|
2090
2220
|
if (!first || first === "$self") return null;
|
|
2091
2221
|
if (first.includes("/") || first.includes("\\")) return null;
|
|
2092
2222
|
const candidate = join6(payload, first);
|
|
2093
|
-
if (!
|
|
2223
|
+
if (!existsSync7(candidate)) return null;
|
|
2094
2224
|
if (NEVER_A_PROGRAM.test(first)) return first;
|
|
2095
2225
|
if (process.platform === "win32") return null;
|
|
2096
2226
|
try {
|
|
@@ -2207,7 +2337,7 @@ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
|
2207
2337
|
const pollProgress = () => {
|
|
2208
2338
|
if (progressError) return;
|
|
2209
2339
|
try {
|
|
2210
|
-
if (!
|
|
2340
|
+
if (!existsSync7(progressFile)) return;
|
|
2211
2341
|
const bytes = readFileSync7(progressFile);
|
|
2212
2342
|
receive(progressDecoder.write(bytes.subarray(progressOffset)));
|
|
2213
2343
|
progressOffset = bytes.length;
|
|
@@ -2241,13 +2371,13 @@ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
|
2241
2371
|
return { ...result, ok: false, code: result.code || 1, error: "installer outcome: invalid child result" };
|
|
2242
2372
|
}
|
|
2243
2373
|
} finally {
|
|
2244
|
-
if (
|
|
2245
|
-
if (
|
|
2374
|
+
if (existsSync7(progressFile)) unlinkSync2(progressFile);
|
|
2375
|
+
if (existsSync7(outcomeFile)) unlinkSync2(outcomeFile);
|
|
2246
2376
|
rmdirSync(outcomeDir);
|
|
2247
2377
|
}
|
|
2248
2378
|
}
|
|
2249
2379
|
function doForward(config, dir, options, command2, argv2, print, unlock) {
|
|
2250
|
-
if (command2 &&
|
|
2380
|
+
if (command2 && existsSync7(command2)) {
|
|
2251
2381
|
print(`${command2} is a file, not a command \u2014 did you mean \`--run ${command2}\`?`);
|
|
2252
2382
|
return 2;
|
|
2253
2383
|
}
|
|
@@ -2327,7 +2457,7 @@ function doDoctor(config, dir, options, print, unlock) {
|
|
|
2327
2457
|
function doLauncherDoctor(config, dir, options, print) {
|
|
2328
2458
|
const tokens = readTokens(dir);
|
|
2329
2459
|
const state = readState(dir);
|
|
2330
|
-
const payloadPresent =
|
|
2460
|
+
const payloadPresent = existsSync7(payloadDir(dir));
|
|
2331
2461
|
print(`product: ${config.product}`);
|
|
2332
2462
|
print(`host: ${config.host}`);
|
|
2333
2463
|
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
|
|
|
@@ -663,7 +703,7 @@ async function installAcquisition(dir, candidate, version, acquisition, options)
|
|
|
663
703
|
}
|
|
664
704
|
|
|
665
705
|
// src/index.ts
|
|
666
|
-
var
|
|
706
|
+
var import_node_child_process5 = require("node:child_process");
|
|
667
707
|
var import_node_fs11 = require("node:fs");
|
|
668
708
|
var import_node_os4 = require("node:os");
|
|
669
709
|
var import_node_path6 = require("node:path");
|
|
@@ -791,15 +831,24 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
791
831
|
const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
|
|
792
832
|
if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
|
|
793
833
|
if (kind === "running" && typeof measure === "string") record.measure = measure;
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
834
|
+
const line = `${JSON.stringify(record)}
|
|
835
|
+
`;
|
|
836
|
+
if (progressFd !== null) {
|
|
837
|
+
try {
|
|
838
|
+
(0, import_node_fs6.writeSync)(progressFd, line);
|
|
839
|
+
return true;
|
|
840
|
+
} catch {
|
|
841
|
+
}
|
|
802
842
|
}
|
|
843
|
+
if (progressFile) {
|
|
844
|
+
try {
|
|
845
|
+
(0, import_node_fs6.appendFileSync)(progressFile, line);
|
|
846
|
+
return true;
|
|
847
|
+
} catch {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return false;
|
|
803
852
|
};
|
|
804
853
|
const welcome = () => continuesFace || nested ? [] : [
|
|
805
854
|
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
@@ -813,7 +862,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
813
862
|
};
|
|
814
863
|
const step = (title, measure = null, kind = "ok") => {
|
|
815
864
|
if (emitMilestone(title, measure, kind)) return "";
|
|
816
|
-
const glyph = title === "Armed hourly updates" && kind === "ok" ? GLYPH.clock : kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(
|
|
865
|
+
const glyph = title === "Armed hourly updates" && kind === "ok" ? GLYPH.clock : kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(identity.accent, GLYPH.check);
|
|
817
866
|
const measured = measure === null || measure === void 0 ? "" : paint(PALETTE.muted, typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : String(measure).trim());
|
|
818
867
|
const time = measured;
|
|
819
868
|
const column = Math.min(44, Math.max(0, width - 8));
|
|
@@ -834,7 +883,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
834
883
|
const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
|
|
835
884
|
const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
|
|
836
885
|
const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
|
|
837
|
-
const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(
|
|
886
|
+
const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(identity.accent, GLYPH.check)}${line.slice(1)}` : line;
|
|
838
887
|
return [
|
|
839
888
|
frame(GLYPH.boxTop, GLYPH.boxTopEnd),
|
|
840
889
|
...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
|
|
@@ -863,7 +912,41 @@ var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
|
863
912
|
// ../face/src/spinner.ts
|
|
864
913
|
var import_node_fs7 = require("node:fs");
|
|
865
914
|
var import_node_worker_threads = require("node:worker_threads");
|
|
866
|
-
|
|
915
|
+
|
|
916
|
+
// ../face/src/encoding.ts
|
|
917
|
+
var import_node_child_process2 = require("node:child_process");
|
|
918
|
+
var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
|
|
919
|
+
var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
|
|
920
|
+
var cachedCodePage;
|
|
921
|
+
function consoleOutputCodePage() {
|
|
922
|
+
if (cachedCodePage !== void 0) return cachedCodePage;
|
|
923
|
+
cachedCodePage = null;
|
|
924
|
+
try {
|
|
925
|
+
const out = (0, import_node_child_process2.execFileSync)("chcp.com", { windowsHide: true, encoding: "utf8", timeout: 5e3 });
|
|
926
|
+
const pages = [...String(out).matchAll(/(\d{3,5})/gu)].map((m) => Number(m[1]));
|
|
927
|
+
cachedCodePage = pages.length ? pages[pages.length - 1] : null;
|
|
928
|
+
} catch {
|
|
929
|
+
cachedCodePage = null;
|
|
930
|
+
}
|
|
931
|
+
return cachedCodePage;
|
|
932
|
+
}
|
|
933
|
+
function consoleRendersUnicode(env = process.env, platform = process.platform, codePage = platform === "win32" ? consoleOutputCodePage() : null) {
|
|
934
|
+
if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
|
|
935
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
936
|
+
if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
|
|
937
|
+
if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
|
|
938
|
+
if (/utf-?8/iu.test(locale)) return true;
|
|
939
|
+
if (platform === "win32") {
|
|
940
|
+
if (env.WT_SESSION || env.ConEmuANSI === "ON") return true;
|
|
941
|
+
return codePage === 65001;
|
|
942
|
+
}
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
946
|
+
return consoleRendersUnicode(env, platform) ? UNICODE_SPINNER_FRAMES : ASCII_SPINNER_FRAMES;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ../face/src/spinner.ts
|
|
867
950
|
var WORKER_SOURCE = `
|
|
868
951
|
const { parentPort, workerData } = require('node:worker_threads');
|
|
869
952
|
const { writeSync, appendFileSync } = require('node:fs');
|
|
@@ -888,8 +971,10 @@ parentPort.on('message', (next) => {
|
|
|
888
971
|
});
|
|
889
972
|
setInterval(draw, workerData.intervalMs);
|
|
890
973
|
`;
|
|
891
|
-
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
974
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath, env = process.env, platform = process.platform }) {
|
|
892
975
|
animate = animate && !face.nested && !face.emitsProgress;
|
|
976
|
+
const glyphs = spinnerFramesFor(env, platform);
|
|
977
|
+
const ascii = glyphs[0] === ASCII_SPINNER_FRAMES[0];
|
|
893
978
|
let timer = null;
|
|
894
979
|
let worker = null;
|
|
895
980
|
let control = null;
|
|
@@ -907,7 +992,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
907
992
|
};
|
|
908
993
|
const render = (text, measure) => {
|
|
909
994
|
const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
|
|
910
|
-
|
|
995
|
+
const prepared = line && ascii ? line.replaceAll(GLYPH.bar, "|") : line;
|
|
996
|
+
frames = prepared ? glyphs.map((glyph) => `\r\x1B[2K${prepared.replace(GLYPH.dot, glyph)}`) : [];
|
|
911
997
|
};
|
|
912
998
|
const draw = () => {
|
|
913
999
|
if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
|
|
@@ -984,7 +1070,6 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
|
|
|
984
1070
|
}
|
|
985
1071
|
};
|
|
986
1072
|
}
|
|
987
|
-
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
988
1073
|
|
|
989
1074
|
// ../face/src/conformance.ts
|
|
990
1075
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
@@ -1088,6 +1173,14 @@ var PHASES = {
|
|
|
1088
1173
|
doctor: ["Checking health", "Checked health"],
|
|
1089
1174
|
rollback: ["Restoring the previous version", "Restored the previous version"]
|
|
1090
1175
|
};
|
|
1176
|
+
var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
|
|
1177
|
+
"preflight",
|
|
1178
|
+
"resolve",
|
|
1179
|
+
"verify-release",
|
|
1180
|
+
"download",
|
|
1181
|
+
"sign-in",
|
|
1182
|
+
"check"
|
|
1183
|
+
]);
|
|
1091
1184
|
function createInstallerRun(value, options = {}) {
|
|
1092
1185
|
const declaration = validateInstallerProduct(value);
|
|
1093
1186
|
const env = options.env ?? process.env;
|
|
@@ -1123,6 +1216,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1123
1216
|
};
|
|
1124
1217
|
const spinner = createSpinner(face, {
|
|
1125
1218
|
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
1219
|
+
env,
|
|
1126
1220
|
...options.write ? { stream: { write: (text) => {
|
|
1127
1221
|
emit(String(text), "spinner");
|
|
1128
1222
|
return true;
|
|
@@ -1134,6 +1228,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1134
1228
|
let chunkColumn = 0;
|
|
1135
1229
|
const trailingCR = /* @__PURE__ */ new Set();
|
|
1136
1230
|
let deferredActivity;
|
|
1231
|
+
const drawnPhaseCompletions = /* @__PURE__ */ new Set();
|
|
1137
1232
|
const closeChunk = () => {
|
|
1138
1233
|
if (chunkOpen) emit("\n", chunkChannel);
|
|
1139
1234
|
chunkOpen = false;
|
|
@@ -1141,6 +1236,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1141
1236
|
};
|
|
1142
1237
|
let started = false;
|
|
1143
1238
|
let finished = false;
|
|
1239
|
+
let operation = options.operation;
|
|
1144
1240
|
const start = () => {
|
|
1145
1241
|
if (started || finished) return;
|
|
1146
1242
|
started = true;
|
|
@@ -1148,7 +1244,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1148
1244
|
const welcome = face.welcome();
|
|
1149
1245
|
if (tty) lines(welcome);
|
|
1150
1246
|
else if (welcome.length) {
|
|
1151
|
-
const warm =
|
|
1247
|
+
const warm = operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
1152
1248
|
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
1153
1249
|
}
|
|
1154
1250
|
};
|
|
@@ -1164,13 +1260,20 @@ function createInstallerRun(value, options = {}) {
|
|
|
1164
1260
|
get errors() {
|
|
1165
1261
|
return [...errors];
|
|
1166
1262
|
},
|
|
1263
|
+
get operation() {
|
|
1264
|
+
return operation;
|
|
1265
|
+
},
|
|
1266
|
+
set operation(value2) {
|
|
1267
|
+
operation = value2;
|
|
1268
|
+
},
|
|
1167
1269
|
start,
|
|
1168
1270
|
phase(id, facts = {}) {
|
|
1169
1271
|
if (finished) throw new Error("installer run already finished");
|
|
1170
1272
|
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
1171
1273
|
start();
|
|
1172
1274
|
const state = facts.state ?? "ok";
|
|
1173
|
-
const
|
|
1275
|
+
const fold = operation === "update" && UPDATE_CHECK_PHASES.has(id) && state !== "fail";
|
|
1276
|
+
const title = PHASES[fold ? "check" : id][state === "ok" ? 1 : 0];
|
|
1174
1277
|
if (state === "running") {
|
|
1175
1278
|
if (face.progress(title, facts.measure ?? null)) return;
|
|
1176
1279
|
if (activeTitle === title) spinner.say(title, facts.measure ?? null);
|
|
@@ -1180,7 +1283,14 @@ function createInstallerRun(value, options = {}) {
|
|
|
1180
1283
|
}
|
|
1181
1284
|
return;
|
|
1182
1285
|
}
|
|
1183
|
-
if (
|
|
1286
|
+
if (fold && id !== "check") {
|
|
1287
|
+
if (facts.detail) run2.relay(facts.detail);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
if (!face.continues(id, state)) {
|
|
1291
|
+
durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
1292
|
+
if (state === "ok") drawnPhaseCompletions.add(title);
|
|
1293
|
+
}
|
|
1184
1294
|
if (facts.detail) run2.relay(facts.detail);
|
|
1185
1295
|
},
|
|
1186
1296
|
surface(facts) {
|
|
@@ -1211,7 +1321,9 @@ function createInstallerRun(value, options = {}) {
|
|
|
1211
1321
|
}
|
|
1212
1322
|
return;
|
|
1213
1323
|
}
|
|
1324
|
+
if (state !== "fail" && drawnPhaseCompletions.has(step)) return;
|
|
1214
1325
|
durable(step, ms === void 0 ? null : ms / 1e3, state);
|
|
1326
|
+
if (state === "ok") drawnPhaseCompletions.add(step);
|
|
1215
1327
|
},
|
|
1216
1328
|
signIn({ url, code }) {
|
|
1217
1329
|
start();
|
|
@@ -1317,7 +1429,7 @@ function createInstallerRun(value, options = {}) {
|
|
|
1317
1429
|
}
|
|
1318
1430
|
|
|
1319
1431
|
// src/autoupdate.ts
|
|
1320
|
-
var
|
|
1432
|
+
var import_node_child_process3 = require("node:child_process");
|
|
1321
1433
|
var import_node_fs10 = require("node:fs");
|
|
1322
1434
|
var import_node_os3 = require("node:os");
|
|
1323
1435
|
var import_node_path5 = require("node:path");
|
|
@@ -1327,7 +1439,7 @@ function schedulePlatform(override) {
|
|
|
1327
1439
|
return null;
|
|
1328
1440
|
}
|
|
1329
1441
|
function defaultExec(command2, args) {
|
|
1330
|
-
const result = (0,
|
|
1442
|
+
const result = (0, import_node_child_process3.spawnSync)(command2, args, { encoding: "utf8", windowsHide: true });
|
|
1331
1443
|
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
1332
1444
|
return {
|
|
1333
1445
|
code: typeof result.status === "number" ? result.status : 1,
|
|
@@ -1492,14 +1604,14 @@ function firstLine(text) {
|
|
|
1492
1604
|
}
|
|
1493
1605
|
|
|
1494
1606
|
// src/login-github.ts
|
|
1495
|
-
var
|
|
1607
|
+
var import_node_child_process4 = require("node:child_process");
|
|
1496
1608
|
var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1497
1609
|
function openBrowser(url) {
|
|
1498
1610
|
if (process.env.LAUNCHER_NO_OPEN === "1") return;
|
|
1499
1611
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
|
|
1500
1612
|
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
1501
1613
|
try {
|
|
1502
|
-
const child = (0,
|
|
1614
|
+
const child = (0, import_node_child_process4.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
1503
1615
|
child.on("error", () => {
|
|
1504
1616
|
});
|
|
1505
1617
|
child.unref();
|
|
@@ -1726,7 +1838,7 @@ function page(title, body) {
|
|
|
1726
1838
|
}
|
|
1727
1839
|
|
|
1728
1840
|
// src/index.ts
|
|
1729
|
-
var LAUNCHER_VERSION = true ? "0.1.
|
|
1841
|
+
var LAUNCHER_VERSION = true ? "0.1.17" : readVersionFromPackage();
|
|
1730
1842
|
function defaultPrint(message) {
|
|
1731
1843
|
process.stdout.write(`${message}
|
|
1732
1844
|
`);
|
|
@@ -1979,7 +2091,7 @@ function defaultRunEntry(entry2, cwd, env) {
|
|
|
1979
2091
|
const [command2, ...args] = entry2;
|
|
1980
2092
|
const shell = needsShell(command2);
|
|
1981
2093
|
const commandLine = shell ? [command2, ...args].map(quoteForShell).join(" ") : command2;
|
|
1982
|
-
const spawnEntry = (progress2) => (0,
|
|
2094
|
+
const spawnEntry = (progress2) => (0, import_node_child_process5.spawnSync)(commandLine, shell ? [] : args, {
|
|
1983
2095
|
cwd,
|
|
1984
2096
|
stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
|
|
1985
2097
|
shell,
|
|
@@ -2027,8 +2139,27 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2027
2139
|
let version = readState(dir)?.version ?? "unknown";
|
|
2028
2140
|
installer.start();
|
|
2029
2141
|
try {
|
|
2142
|
+
const current = readState(dir);
|
|
2143
|
+
const payloadPresent = (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
2030
2144
|
let started = Date.now();
|
|
2145
|
+
installer.phase("resolve", { state: "running" });
|
|
2146
|
+
const pointer = await fetchReleaseVersion(config, fetchImpl);
|
|
2147
|
+
if (pointer && current?.version === pointer && payloadPresent) {
|
|
2148
|
+
version = pointer;
|
|
2149
|
+
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2150
|
+
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
2151
|
+
return await finishLastMile(
|
|
2152
|
+
config,
|
|
2153
|
+
dir,
|
|
2154
|
+
options,
|
|
2155
|
+
installer,
|
|
2156
|
+
version,
|
|
2157
|
+
true,
|
|
2158
|
+
current.acquired ? acquisitionRepairCommand(dir, version) : void 0
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2031
2161
|
installer.phase("sign-in", { state: "running" });
|
|
2162
|
+
started = Date.now();
|
|
2032
2163
|
const accessToken = await fetchAccessTokenOrLogin(config, dir, options, print, installer);
|
|
2033
2164
|
installer.phase("sign-in", { seconds: (Date.now() - started) / 1e3 });
|
|
2034
2165
|
installer.phase("resolve", { state: "running" });
|
|
@@ -2036,7 +2167,6 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2036
2167
|
const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
|
|
2037
2168
|
version = manifest.version;
|
|
2038
2169
|
installer.phase("resolve", { seconds: (Date.now() - started) / 1e3 });
|
|
2039
|
-
const current = readState(dir);
|
|
2040
2170
|
const unchanged = current?.version === version && (0, import_node_fs11.existsSync)(payloadDir(dir));
|
|
2041
2171
|
if (unchanged && current.acquired) {
|
|
2042
2172
|
installer.surface({ id: config.product, from: version, to: version, state: "current" });
|
|
@@ -2052,7 +2182,7 @@ async function installOrUpdate(config, dir, options, print, update) {
|
|
|
2052
2182
|
state: "running",
|
|
2053
2183
|
measure: total ? `${Math.floor(done / total * 100)}% \xB7 ${done}/${total} B` : `${done} B`
|
|
2054
2184
|
});
|
|
2055
|
-
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress });
|
|
2185
|
+
if (!cached) await downloadAndUnpack(config, candidateDir, manifest, accessToken, { fetchImpl, onProgress, cacheDir: dir });
|
|
2056
2186
|
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
2187
|
if (acquisition) {
|
|
2058
2188
|
rememberCandidate(dir, candidate, manifest);
|
|
@@ -2202,7 +2332,7 @@ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
|
2202
2332
|
const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
|
|
2203
2333
|
try {
|
|
2204
2334
|
const result = await new Promise((resolve3) => {
|
|
2205
|
-
const child = (0,
|
|
2335
|
+
const child = (0, import_node_child_process5.spawn)(shell ? [command2, ...args].map(quoteForShell).join(" ") : command2, shell ? [] : args, {
|
|
2206
2336
|
cwd,
|
|
2207
2337
|
shell,
|
|
2208
2338
|
windowsHide: true,
|
package/package.json
CHANGED