@mutmutco/installer-launcher 1.2.2 → 1.2.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 +204 -216
- package/dist/launcher.sea.cjs +212 -225
- package/package.json +1 -1
package/dist/launcher.js
CHANGED
|
@@ -896,35 +896,17 @@ var plainText = (text) => String(text).replaceAll("\u2192", "->").replaceAll("\x
|
|
|
896
896
|
|
|
897
897
|
// ../face/src/spinner.ts
|
|
898
898
|
import { appendFileSync as appendFileSync2, writeSync as writeSync2 } from "node:fs";
|
|
899
|
+
import { isatty } from "node:tty";
|
|
899
900
|
import { Worker } from "node:worker_threads";
|
|
900
901
|
|
|
901
902
|
// ../face/src/encoding.ts
|
|
902
|
-
import { execFileSync } from "node:child_process";
|
|
903
903
|
var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
|
|
904
904
|
var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
|
|
905
|
-
|
|
906
|
-
function consoleOutputCodePage() {
|
|
907
|
-
if (cachedCodePage !== void 0) return cachedCodePage;
|
|
908
|
-
cachedCodePage = null;
|
|
909
|
-
try {
|
|
910
|
-
const out = execFileSync("chcp.com", { windowsHide: true, encoding: "utf8", timeout: 5e3 });
|
|
911
|
-
const pages = [...String(out).matchAll(/(\d{3,5})/gu)].map((m) => Number(m[1]));
|
|
912
|
-
cachedCodePage = pages.length ? pages[pages.length - 1] : null;
|
|
913
|
-
} catch {
|
|
914
|
-
cachedCodePage = null;
|
|
915
|
-
}
|
|
916
|
-
return cachedCodePage;
|
|
917
|
-
}
|
|
918
|
-
function consoleRendersUnicode(env = process.env, platform = process.platform, codePage = platform === "win32" ? consoleOutputCodePage() : null) {
|
|
905
|
+
function consoleRendersUnicode(env = process.env, _platform = process.platform) {
|
|
919
906
|
if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
|
|
920
907
|
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
921
908
|
if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
|
|
922
909
|
if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
|
|
923
|
-
if (/utf-?8/iu.test(locale)) return true;
|
|
924
|
-
if (platform === "win32") {
|
|
925
|
-
if (env.WT_SESSION || env.ConEmuANSI === "ON") return true;
|
|
926
|
-
return codePage === 65001;
|
|
927
|
-
}
|
|
928
910
|
return true;
|
|
929
911
|
}
|
|
930
912
|
function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
@@ -935,6 +917,8 @@ function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
|
935
917
|
var WORKER_SOURCE = `
|
|
936
918
|
const { parentPort, workerData } = require('node:worker_threads');
|
|
937
919
|
const { writeSync, appendFileSync } = require('node:fs');
|
|
920
|
+
const wide = workerData.wide ? new (require('node:tty').WriteStream)(2) : null;
|
|
921
|
+
const put = (text) => wide ? wide.write(text) : writeSync(2, text);
|
|
938
922
|
const control = new Int32Array(workerData.control);
|
|
939
923
|
let frames = workerData.frames, frame = workerData.frame;
|
|
940
924
|
function draw() {
|
|
@@ -942,7 +926,7 @@ function draw() {
|
|
|
942
926
|
try {
|
|
943
927
|
if (Atomics.load(control, 0) || Atomics.load(control, 2) || !frames.length) return;
|
|
944
928
|
const text = frames[frame++ % frames.length].replace('__elapsed__', Math.floor((Date.now() - workerData.started) / 1000) + 's');
|
|
945
|
-
|
|
929
|
+
put(text);
|
|
946
930
|
if (workerData.transcriptPath) appendFileSync(workerData.transcriptPath, JSON.stringify({ channel: 'spinner', text }) + '\\n');
|
|
947
931
|
} finally {
|
|
948
932
|
Atomics.store(control, 1, 0);
|
|
@@ -967,10 +951,12 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath,
|
|
|
967
951
|
let frame = 0;
|
|
968
952
|
let started = 0;
|
|
969
953
|
let suspended = false;
|
|
954
|
+
const wide = process.platform === "win32" && isatty(2);
|
|
970
955
|
const write = (text) => {
|
|
971
956
|
if (stream) stream.write(text);
|
|
972
957
|
else {
|
|
973
|
-
|
|
958
|
+
if (wide) process.stderr.write(`\x1B[1G${text}`);
|
|
959
|
+
else writeSync2(2, text);
|
|
974
960
|
if (transcriptPath) appendFileSync2(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
|
|
975
961
|
`);
|
|
976
962
|
}
|
|
@@ -1017,7 +1003,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath,
|
|
|
1017
1003
|
frame,
|
|
1018
1004
|
intervalMs,
|
|
1019
1005
|
transcriptPath,
|
|
1020
|
-
started
|
|
1006
|
+
started,
|
|
1007
|
+
wide
|
|
1021
1008
|
} });
|
|
1022
1009
|
const active = worker;
|
|
1023
1010
|
worker.on("error", () => {
|
|
@@ -1742,162 +1729,6 @@ function firstLine(text) {
|
|
|
1742
1729
|
return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
1743
1730
|
}
|
|
1744
1731
|
|
|
1745
|
-
// src/refresh.ts
|
|
1746
|
-
import { execFile } from "node:child_process";
|
|
1747
|
-
import { chmodSync, existsSync as existsSync7, lstatSync, readdirSync, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1748
|
-
import { homedir } from "node:os";
|
|
1749
|
-
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
1750
|
-
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
1751
|
-
var RECORD_TIMEOUT_MS = 1e4;
|
|
1752
|
-
function platformKey(platform, arch) {
|
|
1753
|
-
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
1754
|
-
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
1755
|
-
}
|
|
1756
|
-
function verifierFor(target) {
|
|
1757
|
-
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
1758
|
-
}
|
|
1759
|
-
async function fetchRecord(target, options) {
|
|
1760
|
-
let json;
|
|
1761
|
-
try {
|
|
1762
|
-
const response = await (options.fetch ?? fetch)(
|
|
1763
|
-
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
1764
|
-
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
1765
|
-
);
|
|
1766
|
-
if (!response.ok) return null;
|
|
1767
|
-
json = await response.json();
|
|
1768
|
-
} catch {
|
|
1769
|
-
return null;
|
|
1770
|
-
}
|
|
1771
|
-
let record;
|
|
1772
|
-
try {
|
|
1773
|
-
record = parseManifest(json);
|
|
1774
|
-
} catch {
|
|
1775
|
-
return null;
|
|
1776
|
-
}
|
|
1777
|
-
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
1778
|
-
return { version: record.version, files: record.files };
|
|
1779
|
-
}
|
|
1780
|
-
async function readLauncherRecord(target, options = {}) {
|
|
1781
|
-
const record = await fetchRecord(target, options);
|
|
1782
|
-
return record === "bad-signature" ? null : record;
|
|
1783
|
-
}
|
|
1784
|
-
function compareVersions(a, b) {
|
|
1785
|
-
const split = (v) => {
|
|
1786
|
-
const [core, pre = ""] = v.split("-", 2);
|
|
1787
|
-
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
1788
|
-
};
|
|
1789
|
-
const x = split(a);
|
|
1790
|
-
const y = split(b);
|
|
1791
|
-
for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) > (y.nums[i] ?? 0) ? 1 : -1;
|
|
1792
|
-
if (x.pre === y.pre) return 0;
|
|
1793
|
-
if (!x.pre) return 1;
|
|
1794
|
-
if (!y.pre) return -1;
|
|
1795
|
-
return x.pre > y.pre ? 1 : -1;
|
|
1796
|
-
}
|
|
1797
|
-
function recordedPath(productDir) {
|
|
1798
|
-
try {
|
|
1799
|
-
const saved = JSON.parse(readFileSync7(join6(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
1800
|
-
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
1801
|
-
} catch {
|
|
1802
|
-
return null;
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
function installedPath(target, productDir, platform) {
|
|
1806
|
-
return platform === "win32" ? join6(productDir, "bin", `${target.bin}.exe`) : join6(homedir(), ".local", "bin", target.bin);
|
|
1807
|
-
}
|
|
1808
|
-
function parseLauncherVersion(stdout, bin) {
|
|
1809
|
-
const text = String(stdout).trim();
|
|
1810
|
-
if (!text.startsWith(`${bin} `)) return null;
|
|
1811
|
-
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
1812
|
-
}
|
|
1813
|
-
function runVersion(path, bin) {
|
|
1814
|
-
return new Promise((resolve3) => {
|
|
1815
|
-
execFile(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
1816
|
-
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
1817
|
-
});
|
|
1818
|
-
});
|
|
1819
|
-
}
|
|
1820
|
-
function removeStale(file) {
|
|
1821
|
-
const prefix = `${basename(file)}.old-`;
|
|
1822
|
-
let names;
|
|
1823
|
-
try {
|
|
1824
|
-
names = readdirSync(dirname3(file));
|
|
1825
|
-
} catch {
|
|
1826
|
-
return;
|
|
1827
|
-
}
|
|
1828
|
-
for (const name of names) {
|
|
1829
|
-
if (!name.startsWith(prefix)) continue;
|
|
1830
|
-
try {
|
|
1831
|
-
rmSync5(join6(dirname3(file), name), { force: true });
|
|
1832
|
-
} catch {
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
async function refreshLauncher(target, options = {}) {
|
|
1837
|
-
const started = Date.now();
|
|
1838
|
-
const platform = options.platform ?? process.platform;
|
|
1839
|
-
const key = platformKey(platform, options.arch ?? process.arch);
|
|
1840
|
-
if (!key) return null;
|
|
1841
|
-
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
1842
|
-
const recorded = recordedPath(productDir);
|
|
1843
|
-
const file = options.path ?? (recorded && existsSync7(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
1844
|
-
if (!existsSync7(file) || lstatSync(file).isSymbolicLink()) return null;
|
|
1845
|
-
if (!options.dryRun) removeStale(file);
|
|
1846
|
-
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
1847
|
-
if (!from) return null;
|
|
1848
|
-
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
1849
|
-
const record = await fetchRecord(target, options);
|
|
1850
|
-
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
1851
|
-
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
1852
|
-
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
1853
|
-
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
1854
|
-
if (options.dryRun) return arm("ok", record.version, "");
|
|
1855
|
-
let unlock;
|
|
1856
|
-
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
1857
|
-
if (!lockHeld) {
|
|
1858
|
-
try {
|
|
1859
|
-
unlock = lockInstallation(productDir);
|
|
1860
|
-
} catch {
|
|
1861
|
-
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
const next = `${file}.new-${process.pid}`;
|
|
1865
|
-
let moved;
|
|
1866
|
-
try {
|
|
1867
|
-
let bytes;
|
|
1868
|
-
try {
|
|
1869
|
-
const response = await (options.fetch ?? fetch)(
|
|
1870
|
-
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
1871
|
-
{ signal: AbortSignal.timeout(3e5) }
|
|
1872
|
-
);
|
|
1873
|
-
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1874
|
-
bytes = await readDownload(response, void 0, entry2.size);
|
|
1875
|
-
} catch {
|
|
1876
|
-
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1877
|
-
}
|
|
1878
|
-
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
1879
|
-
try {
|
|
1880
|
-
writeFileSync7(next, bytes, { mode: 493 });
|
|
1881
|
-
if (platform !== "win32") chmodSync(next, 493);
|
|
1882
|
-
if (platform === "win32") {
|
|
1883
|
-
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
1884
|
-
renameSync4(file, moved);
|
|
1885
|
-
}
|
|
1886
|
-
renameSync4(next, file);
|
|
1887
|
-
} catch {
|
|
1888
|
-
try {
|
|
1889
|
-
if (moved && !existsSync7(file)) renameSync4(moved, file);
|
|
1890
|
-
} catch {
|
|
1891
|
-
}
|
|
1892
|
-
rmSync5(next, { force: true });
|
|
1893
|
-
return arm("fail", from, "The launcher could not be replaced.");
|
|
1894
|
-
}
|
|
1895
|
-
return arm("ok", record.version, "");
|
|
1896
|
-
} finally {
|
|
1897
|
-
unlock?.();
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
1732
|
// src/login-github.ts
|
|
1902
1733
|
import { spawn, spawnSync as spawnSync3 } from "node:child_process";
|
|
1903
1734
|
var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
@@ -2158,8 +1989,201 @@ function page(title, body) {
|
|
|
2158
1989
|
return `<!doctype html><meta charset="utf-8"><meta name="color-scheme" content="dark"><title>${title}</title><style>${style}</style><main><h1>${title}</h1><p>${body}</p></main>`;
|
|
2159
1990
|
}
|
|
2160
1991
|
|
|
1992
|
+
// src/tokens.ts
|
|
1993
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
1994
|
+
const stored = readTokens(dir);
|
|
1995
|
+
if (!stored) return null;
|
|
1996
|
+
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
1997
|
+
if (!stored.refreshToken) return null;
|
|
1998
|
+
if (config.loginKind === "github") {
|
|
1999
|
+
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2000
|
+
if (!refreshed2) {
|
|
2001
|
+
clearTokens(dir);
|
|
2002
|
+
return null;
|
|
2003
|
+
}
|
|
2004
|
+
writeTokens(dir, {
|
|
2005
|
+
accessToken: refreshed2.accessToken,
|
|
2006
|
+
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2007
|
+
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2008
|
+
});
|
|
2009
|
+
return refreshed2.accessToken;
|
|
2010
|
+
}
|
|
2011
|
+
if (!stored.clientId) {
|
|
2012
|
+
clearTokens(dir);
|
|
2013
|
+
return null;
|
|
2014
|
+
}
|
|
2015
|
+
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2016
|
+
if (!refreshed) {
|
|
2017
|
+
clearTokens(dir);
|
|
2018
|
+
return null;
|
|
2019
|
+
}
|
|
2020
|
+
writeTokens(dir, {
|
|
2021
|
+
accessToken: refreshed.accessToken,
|
|
2022
|
+
refreshToken: refreshed.refreshToken,
|
|
2023
|
+
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2024
|
+
clientId: stored.clientId
|
|
2025
|
+
});
|
|
2026
|
+
return refreshed.accessToken;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// src/refresh.ts
|
|
2030
|
+
import { execFile } from "node:child_process";
|
|
2031
|
+
import { chmodSync, existsSync as existsSync7, lstatSync, readdirSync, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2032
|
+
import { homedir } from "node:os";
|
|
2033
|
+
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
2034
|
+
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
2035
|
+
var RECORD_TIMEOUT_MS = 1e4;
|
|
2036
|
+
function platformKey(platform, arch) {
|
|
2037
|
+
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
2038
|
+
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
2039
|
+
}
|
|
2040
|
+
function verifierFor(target) {
|
|
2041
|
+
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
2042
|
+
}
|
|
2043
|
+
async function fetchRecord(target, options) {
|
|
2044
|
+
let json;
|
|
2045
|
+
try {
|
|
2046
|
+
const response = await (options.fetch ?? fetch)(
|
|
2047
|
+
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
2048
|
+
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
2049
|
+
);
|
|
2050
|
+
if (!response.ok) return null;
|
|
2051
|
+
json = await response.json();
|
|
2052
|
+
} catch {
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
2055
|
+
let record;
|
|
2056
|
+
try {
|
|
2057
|
+
record = parseManifest(json);
|
|
2058
|
+
} catch {
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
2062
|
+
return { version: record.version, files: record.files };
|
|
2063
|
+
}
|
|
2064
|
+
async function readLauncherRecord(target, options = {}) {
|
|
2065
|
+
const record = await fetchRecord(target, options);
|
|
2066
|
+
return record === "bad-signature" ? null : record;
|
|
2067
|
+
}
|
|
2068
|
+
function compareVersions(a, b) {
|
|
2069
|
+
const split = (v) => {
|
|
2070
|
+
const [core, pre = ""] = v.split("-", 2);
|
|
2071
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
2072
|
+
};
|
|
2073
|
+
const x = split(a);
|
|
2074
|
+
const y = split(b);
|
|
2075
|
+
for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) > (y.nums[i] ?? 0) ? 1 : -1;
|
|
2076
|
+
if (x.pre === y.pre) return 0;
|
|
2077
|
+
if (!x.pre) return 1;
|
|
2078
|
+
if (!y.pre) return -1;
|
|
2079
|
+
return x.pre > y.pre ? 1 : -1;
|
|
2080
|
+
}
|
|
2081
|
+
function recordedPath(productDir) {
|
|
2082
|
+
try {
|
|
2083
|
+
const saved = JSON.parse(readFileSync7(join6(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
2084
|
+
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
2085
|
+
} catch {
|
|
2086
|
+
return null;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
function installedPath(target, productDir, platform) {
|
|
2090
|
+
return platform === "win32" ? join6(productDir, "bin", `${target.bin}.exe`) : join6(homedir(), ".local", "bin", target.bin);
|
|
2091
|
+
}
|
|
2092
|
+
function parseLauncherVersion(stdout, bin) {
|
|
2093
|
+
const text = String(stdout).trim();
|
|
2094
|
+
if (!text.startsWith(`${bin} `)) return null;
|
|
2095
|
+
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
2096
|
+
}
|
|
2097
|
+
function runVersion(path, bin) {
|
|
2098
|
+
return new Promise((resolve3) => {
|
|
2099
|
+
execFile(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
2100
|
+
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
2101
|
+
});
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
function removeStale(file) {
|
|
2105
|
+
const prefix = `${basename(file)}.old-`;
|
|
2106
|
+
let names;
|
|
2107
|
+
try {
|
|
2108
|
+
names = readdirSync(dirname3(file));
|
|
2109
|
+
} catch {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
for (const name of names) {
|
|
2113
|
+
if (!name.startsWith(prefix)) continue;
|
|
2114
|
+
try {
|
|
2115
|
+
rmSync5(join6(dirname3(file), name), { force: true });
|
|
2116
|
+
} catch {
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
async function refreshLauncher(target, options = {}) {
|
|
2121
|
+
const started = Date.now();
|
|
2122
|
+
const platform = options.platform ?? process.platform;
|
|
2123
|
+
const key = platformKey(platform, options.arch ?? process.arch);
|
|
2124
|
+
if (!key) return null;
|
|
2125
|
+
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
2126
|
+
const recorded = recordedPath(productDir);
|
|
2127
|
+
const file = options.path ?? (recorded && existsSync7(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
2128
|
+
if (!existsSync7(file) || lstatSync(file).isSymbolicLink()) return null;
|
|
2129
|
+
if (!options.dryRun) removeStale(file);
|
|
2130
|
+
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
2131
|
+
if (!from) return null;
|
|
2132
|
+
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
2133
|
+
const record = await fetchRecord(target, options);
|
|
2134
|
+
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
2135
|
+
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
2136
|
+
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
2137
|
+
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
2138
|
+
if (options.dryRun) return arm("ok", record.version, "");
|
|
2139
|
+
let unlock;
|
|
2140
|
+
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
2141
|
+
if (!lockHeld) {
|
|
2142
|
+
try {
|
|
2143
|
+
unlock = lockInstallation(productDir);
|
|
2144
|
+
} catch {
|
|
2145
|
+
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
const next = `${file}.new-${process.pid}`;
|
|
2149
|
+
let moved;
|
|
2150
|
+
try {
|
|
2151
|
+
let bytes;
|
|
2152
|
+
try {
|
|
2153
|
+
const response = await (options.fetch ?? fetch)(
|
|
2154
|
+
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
2155
|
+
{ signal: AbortSignal.timeout(3e5) }
|
|
2156
|
+
);
|
|
2157
|
+
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2158
|
+
bytes = await readDownload(response, void 0, entry2.size);
|
|
2159
|
+
} catch {
|
|
2160
|
+
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2161
|
+
}
|
|
2162
|
+
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
2163
|
+
try {
|
|
2164
|
+
writeFileSync7(next, bytes, { mode: 493 });
|
|
2165
|
+
if (platform !== "win32") chmodSync(next, 493);
|
|
2166
|
+
if (platform === "win32") {
|
|
2167
|
+
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
2168
|
+
renameSync4(file, moved);
|
|
2169
|
+
}
|
|
2170
|
+
renameSync4(next, file);
|
|
2171
|
+
} catch {
|
|
2172
|
+
try {
|
|
2173
|
+
if (moved && !existsSync7(file)) renameSync4(moved, file);
|
|
2174
|
+
} catch {
|
|
2175
|
+
}
|
|
2176
|
+
rmSync5(next, { force: true });
|
|
2177
|
+
return arm("fail", from, "The launcher could not be replaced.");
|
|
2178
|
+
}
|
|
2179
|
+
return arm("ok", record.version, "");
|
|
2180
|
+
} finally {
|
|
2181
|
+
unlock?.();
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2161
2185
|
// src/index.ts
|
|
2162
|
-
var LAUNCHER_VERSION = true ? "1.2.
|
|
2186
|
+
var LAUNCHER_VERSION = true ? "1.2.4" : readVersionFromPackage();
|
|
2163
2187
|
function defaultPrint(message) {
|
|
2164
2188
|
process.stdout.write(`${message}
|
|
2165
2189
|
`);
|
|
@@ -2382,41 +2406,6 @@ async function runFile(file, args, printErr) {
|
|
|
2382
2406
|
}
|
|
2383
2407
|
return code;
|
|
2384
2408
|
}
|
|
2385
|
-
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2386
|
-
const stored = readTokens(dir);
|
|
2387
|
-
if (!stored) return null;
|
|
2388
|
-
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2389
|
-
if (!stored.refreshToken) return null;
|
|
2390
|
-
if (config.loginKind === "github") {
|
|
2391
|
-
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2392
|
-
if (!refreshed2) {
|
|
2393
|
-
clearTokens(dir);
|
|
2394
|
-
return null;
|
|
2395
|
-
}
|
|
2396
|
-
writeTokens(dir, {
|
|
2397
|
-
accessToken: refreshed2.accessToken,
|
|
2398
|
-
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2399
|
-
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2400
|
-
});
|
|
2401
|
-
return refreshed2.accessToken;
|
|
2402
|
-
}
|
|
2403
|
-
if (!stored.clientId) {
|
|
2404
|
-
clearTokens(dir);
|
|
2405
|
-
return null;
|
|
2406
|
-
}
|
|
2407
|
-
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2408
|
-
if (!refreshed) {
|
|
2409
|
-
clearTokens(dir);
|
|
2410
|
-
return null;
|
|
2411
|
-
}
|
|
2412
|
-
writeTokens(dir, {
|
|
2413
|
-
accessToken: refreshed.accessToken,
|
|
2414
|
-
refreshToken: refreshed.refreshToken,
|
|
2415
|
-
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2416
|
-
clientId: stored.clientId
|
|
2417
|
-
});
|
|
2418
|
-
return refreshed.accessToken;
|
|
2419
|
-
}
|
|
2420
2409
|
async function doLogin(config, dir, options, prompt, installer) {
|
|
2421
2410
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2422
2411
|
const open = options.open ?? openBrowser;
|
|
@@ -3064,7 +3053,6 @@ if (invokedAsMain) {
|
|
|
3064
3053
|
export {
|
|
3065
3054
|
LAUNCHER_VERSION,
|
|
3066
3055
|
defaultRunEntry,
|
|
3067
|
-
ensureFreshToken,
|
|
3068
3056
|
formatMb,
|
|
3069
3057
|
parseArgv,
|
|
3070
3058
|
readPayloadEntry,
|
package/dist/launcher.sea.cjs
CHANGED
|
@@ -22,7 +22,6 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
LAUNCHER_VERSION: () => LAUNCHER_VERSION,
|
|
24
24
|
defaultRunEntry: () => defaultRunEntry,
|
|
25
|
-
ensureFreshToken: () => ensureFreshToken,
|
|
26
25
|
formatMb: () => formatMb,
|
|
27
26
|
parseArgv: () => parseArgv,
|
|
28
27
|
readPayloadEntry: () => readPayloadEntry,
|
|
@@ -710,7 +709,7 @@ async function installAcquisition(dir, candidate, version, acquisition, options)
|
|
|
710
709
|
}
|
|
711
710
|
|
|
712
711
|
// src/index.ts
|
|
713
|
-
var
|
|
712
|
+
var import_node_child_process5 = require("node:child_process");
|
|
714
713
|
var import_node_fs12 = require("node:fs");
|
|
715
714
|
var import_node_os5 = require("node:os");
|
|
716
715
|
var import_node_path7 = require("node:path");
|
|
@@ -930,35 +929,17 @@ var plainText = (text) => String(text).replaceAll("\u2192", "->").replaceAll("\x
|
|
|
930
929
|
|
|
931
930
|
// ../face/src/spinner.ts
|
|
932
931
|
var import_node_fs7 = require("node:fs");
|
|
932
|
+
var import_node_tty = require("node:tty");
|
|
933
933
|
var import_node_worker_threads = require("node:worker_threads");
|
|
934
934
|
|
|
935
935
|
// ../face/src/encoding.ts
|
|
936
|
-
var import_node_child_process2 = require("node:child_process");
|
|
937
936
|
var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
|
|
938
937
|
var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
|
|
939
|
-
|
|
940
|
-
function consoleOutputCodePage() {
|
|
941
|
-
if (cachedCodePage !== void 0) return cachedCodePage;
|
|
942
|
-
cachedCodePage = null;
|
|
943
|
-
try {
|
|
944
|
-
const out = (0, import_node_child_process2.execFileSync)("chcp.com", { windowsHide: true, encoding: "utf8", timeout: 5e3 });
|
|
945
|
-
const pages = [...String(out).matchAll(/(\d{3,5})/gu)].map((m) => Number(m[1]));
|
|
946
|
-
cachedCodePage = pages.length ? pages[pages.length - 1] : null;
|
|
947
|
-
} catch {
|
|
948
|
-
cachedCodePage = null;
|
|
949
|
-
}
|
|
950
|
-
return cachedCodePage;
|
|
951
|
-
}
|
|
952
|
-
function consoleRendersUnicode(env = process.env, platform = process.platform, codePage = platform === "win32" ? consoleOutputCodePage() : null) {
|
|
938
|
+
function consoleRendersUnicode(env = process.env, _platform = process.platform) {
|
|
953
939
|
if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
|
|
954
940
|
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
955
941
|
if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
|
|
956
942
|
if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
|
|
957
|
-
if (/utf-?8/iu.test(locale)) return true;
|
|
958
|
-
if (platform === "win32") {
|
|
959
|
-
if (env.WT_SESSION || env.ConEmuANSI === "ON") return true;
|
|
960
|
-
return codePage === 65001;
|
|
961
|
-
}
|
|
962
943
|
return true;
|
|
963
944
|
}
|
|
964
945
|
function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
@@ -969,6 +950,8 @@ function spinnerFramesFor(env = process.env, platform = process.platform) {
|
|
|
969
950
|
var WORKER_SOURCE = `
|
|
970
951
|
const { parentPort, workerData } = require('node:worker_threads');
|
|
971
952
|
const { writeSync, appendFileSync } = require('node:fs');
|
|
953
|
+
const wide = workerData.wide ? new (require('node:tty').WriteStream)(2) : null;
|
|
954
|
+
const put = (text) => wide ? wide.write(text) : writeSync(2, text);
|
|
972
955
|
const control = new Int32Array(workerData.control);
|
|
973
956
|
let frames = workerData.frames, frame = workerData.frame;
|
|
974
957
|
function draw() {
|
|
@@ -976,7 +959,7 @@ function draw() {
|
|
|
976
959
|
try {
|
|
977
960
|
if (Atomics.load(control, 0) || Atomics.load(control, 2) || !frames.length) return;
|
|
978
961
|
const text = frames[frame++ % frames.length].replace('__elapsed__', Math.floor((Date.now() - workerData.started) / 1000) + 's');
|
|
979
|
-
|
|
962
|
+
put(text);
|
|
980
963
|
if (workerData.transcriptPath) appendFileSync(workerData.transcriptPath, JSON.stringify({ channel: 'spinner', text }) + '\\n');
|
|
981
964
|
} finally {
|
|
982
965
|
Atomics.store(control, 1, 0);
|
|
@@ -1001,10 +984,12 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath,
|
|
|
1001
984
|
let frame = 0;
|
|
1002
985
|
let started = 0;
|
|
1003
986
|
let suspended = false;
|
|
987
|
+
const wide = process.platform === "win32" && (0, import_node_tty.isatty)(2);
|
|
1004
988
|
const write = (text) => {
|
|
1005
989
|
if (stream) stream.write(text);
|
|
1006
990
|
else {
|
|
1007
|
-
(
|
|
991
|
+
if (wide) process.stderr.write(`\x1B[1G${text}`);
|
|
992
|
+
else (0, import_node_fs7.writeSync)(2, text);
|
|
1008
993
|
if (transcriptPath) (0, import_node_fs7.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
|
|
1009
994
|
`);
|
|
1010
995
|
}
|
|
@@ -1051,7 +1036,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath,
|
|
|
1051
1036
|
frame,
|
|
1052
1037
|
intervalMs,
|
|
1053
1038
|
transcriptPath,
|
|
1054
|
-
started
|
|
1039
|
+
started,
|
|
1040
|
+
wide
|
|
1055
1041
|
} });
|
|
1056
1042
|
const active = worker;
|
|
1057
1043
|
worker.on("error", () => {
|
|
@@ -1587,7 +1573,7 @@ var plain = (text, lane) => lane.tty ? String(text) : plainText(stripColor(Strin
|
|
|
1587
1573
|
var rowTitle = (row) => `${row.surface}${row.aspect ? ` ${row.aspect}` : ""}${row.instance ? ` \xB7 ${row.instance}` : ""} ${row.state}`;
|
|
1588
1574
|
|
|
1589
1575
|
// src/autoupdate.ts
|
|
1590
|
-
var
|
|
1576
|
+
var import_node_child_process2 = require("node:child_process");
|
|
1591
1577
|
var import_node_fs10 = require("node:fs");
|
|
1592
1578
|
var import_node_os3 = require("node:os");
|
|
1593
1579
|
var import_node_path5 = require("node:path");
|
|
@@ -1597,7 +1583,7 @@ function schedulePlatform(override) {
|
|
|
1597
1583
|
return null;
|
|
1598
1584
|
}
|
|
1599
1585
|
function defaultExec(command2, args) {
|
|
1600
|
-
const result = (0,
|
|
1586
|
+
const result = (0, import_node_child_process2.spawnSync)(command2, args, { encoding: "utf8", windowsHide: true });
|
|
1601
1587
|
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
1602
1588
|
return {
|
|
1603
1589
|
code: typeof result.status === "number" ? result.status : 1,
|
|
@@ -1776,171 +1762,15 @@ function firstLine(text) {
|
|
|
1776
1762
|
return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
1777
1763
|
}
|
|
1778
1764
|
|
|
1779
|
-
// src/refresh.ts
|
|
1780
|
-
var import_node_child_process4 = require("node:child_process");
|
|
1781
|
-
var import_node_fs11 = require("node:fs");
|
|
1782
|
-
var import_node_os4 = require("node:os");
|
|
1783
|
-
var import_node_path6 = require("node:path");
|
|
1784
|
-
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
1785
|
-
var RECORD_TIMEOUT_MS = 1e4;
|
|
1786
|
-
function platformKey(platform, arch) {
|
|
1787
|
-
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
1788
|
-
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
1789
|
-
}
|
|
1790
|
-
function verifierFor(target) {
|
|
1791
|
-
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
1792
|
-
}
|
|
1793
|
-
async function fetchRecord(target, options) {
|
|
1794
|
-
let json;
|
|
1795
|
-
try {
|
|
1796
|
-
const response = await (options.fetch ?? fetch)(
|
|
1797
|
-
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
1798
|
-
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
1799
|
-
);
|
|
1800
|
-
if (!response.ok) return null;
|
|
1801
|
-
json = await response.json();
|
|
1802
|
-
} catch {
|
|
1803
|
-
return null;
|
|
1804
|
-
}
|
|
1805
|
-
let record;
|
|
1806
|
-
try {
|
|
1807
|
-
record = parseManifest(json);
|
|
1808
|
-
} catch {
|
|
1809
|
-
return null;
|
|
1810
|
-
}
|
|
1811
|
-
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
1812
|
-
return { version: record.version, files: record.files };
|
|
1813
|
-
}
|
|
1814
|
-
async function readLauncherRecord(target, options = {}) {
|
|
1815
|
-
const record = await fetchRecord(target, options);
|
|
1816
|
-
return record === "bad-signature" ? null : record;
|
|
1817
|
-
}
|
|
1818
|
-
function compareVersions(a, b) {
|
|
1819
|
-
const split = (v) => {
|
|
1820
|
-
const [core, pre = ""] = v.split("-", 2);
|
|
1821
|
-
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
1822
|
-
};
|
|
1823
|
-
const x = split(a);
|
|
1824
|
-
const y = split(b);
|
|
1825
|
-
for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) > (y.nums[i] ?? 0) ? 1 : -1;
|
|
1826
|
-
if (x.pre === y.pre) return 0;
|
|
1827
|
-
if (!x.pre) return 1;
|
|
1828
|
-
if (!y.pre) return -1;
|
|
1829
|
-
return x.pre > y.pre ? 1 : -1;
|
|
1830
|
-
}
|
|
1831
|
-
function recordedPath(productDir) {
|
|
1832
|
-
try {
|
|
1833
|
-
const saved = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
1834
|
-
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
1835
|
-
} catch {
|
|
1836
|
-
return null;
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
function installedPath(target, productDir, platform) {
|
|
1840
|
-
return platform === "win32" ? (0, import_node_path6.join)(productDir, "bin", `${target.bin}.exe`) : (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".local", "bin", target.bin);
|
|
1841
|
-
}
|
|
1842
|
-
function parseLauncherVersion(stdout, bin) {
|
|
1843
|
-
const text = String(stdout).trim();
|
|
1844
|
-
if (!text.startsWith(`${bin} `)) return null;
|
|
1845
|
-
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
1846
|
-
}
|
|
1847
|
-
function runVersion(path, bin) {
|
|
1848
|
-
return new Promise((resolve3) => {
|
|
1849
|
-
(0, import_node_child_process4.execFile)(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
1850
|
-
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
1851
|
-
});
|
|
1852
|
-
});
|
|
1853
|
-
}
|
|
1854
|
-
function removeStale(file) {
|
|
1855
|
-
const prefix = `${(0, import_node_path6.basename)(file)}.old-`;
|
|
1856
|
-
let names;
|
|
1857
|
-
try {
|
|
1858
|
-
names = (0, import_node_fs11.readdirSync)((0, import_node_path6.dirname)(file));
|
|
1859
|
-
} catch {
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
for (const name of names) {
|
|
1863
|
-
if (!name.startsWith(prefix)) continue;
|
|
1864
|
-
try {
|
|
1865
|
-
(0, import_node_fs11.rmSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(file), name), { force: true });
|
|
1866
|
-
} catch {
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
async function refreshLauncher(target, options = {}) {
|
|
1871
|
-
const started = Date.now();
|
|
1872
|
-
const platform = options.platform ?? process.platform;
|
|
1873
|
-
const key = platformKey(platform, options.arch ?? process.arch);
|
|
1874
|
-
if (!key) return null;
|
|
1875
|
-
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
1876
|
-
const recorded = recordedPath(productDir);
|
|
1877
|
-
const file = options.path ?? (recorded && (0, import_node_fs11.existsSync)(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
1878
|
-
if (!(0, import_node_fs11.existsSync)(file) || (0, import_node_fs11.lstatSync)(file).isSymbolicLink()) return null;
|
|
1879
|
-
if (!options.dryRun) removeStale(file);
|
|
1880
|
-
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
1881
|
-
if (!from) return null;
|
|
1882
|
-
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
1883
|
-
const record = await fetchRecord(target, options);
|
|
1884
|
-
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
1885
|
-
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
1886
|
-
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
1887
|
-
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
1888
|
-
if (options.dryRun) return arm("ok", record.version, "");
|
|
1889
|
-
let unlock;
|
|
1890
|
-
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
1891
|
-
if (!lockHeld) {
|
|
1892
|
-
try {
|
|
1893
|
-
unlock = lockInstallation(productDir);
|
|
1894
|
-
} catch {
|
|
1895
|
-
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
const next = `${file}.new-${process.pid}`;
|
|
1899
|
-
let moved;
|
|
1900
|
-
try {
|
|
1901
|
-
let bytes;
|
|
1902
|
-
try {
|
|
1903
|
-
const response = await (options.fetch ?? fetch)(
|
|
1904
|
-
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
1905
|
-
{ signal: AbortSignal.timeout(3e5) }
|
|
1906
|
-
);
|
|
1907
|
-
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1908
|
-
bytes = await readDownload(response, void 0, entry2.size);
|
|
1909
|
-
} catch {
|
|
1910
|
-
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1911
|
-
}
|
|
1912
|
-
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
1913
|
-
try {
|
|
1914
|
-
(0, import_node_fs11.writeFileSync)(next, bytes, { mode: 493 });
|
|
1915
|
-
if (platform !== "win32") (0, import_node_fs11.chmodSync)(next, 493);
|
|
1916
|
-
if (platform === "win32") {
|
|
1917
|
-
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
1918
|
-
(0, import_node_fs11.renameSync)(file, moved);
|
|
1919
|
-
}
|
|
1920
|
-
(0, import_node_fs11.renameSync)(next, file);
|
|
1921
|
-
} catch {
|
|
1922
|
-
try {
|
|
1923
|
-
if (moved && !(0, import_node_fs11.existsSync)(file)) (0, import_node_fs11.renameSync)(moved, file);
|
|
1924
|
-
} catch {
|
|
1925
|
-
}
|
|
1926
|
-
(0, import_node_fs11.rmSync)(next, { force: true });
|
|
1927
|
-
return arm("fail", from, "The launcher could not be replaced.");
|
|
1928
|
-
}
|
|
1929
|
-
return arm("ok", record.version, "");
|
|
1930
|
-
} finally {
|
|
1931
|
-
unlock?.();
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
1765
|
// src/login-github.ts
|
|
1936
|
-
var
|
|
1766
|
+
var import_node_child_process3 = require("node:child_process");
|
|
1937
1767
|
var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1938
1768
|
function openBrowser(url) {
|
|
1939
1769
|
if (process.env.LAUNCHER_NO_OPEN === "1") return;
|
|
1940
1770
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
|
|
1941
1771
|
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
1942
1772
|
try {
|
|
1943
|
-
const child = (0,
|
|
1773
|
+
const child = (0, import_node_child_process3.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
1944
1774
|
child.on("error", () => {
|
|
1945
1775
|
});
|
|
1946
1776
|
child.unref();
|
|
@@ -2058,7 +1888,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
|
|
|
2058
1888
|
}
|
|
2059
1889
|
function readGhToken() {
|
|
2060
1890
|
try {
|
|
2061
|
-
const result = (0,
|
|
1891
|
+
const result = (0, import_node_child_process3.spawnSync)("gh", ["auth", "token", "--hostname", "github.com"], { encoding: "utf8", timeout: 1e4, windowsHide: true });
|
|
2062
1892
|
const token = result.status === 0 ? String(result.stdout).trim() : "";
|
|
2063
1893
|
return token && !/\s/u.test(token) ? token : null;
|
|
2064
1894
|
} catch {
|
|
@@ -2192,8 +2022,201 @@ function page(title, body) {
|
|
|
2192
2022
|
return `<!doctype html><meta charset="utf-8"><meta name="color-scheme" content="dark"><title>${title}</title><style>${style}</style><main><h1>${title}</h1><p>${body}</p></main>`;
|
|
2193
2023
|
}
|
|
2194
2024
|
|
|
2025
|
+
// src/tokens.ts
|
|
2026
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2027
|
+
const stored = readTokens(dir);
|
|
2028
|
+
if (!stored) return null;
|
|
2029
|
+
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2030
|
+
if (!stored.refreshToken) return null;
|
|
2031
|
+
if (config.loginKind === "github") {
|
|
2032
|
+
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2033
|
+
if (!refreshed2) {
|
|
2034
|
+
clearTokens(dir);
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
writeTokens(dir, {
|
|
2038
|
+
accessToken: refreshed2.accessToken,
|
|
2039
|
+
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2040
|
+
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2041
|
+
});
|
|
2042
|
+
return refreshed2.accessToken;
|
|
2043
|
+
}
|
|
2044
|
+
if (!stored.clientId) {
|
|
2045
|
+
clearTokens(dir);
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2049
|
+
if (!refreshed) {
|
|
2050
|
+
clearTokens(dir);
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
writeTokens(dir, {
|
|
2054
|
+
accessToken: refreshed.accessToken,
|
|
2055
|
+
refreshToken: refreshed.refreshToken,
|
|
2056
|
+
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2057
|
+
clientId: stored.clientId
|
|
2058
|
+
});
|
|
2059
|
+
return refreshed.accessToken;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// src/refresh.ts
|
|
2063
|
+
var import_node_child_process4 = require("node:child_process");
|
|
2064
|
+
var import_node_fs11 = require("node:fs");
|
|
2065
|
+
var import_node_os4 = require("node:os");
|
|
2066
|
+
var import_node_path6 = require("node:path");
|
|
2067
|
+
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
2068
|
+
var RECORD_TIMEOUT_MS = 1e4;
|
|
2069
|
+
function platformKey(platform, arch) {
|
|
2070
|
+
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
2071
|
+
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
2072
|
+
}
|
|
2073
|
+
function verifierFor(target) {
|
|
2074
|
+
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
2075
|
+
}
|
|
2076
|
+
async function fetchRecord(target, options) {
|
|
2077
|
+
let json;
|
|
2078
|
+
try {
|
|
2079
|
+
const response = await (options.fetch ?? fetch)(
|
|
2080
|
+
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
2081
|
+
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
2082
|
+
);
|
|
2083
|
+
if (!response.ok) return null;
|
|
2084
|
+
json = await response.json();
|
|
2085
|
+
} catch {
|
|
2086
|
+
return null;
|
|
2087
|
+
}
|
|
2088
|
+
let record;
|
|
2089
|
+
try {
|
|
2090
|
+
record = parseManifest(json);
|
|
2091
|
+
} catch {
|
|
2092
|
+
return null;
|
|
2093
|
+
}
|
|
2094
|
+
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
2095
|
+
return { version: record.version, files: record.files };
|
|
2096
|
+
}
|
|
2097
|
+
async function readLauncherRecord(target, options = {}) {
|
|
2098
|
+
const record = await fetchRecord(target, options);
|
|
2099
|
+
return record === "bad-signature" ? null : record;
|
|
2100
|
+
}
|
|
2101
|
+
function compareVersions(a, b) {
|
|
2102
|
+
const split = (v) => {
|
|
2103
|
+
const [core, pre = ""] = v.split("-", 2);
|
|
2104
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
2105
|
+
};
|
|
2106
|
+
const x = split(a);
|
|
2107
|
+
const y = split(b);
|
|
2108
|
+
for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) > (y.nums[i] ?? 0) ? 1 : -1;
|
|
2109
|
+
if (x.pre === y.pre) return 0;
|
|
2110
|
+
if (!x.pre) return 1;
|
|
2111
|
+
if (!y.pre) return -1;
|
|
2112
|
+
return x.pre > y.pre ? 1 : -1;
|
|
2113
|
+
}
|
|
2114
|
+
function recordedPath(productDir) {
|
|
2115
|
+
try {
|
|
2116
|
+
const saved = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
2117
|
+
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
2118
|
+
} catch {
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
function installedPath(target, productDir, platform) {
|
|
2123
|
+
return platform === "win32" ? (0, import_node_path6.join)(productDir, "bin", `${target.bin}.exe`) : (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".local", "bin", target.bin);
|
|
2124
|
+
}
|
|
2125
|
+
function parseLauncherVersion(stdout, bin) {
|
|
2126
|
+
const text = String(stdout).trim();
|
|
2127
|
+
if (!text.startsWith(`${bin} `)) return null;
|
|
2128
|
+
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
2129
|
+
}
|
|
2130
|
+
function runVersion(path, bin) {
|
|
2131
|
+
return new Promise((resolve3) => {
|
|
2132
|
+
(0, import_node_child_process4.execFile)(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
2133
|
+
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
2134
|
+
});
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
function removeStale(file) {
|
|
2138
|
+
const prefix = `${(0, import_node_path6.basename)(file)}.old-`;
|
|
2139
|
+
let names;
|
|
2140
|
+
try {
|
|
2141
|
+
names = (0, import_node_fs11.readdirSync)((0, import_node_path6.dirname)(file));
|
|
2142
|
+
} catch {
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
for (const name of names) {
|
|
2146
|
+
if (!name.startsWith(prefix)) continue;
|
|
2147
|
+
try {
|
|
2148
|
+
(0, import_node_fs11.rmSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(file), name), { force: true });
|
|
2149
|
+
} catch {
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
async function refreshLauncher(target, options = {}) {
|
|
2154
|
+
const started = Date.now();
|
|
2155
|
+
const platform = options.platform ?? process.platform;
|
|
2156
|
+
const key = platformKey(platform, options.arch ?? process.arch);
|
|
2157
|
+
if (!key) return null;
|
|
2158
|
+
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
2159
|
+
const recorded = recordedPath(productDir);
|
|
2160
|
+
const file = options.path ?? (recorded && (0, import_node_fs11.existsSync)(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
2161
|
+
if (!(0, import_node_fs11.existsSync)(file) || (0, import_node_fs11.lstatSync)(file).isSymbolicLink()) return null;
|
|
2162
|
+
if (!options.dryRun) removeStale(file);
|
|
2163
|
+
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
2164
|
+
if (!from) return null;
|
|
2165
|
+
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
2166
|
+
const record = await fetchRecord(target, options);
|
|
2167
|
+
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
2168
|
+
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
2169
|
+
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
2170
|
+
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
2171
|
+
if (options.dryRun) return arm("ok", record.version, "");
|
|
2172
|
+
let unlock;
|
|
2173
|
+
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
2174
|
+
if (!lockHeld) {
|
|
2175
|
+
try {
|
|
2176
|
+
unlock = lockInstallation(productDir);
|
|
2177
|
+
} catch {
|
|
2178
|
+
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
const next = `${file}.new-${process.pid}`;
|
|
2182
|
+
let moved;
|
|
2183
|
+
try {
|
|
2184
|
+
let bytes;
|
|
2185
|
+
try {
|
|
2186
|
+
const response = await (options.fetch ?? fetch)(
|
|
2187
|
+
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
2188
|
+
{ signal: AbortSignal.timeout(3e5) }
|
|
2189
|
+
);
|
|
2190
|
+
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2191
|
+
bytes = await readDownload(response, void 0, entry2.size);
|
|
2192
|
+
} catch {
|
|
2193
|
+
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2194
|
+
}
|
|
2195
|
+
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
2196
|
+
try {
|
|
2197
|
+
(0, import_node_fs11.writeFileSync)(next, bytes, { mode: 493 });
|
|
2198
|
+
if (platform !== "win32") (0, import_node_fs11.chmodSync)(next, 493);
|
|
2199
|
+
if (platform === "win32") {
|
|
2200
|
+
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
2201
|
+
(0, import_node_fs11.renameSync)(file, moved);
|
|
2202
|
+
}
|
|
2203
|
+
(0, import_node_fs11.renameSync)(next, file);
|
|
2204
|
+
} catch {
|
|
2205
|
+
try {
|
|
2206
|
+
if (moved && !(0, import_node_fs11.existsSync)(file)) (0, import_node_fs11.renameSync)(moved, file);
|
|
2207
|
+
} catch {
|
|
2208
|
+
}
|
|
2209
|
+
(0, import_node_fs11.rmSync)(next, { force: true });
|
|
2210
|
+
return arm("fail", from, "The launcher could not be replaced.");
|
|
2211
|
+
}
|
|
2212
|
+
return arm("ok", record.version, "");
|
|
2213
|
+
} finally {
|
|
2214
|
+
unlock?.();
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2195
2218
|
// src/index.ts
|
|
2196
|
-
var LAUNCHER_VERSION = true ? "1.2.
|
|
2219
|
+
var LAUNCHER_VERSION = true ? "1.2.4" : readVersionFromPackage();
|
|
2197
2220
|
function defaultPrint(message) {
|
|
2198
2221
|
process.stdout.write(`${message}
|
|
2199
2222
|
`);
|
|
@@ -2416,41 +2439,6 @@ async function runFile(file, args, printErr) {
|
|
|
2416
2439
|
}
|
|
2417
2440
|
return code;
|
|
2418
2441
|
}
|
|
2419
|
-
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2420
|
-
const stored = readTokens(dir);
|
|
2421
|
-
if (!stored) return null;
|
|
2422
|
-
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2423
|
-
if (!stored.refreshToken) return null;
|
|
2424
|
-
if (config.loginKind === "github") {
|
|
2425
|
-
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2426
|
-
if (!refreshed2) {
|
|
2427
|
-
clearTokens(dir);
|
|
2428
|
-
return null;
|
|
2429
|
-
}
|
|
2430
|
-
writeTokens(dir, {
|
|
2431
|
-
accessToken: refreshed2.accessToken,
|
|
2432
|
-
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2433
|
-
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2434
|
-
});
|
|
2435
|
-
return refreshed2.accessToken;
|
|
2436
|
-
}
|
|
2437
|
-
if (!stored.clientId) {
|
|
2438
|
-
clearTokens(dir);
|
|
2439
|
-
return null;
|
|
2440
|
-
}
|
|
2441
|
-
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2442
|
-
if (!refreshed) {
|
|
2443
|
-
clearTokens(dir);
|
|
2444
|
-
return null;
|
|
2445
|
-
}
|
|
2446
|
-
writeTokens(dir, {
|
|
2447
|
-
accessToken: refreshed.accessToken,
|
|
2448
|
-
refreshToken: refreshed.refreshToken,
|
|
2449
|
-
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2450
|
-
clientId: stored.clientId
|
|
2451
|
-
});
|
|
2452
|
-
return refreshed.accessToken;
|
|
2453
|
-
}
|
|
2454
2442
|
async function doLogin(config, dir, options, prompt, installer) {
|
|
2455
2443
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2456
2444
|
const open = options.open ?? openBrowser;
|
|
@@ -2569,7 +2557,7 @@ function defaultRunEntry(entry2, cwd, env) {
|
|
|
2569
2557
|
const [command2, ...args] = entry2;
|
|
2570
2558
|
const shell = needsShell(command2);
|
|
2571
2559
|
const commandLine = shell ? [command2, ...args].map(quoteForShell).join(" ") : command2;
|
|
2572
|
-
const spawnEntry = (progress2) => (0,
|
|
2560
|
+
const spawnEntry = (progress2) => (0, import_node_child_process5.spawnSync)(commandLine, shell ? [] : args, {
|
|
2573
2561
|
cwd,
|
|
2574
2562
|
stdio: progress2 ? ["inherit", "inherit", "inherit", "pipe"] : "inherit",
|
|
2575
2563
|
shell,
|
|
@@ -2861,7 +2849,7 @@ async function runInstallEntry(entry2, cwd, env, installer, tokens = null) {
|
|
|
2861
2849
|
const redact = (text) => secrets.reduce((safe, value) => safe.replaceAll(value, "[redacted]"), text);
|
|
2862
2850
|
try {
|
|
2863
2851
|
const result = await new Promise((resolve3) => {
|
|
2864
|
-
const child = (0,
|
|
2852
|
+
const child = (0, import_node_child_process5.spawn)(shell ? [command2, ...args].map(quoteForShell).join(" ") : command2, shell ? [] : args, {
|
|
2865
2853
|
cwd,
|
|
2866
2854
|
shell,
|
|
2867
2855
|
windowsHide: true,
|
|
@@ -3099,7 +3087,6 @@ if (invokedAsMain) {
|
|
|
3099
3087
|
0 && (module.exports = {
|
|
3100
3088
|
LAUNCHER_VERSION,
|
|
3101
3089
|
defaultRunEntry,
|
|
3102
|
-
ensureFreshToken,
|
|
3103
3090
|
formatMb,
|
|
3104
3091
|
parseArgv,
|
|
3105
3092
|
readPayloadEntry,
|
package/package.json
CHANGED