* fix(#1619): normalize pruned mise node execPath to the stable shim resolveNodeRunner() bakes process.execPath into managed .js hook commands. Node realpaths execPath, so under mise it resolves to a concrete <data>/installs/node/<ver>/bin/node that mise prunes on `mise up`, after which every managed hook 404s — the same ephemeral-path failure #977 fixed for fnm and #3181 for Homebrew. normalizeNodePath now rewrites a mise versioned install path to the stable sibling shim <data>/shims/node when it exists (deriving <data> from execPath so a custom MISE_DATA_DIR works), falling back to the raw execPath otherwise. Tests folded into install.test.cjs per the regression test-name lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(changeset): set pr number to 1621 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Joe Seymour <joese@iarx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
5
.changeset/1619-mise-shim-node-path.md
Normal file
5
.changeset/1619-mise-shim-node-path.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1621
|
||||
---
|
||||
**`normalizeNodePath` now maps pruned mise node paths to the stable shim (#1619)** — `resolveNodeRunner()` bakes `process.execPath` into managed `.js` hook commands. Node realpaths execPath, so under mise it resolves to `<data>/installs/node/<ver>/bin/node` — a concrete version mise prunes on `mise up`, after which every managed hook fails to spawn (`No such file or directory` on every SessionStart and tool event), the same ephemeral-path failure #977 fixed for fnm and #3181 for Homebrew. `normalizeNodePath` now rewrites a mise versioned install path to the stable sibling shim `<data>/shims/node` (`.exe` preserved on Windows) when that shim exists, deriving `<data>` from execPath so a custom `MISE_DATA_DIR` works, and falling back to the raw execPath unchanged otherwise. (#1619)
|
||||
@@ -253,6 +253,23 @@ function normalizeNodePath(execPath: string, opts?: NodeNormOpts): string {
|
||||
if (/^\/opt\/homebrew\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/.test(execPath)) {
|
||||
return '/opt/homebrew/bin/node';
|
||||
}
|
||||
|
||||
// mise pins a concrete node version at <data>/installs/node/<ver>/bin/node
|
||||
// (Windows: <data>/installs/node/<ver>/node.exe). Node realpaths
|
||||
// process.execPath to that versioned path, and `mise up` prunes old versions,
|
||||
// so a baked hook command 404s after any node bump — the same ephemeral-path
|
||||
// failure #977 fixed for fnm. The stable alias is the sibling shim
|
||||
// (<data>/shims/node), which always resolves to the active version, like the
|
||||
// Homebrew symlink survives `brew upgrade node`. Derive <data> from execPath
|
||||
// so a custom MISE_DATA_DIR layout still works, and only rewrite when the shim
|
||||
// exists — otherwise fall back to the raw execPath unchanged.
|
||||
const miseMatch = normalizedForMatch.match(
|
||||
/^(.*)\/installs\/node\/[^/]+\/(?:bin\/)?node(\.exe)?$/,
|
||||
);
|
||||
if (miseMatch) {
|
||||
const shim = `${miseMatch[1]}/shims/node${miseMatch[2] || ''}`;
|
||||
if (existsSync(shim)) return shim;
|
||||
}
|
||||
return execPath;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const {
|
||||
resolveKiloConfigPath,
|
||||
configureKiloPermissions,
|
||||
selectRuntimesFromArgs,
|
||||
normalizeNodePath,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
@@ -1769,3 +1770,63 @@ describe('#767 Parity: docs/AGENTS.md "Disallowed Tools" rows match READONLY_AGE
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ─── normalizeNodePath — mise versioned install path → stable shim (#1619) ────
|
||||
//
|
||||
// Bug #1619: `resolveNodeRunner()` bakes process.execPath into managed hook
|
||||
// commands. Node realpaths execPath, so under mise it resolves to
|
||||
// `<data>/installs/node/<ver>/bin/node` — a concrete version mise prunes on
|
||||
// `mise up`, after which every managed hook 404s (same class as #977 fnm /
|
||||
// #3181 Homebrew). normalizeNodePath now rewrites it to the stable sibling
|
||||
// shim `<data>/shims/node` when that shim exists, deriving <data> from the
|
||||
// path so a custom MISE_DATA_DIR works, and falling back to execPath otherwise.
|
||||
// Folded into install.test.cjs (not a new bug-NNNN file) per the regression
|
||||
// test-name lint. Assertions go against the exported function's return values.
|
||||
describe('normalizeNodePath — mise versioned path → sibling shim (#1619)', () => {
|
||||
const MISE_DATA = '/Users/u/.local/share/mise';
|
||||
const MISE_NODE_PINNED = `${MISE_DATA}/installs/node/26.3.0/bin/node`;
|
||||
const MISE_SHIM = `${MISE_DATA}/shims/node`;
|
||||
const MISE_WIN_DATA = 'C:/Users/u/AppData/Local/mise';
|
||||
const MISE_WIN_NODE = `${MISE_WIN_DATA}/installs/node/22.1.0/node.exe`; // no bin/ on Windows
|
||||
const MISE_WIN_SHIM = `${MISE_WIN_DATA}/shims/node.exe`;
|
||||
const MISE_CUSTOM_DATA = '/opt/mise-data';
|
||||
const MISE_CUSTOM_NODE = `${MISE_CUSTOM_DATA}/installs/node/20.0.0/bin/node`;
|
||||
const MISE_CUSTOM_SHIM = `${MISE_CUSTOM_DATA}/shims/node`;
|
||||
|
||||
test('POSIX pinned install path + shim exists → sibling shim', () => {
|
||||
assert.equal(
|
||||
normalizeNodePath(MISE_NODE_PINNED, { existsSync: p => p === MISE_SHIM }),
|
||||
MISE_SHIM);
|
||||
});
|
||||
|
||||
test('Windows node.exe + shim exists → shims/node.exe (.exe preserved)', () => {
|
||||
assert.equal(
|
||||
normalizeNodePath(MISE_WIN_NODE, { existsSync: p => p === MISE_WIN_SHIM }),
|
||||
MISE_WIN_SHIM);
|
||||
});
|
||||
|
||||
test('backslash Windows path normalizes the same as forward-slash', () => {
|
||||
assert.equal(
|
||||
normalizeNodePath(MISE_WIN_NODE.replace(/\//g, '\\'),
|
||||
{ existsSync: p => p === MISE_WIN_SHIM }),
|
||||
MISE_WIN_SHIM);
|
||||
});
|
||||
|
||||
test('custom MISE_DATA_DIR layout → shim derived from execPath, not env', () => {
|
||||
assert.equal(
|
||||
normalizeNodePath(MISE_CUSTOM_NODE, { existsSync: p => p === MISE_CUSTOM_SHIM }),
|
||||
MISE_CUSTOM_SHIM);
|
||||
});
|
||||
|
||||
test('no regression: shim absent → falls back to raw execPath unchanged', () => {
|
||||
assert.equal(
|
||||
normalizeNodePath(MISE_NODE_PINNED, { existsSync: () => false }),
|
||||
MISE_NODE_PINNED);
|
||||
});
|
||||
|
||||
test('non-mise path (Homebrew symlink) is left unchanged here', () => {
|
||||
assert.equal(
|
||||
normalizeNodePath('/opt/homebrew/bin/node', { existsSync: () => true }),
|
||||
'/opt/homebrew/bin/node');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user