@mutmutco/installer-launcher 1.2.3 → 1.2.5
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 +208 -194
- package/dist/launcher.sea.cjs +211 -198
- package/package.json +1 -1
package/dist/launcher.js
CHANGED
|
@@ -687,6 +687,9 @@ import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
|
687
687
|
import { appendFileSync, writeSync } from "node:fs";
|
|
688
688
|
|
|
689
689
|
// ../face/src/products.ts
|
|
690
|
+
function closingLine(identity) {
|
|
691
|
+
return identity.open ? `Open it any time: ${identity.open}` : `Check health any time: ${identity.doctor}`;
|
|
692
|
+
}
|
|
690
693
|
var PRODUCTS = Object.freeze({
|
|
691
694
|
"mm-strategy": Object.freeze({
|
|
692
695
|
name: "MM Strategy",
|
|
@@ -712,6 +715,16 @@ var PRODUCTS = Object.freeze({
|
|
|
712
715
|
warm: "Welcome back. Checking for updates...",
|
|
713
716
|
doctor: "jerv doctor"
|
|
714
717
|
}),
|
|
718
|
+
// #7318: the MM-Work desktop app (a signed setup, not a launcher). Accent: owner pick 2026-09-25 (indigo).
|
|
719
|
+
"mm-work": Object.freeze({
|
|
720
|
+
name: "MM-Work",
|
|
721
|
+
installWarm: "Welcome. Let's get you ready.",
|
|
722
|
+
checkWarm: "Here's where things stand.",
|
|
723
|
+
accent: "38;2;129;140;248",
|
|
724
|
+
warm: "Welcome back. Checking for updates...",
|
|
725
|
+
doctor: "",
|
|
726
|
+
open: "Start menu > MM-Work"
|
|
727
|
+
}),
|
|
715
728
|
jervcode: Object.freeze({
|
|
716
729
|
name: "JervCode",
|
|
717
730
|
installWarm: "Welcome. Let's get you ready.",
|
|
@@ -880,7 +893,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
880
893
|
return receipt([
|
|
881
894
|
`${GLYPH.check} ${identity.name} is ready.`,
|
|
882
895
|
fact,
|
|
883
|
-
|
|
896
|
+
closingLine(identity)
|
|
884
897
|
], options);
|
|
885
898
|
};
|
|
886
899
|
const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
@@ -1729,162 +1742,6 @@ function firstLine(text) {
|
|
|
1729
1742
|
return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
1730
1743
|
}
|
|
1731
1744
|
|
|
1732
|
-
// src/refresh.ts
|
|
1733
|
-
import { execFile } from "node:child_process";
|
|
1734
|
-
import { chmodSync, existsSync as existsSync7, lstatSync, readdirSync, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1735
|
-
import { homedir } from "node:os";
|
|
1736
|
-
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
1737
|
-
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
1738
|
-
var RECORD_TIMEOUT_MS = 1e4;
|
|
1739
|
-
function platformKey(platform, arch) {
|
|
1740
|
-
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
1741
|
-
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
1742
|
-
}
|
|
1743
|
-
function verifierFor(target) {
|
|
1744
|
-
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
1745
|
-
}
|
|
1746
|
-
async function fetchRecord(target, options) {
|
|
1747
|
-
let json;
|
|
1748
|
-
try {
|
|
1749
|
-
const response = await (options.fetch ?? fetch)(
|
|
1750
|
-
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
1751
|
-
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
1752
|
-
);
|
|
1753
|
-
if (!response.ok) return null;
|
|
1754
|
-
json = await response.json();
|
|
1755
|
-
} catch {
|
|
1756
|
-
return null;
|
|
1757
|
-
}
|
|
1758
|
-
let record;
|
|
1759
|
-
try {
|
|
1760
|
-
record = parseManifest(json);
|
|
1761
|
-
} catch {
|
|
1762
|
-
return null;
|
|
1763
|
-
}
|
|
1764
|
-
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
1765
|
-
return { version: record.version, files: record.files };
|
|
1766
|
-
}
|
|
1767
|
-
async function readLauncherRecord(target, options = {}) {
|
|
1768
|
-
const record = await fetchRecord(target, options);
|
|
1769
|
-
return record === "bad-signature" ? null : record;
|
|
1770
|
-
}
|
|
1771
|
-
function compareVersions(a, b) {
|
|
1772
|
-
const split = (v) => {
|
|
1773
|
-
const [core, pre = ""] = v.split("-", 2);
|
|
1774
|
-
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
1775
|
-
};
|
|
1776
|
-
const x = split(a);
|
|
1777
|
-
const y = split(b);
|
|
1778
|
-
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;
|
|
1779
|
-
if (x.pre === y.pre) return 0;
|
|
1780
|
-
if (!x.pre) return 1;
|
|
1781
|
-
if (!y.pre) return -1;
|
|
1782
|
-
return x.pre > y.pre ? 1 : -1;
|
|
1783
|
-
}
|
|
1784
|
-
function recordedPath(productDir) {
|
|
1785
|
-
try {
|
|
1786
|
-
const saved = JSON.parse(readFileSync7(join6(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
1787
|
-
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
1788
|
-
} catch {
|
|
1789
|
-
return null;
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
function installedPath(target, productDir, platform) {
|
|
1793
|
-
return platform === "win32" ? join6(productDir, "bin", `${target.bin}.exe`) : join6(homedir(), ".local", "bin", target.bin);
|
|
1794
|
-
}
|
|
1795
|
-
function parseLauncherVersion(stdout, bin) {
|
|
1796
|
-
const text = String(stdout).trim();
|
|
1797
|
-
if (!text.startsWith(`${bin} `)) return null;
|
|
1798
|
-
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
1799
|
-
}
|
|
1800
|
-
function runVersion(path, bin) {
|
|
1801
|
-
return new Promise((resolve3) => {
|
|
1802
|
-
execFile(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
1803
|
-
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
1804
|
-
});
|
|
1805
|
-
});
|
|
1806
|
-
}
|
|
1807
|
-
function removeStale(file) {
|
|
1808
|
-
const prefix = `${basename(file)}.old-`;
|
|
1809
|
-
let names;
|
|
1810
|
-
try {
|
|
1811
|
-
names = readdirSync(dirname3(file));
|
|
1812
|
-
} catch {
|
|
1813
|
-
return;
|
|
1814
|
-
}
|
|
1815
|
-
for (const name of names) {
|
|
1816
|
-
if (!name.startsWith(prefix)) continue;
|
|
1817
|
-
try {
|
|
1818
|
-
rmSync5(join6(dirname3(file), name), { force: true });
|
|
1819
|
-
} catch {
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
async function refreshLauncher(target, options = {}) {
|
|
1824
|
-
const started = Date.now();
|
|
1825
|
-
const platform = options.platform ?? process.platform;
|
|
1826
|
-
const key = platformKey(platform, options.arch ?? process.arch);
|
|
1827
|
-
if (!key) return null;
|
|
1828
|
-
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
1829
|
-
const recorded = recordedPath(productDir);
|
|
1830
|
-
const file = options.path ?? (recorded && existsSync7(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
1831
|
-
if (!existsSync7(file) || lstatSync(file).isSymbolicLink()) return null;
|
|
1832
|
-
if (!options.dryRun) removeStale(file);
|
|
1833
|
-
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
1834
|
-
if (!from) return null;
|
|
1835
|
-
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
1836
|
-
const record = await fetchRecord(target, options);
|
|
1837
|
-
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
1838
|
-
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
1839
|
-
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
1840
|
-
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
1841
|
-
if (options.dryRun) return arm("ok", record.version, "");
|
|
1842
|
-
let unlock;
|
|
1843
|
-
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
1844
|
-
if (!lockHeld) {
|
|
1845
|
-
try {
|
|
1846
|
-
unlock = lockInstallation(productDir);
|
|
1847
|
-
} catch {
|
|
1848
|
-
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
const next = `${file}.new-${process.pid}`;
|
|
1852
|
-
let moved;
|
|
1853
|
-
try {
|
|
1854
|
-
let bytes;
|
|
1855
|
-
try {
|
|
1856
|
-
const response = await (options.fetch ?? fetch)(
|
|
1857
|
-
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
1858
|
-
{ signal: AbortSignal.timeout(3e5) }
|
|
1859
|
-
);
|
|
1860
|
-
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1861
|
-
bytes = await readDownload(response, void 0, entry2.size);
|
|
1862
|
-
} catch {
|
|
1863
|
-
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1864
|
-
}
|
|
1865
|
-
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
1866
|
-
try {
|
|
1867
|
-
writeFileSync7(next, bytes, { mode: 493 });
|
|
1868
|
-
if (platform !== "win32") chmodSync(next, 493);
|
|
1869
|
-
if (platform === "win32") {
|
|
1870
|
-
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
1871
|
-
renameSync4(file, moved);
|
|
1872
|
-
}
|
|
1873
|
-
renameSync4(next, file);
|
|
1874
|
-
} catch {
|
|
1875
|
-
try {
|
|
1876
|
-
if (moved && !existsSync7(file)) renameSync4(moved, file);
|
|
1877
|
-
} catch {
|
|
1878
|
-
}
|
|
1879
|
-
rmSync5(next, { force: true });
|
|
1880
|
-
return arm("fail", from, "The launcher could not be replaced.");
|
|
1881
|
-
}
|
|
1882
|
-
return arm("ok", record.version, "");
|
|
1883
|
-
} finally {
|
|
1884
|
-
unlock?.();
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
1745
|
// src/login-github.ts
|
|
1889
1746
|
import { spawn, spawnSync as spawnSync3 } from "node:child_process";
|
|
1890
1747
|
var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
@@ -2145,8 +2002,201 @@ function page(title, body) {
|
|
|
2145
2002
|
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>`;
|
|
2146
2003
|
}
|
|
2147
2004
|
|
|
2005
|
+
// src/tokens.ts
|
|
2006
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2007
|
+
const stored = readTokens(dir);
|
|
2008
|
+
if (!stored) return null;
|
|
2009
|
+
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2010
|
+
if (!stored.refreshToken) return null;
|
|
2011
|
+
if (config.loginKind === "github") {
|
|
2012
|
+
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2013
|
+
if (!refreshed2) {
|
|
2014
|
+
clearTokens(dir);
|
|
2015
|
+
return null;
|
|
2016
|
+
}
|
|
2017
|
+
writeTokens(dir, {
|
|
2018
|
+
accessToken: refreshed2.accessToken,
|
|
2019
|
+
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2020
|
+
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2021
|
+
});
|
|
2022
|
+
return refreshed2.accessToken;
|
|
2023
|
+
}
|
|
2024
|
+
if (!stored.clientId) {
|
|
2025
|
+
clearTokens(dir);
|
|
2026
|
+
return null;
|
|
2027
|
+
}
|
|
2028
|
+
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2029
|
+
if (!refreshed) {
|
|
2030
|
+
clearTokens(dir);
|
|
2031
|
+
return null;
|
|
2032
|
+
}
|
|
2033
|
+
writeTokens(dir, {
|
|
2034
|
+
accessToken: refreshed.accessToken,
|
|
2035
|
+
refreshToken: refreshed.refreshToken,
|
|
2036
|
+
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2037
|
+
clientId: stored.clientId
|
|
2038
|
+
});
|
|
2039
|
+
return refreshed.accessToken;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
// src/refresh.ts
|
|
2043
|
+
import { execFile } from "node:child_process";
|
|
2044
|
+
import { chmodSync, existsSync as existsSync7, lstatSync, readdirSync, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2045
|
+
import { homedir } from "node:os";
|
|
2046
|
+
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
2047
|
+
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
2048
|
+
var RECORD_TIMEOUT_MS = 1e4;
|
|
2049
|
+
function platformKey(platform, arch) {
|
|
2050
|
+
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
2051
|
+
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
2052
|
+
}
|
|
2053
|
+
function verifierFor(target) {
|
|
2054
|
+
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
2055
|
+
}
|
|
2056
|
+
async function fetchRecord(target, options) {
|
|
2057
|
+
let json;
|
|
2058
|
+
try {
|
|
2059
|
+
const response = await (options.fetch ?? fetch)(
|
|
2060
|
+
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
2061
|
+
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
2062
|
+
);
|
|
2063
|
+
if (!response.ok) return null;
|
|
2064
|
+
json = await response.json();
|
|
2065
|
+
} catch {
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
let record;
|
|
2069
|
+
try {
|
|
2070
|
+
record = parseManifest(json);
|
|
2071
|
+
} catch {
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
2075
|
+
return { version: record.version, files: record.files };
|
|
2076
|
+
}
|
|
2077
|
+
async function readLauncherRecord(target, options = {}) {
|
|
2078
|
+
const record = await fetchRecord(target, options);
|
|
2079
|
+
return record === "bad-signature" ? null : record;
|
|
2080
|
+
}
|
|
2081
|
+
function compareVersions(a, b) {
|
|
2082
|
+
const split = (v) => {
|
|
2083
|
+
const [core, pre = ""] = v.split("-", 2);
|
|
2084
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
2085
|
+
};
|
|
2086
|
+
const x = split(a);
|
|
2087
|
+
const y = split(b);
|
|
2088
|
+
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;
|
|
2089
|
+
if (x.pre === y.pre) return 0;
|
|
2090
|
+
if (!x.pre) return 1;
|
|
2091
|
+
if (!y.pre) return -1;
|
|
2092
|
+
return x.pre > y.pre ? 1 : -1;
|
|
2093
|
+
}
|
|
2094
|
+
function recordedPath(productDir) {
|
|
2095
|
+
try {
|
|
2096
|
+
const saved = JSON.parse(readFileSync7(join6(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
2097
|
+
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
2098
|
+
} catch {
|
|
2099
|
+
return null;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
function installedPath(target, productDir, platform) {
|
|
2103
|
+
return platform === "win32" ? join6(productDir, "bin", `${target.bin}.exe`) : join6(homedir(), ".local", "bin", target.bin);
|
|
2104
|
+
}
|
|
2105
|
+
function parseLauncherVersion(stdout, bin) {
|
|
2106
|
+
const text = String(stdout).trim();
|
|
2107
|
+
if (!text.startsWith(`${bin} `)) return null;
|
|
2108
|
+
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
2109
|
+
}
|
|
2110
|
+
function runVersion(path, bin) {
|
|
2111
|
+
return new Promise((resolve3) => {
|
|
2112
|
+
execFile(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
2113
|
+
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
2114
|
+
});
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
function removeStale(file) {
|
|
2118
|
+
const prefix = `${basename(file)}.old-`;
|
|
2119
|
+
let names;
|
|
2120
|
+
try {
|
|
2121
|
+
names = readdirSync(dirname3(file));
|
|
2122
|
+
} catch {
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
for (const name of names) {
|
|
2126
|
+
if (!name.startsWith(prefix)) continue;
|
|
2127
|
+
try {
|
|
2128
|
+
rmSync5(join6(dirname3(file), name), { force: true });
|
|
2129
|
+
} catch {
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
async function refreshLauncher(target, options = {}) {
|
|
2134
|
+
const started = Date.now();
|
|
2135
|
+
const platform = options.platform ?? process.platform;
|
|
2136
|
+
const key = platformKey(platform, options.arch ?? process.arch);
|
|
2137
|
+
if (!key) return null;
|
|
2138
|
+
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
2139
|
+
const recorded = recordedPath(productDir);
|
|
2140
|
+
const file = options.path ?? (recorded && existsSync7(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
2141
|
+
if (!existsSync7(file) || lstatSync(file).isSymbolicLink()) return null;
|
|
2142
|
+
if (!options.dryRun) removeStale(file);
|
|
2143
|
+
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
2144
|
+
if (!from) return null;
|
|
2145
|
+
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
2146
|
+
const record = await fetchRecord(target, options);
|
|
2147
|
+
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
2148
|
+
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
2149
|
+
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
2150
|
+
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
2151
|
+
if (options.dryRun) return arm("ok", record.version, "");
|
|
2152
|
+
let unlock;
|
|
2153
|
+
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
2154
|
+
if (!lockHeld) {
|
|
2155
|
+
try {
|
|
2156
|
+
unlock = lockInstallation(productDir);
|
|
2157
|
+
} catch {
|
|
2158
|
+
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
const next = `${file}.new-${process.pid}`;
|
|
2162
|
+
let moved;
|
|
2163
|
+
try {
|
|
2164
|
+
let bytes;
|
|
2165
|
+
try {
|
|
2166
|
+
const response = await (options.fetch ?? fetch)(
|
|
2167
|
+
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
2168
|
+
{ signal: AbortSignal.timeout(3e5) }
|
|
2169
|
+
);
|
|
2170
|
+
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2171
|
+
bytes = await readDownload(response, void 0, entry2.size);
|
|
2172
|
+
} catch {
|
|
2173
|
+
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2174
|
+
}
|
|
2175
|
+
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
2176
|
+
try {
|
|
2177
|
+
writeFileSync7(next, bytes, { mode: 493 });
|
|
2178
|
+
if (platform !== "win32") chmodSync(next, 493);
|
|
2179
|
+
if (platform === "win32") {
|
|
2180
|
+
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
2181
|
+
renameSync4(file, moved);
|
|
2182
|
+
}
|
|
2183
|
+
renameSync4(next, file);
|
|
2184
|
+
} catch {
|
|
2185
|
+
try {
|
|
2186
|
+
if (moved && !existsSync7(file)) renameSync4(moved, file);
|
|
2187
|
+
} catch {
|
|
2188
|
+
}
|
|
2189
|
+
rmSync5(next, { force: true });
|
|
2190
|
+
return arm("fail", from, "The launcher could not be replaced.");
|
|
2191
|
+
}
|
|
2192
|
+
return arm("ok", record.version, "");
|
|
2193
|
+
} finally {
|
|
2194
|
+
unlock?.();
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2148
2198
|
// src/index.ts
|
|
2149
|
-
var LAUNCHER_VERSION = true ? "1.2.
|
|
2199
|
+
var LAUNCHER_VERSION = true ? "1.2.5" : readVersionFromPackage();
|
|
2150
2200
|
function defaultPrint(message) {
|
|
2151
2201
|
process.stdout.write(`${message}
|
|
2152
2202
|
`);
|
|
@@ -2369,41 +2419,6 @@ async function runFile(file, args, printErr) {
|
|
|
2369
2419
|
}
|
|
2370
2420
|
return code;
|
|
2371
2421
|
}
|
|
2372
|
-
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2373
|
-
const stored = readTokens(dir);
|
|
2374
|
-
if (!stored) return null;
|
|
2375
|
-
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2376
|
-
if (!stored.refreshToken) return null;
|
|
2377
|
-
if (config.loginKind === "github") {
|
|
2378
|
-
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2379
|
-
if (!refreshed2) {
|
|
2380
|
-
clearTokens(dir);
|
|
2381
|
-
return null;
|
|
2382
|
-
}
|
|
2383
|
-
writeTokens(dir, {
|
|
2384
|
-
accessToken: refreshed2.accessToken,
|
|
2385
|
-
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2386
|
-
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2387
|
-
});
|
|
2388
|
-
return refreshed2.accessToken;
|
|
2389
|
-
}
|
|
2390
|
-
if (!stored.clientId) {
|
|
2391
|
-
clearTokens(dir);
|
|
2392
|
-
return null;
|
|
2393
|
-
}
|
|
2394
|
-
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2395
|
-
if (!refreshed) {
|
|
2396
|
-
clearTokens(dir);
|
|
2397
|
-
return null;
|
|
2398
|
-
}
|
|
2399
|
-
writeTokens(dir, {
|
|
2400
|
-
accessToken: refreshed.accessToken,
|
|
2401
|
-
refreshToken: refreshed.refreshToken,
|
|
2402
|
-
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2403
|
-
clientId: stored.clientId
|
|
2404
|
-
});
|
|
2405
|
-
return refreshed.accessToken;
|
|
2406
|
-
}
|
|
2407
2422
|
async function doLogin(config, dir, options, prompt, installer) {
|
|
2408
2423
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2409
2424
|
const open = options.open ?? openBrowser;
|
|
@@ -3051,7 +3066,6 @@ if (invokedAsMain) {
|
|
|
3051
3066
|
export {
|
|
3052
3067
|
LAUNCHER_VERSION,
|
|
3053
3068
|
defaultRunEntry,
|
|
3054
|
-
ensureFreshToken,
|
|
3055
3069
|
formatMb,
|
|
3056
3070
|
parseArgv,
|
|
3057
3071
|
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,
|
|
@@ -721,6 +720,9 @@ var import_node_url2 = require("node:url");
|
|
|
721
720
|
var import_node_fs6 = require("node:fs");
|
|
722
721
|
|
|
723
722
|
// ../face/src/products.ts
|
|
723
|
+
function closingLine(identity) {
|
|
724
|
+
return identity.open ? `Open it any time: ${identity.open}` : `Check health any time: ${identity.doctor}`;
|
|
725
|
+
}
|
|
724
726
|
var PRODUCTS = Object.freeze({
|
|
725
727
|
"mm-strategy": Object.freeze({
|
|
726
728
|
name: "MM Strategy",
|
|
@@ -746,6 +748,16 @@ var PRODUCTS = Object.freeze({
|
|
|
746
748
|
warm: "Welcome back. Checking for updates...",
|
|
747
749
|
doctor: "jerv doctor"
|
|
748
750
|
}),
|
|
751
|
+
// #7318: the MM-Work desktop app (a signed setup, not a launcher). Accent: owner pick 2026-09-25 (indigo).
|
|
752
|
+
"mm-work": Object.freeze({
|
|
753
|
+
name: "MM-Work",
|
|
754
|
+
installWarm: "Welcome. Let's get you ready.",
|
|
755
|
+
checkWarm: "Here's where things stand.",
|
|
756
|
+
accent: "38;2;129;140;248",
|
|
757
|
+
warm: "Welcome back. Checking for updates...",
|
|
758
|
+
doctor: "",
|
|
759
|
+
open: "Start menu > MM-Work"
|
|
760
|
+
}),
|
|
749
761
|
jervcode: Object.freeze({
|
|
750
762
|
name: "JervCode",
|
|
751
763
|
installWarm: "Welcome. Let's get you ready.",
|
|
@@ -914,7 +926,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
914
926
|
return receipt([
|
|
915
927
|
`${GLYPH.check} ${identity.name} is ready.`,
|
|
916
928
|
fact,
|
|
917
|
-
|
|
929
|
+
closingLine(identity)
|
|
918
930
|
], options);
|
|
919
931
|
};
|
|
920
932
|
const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
@@ -1763,171 +1775,15 @@ function firstLine(text) {
|
|
|
1763
1775
|
return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
1764
1776
|
}
|
|
1765
1777
|
|
|
1766
|
-
// src/refresh.ts
|
|
1767
|
-
var import_node_child_process3 = require("node:child_process");
|
|
1768
|
-
var import_node_fs11 = require("node:fs");
|
|
1769
|
-
var import_node_os4 = require("node:os");
|
|
1770
|
-
var import_node_path6 = require("node:path");
|
|
1771
|
-
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
1772
|
-
var RECORD_TIMEOUT_MS = 1e4;
|
|
1773
|
-
function platformKey(platform, arch) {
|
|
1774
|
-
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
1775
|
-
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
1776
|
-
}
|
|
1777
|
-
function verifierFor(target) {
|
|
1778
|
-
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
1779
|
-
}
|
|
1780
|
-
async function fetchRecord(target, options) {
|
|
1781
|
-
let json;
|
|
1782
|
-
try {
|
|
1783
|
-
const response = await (options.fetch ?? fetch)(
|
|
1784
|
-
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
1785
|
-
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
1786
|
-
);
|
|
1787
|
-
if (!response.ok) return null;
|
|
1788
|
-
json = await response.json();
|
|
1789
|
-
} catch {
|
|
1790
|
-
return null;
|
|
1791
|
-
}
|
|
1792
|
-
let record;
|
|
1793
|
-
try {
|
|
1794
|
-
record = parseManifest(json);
|
|
1795
|
-
} catch {
|
|
1796
|
-
return null;
|
|
1797
|
-
}
|
|
1798
|
-
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
1799
|
-
return { version: record.version, files: record.files };
|
|
1800
|
-
}
|
|
1801
|
-
async function readLauncherRecord(target, options = {}) {
|
|
1802
|
-
const record = await fetchRecord(target, options);
|
|
1803
|
-
return record === "bad-signature" ? null : record;
|
|
1804
|
-
}
|
|
1805
|
-
function compareVersions(a, b) {
|
|
1806
|
-
const split = (v) => {
|
|
1807
|
-
const [core, pre = ""] = v.split("-", 2);
|
|
1808
|
-
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
1809
|
-
};
|
|
1810
|
-
const x = split(a);
|
|
1811
|
-
const y = split(b);
|
|
1812
|
-
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;
|
|
1813
|
-
if (x.pre === y.pre) return 0;
|
|
1814
|
-
if (!x.pre) return 1;
|
|
1815
|
-
if (!y.pre) return -1;
|
|
1816
|
-
return x.pre > y.pre ? 1 : -1;
|
|
1817
|
-
}
|
|
1818
|
-
function recordedPath(productDir) {
|
|
1819
|
-
try {
|
|
1820
|
-
const saved = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
1821
|
-
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
1822
|
-
} catch {
|
|
1823
|
-
return null;
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
function installedPath(target, productDir, platform) {
|
|
1827
|
-
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);
|
|
1828
|
-
}
|
|
1829
|
-
function parseLauncherVersion(stdout, bin) {
|
|
1830
|
-
const text = String(stdout).trim();
|
|
1831
|
-
if (!text.startsWith(`${bin} `)) return null;
|
|
1832
|
-
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
1833
|
-
}
|
|
1834
|
-
function runVersion(path, bin) {
|
|
1835
|
-
return new Promise((resolve3) => {
|
|
1836
|
-
(0, import_node_child_process3.execFile)(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
1837
|
-
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
1838
|
-
});
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
function removeStale(file) {
|
|
1842
|
-
const prefix = `${(0, import_node_path6.basename)(file)}.old-`;
|
|
1843
|
-
let names;
|
|
1844
|
-
try {
|
|
1845
|
-
names = (0, import_node_fs11.readdirSync)((0, import_node_path6.dirname)(file));
|
|
1846
|
-
} catch {
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
for (const name of names) {
|
|
1850
|
-
if (!name.startsWith(prefix)) continue;
|
|
1851
|
-
try {
|
|
1852
|
-
(0, import_node_fs11.rmSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(file), name), { force: true });
|
|
1853
|
-
} catch {
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
async function refreshLauncher(target, options = {}) {
|
|
1858
|
-
const started = Date.now();
|
|
1859
|
-
const platform = options.platform ?? process.platform;
|
|
1860
|
-
const key = platformKey(platform, options.arch ?? process.arch);
|
|
1861
|
-
if (!key) return null;
|
|
1862
|
-
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
1863
|
-
const recorded = recordedPath(productDir);
|
|
1864
|
-
const file = options.path ?? (recorded && (0, import_node_fs11.existsSync)(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
1865
|
-
if (!(0, import_node_fs11.existsSync)(file) || (0, import_node_fs11.lstatSync)(file).isSymbolicLink()) return null;
|
|
1866
|
-
if (!options.dryRun) removeStale(file);
|
|
1867
|
-
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
1868
|
-
if (!from) return null;
|
|
1869
|
-
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
1870
|
-
const record = await fetchRecord(target, options);
|
|
1871
|
-
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
1872
|
-
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
1873
|
-
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
1874
|
-
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
1875
|
-
if (options.dryRun) return arm("ok", record.version, "");
|
|
1876
|
-
let unlock;
|
|
1877
|
-
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
1878
|
-
if (!lockHeld) {
|
|
1879
|
-
try {
|
|
1880
|
-
unlock = lockInstallation(productDir);
|
|
1881
|
-
} catch {
|
|
1882
|
-
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
const next = `${file}.new-${process.pid}`;
|
|
1886
|
-
let moved;
|
|
1887
|
-
try {
|
|
1888
|
-
let bytes;
|
|
1889
|
-
try {
|
|
1890
|
-
const response = await (options.fetch ?? fetch)(
|
|
1891
|
-
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
1892
|
-
{ signal: AbortSignal.timeout(3e5) }
|
|
1893
|
-
);
|
|
1894
|
-
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1895
|
-
bytes = await readDownload(response, void 0, entry2.size);
|
|
1896
|
-
} catch {
|
|
1897
|
-
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
1898
|
-
}
|
|
1899
|
-
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
1900
|
-
try {
|
|
1901
|
-
(0, import_node_fs11.writeFileSync)(next, bytes, { mode: 493 });
|
|
1902
|
-
if (platform !== "win32") (0, import_node_fs11.chmodSync)(next, 493);
|
|
1903
|
-
if (platform === "win32") {
|
|
1904
|
-
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
1905
|
-
(0, import_node_fs11.renameSync)(file, moved);
|
|
1906
|
-
}
|
|
1907
|
-
(0, import_node_fs11.renameSync)(next, file);
|
|
1908
|
-
} catch {
|
|
1909
|
-
try {
|
|
1910
|
-
if (moved && !(0, import_node_fs11.existsSync)(file)) (0, import_node_fs11.renameSync)(moved, file);
|
|
1911
|
-
} catch {
|
|
1912
|
-
}
|
|
1913
|
-
(0, import_node_fs11.rmSync)(next, { force: true });
|
|
1914
|
-
return arm("fail", from, "The launcher could not be replaced.");
|
|
1915
|
-
}
|
|
1916
|
-
return arm("ok", record.version, "");
|
|
1917
|
-
} finally {
|
|
1918
|
-
unlock?.();
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
1778
|
// src/login-github.ts
|
|
1923
|
-
var
|
|
1779
|
+
var import_node_child_process3 = require("node:child_process");
|
|
1924
1780
|
var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1925
1781
|
function openBrowser(url) {
|
|
1926
1782
|
if (process.env.LAUNCHER_NO_OPEN === "1") return;
|
|
1927
1783
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
|
|
1928
1784
|
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
1929
1785
|
try {
|
|
1930
|
-
const child = (0,
|
|
1786
|
+
const child = (0, import_node_child_process3.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
1931
1787
|
child.on("error", () => {
|
|
1932
1788
|
});
|
|
1933
1789
|
child.unref();
|
|
@@ -2045,7 +1901,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
|
|
|
2045
1901
|
}
|
|
2046
1902
|
function readGhToken() {
|
|
2047
1903
|
try {
|
|
2048
|
-
const result = (0,
|
|
1904
|
+
const result = (0, import_node_child_process3.spawnSync)("gh", ["auth", "token", "--hostname", "github.com"], { encoding: "utf8", timeout: 1e4, windowsHide: true });
|
|
2049
1905
|
const token = result.status === 0 ? String(result.stdout).trim() : "";
|
|
2050
1906
|
return token && !/\s/u.test(token) ? token : null;
|
|
2051
1907
|
} catch {
|
|
@@ -2179,8 +2035,201 @@ function page(title, body) {
|
|
|
2179
2035
|
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>`;
|
|
2180
2036
|
}
|
|
2181
2037
|
|
|
2038
|
+
// src/tokens.ts
|
|
2039
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2040
|
+
const stored = readTokens(dir);
|
|
2041
|
+
if (!stored) return null;
|
|
2042
|
+
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2043
|
+
if (!stored.refreshToken) return null;
|
|
2044
|
+
if (config.loginKind === "github") {
|
|
2045
|
+
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2046
|
+
if (!refreshed2) {
|
|
2047
|
+
clearTokens(dir);
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
writeTokens(dir, {
|
|
2051
|
+
accessToken: refreshed2.accessToken,
|
|
2052
|
+
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2053
|
+
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2054
|
+
});
|
|
2055
|
+
return refreshed2.accessToken;
|
|
2056
|
+
}
|
|
2057
|
+
if (!stored.clientId) {
|
|
2058
|
+
clearTokens(dir);
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2062
|
+
if (!refreshed) {
|
|
2063
|
+
clearTokens(dir);
|
|
2064
|
+
return null;
|
|
2065
|
+
}
|
|
2066
|
+
writeTokens(dir, {
|
|
2067
|
+
accessToken: refreshed.accessToken,
|
|
2068
|
+
refreshToken: refreshed.refreshToken,
|
|
2069
|
+
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2070
|
+
clientId: stored.clientId
|
|
2071
|
+
});
|
|
2072
|
+
return refreshed.accessToken;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/refresh.ts
|
|
2076
|
+
var import_node_child_process4 = require("node:child_process");
|
|
2077
|
+
var import_node_fs11 = require("node:fs");
|
|
2078
|
+
var import_node_os4 = require("node:os");
|
|
2079
|
+
var import_node_path6 = require("node:path");
|
|
2080
|
+
var LAUNCHER_RECORD_FILE = "launcher.json";
|
|
2081
|
+
var RECORD_TIMEOUT_MS = 1e4;
|
|
2082
|
+
function platformKey(platform, arch) {
|
|
2083
|
+
if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
|
|
2084
|
+
return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
|
|
2085
|
+
}
|
|
2086
|
+
function verifierFor(target) {
|
|
2087
|
+
return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
|
|
2088
|
+
}
|
|
2089
|
+
async function fetchRecord(target, options) {
|
|
2090
|
+
let json;
|
|
2091
|
+
try {
|
|
2092
|
+
const response = await (options.fetch ?? fetch)(
|
|
2093
|
+
`${target.host.replace(/\/+$/u, "")}/release/launcher`,
|
|
2094
|
+
{ signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
|
|
2095
|
+
);
|
|
2096
|
+
if (!response.ok) return null;
|
|
2097
|
+
json = await response.json();
|
|
2098
|
+
} catch {
|
|
2099
|
+
return null;
|
|
2100
|
+
}
|
|
2101
|
+
let record;
|
|
2102
|
+
try {
|
|
2103
|
+
record = parseManifest(json);
|
|
2104
|
+
} catch {
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
|
|
2108
|
+
return { version: record.version, files: record.files };
|
|
2109
|
+
}
|
|
2110
|
+
async function readLauncherRecord(target, options = {}) {
|
|
2111
|
+
const record = await fetchRecord(target, options);
|
|
2112
|
+
return record === "bad-signature" ? null : record;
|
|
2113
|
+
}
|
|
2114
|
+
function compareVersions(a, b) {
|
|
2115
|
+
const split = (v) => {
|
|
2116
|
+
const [core, pre = ""] = v.split("-", 2);
|
|
2117
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
2118
|
+
};
|
|
2119
|
+
const x = split(a);
|
|
2120
|
+
const y = split(b);
|
|
2121
|
+
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;
|
|
2122
|
+
if (x.pre === y.pre) return 0;
|
|
2123
|
+
if (!x.pre) return 1;
|
|
2124
|
+
if (!y.pre) return -1;
|
|
2125
|
+
return x.pre > y.pre ? 1 : -1;
|
|
2126
|
+
}
|
|
2127
|
+
function recordedPath(productDir) {
|
|
2128
|
+
try {
|
|
2129
|
+
const saved = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(productDir, LAUNCHER_RECORD_FILE), "utf8"));
|
|
2130
|
+
return typeof saved.path === "string" && saved.path ? saved.path : null;
|
|
2131
|
+
} catch {
|
|
2132
|
+
return null;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
function installedPath(target, productDir, platform) {
|
|
2136
|
+
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);
|
|
2137
|
+
}
|
|
2138
|
+
function parseLauncherVersion(stdout, bin) {
|
|
2139
|
+
const text = String(stdout).trim();
|
|
2140
|
+
if (!text.startsWith(`${bin} `)) return null;
|
|
2141
|
+
return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
|
|
2142
|
+
}
|
|
2143
|
+
function runVersion(path, bin) {
|
|
2144
|
+
return new Promise((resolve3) => {
|
|
2145
|
+
(0, import_node_child_process4.execFile)(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
2146
|
+
resolve3(error ? null : parseLauncherVersion(stdout, bin));
|
|
2147
|
+
});
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
function removeStale(file) {
|
|
2151
|
+
const prefix = `${(0, import_node_path6.basename)(file)}.old-`;
|
|
2152
|
+
let names;
|
|
2153
|
+
try {
|
|
2154
|
+
names = (0, import_node_fs11.readdirSync)((0, import_node_path6.dirname)(file));
|
|
2155
|
+
} catch {
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
for (const name of names) {
|
|
2159
|
+
if (!name.startsWith(prefix)) continue;
|
|
2160
|
+
try {
|
|
2161
|
+
(0, import_node_fs11.rmSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(file), name), { force: true });
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
async function refreshLauncher(target, options = {}) {
|
|
2167
|
+
const started = Date.now();
|
|
2168
|
+
const platform = options.platform ?? process.platform;
|
|
2169
|
+
const key = platformKey(platform, options.arch ?? process.arch);
|
|
2170
|
+
if (!key) return null;
|
|
2171
|
+
const productDir = options.productDir ?? defaultProductDir(target.product);
|
|
2172
|
+
const recorded = recordedPath(productDir);
|
|
2173
|
+
const file = options.path ?? (recorded && (0, import_node_fs11.existsSync)(recorded) ? recorded : installedPath(target, productDir, platform));
|
|
2174
|
+
if (!(0, import_node_fs11.existsSync)(file) || (0, import_node_fs11.lstatSync)(file).isSymbolicLink()) return null;
|
|
2175
|
+
if (!options.dryRun) removeStale(file);
|
|
2176
|
+
const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
|
|
2177
|
+
if (!from) return null;
|
|
2178
|
+
const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
|
|
2179
|
+
const record = await fetchRecord(target, options);
|
|
2180
|
+
if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
|
|
2181
|
+
const entry2 = record?.files.find((candidate) => candidate.path === key);
|
|
2182
|
+
if (!record || !entry2) return arm("skip", from, "No launcher update is offered yet.");
|
|
2183
|
+
if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
|
|
2184
|
+
if (options.dryRun) return arm("ok", record.version, "");
|
|
2185
|
+
let unlock;
|
|
2186
|
+
const lockHeld = options.lockHeld ?? process.env.MM_OUTER_CONSOLE === "1";
|
|
2187
|
+
if (!lockHeld) {
|
|
2188
|
+
try {
|
|
2189
|
+
unlock = lockInstallation(productDir);
|
|
2190
|
+
} catch {
|
|
2191
|
+
return arm("defer", from, "Another install is running; the launcher updates next time.");
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
const next = `${file}.new-${process.pid}`;
|
|
2195
|
+
let moved;
|
|
2196
|
+
try {
|
|
2197
|
+
let bytes;
|
|
2198
|
+
try {
|
|
2199
|
+
const response = await (options.fetch ?? fetch)(
|
|
2200
|
+
`${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
|
|
2201
|
+
{ signal: AbortSignal.timeout(3e5) }
|
|
2202
|
+
);
|
|
2203
|
+
if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2204
|
+
bytes = await readDownload(response, void 0, entry2.size);
|
|
2205
|
+
} catch {
|
|
2206
|
+
return arm("defer", from, "The launcher download did not complete; it retries next time.");
|
|
2207
|
+
}
|
|
2208
|
+
if (!verifyFileBytes(entry2, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
|
|
2209
|
+
try {
|
|
2210
|
+
(0, import_node_fs11.writeFileSync)(next, bytes, { mode: 493 });
|
|
2211
|
+
if (platform !== "win32") (0, import_node_fs11.chmodSync)(next, 493);
|
|
2212
|
+
if (platform === "win32") {
|
|
2213
|
+
moved = `${file}.old-${process.pid}-${Date.now()}`;
|
|
2214
|
+
(0, import_node_fs11.renameSync)(file, moved);
|
|
2215
|
+
}
|
|
2216
|
+
(0, import_node_fs11.renameSync)(next, file);
|
|
2217
|
+
} catch {
|
|
2218
|
+
try {
|
|
2219
|
+
if (moved && !(0, import_node_fs11.existsSync)(file)) (0, import_node_fs11.renameSync)(moved, file);
|
|
2220
|
+
} catch {
|
|
2221
|
+
}
|
|
2222
|
+
(0, import_node_fs11.rmSync)(next, { force: true });
|
|
2223
|
+
return arm("fail", from, "The launcher could not be replaced.");
|
|
2224
|
+
}
|
|
2225
|
+
return arm("ok", record.version, "");
|
|
2226
|
+
} finally {
|
|
2227
|
+
unlock?.();
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2182
2231
|
// src/index.ts
|
|
2183
|
-
var LAUNCHER_VERSION = true ? "1.2.
|
|
2232
|
+
var LAUNCHER_VERSION = true ? "1.2.5" : readVersionFromPackage();
|
|
2184
2233
|
function defaultPrint(message) {
|
|
2185
2234
|
process.stdout.write(`${message}
|
|
2186
2235
|
`);
|
|
@@ -2403,41 +2452,6 @@ async function runFile(file, args, printErr) {
|
|
|
2403
2452
|
}
|
|
2404
2453
|
return code;
|
|
2405
2454
|
}
|
|
2406
|
-
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2407
|
-
const stored = readTokens(dir);
|
|
2408
|
-
if (!stored) return null;
|
|
2409
|
-
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2410
|
-
if (!stored.refreshToken) return null;
|
|
2411
|
-
if (config.loginKind === "github") {
|
|
2412
|
-
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2413
|
-
if (!refreshed2) {
|
|
2414
|
-
clearTokens(dir);
|
|
2415
|
-
return null;
|
|
2416
|
-
}
|
|
2417
|
-
writeTokens(dir, {
|
|
2418
|
-
accessToken: refreshed2.accessToken,
|
|
2419
|
-
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
2420
|
-
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
2421
|
-
});
|
|
2422
|
-
return refreshed2.accessToken;
|
|
2423
|
-
}
|
|
2424
|
-
if (!stored.clientId) {
|
|
2425
|
-
clearTokens(dir);
|
|
2426
|
-
return null;
|
|
2427
|
-
}
|
|
2428
|
-
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2429
|
-
if (!refreshed) {
|
|
2430
|
-
clearTokens(dir);
|
|
2431
|
-
return null;
|
|
2432
|
-
}
|
|
2433
|
-
writeTokens(dir, {
|
|
2434
|
-
accessToken: refreshed.accessToken,
|
|
2435
|
-
refreshToken: refreshed.refreshToken,
|
|
2436
|
-
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
2437
|
-
clientId: stored.clientId
|
|
2438
|
-
});
|
|
2439
|
-
return refreshed.accessToken;
|
|
2440
|
-
}
|
|
2441
2455
|
async function doLogin(config, dir, options, prompt, installer) {
|
|
2442
2456
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2443
2457
|
const open = options.open ?? openBrowser;
|
|
@@ -3086,7 +3100,6 @@ if (invokedAsMain) {
|
|
|
3086
3100
|
0 && (module.exports = {
|
|
3087
3101
|
LAUNCHER_VERSION,
|
|
3088
3102
|
defaultRunEntry,
|
|
3089
|
-
ensureFreshToken,
|
|
3090
3103
|
formatMb,
|
|
3091
3104
|
parseArgv,
|
|
3092
3105
|
readPayloadEntry,
|
package/package.json
CHANGED