"use strict"; /** * Binary discovery + child environment for GUI-context launches. * * From Finder/launchd an app inherits PATH=/usr/bin:/bin:/usr/sbin:/sbin — the * `#!/usr/bin/env node` shebangs of dsh and npm cannot resolve there. All * spawned children therefore run through childEnv(), which prepends the * directories that actually hold dsh, npm, and node: both their symlink spots * (~/.local/bin) and their realpath homes (e.g. a version manager under * ~/.hermes/node/bin). */ const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); function firstExisting(candidates) { for (const candidate of candidates) { try { if (candidate && fs.existsSync(candidate)) return candidate; } catch { /* next */ } } return null; } function pathDirs() { return (process.env.PATH || "").split(":").filter(Boolean); } /** The dsh binary: explicit override, common user install spots, then PATH. */ function findDsh() { const override = process.env.DSH_DESKTOP_DSH_BIN; if (override) return override; const home = os.homedir(); return firstExisting([ path.join(home, ".local/bin/dsh"), "/opt/homebrew/bin/dsh", "/usr/local/bin/dsh", ...pathDirs().map((dir) => path.join(dir, "dsh")), ]) || "dsh"; } /** The npm binary: the same discovery strategy. */ function findNpm() { const home = os.homedir(); return firstExisting([ path.join(home, ".local/bin/npm"), "/opt/homebrew/bin/npm", "/usr/local/bin/npm", ...pathDirs().map((dir) => path.join(dir, "npm")), ]) || "npm"; } const DSH_BIN = findDsh(); const NPM_BIN = findNpm(); /** * Re-resolve the dsh binary right before every spawn. `npm install -g` * replaces the bin symlink during upgrades; a path captured at shell startup * (or a bare "dsh" fallback resolved while the link was missing) would then * hit ENOENT — exactly the upgrade-race failure seen in the wild. */ function resolveDsh() { return findDsh(); } /** Environment (PATH augmented) for every child process we spawn. */ function childEnv() { const dirs = []; for (const bin of [DSH_BIN, NPM_BIN]) { if (path.isAbsolute(bin)) dirs.push(path.dirname(bin)); try { dirs.push(path.dirname(fs.realpathSync(bin))); } catch { /* not a file */ } } dirs.push( path.join(os.homedir(), ".local/bin"), "/opt/homebrew/bin", "/usr/local/bin", ); const unique = [...new Set(dirs.filter(Boolean))]; return { ...process.env, PATH: `${unique.join(":")}:${process.env.PATH || ""}` }; } module.exports = { DSH_BIN, NPM_BIN, findDsh, findNpm, resolveDsh, childEnv };