feat!: remove deprecated managePackageManagerVersions / packageManagerStrict / packageManagerStrictVersion (#11278)

* feat!: remove managePackageManagerVersions / packageManagerStrict / packageManagerStrictVersion

These three settings existed only to derive the `onFail` behavior for
the legacy `packageManager` field. The `pmOnFail` setting introduced
in #11275 subsumes all three — it directly sets `onFail` for both
`packageManager` and `devEngines.packageManager`.

Legacy `packageManager` now defaults to `onFail: 'download'` when no
override is set. `COREPACK_ENABLE_STRICT` is no longer read (it only
gated `packageManagerStrict`); `pmOnFail` is the replacement.

Also drops pass-through `packageManagerStrict*` option fields from
cli.utils / workspace.projects-reader (they were unused) and the
unused `managePackageManagerVersions` Pick in engine.pm.commands'
`SelfUpdateCommandOptions`.

* fix: use kebab-case setting name in BAD_PM_VERSION hint

Copilot review feedback: user-facing error hints for configuration keys
conventionally use the kebab-case form that matches both the CLI flag
(`--pm-on-fail`) and the `.npmrc` key, consistent with the prior hint
text that referenced `package-manager-strict`. The `pnpm-workspace.yaml`
field (`pmOnFail`) is camelCase but that mapping is documented
elsewhere.

* Revert "fix: use kebab-case setting name in BAD_PM_VERSION hint"

This reverts commit e03c29b17. pnpm-workspace.yaml uses camelCase
(`pmOnFail`) — the primary config location for pnpm 11 — so the
hint keeps the camelCase form. The CLI flag is already shown
alongside.
This commit is contained in:
Zoltan Kochan
2026-04-17 00:57:33 +02:00
committed by GitHub
parent 4ab3d9ba9e
commit cee550a57d
18 changed files with 39 additions and 65 deletions

View File

@@ -0,0 +1,19 @@
---
"@pnpm/cli.utils": major
"@pnpm/config.reader": major
"@pnpm/engine.pm.commands": major
"@pnpm/workspace.projects-reader": major
"pnpm": major
---
**Breaking:** removed the `managePackageManagerVersions`, `packageManagerStrict`, and `packageManagerStrictVersion` settings. They existed only to derive the `onFail` behavior for the legacy `packageManager` field, and the `pmOnFail` setting introduced alongside `pnpm with` subsumes all three — it directly sets the `onFail` behavior of both `packageManager` and `devEngines.packageManager`. The `COREPACK_ENABLE_STRICT` environment variable is no longer honored (it only gated `packageManagerStrict`); use `pmOnFail` instead.
Migration:
| Removed setting | Replace with |
| ------------------------------------- | -------------------- |
| `managePackageManagerVersions: true` | `pmOnFail: download` (default) |
| `managePackageManagerVersions: false` | `pmOnFail: ignore` |
| `packageManagerStrict: false` | `pmOnFail: warn` |
| `packageManagerStrictVersion: true` | `pmOnFail: error` |
| `COREPACK_ENABLE_STRICT=0` | `pmOnFail: warn` |

View File

@@ -84,8 +84,8 @@ for bin in "${VARIANT_BINS[@]}"; do
done
for i in "${!VARIANTS[@]}"; do
# Run --version from BENCH_DIR to avoid pnpm's manage-package-manager-versions
# switching the CLI based on a packageManager field in the current directory.
# Run --version from BENCH_DIR to avoid pnpm's automatic version switching
# based on a packageManager field in the current directory.
echo "${VARIANTS[$i]}: $(cd "$BENCH_DIR" && node "${VARIANT_BINS[$i]}" --version) (${VARIANT_DIRS[$i]})"
done
echo "workdir: $BENCH_DIR"

View File

@@ -13,8 +13,6 @@ export function packageIsInstallable (
libc?: string[]
},
opts: {
packageManagerStrict?: boolean
packageManagerStrictVersion?: boolean
engineStrict?: boolean
nodeVersion?: string
supportedArchitectures?: SupportedArchitectures

View File

@@ -5,8 +5,6 @@ import { packageIsInstallable } from './packageIsInstallable.js'
export interface ReadProjectManifestOpts {
engineStrict?: boolean
packageManagerStrict?: boolean
packageManagerStrictVersion?: boolean
nodeVersion?: string
supportedArchitectures?: SupportedArchitectures
}

View File

@@ -235,12 +235,9 @@ export interface Config extends OptionsFromRootManifest {
dedupeInjectedDeps?: boolean
nodeOptions?: string
pmOnFail?: 'download' | 'error' | 'warn' | 'ignore'
packageManagerStrict?: boolean
packageManagerStrictVersion?: boolean
virtualStoreDirMaxLength: number
peersSuffixMaxLength?: number
strictStorePkgContentCheck: boolean
managePackageManagerVersions: boolean
strictDepBuilds: boolean
syncInjectedDepsAfterScripts?: string[]
initPackageManager: boolean

View File

@@ -95,7 +95,6 @@ export const excludedPnpmKeys = [
'lockfile-dir',
'lockfile-include-tarball-url',
'lockfile-only',
'manage-package-manager-versions',
'modules-dir',
'node-linker',
'offline',
@@ -104,8 +103,6 @@ export const excludedPnpmKeys = [
'patches-dir',
'pnpmfile',
'pm-on-fail',
'package-manager-strict',
'package-manager-strict-version',
'prefer-workspace-packages',
'preserve-absolute-paths',
'production',

View File

@@ -170,15 +170,12 @@ export async function getConfig (opts: {
'inject-workspace-packages': false,
'link-workspace-packages': false,
'lockfile-include-tarball-url': false,
'manage-package-manager-versions': true,
'minimum-release-age': 24 * 60, // 1 day
'modules-cache-max-age': 7 * 24 * 60, // 7 days
'dlx-cache-max-age': 24 * 60, // 1 day
'node-linker': 'isolated',
'package-lock': npmDefaults['package-lock'],
pending: false,
'package-manager-strict': process.env.COREPACK_ENABLE_STRICT !== '0',
'package-manager-strict-version': false,
'prefer-workspace-packages': false,
'public-hoist-pattern': [],
'recursive-install': true,
@@ -619,22 +616,14 @@ export async function getConfig (opts: {
// The `pmOnFail` config setting overrides whatever onFail the
// wantedPackageManager carried, so users (and internal callers) can force
// a specific behavior without editing the manifest.
// Otherwise, for the legacy packageManager field, derive onFail from config
// settings. devEngines.packageManager already has onFail set during parsing.
// a specific behavior without editing the manifest. Otherwise, the legacy
// `packageManager` field defaults to `download` — `devEngines.packageManager`
// already has onFail set during parsing.
if (pnpmConfig.wantedPackageManager) {
if (pnpmConfig.pmOnFail) {
pnpmConfig.wantedPackageManager.onFail = pnpmConfig.pmOnFail
} else if (pnpmConfig.wantedPackageManager.onFail == null) {
if (pnpmConfig.packageManagerStrict === false) {
pnpmConfig.wantedPackageManager.onFail = 'warn'
} else if (pnpmConfig.managePackageManagerVersions) {
pnpmConfig.wantedPackageManager.onFail = 'download'
} else if (pnpmConfig.packageManagerStrictVersion) {
pnpmConfig.wantedPackageManager.onFail = 'error'
} else {
pnpmConfig.wantedPackageManager.onFail = 'ignore'
}
pnpmConfig.wantedPackageManager.onFail = 'download'
}
}

View File

@@ -65,7 +65,6 @@ export const pnpmTypes = {
'lockfile-include-tarball-url': Boolean,
'lockfile-only': Boolean,
loglevel: ['silent', 'error', 'warn', 'info', 'debug'],
'manage-package-manager-versions': Boolean,
maxsockets: Number,
'modules-cache-max-age': Number,
'dlx-cache-max-age': Number,
@@ -85,8 +84,6 @@ export const pnpmTypes = {
'patches-dir': String,
pnpmfile: String,
'pm-on-fail': ['download', 'error', 'warn', 'ignore'],
'package-manager-strict': Boolean,
'package-manager-strict-version': Boolean,
'prefer-frozen-lockfile': Boolean,
'prefer-offline': Boolean,
'prefer-symlinked-executables': Boolean,

View File

@@ -49,7 +49,6 @@ export function help (): string {
export type SelfUpdateCommandOptions = CreateStoreControllerOptions & Pick<Config,
| 'globalPkgDir'
| 'lockfileDir'
| 'managePackageManagerVersions'
| 'modulesDir'
| 'pnpmHomeDir'
> & Pick<ConfigContext,

View File

@@ -65,7 +65,6 @@ function prepareOptions (dir: string) {
cacheDir: path.join(dir, '.cache'),
virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120,
dir,
managePackageManagerVersions: false,
}
}
@@ -226,7 +225,6 @@ test('should update packageManager field when a newer pnpm version is available'
const output = await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '8.0.0',
@@ -249,7 +247,6 @@ test('should not update packageManager field when current version matches latest
const output = await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '9.0.0',
@@ -274,7 +271,6 @@ test('should update devEngines.packageManager version when a newer pnpm version
const output = await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '8.0.0',
@@ -304,7 +300,6 @@ test('should update pnpm entry in devEngines.packageManager array', async () =>
const output = await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '8.0.0',
@@ -332,7 +327,6 @@ test('should not modify devEngines.packageManager range when resolved version st
const output = await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '>=8.0.0',
@@ -362,7 +356,6 @@ test('should fall back to ^version when complex range cannot accommodate the new
await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '>=8.0.0 <9.0.0',
@@ -387,7 +380,6 @@ test('should update devEngines.packageManager range when resolved version no lon
const output = await selfUpdate.handler({
...opts,
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '^8',
@@ -480,7 +472,6 @@ test('self-update updates the packageManager field in package.json', async () =>
})
const opts = {
...prepareOptions(process.cwd()),
managePackageManagerVersions: true,
wantedPackageManager: {
name: 'pnpm',
version: '9.0.0',

View File

@@ -337,8 +337,6 @@ gitChecks: false
hoistPattern:
- jest-runner
managePackageManagerVersions: true
minimumReleaseAge: 1440 # At least a day
minimumReleaseAgeExclude:

View File

@@ -95,16 +95,14 @@ const pnpmPackageJson = JSON.parse(fs.readFileSync(pathLib.join(import.meta.dirn
const { status } = childProcess.spawnSync(nodeBin, [
'--enable-source-maps',
pathLib.resolve(import.meta.dirname, 'dist/pnpm.mjs'),
'--config.manage-package-manager-versions=false',
'--pm-on-fail=ignore',
...process.argv.slice(2),
], {
stdio: 'inherit',
env: {
...process.env,
// During local development we don't want to switch to another version of pnpm
// NOTE: Disabling through env variable stopped working for some reasone!
// We need to check why. We set it through CLI argument for now.
npm_config_manage_package_manager_versions: false,
// During local development we don't want to switch to another version of pnpm.
pnpm_config_pm_on_fail: 'ignore',
},
})
process.exit(status)

View File

@@ -403,7 +403,7 @@ function checkPackageManager (pm: EngineDependency): void {
const msg = `This project is configured to use ${pm.version} of pnpm. Your current pnpm is v${currentPnpmVersion}`
if (shouldError) {
throw new PnpmError('BAD_PM_VERSION', msg, {
hint: 'If you want to bypass this version check, you can set the "package-manager-strict" configuration to "false" or set the "COREPACK_ENABLE_STRICT" environment variable to "0". If using "devEngines.packageManager", you can set its "onFail" to "warn" or "ignore"',
hint: 'If you want to bypass this version check, you can set the "pmOnFail" configuration to "warn" or "ignore" (e.g. via --pm-on-fail=ignore). If using "devEngines.packageManager", you can set its "onFail" to "warn" or "ignore"',
})
} else {
globalWarn(msg)

View File

@@ -93,7 +93,6 @@ test('global install warns when project has packageManager configured', async ()
'add',
'--global',
'is-positive',
'--config.package-manager-strict=true',
], { env })
expect(status).toBe(0)

View File

@@ -37,8 +37,8 @@ test('version switch reuses pnpm previously installed by self-update', async ()
XDG_DATA_HOME: path.resolve('data'),
}
// self-update without managePackageManagerVersions installs pnpm 10.0.0
// globally (with GVS enabled), populating the global virtual store
// self-update installs pnpm 10.0.0 globally (with GVS enabled),
// populating the global virtual store
await execPnpm(['self-update', '10.0.0'], { env })
// Write packageManager field so the version switch triggers.

View File

@@ -27,9 +27,9 @@ test('install should not fail if the used pnpm version does not satisfy the pnpm
packageManager: 'pnpm@0.0.0',
})
expect(execPnpmSync(['install', '--config.manage-package-manager-versions=false']).status).toBe(0)
expect(execPnpmSync(['install', '--pm-on-fail=ignore']).status).toBe(0)
const { status, stderr } = execPnpmSync(['install', '--config.manage-package-manager-versions=false', '--config.package-manager-strict-version=true'])
const { status, stderr } = execPnpmSync(['install', '--pm-on-fail=error'])
expect(status).toBe(1)
expect(stderr.toString()).toContain('This project is configured to use 0.0.0 of pnpm. Your current pnpm is')
@@ -43,12 +43,12 @@ test('install should fail if the project requires a different package manager',
packageManager: 'yarn@4.0.0',
})
const { status, stderr } = execPnpmSync(['install', '--config.manage-package-manager-versions=true'])
const { status, stderr } = execPnpmSync(['install'])
expect(status).toBe(1)
expect(stderr.toString()).toContain('This project is configured to use yarn')
expect(execPnpmSync(['install', '--config.package-manager-strict=false']).status).toBe(0)
expect(execPnpmSync(['install', '--pm-on-fail=warn']).status).toBe(0)
})
test('install should not fail for packageManager field with hash', async () => {

View File

@@ -21,12 +21,12 @@ test('switch to the pnpm version specified in the packageManager field of packag
expect(stdout.toString()).toContain('Version 9.3.0')
})
test('do not switch to the pnpm version specified in the packageManager field of package.json, if managePackageManagerVersions is set to false', async () => {
test('do not switch to the pnpm version specified in the packageManager field of package.json, if pmOnFail is set to ignore', async () => {
prepare()
const pnpmHome = path.resolve('pnpm')
const env = { PNPM_HOME: pnpmHome }
writeYamlFileSync('pnpm-workspace.yaml', {
managePackageManagerVersions: false,
pmOnFail: 'ignore',
})
writeJsonFileSync('package.json', {
packageManager: 'pnpm@9.3.0',
@@ -180,9 +180,6 @@ test('devEngines.packageManager without onFail=download does not switch version'
prepare()
const pnpmHome = path.resolve('pnpm')
const env = { PNPM_HOME: pnpmHome }
writeYamlFileSync('pnpm-workspace.yaml', {
managePackageManagerVersions: false,
})
writeJsonFileSync('package.json', {
devEngines: {
packageManager: {
@@ -201,7 +198,6 @@ test('devEngines.packageManager without onFail=download does not switch version'
test('throws error if pnpm binary in store is corrupt', () => {
prepare()
const config = ['--config.manage-package-manager-versions=true'] as const
const pnpmHome = path.resolve('pnpm')
const storeDir = path.resolve('store')
const env = { PNPM_HOME: pnpmHome, pnpm_config_store_dir: storeDir }
@@ -212,7 +208,7 @@ test('throws error if pnpm binary in store is corrupt', () => {
})
// Run pnpm once to ensure pnpm is installed to the store.
execPnpmSync([...config, 'help'], { env })
execPnpmSync(['help'], { env })
// Find the pnpm binary in the global virtual store and corrupt it.
const entries = fs.readdirSync(storeDir, { recursive: true }) as string[]
@@ -226,6 +222,6 @@ test('throws error if pnpm binary in store is corrupt', () => {
fs.rmSync(path.join(storeDir, pnpmBinEntry + '.cmd'))
}
const { stderr } = execPnpmSync([...config, 'help'], { env })
const { stderr } = execPnpmSync(['help'], { env })
expect(stderr.toString()).toContain('Failed to switch pnpm to v9.3.0. Looks like pnpm CLI is missing')
})

View File

@@ -18,8 +18,6 @@ export interface FindWorkspaceProjectsOpts {
patterns?: string[]
engineStrict?: boolean
packageManagerStrict?: boolean
packageManagerStrictVersion?: boolean
nodeVersion?: string
sharedWorkspaceLockfile?: boolean
supportedArchitectures?: SupportedArchitectures