@mutmutco/hub 4.5.75 → 4.5.77

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.
Files changed (2) hide show
  1. package/dist/index.cjs +47 -7
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1507,15 +1507,54 @@ function recordedPath(productDir) {
1507
1507
  function installedPath(target, productDir, platform) {
1508
1508
  return platform === "win32" ? (0, import_node_path3.join)(productDir, "bin", `${target.bin}.exe`) : (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".local", "bin", target.bin);
1509
1509
  }
1510
+ function onPath(bin, platform, env) {
1511
+ const win = platform === "win32";
1512
+ const read = (name) => env[name] ?? env[Object.keys(env).find((key) => key.toLowerCase() === name.toLowerCase()) ?? ""];
1513
+ const exts = win ? ["", ...(read("PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)] : [""];
1514
+ const found = [];
1515
+ for (const dir of (read("PATH") ?? "").split(win ? ";" : ":").filter(Boolean)) {
1516
+ for (const ext of exts) {
1517
+ const candidate = (0, import_node_path3.join)(dir.replace(/^"|"$/gu, ""), `${bin}${ext}`);
1518
+ try {
1519
+ if (!(0, import_node_fs6.statSync)(candidate).isFile()) continue;
1520
+ if (!win) (0, import_node_fs6.accessSync)(candidate, import_node_fs6.constants.X_OK);
1521
+ found.push(candidate);
1522
+ } catch {
1523
+ }
1524
+ }
1525
+ }
1526
+ return found;
1527
+ }
1528
+ function findPathShadow(bin, file, options = {}) {
1529
+ const platform = options.platform ?? process.platform;
1530
+ const same = (a, b) => {
1531
+ const real = (path) => {
1532
+ try {
1533
+ return (0, import_node_fs6.realpathSync)(path);
1534
+ } catch {
1535
+ return (0, import_node_path3.resolve)(path);
1536
+ }
1537
+ };
1538
+ const [x, y] = [real(a), real(b)];
1539
+ return platform === "win32" ? x.toLowerCase() === y.toLowerCase() : x === y;
1540
+ };
1541
+ const hits = onPath(bin, platform, options.env ?? process.env);
1542
+ const other = hits.find((hit) => !same(hit, file));
1543
+ if (!other) return null;
1544
+ if (hits.findIndex((hit) => same(hit, file)) === 0) {
1545
+ return { state: "shadowing", other, line: `Warning: ${file} shadows another ${bin} on PATH: ${other}.` };
1546
+ }
1547
+ return { state: "shadowed", other, line: `Warning: ${file} is shadowed by another ${bin} earlier on PATH: ${other}.` };
1548
+ }
1510
1549
  function parseLauncherVersion(stdout, bin) {
1511
1550
  const text = String(stdout).trim();
1512
1551
  if (!text.startsWith(`${bin} `)) return null;
1513
1552
  return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
1514
1553
  }
1515
1554
  function runVersion(path, bin) {
1516
- return new Promise((resolve2) => {
1555
+ return new Promise((resolve3) => {
1517
1556
  (0, import_node_child_process.execFile)(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
1518
- resolve2(error ? null : parseLauncherVersion(stdout, bin));
1557
+ resolve3(error ? null : parseLauncherVersion(stdout, bin));
1519
1558
  });
1520
1559
  });
1521
1560
  }
@@ -1547,7 +1586,8 @@ async function refreshLauncher(target, options = {}) {
1547
1586
  if (!options.dryRun) removeStale(file);
1548
1587
  const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
1549
1588
  if (!from) return null;
1550
- const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
1589
+ const shadow = findPathShadow(target.bin, file, { platform, ...options.env ? { env: options.env } : {} });
1590
+ const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail: [detail, shadow?.line].filter(Boolean).join(" "), durationMs: Date.now() - started });
1551
1591
  const record = await fetchRecord(target, options);
1552
1592
  if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
1553
1593
  const entry = record?.files.find((candidate) => candidate.path === key);
@@ -1824,14 +1864,14 @@ function runNpm(args, env = process.env, options = {}) {
1824
1864
  function runNpmAsync(args, env = process.env, options = {}) {
1825
1865
  const npm = resolveNpm(env);
1826
1866
  const [file, argv, shell] = npm.kind === "node-script" ? [process.execPath, [npm.script, ...args], false] : [npm.command, args, process.platform === "win32"];
1827
- return new Promise((resolve2) => {
1867
+ return new Promise((resolve3) => {
1828
1868
  let stdout = "";
1829
1869
  let stderr = "";
1830
1870
  let child;
1831
1871
  try {
1832
1872
  child = (0, import_node_child_process2.spawn)(file, argv, { env, cwd: options.cwd, windowsHide: true, shell });
1833
1873
  } catch (error) {
1834
- resolve2({ status: null, stdout, stderr: String(error?.message ?? error) });
1874
+ resolve3({ status: null, stdout, stderr: String(error?.message ?? error) });
1835
1875
  return;
1836
1876
  }
1837
1877
  child.stdout?.setEncoding("utf8");
@@ -1842,8 +1882,8 @@ function runNpmAsync(args, env = process.env, options = {}) {
1842
1882
  child.stderr?.on("data", (chunk) => {
1843
1883
  stderr += chunk;
1844
1884
  });
1845
- child.on("error", (error) => resolve2({ status: null, stdout, stderr: String(error?.message ?? error) }));
1846
- child.on("close", (status) => resolve2({ status, stdout, stderr }));
1885
+ child.on("error", (error) => resolve3({ status: null, stdout, stderr: String(error?.message ?? error) }));
1886
+ child.on("close", (status) => resolve3({ status, stdout, stderr }));
1847
1887
  });
1848
1888
  }
1849
1889
  var globalRootMemo = /* @__PURE__ */ new WeakMap();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.5.75",
3
+ "version": "4.5.77",
4
4
  "description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",