From 7d25bc113655efeef573c0ad6174b63b2257dda6 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 20 Apr 2026 01:08:15 +0200 Subject: [PATCH] fix: suppress packageManager/devEngines.packageManager conflict warning when values match exactly (#11307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../dev-engines-package-manager-compatible.md | 6 +++ config/reader/src/index.ts | 5 +- pnpm/test/packageManagerCheck.test.ts | 51 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 .changeset/dev-engines-package-manager-compatible.md diff --git a/.changeset/dev-engines-package-manager-compatible.md b/.changeset/dev-engines-package-manager-compatible.md new file mode 100644 index 0000000000..a1f217a5fe --- /dev/null +++ b/.changeset/dev-engines-package-manager-compatible.md @@ -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). diff --git a/config/reader/src/index.ts b/config/reader/src/index.ts index 2800876c3d..b30e6bfbc9 100644 --- a/config/reader/src/index.ts +++ b/config/reader/src/index.ts @@ -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 } } diff --git a/pnpm/test/packageManagerCheck.test.ts b/pnpm/test/packageManagerCheck.test.ts index 126b9dd98d..a4a1648a0c 100644 --- a/pnpm/test/packageManagerCheck.test.ts +++ b/pnpm/test/packageManagerCheck.test.ts @@ -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: {