mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-29 11:11:43 -04:00
* fix: prevent fork-bomb during packageManager-driven version switching When pnpm was installed via one method (e.g. `npm install -g pnpm@A`) and run in a project whose package.json's packageManager field selected a different pnpm version (pnpm@B), and a pnpm-workspace.yaml existed at the project root, the install-child spawned by `installPnpmToTools` to fetch pnpm@B inherited a cwd under the pnpm home directory. pnpm's workspace walk-up from there discovered the ancestor pnpm-workspace.yaml, adopted the root package.json, and re-triggered switchCliVersion inside the child. Because the target tool dir had not yet been symlinked in, the recursive installPnpmToTools call saw alreadyExisted === false and kicked off another nested install, recursing forever at 100% CPU. Force the install-child's environment to disable its own version handling: - `npm_config_manage_package_manager_versions=false` (v10 setting name) - `pnpm_config_pm_on_fail=ignore` (v11+ setting name) Also set the v11 setting on the final spawn at the end of switchCliVersion, so when v10 hands off to a v11 target the child's check/download paths stay disabled regardless of which env-var convention the child reads. Closes #11337. * test: add v11-switch and same-version regression tests for #11337 - v11 switch with a root pnpm-workspace.yaml: covers the primary #11337 reproducer (target major differs from running major). Before the fix this fork-bombed via the install-child's workspace walk-up; now it reaches the terminal spawn and `installPnpmToTools` completes. - Same-version short-circuit: with a root pnpm-workspace.yaml and `packageManager: pnpm@<current>`, `switchCliVersion` must return at the `pm.version === packageManager.version` guard, and the tool dir must not be created. Guards against a future regression where the ancestor pnpm-workspace.yaml alone accidentally triggers an install. * fix(installPnpmToTools): isolate the install-child from the caller's workspace Pass `--ignore-workspace` to the child pnpm so it doesn't walk up from the stage directory and adopt the caller's pnpm-workspace.yaml as its own root. That walk-up was both (a) the mechanism that caused the #11337 fork-bomb (the child would rediscover the caller's packageManager field and re-enter switchCliVersion) and (b) a correctness problem in its own right: once the child treats the caller's project as its workspace, `pnpm add` runs with semantics that don't match an isolated tool-dir install. The env-var guards from the previous commit stay in place as a defense-in-depth measure in case any future code path surfaces a wantedPackageManager without going through workspace discovery. Also fold the new v11-switch regression into the existing v11 test rather than adding a second v11 install, so CI doesn't fetch pnpm@11.0.0-rc.5 from the real npmjs registry twice. The tool-dir assertion in that test now doubles as a fork-bomb regression check for the v11-target path.
22 lines
651 B
TypeScript
22 lines
651 B
TypeScript
import path from 'path'
|
|
import { sync as execSync } from 'execa'
|
|
|
|
export function runPnpmCli (
|
|
command: string[],
|
|
{ cwd, env }: { cwd: string, env?: NodeJS.ProcessEnv }
|
|
): void {
|
|
const execOpts = {
|
|
cwd,
|
|
stdio: 'inherit' as const,
|
|
...(env ? { env } : {}),
|
|
}
|
|
const execFileName = path.basename(process.execPath).toLowerCase()
|
|
if (execFileName === 'pnpm' || execFileName === 'pnpm.exe') {
|
|
execSync(process.execPath, command, execOpts)
|
|
} else if (path.basename(process.argv[1]) === 'pnpm.cjs') {
|
|
execSync(process.execPath, [process.argv[1], ...command], execOpts)
|
|
} else {
|
|
execSync('pnpm', command, execOpts)
|
|
}
|
|
}
|