fix: suppress packageManager/devEngines.packageManager conflict warning when values match exactly (#11307)

- Suppress the `Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored` warning only when both fields specify the exact same package manager name and the exact same version string. Any other divergence (different name, range vs. exact version, prefixed versions like `v1.2.3`, etc.) still warns.
- Lets projects keep both fields during migration (e.g. so v10 installs still auto-switch via `packageManager`, while v11 uses `devEngines.packageManager` and `npm install` still errors) without a noisy warning — as long as the two values are kept in sync.

Closes #11301
This commit is contained in:
Zoltan Kochan
2026-04-20 01:08:15 +02:00
committed by GitHub
parent 53668a42b8
commit 7d25bc1136
3 changed files with 61 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/config.reader": patch
"pnpm": patch
---
Do not print the `Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored` warning when the two fields specify the exact same package manager name and version string. This lets projects keep both fields during the migration from `packageManager` to `devEngines.packageManager` without a noisy warning [#11301](https://github.com/pnpm/pnpm/issues/11301).

View File

@@ -669,7 +669,10 @@ function getWantedPackageManager (manifest: ProjectManifest): { pm?: WantedPacka
pmFromDevEngines.version = undefined
}
if (manifest.packageManager) {
warnings.push('Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored')
const legacyPm = parsePackageManager(manifest.packageManager)
if (legacyPm.name !== pmFromDevEngines.name || legacyPm.version !== pmFromDevEngines.version) {
warnings.push('Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored')
}
}
return { pm: { ...pmFromDevEngines, fromDevEngines: true }, warnings }
}

View File

@@ -247,6 +247,57 @@ test('devEngines.packageManager takes precedence over packageManager field', asy
expect(stderr.toString()).toContain('"packageManager" will be ignored')
})
test('no warning when packageManager and devEngines.packageManager specify the same exact version', async () => {
prepare({
packageManager: 'pnpm@1.2.3',
devEngines: {
packageManager: {
name: 'pnpm',
version: '1.2.3',
onFail: 'ignore',
},
},
})
const { stderr } = execPnpmSync(['install'])
expect(stderr.toString()).not.toContain('Cannot use both')
})
test('warns when packageManager specifies a different package manager from devEngines.packageManager', async () => {
prepare({
packageManager: 'yarn@1.2.3',
devEngines: {
packageManager: {
name: 'pnpm',
version: '1.2.3',
onFail: 'ignore',
},
},
})
const { stderr } = execPnpmSync(['install'])
expect(stderr.toString()).toContain('Cannot use both "packageManager" and "devEngines.packageManager"')
})
test('warns when packageManager version does not match the devEngines.packageManager version string exactly', async () => {
prepare({
packageManager: 'pnpm@1.2.3',
devEngines: {
packageManager: {
name: 'pnpm',
version: '>=1.0.0',
onFail: 'ignore',
},
},
})
const { stderr } = execPnpmSync(['install'])
expect(stderr.toString()).toContain('Cannot use both "packageManager" and "devEngines.packageManager"')
})
test('pmOnFail=ignore via env var bypasses the devEngines.packageManager check', async () => {
prepare({
devEngines: {