Files
pnpm/pkg-manager/plugin-commands-installation/test/nodeExecPath.ts
Zoltan Kochan 78951f2adb fix: global bin shim invokes pnpm instead of Node when installed via @pnpm/exe (#11335)
* fix(installation): skip pnpm exe when no Node.js is on PATH

When pnpm is installed as @pnpm/exe (a Single Executable Application
that bundles Node.js into the pnpm binary) and the user has no separate
Node.js on PATH, `which('node')` fails and `getNodeExecPath` used to
fall back to `process.execPath` - which in @pnpm/exe is the pnpm binary
itself, not a Node binary. That path got baked into generated global
bin shims via nodeExecPath, so running any globally-installed CLI
invoked pnpm with the target script as its first positional arg,
producing `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` from the current
working directory.

Detect the @pnpm/exe case via `detectIfCurrentPkgIsExecutable` and
return undefined from the fallback so the shim emits a plain
`exec node <target>` instead.

Closes #11291
Refs #4645

* Update pkg-manager/plugin-commands-installation/src/nodeExecPath.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 14:41:12 +02:00

38 lines
1.3 KiB
TypeScript

import { detectIfCurrentPkgIsExecutable } from '@pnpm/cli-meta'
import which from 'which'
import { getNodeExecPath } from '../lib/nodeExecPath.js'
jest.mock('which', () => jest.fn())
jest.mock('@pnpm/cli-meta', () => ({
detectIfCurrentPkgIsExecutable: jest.fn(),
}))
const whichMock = jest.mocked(which)
const detectMock = jest.mocked(detectIfCurrentPkgIsExecutable)
afterEach(() => {
whichMock.mockReset()
detectMock.mockReset()
})
test('returns undefined when node is not on PATH and pnpm is running as @pnpm/exe', async () => {
const enoent: NodeJS.ErrnoException = Object.assign(new Error('not found: node'), { code: 'ENOENT' })
whichMock.mockRejectedValue(enoent)
detectMock.mockReturnValue(true)
await expect(getNodeExecPath()).resolves.toBeUndefined()
})
test('falls back to process.execPath when node is not on PATH and pnpm is running under a real Node.js', async () => {
const enoent: NodeJS.ErrnoException = Object.assign(new Error('not found: node'), { code: 'ENOENT' })
whichMock.mockRejectedValue(enoent)
detectMock.mockReturnValue(false)
const savedNode = process.env.NODE
delete process.env.NODE
try {
await expect(getNodeExecPath()).resolves.toBe(process.execPath)
} finally {
if (savedNode != null) process.env.NODE = savedNode
}
})