@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 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
- `Check health any time: ${identity.doctor}`
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.3" : readVersionFromPackage();
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,
@@ -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
- `Check health any time: ${identity.doctor}`
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 import_node_child_process4 = require("node:child_process");
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, import_node_child_process4.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
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, import_node_child_process4.spawnSync)("gh", ["auth", "token", "--hostname", "github.com"], { encoding: "utf8", timeout: 1e4, windowsHide: true });
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.3" : readVersionFromPackage();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/installer-launcher",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
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",