`resolvePnpmSelfCommand` decided whether `process.argv[1]` was pnpm from its basename and the manifest above it, rejecting a script only when another package claimed it as a bin. A script that no package claims, a project's own `scripts/pnpm.mjs` among them, still passed. pnpm's own code knows where it lives. The entry script is now accepted only when its real path is the running module itself, as when a bundle is run directly or copied elsewhere, or lies in the same package as that module, as with `bin/pnpm.mjs` loading `dist/pnpm.mjs`. The nearest `package.json` marks the package; the published `pnpm` and `@pnpm/exe` tarballs carry only the one at their root. The basename set stays to keep out hosts such as Jest, and a `pnpx` entry maps to the `pnpm.mjs` beside it, since `pnpx` prepends `dlx` to `process.argv`. An entry that cannot be resolved now falls back to the pnpm on PATH, which is safe because the bundle-copy case no longer depends on failing open. The resolver moves to `@pnpm/cli.meta`, next to `detectIfCurrentPkgIsExecutable`, so `npm_execpath` can share it without `@pnpm/exec.npm-lifecycle` depending on the CLI runner. `npm_execpath` previously took `process.argv[1]` unchecked, so a script run under `pnpx` got `pnpx.mjs` and `$npm_execpath install` became `dlx install`. It now gets the running pnpm's entry or executable, or the pnpm on pnpm's own PATH when there is none, matching the runner's fallback. The `process.pkg` branch it replaces was dead since `@pnpm/exe` moved to a Node.js SEA. The lookup is cached for the process, since lifecycle scripts ask once per script and neither `process.argv[1]` nor the module moves. On Windows, `@pnpm/exe` installs `pnpx.exe` and `pnx.exe` as hardlinks of the pnpm binary, which runs as `pnpm dlx` when launched through either name. Re-invoking the current executable under one of them prepended `dlx` again. Both versions now re-invoke the `pnpm` executable beside the alias: v11 in `resolvePnpmSelfCommand` and `npm_execpath`, and v12 through `pnpm_executor::current_pnpm_exe()` in `npm_execpath`, `doctor`, the `verifyDepsBeforeRun` install, the pipeline agent and the git-dependency build. The `npm_execpath` fallback is resolved to an absolute path before a script's PATH puts `node_modules/.bin` first, so a dependency's `pnpm` bin cannot stand in for it.
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import { afterEach, expect, jest, test } from '@jest/globals'
|
|
|
|
const execaSync = jest.fn()
|
|
jest.unstable_mockModule('execa', () => ({
|
|
sync: execaSync,
|
|
}))
|
|
|
|
const resolvePnpmSelfCommand = jest.fn<() => string[]>()
|
|
jest.unstable_mockModule('@pnpm/cli.meta', () => ({
|
|
resolvePnpmSelfCommand,
|
|
}))
|
|
|
|
const { runPnpmCli } = await import('@pnpm/exec.pnpm-cli-runner')
|
|
|
|
afterEach(() => {
|
|
execaSync.mockClear()
|
|
resolvePnpmSelfCommand.mockReset()
|
|
})
|
|
|
|
test.each([
|
|
[['/usr/bin/node', '/path/to/pnpm/bin/pnpm.mjs']],
|
|
[['/path/to/pnpm']],
|
|
[['pnpm']],
|
|
])('the command is appended to the self command %j', (selfCommand) => {
|
|
resolvePnpmSelfCommand.mockReturnValue(selfCommand)
|
|
|
|
runPnpmCli(['add', 'express'], { cwd: '/test' })
|
|
|
|
const [executable, ...selfArgs] = selfCommand
|
|
expect(execaSync).toHaveBeenCalledWith(executable, [...selfArgs, 'add', 'express'], {
|
|
cwd: '/test',
|
|
stdio: 'inherit',
|
|
})
|
|
})
|
|
|
|
test('the reporter is passed to the spawned CLI', () => {
|
|
resolvePnpmSelfCommand.mockReturnValue(['/usr/bin/node', '/path/to/pnpm/bin/pnpm.mjs'])
|
|
|
|
runPnpmCli(['install'], { cwd: '/test', reporter: 'silent' })
|
|
|
|
expect(execaSync).toHaveBeenCalledWith('/usr/bin/node', ['/path/to/pnpm/bin/pnpm.mjs', 'install', '--reporter=silent'], {
|
|
cwd: '/test',
|
|
stdio: 'inherit',
|
|
})
|
|
})
|