@mutmutco/installer-launcher 1.2.3 → 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 CHANGED
@@ -1729,162 +1729,6 @@ function firstLine(text) {
1729
1729
  return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
1730
1730
  }
1731
1731
 
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
1732
  // src/login-github.ts
1889
1733
  import { spawn, spawnSync as spawnSync3 } from "node:child_process";
1890
1734
  var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
@@ -2145,8 +1989,201 @@ function page(title, body) {
2145
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>`;
2146
1990
  }
2147
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
+
2148
2185
  // src/index.ts
2149
- var LAUNCHER_VERSION = true ? "1.2.3" : readVersionFromPackage();
2186
+ var LAUNCHER_VERSION = true ? "1.2.4" : readVersionFromPackage();
2150
2187
  function defaultPrint(message) {
2151
2188
  process.stdout.write(`${message}
2152
2189
  `);
@@ -2369,41 +2406,6 @@ async function runFile(file, args, printErr) {
2369
2406
  }
2370
2407
  return code;
2371
2408
  }
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
2409
  async function doLogin(config, dir, options, prompt, installer) {
2408
2410
  const fetchImpl = options.fetchImpl ?? fetch;
2409
2411
  const open = options.open ?? openBrowser;
@@ -3051,7 +3053,6 @@ if (invokedAsMain) {
3051
3053
  export {
3052
3054
  LAUNCHER_VERSION,
3053
3055
  defaultRunEntry,
3054
- ensureFreshToken,
3055
3056
  formatMb,
3056
3057
  parseArgv,
3057
3058
  readPayloadEntry,
@@ -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,
@@ -1763,171 +1762,15 @@ function firstLine(text) {
1763
1762
  return String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
1764
1763
  }
1765
1764
 
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
1765
  // src/login-github.ts
1923
- var import_node_child_process4 = require("node:child_process");
1766
+ var import_node_child_process3 = require("node:child_process");
1924
1767
  var realSleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
1925
1768
  function openBrowser(url) {
1926
1769
  if (process.env.LAUNCHER_NO_OPEN === "1") return;
1927
1770
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
1928
1771
  const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
1929
1772
  try {
1930
- const child = (0, import_node_child_process4.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
1773
+ const child = (0, import_node_child_process3.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
1931
1774
  child.on("error", () => {
1932
1775
  });
1933
1776
  child.unref();
@@ -2045,7 +1888,7 @@ async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
2045
1888
  }
2046
1889
  function readGhToken() {
2047
1890
  try {
2048
- const result = (0, import_node_child_process4.spawnSync)("gh", ["auth", "token", "--hostname", "github.com"], { encoding: "utf8", timeout: 1e4, windowsHide: true });
1891
+ const result = (0, import_node_child_process3.spawnSync)("gh", ["auth", "token", "--hostname", "github.com"], { encoding: "utf8", timeout: 1e4, windowsHide: true });
2049
1892
  const token = result.status === 0 ? String(result.stdout).trim() : "";
2050
1893
  return token && !/\s/u.test(token) ? token : null;
2051
1894
  } catch {
@@ -2179,8 +2022,201 @@ function page(title, body) {
2179
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>`;
2180
2023
  }
2181
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
+
2182
2218
  // src/index.ts
2183
- var LAUNCHER_VERSION = true ? "1.2.3" : readVersionFromPackage();
2219
+ var LAUNCHER_VERSION = true ? "1.2.4" : readVersionFromPackage();
2184
2220
  function defaultPrint(message) {
2185
2221
  process.stdout.write(`${message}
2186
2222
  `);
@@ -2403,41 +2439,6 @@ async function runFile(file, args, printErr) {
2403
2439
  }
2404
2440
  return code;
2405
2441
  }
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
2442
  async function doLogin(config, dir, options, prompt, installer) {
2442
2443
  const fetchImpl = options.fetchImpl ?? fetch;
2443
2444
  const open = options.open ?? openBrowser;
@@ -3086,7 +3087,6 @@ if (invokedAsMain) {
3086
3087
  0 && (module.exports = {
3087
3088
  LAUNCHER_VERSION,
3088
3089
  defaultRunEntry,
3089
- ensureFreshToken,
3090
3090
  formatMb,
3091
3091
  parseArgv,
3092
3092
  readPayloadEntry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/installer-launcher",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Single-executable (SEA) launcher: sign-in, gated payload fetch, self-update. One copy ships per product.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",