Conversation
…ng when compatible Only emit the "Cannot use both packageManager and devEngines.packageManager" warning when the two fields actually conflict: different package manager name, or the legacy packageManager exact version does not satisfy the devEngines.packageManager version range. This lets projects keep both fields during migration without a noisy warning. Closes #11301
There was a problem hiding this comment.
Pull request overview
This PR refines pnpm’s packageManager vs devEngines.packageManager warning behavior so projects can keep both fields during migration without noisy warnings when they’re effectively compatible.
Changes:
- Only emits the “Cannot use both …” warning when the two fields actually conflict (PM name differs, or legacy
packageManagerexact version does not satisfy thedevEngines.packageManagerrange). - Adds test coverage for compatible vs conflicting combinations.
- Adds a changeset for patch releases of
pnpmand@pnpm/config.reader.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
config/reader/src/index.ts |
Adds compatibility/conflict detection to suppress the warning when fields align. |
pnpm/test/packageManagerCheck.test.ts |
Adds new test cases for warning suppression and conflict warnings. |
.changeset/dev-engines-package-manager-compatible.md |
Documents the user-facing behavior change for release notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // If either side lacks a version, there's nothing concrete to conflict on. | ||
| if (pmFromDevEngines.version == null || legacyPm.version == null) return false | ||
| const legacyVersion = semver.valid(legacyPm.version) | ||
| if (legacyVersion == null) return true |
There was a problem hiding this comment.
packageManagerFieldsConflict() treats legacy packageManager versions like pnpm@v1.2.3 as compatible because semver.valid() normalizes them to 1.2.3. However, elsewhere in this same function (getWantedPackageManager when only packageManager is used) non-canonical versions are explicitly rejected (cleanVersion !== pm.version). With both fields present, this change can now suppress the conflict warning even though the legacy packageManager value would be ignored by pnpm v10/v11 auto-switching logic. Consider treating cleanVersion !== legacyPm.version as a conflict (or otherwise preserving the warning) so the warning is only suppressed when packageManager contains a valid exact version string.
| if (legacyVersion == null) return true | |
| if (legacyVersion == null) return true | |
| if (legacyVersion !== legacyPm.version) return true |
There was a problem hiding this comment.
Good catch — fixed in e563e5d. packageManagerFieldsConflict now returns true when the legacy version isn't a canonical exact version, mirroring the dedicated "not a valid exact version" warning from the non-devEngines path.
|
|
||
| expect(stderr.toString()).not.toContain('Cannot use both') | ||
| }) | ||
|
|
There was a problem hiding this comment.
The new warning-suppression behavior isn’t covered for non-canonical legacy versions (e.g. packageManager: "pnpm@v1.2.3") which currently become “compatible” due to semver normalization. Adding a test for this case would prevent regressions and clarify the intended behavior (warn vs suppress) when packageManager is not a strict exact version.
| test('no warning when packageManager uses a non-canonical legacy exact version compatible via semver normalization', async () => { | |
| prepare({ | |
| packageManager: 'pnpm@v1.2.3', | |
| devEngines: { | |
| packageManager: { | |
| name: 'pnpm', | |
| version: '1.2.3', | |
| onFail: 'ignore', | |
| }, | |
| }, | |
| }) | |
| const { stderr } = execPnpmSync(['install']) | |
| expect(stderr.toString()).not.toContain('Cannot use both') | |
| }) |
There was a problem hiding this comment.
Added this test in e563e5d — it asserts the warning still fires for pnpm@v1.2.3 even when the devEngines.packageManager value would normalize to match.
Address Copilot review feedback: the legacy `packageManager` field is only respected for canonical exact versions (e.g. "pnpm@1.2.3"). A value like "pnpm@v1.2.3" is normally rejected by the dedicated "not a valid exact version" warning. When both fields are present, we should not silently suppress that signal by normalizing the value via semver.valid — keep the warning so the malformed legacy value still surfaces.
…er fields Simplify the compatibility check: the "Cannot use both" warning is suppressed only when the `packageManager` and `devEngines.packageManager` fields specify the same package manager name and the exact same version string. Any other mismatch — including ranges like `>=1.2.3` that would have previously been considered "satisfied" — now warns.
Adopts pnpm for the lib and both demo apps, replacing npm. The
substantive motivation is supply-chain hardening: pnpm ships
configurable guardrails (publish cooldown, post-install-script
allowlist) and a strict node_modules layout that npm doesn't have.
Secondary motivation is fixing the cross-platform optional-deps
bug in npm's lockfile [1] that has been hitting demo app builds.
Based on the realtime PR [2] but diverges from it in three
deliberate ways:
- Strict node_modules layout (the pnpm default), rather than
realtime's `nodeLinker: hoisted`. Realtime opted for hoisted
because its codebase had phantom dependencies to clean up
first; this project is younger and we'd rather catch any
phantom imports immediately by relying on strict isolation
from the start.
- `devEngines.packageManager` instead of the `only-allow`
preinstall hook. `only-allow` was archived on 22/4/2026 [3];
`devEngines.packageManager` is its successor and requires
pnpm 11+. We're not constrained to pnpm 10 (realtime is,
hence its choice).
- No mise. Realtime uses mise as a polyglot toolchain manager
(Go + Node + Erlang + protoc) and as its task runner. This
project is single-language and has no other tools to
coordinate. Skip mise; rely on Corepack to dispatch the
pinned pnpm version, and on the existing `engines.node` to
constrain Node.
Lib root changes (package.json + pnpm-workspace.yaml):
- `packageManager: "pnpm@11.3.0"` for Corepack / CI dispatch.
- `devEngines.packageManager` with the same exact version pin,
so the two fields don't conflict. pnpm flags any discrepancy
between them with a warning; matching values suppresses it [4].
- `pnpm-workspace.yaml` carries supply-chain config: an
`allowBuilds` allowlist (esbuild, unrs-resolver — derived
empirically from ERR_PNPM_IGNORED_BUILDS), a 7-day publish
cooldown via `minimumReleaseAge`, and an exclude list for
Ably's own packages.
- `package-lock.json` replaced with `pnpm-lock.yaml`.
- The lib's `prepare`, `build` and `precommit` script chains
now invoke `pnpm run` rather than `npm run` — required
because the `prepare` hook runs at install time and
`devEngines.packageManager` blocks npm from running inside
the repo.
Demos (use-chat and use-client-session):
- Each demo's `package-lock.json` is replaced with
`pnpm-lock.yaml`.
- Each demo gets its own `pnpm-workspace.yaml` mirroring the
root (with `sharp` added to `allowBuilds` for Next.js image
optimisation).
- Each demo's `package.json` gains its own `packageManager`
and `devEngines.packageManager` fields. Without these,
`npm install` in a demo directory would proceed silently
(the root's devEngines is not visible to npm in a
subdirectory), then fail later when the lib's prepare
invokes pnpm. The duplication across lib + demos is kept
in sync manually.
- `use-chat` demo's `prebuild` and `vercel.json`
`installCommand` switch from `npm install` to
`pnpm install`.
- Each demo's `allowBuilds` was derived the same way as the
lib: running pnpm install in the demo and inspecting the
ERR_PNPM_IGNORED_BUILDS output.
The decision to enforce pnpm in the demos as well as the lib is
deliberate. A softer position — letting npm drive demo installs
while still requiring pnpm for the lib build — would give npm
users non-reproducible installs (their `package-lock.json` would
drift from our committed `pnpm-lock.yaml`) and would bypass the
supply-chain hardening config (cooldown, allowBuilds) which is
pnpm-specific. Whether the demos are a thing users actually
clone and run or whether they're primarily internal references
is not entirely clear; for now we've optimised for contributor
experience consistency (one PM everywhere). A follow-up could
add a README to each demo documenting the pnpm commands.
Claude suggested a follow-up converting the demos to formal pnpm
workspace members. The motivation would be to eliminate the
`cd ../../../.. && pnpm install && pnpm run build` prebuild
dance, collapse the three lockfiles to one root lockfile, and
pull all the duplicated config (the per-demo
`pnpm-workspace.yaml`, the duplicated `packageManager` /
`devEngines.packageManager` in each demo `package.json`) up
into a single root declaration. Deferred: workspaces are an npm
feature too (since npm 7) and not pnpm-specific, so the fact
that this project hadn't already adopted them under npm
suggests the question of whether they're a good fit deserves
its own consideration. The pnpm migration doesn't change that
calculus.
CI (all five workflows):
- `pnpm/action-setup@v4` added before each `actions/setup-node`
step (the action picks up the version from `packageManager`).
Order matters: setup-node's `cache: pnpm` input invokes
`pnpm store path` and requires pnpm on PATH.
- `npm ci` → `pnpm install --frozen-lockfile`.
- `npm run X` → `pnpm run X`.
- `release.yml` uses `pnpm publish --no-git-checks` because
pnpm's default publish-branch check fails when HEAD is
detached: `release: types: [published]` puts
actions/checkout in detached HEAD on the release tag, and
pnpm then errors with ERR_PNPM_GIT_UNKNOWN_BRANCH ("Git HEAD
may not [be] attached to any branch, but your
'publish-branch' is set to 'master|main'"). Reproduced
locally via `pnpm publish --dry-run` against a checked-out
tag; not yet exercised against a real CI release, so this
rationale is partly inferred.
Contributor docs (CONTRIBUTING.md, README.md Development
section, CLAUDE.md, .claude/rules/TESTS.md,
.claude/skills/release/SKILL.md):
- All `npm` invocations switched to `pnpm`.
- CONTRIBUTING.md and CLAUDE.md gain a short note explaining
that `npm install` is rejected by `devEngines.packageManager`
and that contributors should `corepack enable` to pick up
the pinned pnpm version.
- Consumer-facing install instructions in README.md and
docs/get-started/ deliberately stay as `npm install` — end
users of the published package use whichever package
manager they prefer.
[1] npm/cli#4828
[2] ably/realtime#8261
[3] https://github.com/pnpm/only-allow
[4] pnpm/pnpm#11307
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts pnpm for the lib and both demo apps, replacing npm. The
substantive motivation is supply-chain hardening: pnpm ships
configurable guardrails (publish cooldown, post-install-script
allowlist) and a strict node_modules layout that npm doesn't have.
Secondary motivation is fixing the cross-platform optional-deps
bug in npm's lockfile [1] that has been hitting demo app builds.
Based on the realtime PR [2] but diverges from it in three
deliberate ways:
- Strict node_modules layout (the pnpm default), rather than
realtime's `nodeLinker: hoisted`. Realtime opted for hoisted
because its codebase had phantom dependencies to clean up
first; this project is younger and we'd rather catch any
phantom imports immediately by relying on strict isolation
from the start.
- `devEngines.packageManager` instead of the `only-allow`
preinstall hook. `only-allow` was archived on 22/4/2026 [3];
`devEngines.packageManager` is its successor and requires
pnpm 11+. We're not constrained to pnpm 10 (realtime is,
hence its choice).
- No mise. Realtime uses mise as a polyglot toolchain manager
(Go + Node + Erlang + protoc) and as its task runner. This
project is single-language and has no other tools to
coordinate. Skip mise; rely on Corepack to dispatch the
pinned pnpm version, and on the existing `engines.node` to
constrain Node.
Lib root changes (package.json + pnpm-workspace.yaml):
- `packageManager: "pnpm@11.3.0"` for Corepack / CI dispatch.
- `devEngines.packageManager` with the same exact version pin,
so the two fields don't conflict. pnpm flags any discrepancy
between them with a warning; matching values suppresses it [4].
- `pnpm-workspace.yaml` carries supply-chain config: an
`allowBuilds` allowlist (esbuild, unrs-resolver — derived
empirically from ERR_PNPM_IGNORED_BUILDS), a 7-day publish
cooldown via `minimumReleaseAge`, and an exclude list for
Ably's own packages.
- `package-lock.json` replaced with `pnpm-lock.yaml`.
- The lib's `prepare`, `build` and `precommit` script chains
now invoke `pnpm run` rather than `npm run` — required
because the `prepare` hook runs at install time and
`devEngines.packageManager` blocks npm from running inside
the repo.
Demos (use-chat and use-client-session):
- Each demo's `package-lock.json` is replaced with
`pnpm-lock.yaml`.
- Each demo gets its own `pnpm-workspace.yaml` mirroring the
root (with `sharp` added to `allowBuilds` for Next.js image
optimisation).
- Each demo's `package.json` gains its own `packageManager`
and `devEngines.packageManager` fields. Without these,
`npm install` in a demo directory would proceed silently
(the root's devEngines is not visible to npm in a
subdirectory), then fail later when the lib's prepare
invokes pnpm. The duplication across lib + demos is kept
in sync manually.
- `use-chat` demo's `prebuild` and `vercel.json`
`installCommand` switch from `npm install` to
`pnpm install`.
- Each demo's `allowBuilds` was derived the same way as the
lib: running pnpm install in the demo and inspecting the
ERR_PNPM_IGNORED_BUILDS output.
The decision to enforce pnpm in the demos as well as the lib is
deliberate. A softer position — letting npm drive demo installs
while still requiring pnpm for the lib build — would give npm
users non-reproducible installs (their `package-lock.json` would
drift from our committed `pnpm-lock.yaml`) and would bypass the
supply-chain hardening config (cooldown, allowBuilds) which is
pnpm-specific. Whether the demos are a thing users actually
clone and run or whether they're primarily internal references
is not entirely clear; for now we've optimised for contributor
experience consistency (one PM everywhere). A follow-up could
add a README to each demo documenting the pnpm commands.
Claude suggested a follow-up converting the demos to formal pnpm
workspace members. The motivation would be to eliminate the
`cd ../../../.. && pnpm install && pnpm run build` prebuild
dance, collapse the three lockfiles to one root lockfile, and
pull all the duplicated config (the per-demo
`pnpm-workspace.yaml`, the duplicated `packageManager` /
`devEngines.packageManager` in each demo `package.json`) up
into a single root declaration. Deferred: workspaces are an npm
feature too (since npm 7) and not pnpm-specific, so the fact
that this project hadn't already adopted them under npm
suggests the question of whether they're a good fit deserves
its own consideration. The pnpm migration doesn't change that
calculus.
CI (all five workflows):
- `pnpm/action-setup@v4` added before each `actions/setup-node`
step (the action picks up the version from `packageManager`).
Order matters: setup-node's `cache: pnpm` input invokes
`pnpm store path` and requires pnpm on PATH.
- `npm ci` → `pnpm install --frozen-lockfile`.
- `npm run X` → `pnpm run X`.
- `release.yml` uses `pnpm publish --no-git-checks` because
pnpm's default publish-branch check fails when HEAD is
detached: `release: types: [published]` puts
actions/checkout in detached HEAD on the release tag, and
pnpm then errors with ERR_PNPM_GIT_UNKNOWN_BRANCH ("Git HEAD
may not [be] attached to any branch, but your
'publish-branch' is set to 'master|main'"). Reproduced
locally via `pnpm publish --dry-run` against a checked-out
tag; not yet exercised against a real CI release, so this
rationale is partly inferred.
Contributor docs (CONTRIBUTING.md, README.md Development
section, CLAUDE.md, .claude/rules/TESTS.md,
.claude/skills/release/SKILL.md):
- All `npm` invocations switched to `pnpm`.
- CONTRIBUTING.md and CLAUDE.md gain a short note explaining
that `npm install` is rejected by `devEngines.packageManager`
and that contributors should `corepack enable` to pick up
the pinned pnpm version.
- Consumer-facing install instructions in README.md and
docs/get-started/ deliberately stay as `npm install` — end
users of the published package use whichever package
manager they prefer.
[1] npm/cli#4828
[2] ably/realtime#8261
[3] https://github.com/pnpm/only-allow
[4] pnpm/pnpm#11307
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts pnpm for the lib and both demo apps, replacing npm. The
substantive motivation is supply-chain hardening: pnpm ships
configurable guardrails (publish cooldown, post-install-script
allowlist) and a strict node_modules layout that npm doesn't have.
Secondary motivation is fixing the cross-platform optional-deps
bug in npm's lockfile [1] that has been hitting demo app builds.
Based on the realtime PR [2] but diverges from it in three
deliberate ways:
- Strict node_modules layout (the pnpm default), rather than
realtime's `nodeLinker: hoisted`. Realtime opted for hoisted
because its codebase had phantom dependencies to clean up
first; this project is younger and we'd rather catch any
phantom imports immediately by relying on strict isolation
from the start.
- `devEngines.packageManager` instead of the `only-allow`
preinstall hook. `only-allow` was archived on 22/4/2026 [3];
`devEngines.packageManager` is its successor and requires
pnpm 11+. We're not constrained to pnpm 10 (realtime is,
hence its choice).
- No mise. Realtime uses mise as a polyglot toolchain manager
(Go + Node + Erlang + protoc) and as its task runner. This
project is single-language and has no other tools to
coordinate. Skip mise; rely on Corepack to dispatch the
pinned pnpm version, and on the existing `engines.node` to
constrain Node.
Lib root changes (package.json + pnpm-workspace.yaml):
- `packageManager: "pnpm@11.3.0"` for Corepack / CI dispatch.
- `devEngines.packageManager` with the same exact version pin,
so the two fields don't conflict. pnpm flags any discrepancy
between them with a warning; matching values suppresses it [4].
- `pnpm-workspace.yaml` carries supply-chain config: an
`allowBuilds` allowlist (esbuild, unrs-resolver — derived
empirically from ERR_PNPM_IGNORED_BUILDS), a 7-day publish
cooldown via `minimumReleaseAge`, and an exclude list for
Ably's own packages.
- `package-lock.json` replaced with `pnpm-lock.yaml`.
- The lib's `prepare`, `build` and `precommit` script chains
now invoke `pnpm run` rather than `npm run` — required
because the `prepare` hook runs at install time and
`devEngines.packageManager` blocks npm from running inside
the repo.
Demos (use-chat and use-client-session):
- Each demo's `package-lock.json` is replaced with
`pnpm-lock.yaml`.
- Each demo gets its own `pnpm-workspace.yaml` mirroring the
root (with `sharp` added to `allowBuilds` for Next.js image
optimisation).
- Each demo's `package.json` gains its own `packageManager`
and `devEngines.packageManager` fields. Without these,
`npm install` in a demo directory would proceed silently
(the root's devEngines is not visible to npm in a
subdirectory), then fail later when the lib's prepare
invokes pnpm. The duplication across lib + demos is kept
in sync manually.
- `use-chat` demo's `prebuild` and `vercel.json`
`installCommand` switch from `npm install` to
`pnpm install`.
- Each demo's `allowBuilds` was derived the same way as the
lib: running pnpm install in the demo and inspecting the
ERR_PNPM_IGNORED_BUILDS output.
The decision to enforce pnpm in the demos as well as the lib is
deliberate. A softer position — letting npm drive demo installs
while still requiring pnpm for the lib build — would give npm
users non-reproducible installs (their `package-lock.json` would
drift from our committed `pnpm-lock.yaml`) and would bypass the
supply-chain hardening config (cooldown, allowBuilds) which is
pnpm-specific. Whether the demos are a thing users actually
clone and run or whether they're primarily internal references
is not entirely clear; for now we've optimised for contributor
experience consistency (one PM everywhere). A follow-up could
add a README to each demo documenting the pnpm commands.
Claude suggested a follow-up converting the demos to formal pnpm
workspace members. The motivation would be to eliminate the
`cd ../../../.. && pnpm install && pnpm run build` prebuild
dance, collapse the three lockfiles to one root lockfile, and
pull all the duplicated config (the per-demo
`pnpm-workspace.yaml`, the duplicated `packageManager` /
`devEngines.packageManager` in each demo `package.json`) up
into a single root declaration. Deferred: workspaces are an npm
feature too (since npm 7) and not pnpm-specific, so the fact
that this project hadn't already adopted them under npm
suggests the question of whether they're a good fit deserves
its own consideration. The pnpm migration doesn't change that
calculus.
CI (all five workflows):
- `pnpm/action-setup@v4` added before each `actions/setup-node`
step (the action picks up the version from `packageManager`).
Order matters: setup-node's `cache: pnpm` input invokes
`pnpm store path` and requires pnpm on PATH.
- `npm ci` → `pnpm install --frozen-lockfile`.
- `npm run X` → `pnpm run X`.
- `release.yml` uses `pnpm publish --no-git-checks` because
pnpm's default publish-branch check fails when HEAD is
detached: `release: types: [published]` puts
actions/checkout in detached HEAD on the release tag, and
pnpm then errors with ERR_PNPM_GIT_UNKNOWN_BRANCH ("Git HEAD
may not [be] attached to any branch, but your
'publish-branch' is set to 'master|main'"). Reproduced
locally via `pnpm publish --dry-run` against a checked-out
tag; not yet exercised against a real CI release, so this
rationale is partly inferred.
Contributor docs (CONTRIBUTING.md, README.md Development
section, CLAUDE.md, .claude/rules/TESTS.md,
.claude/skills/release/SKILL.md):
- All `npm` invocations switched to `pnpm`.
- CONTRIBUTING.md and CLAUDE.md gain a short note explaining
that `npm install` is rejected by `devEngines.packageManager`
and that contributors should `corepack enable` to pick up
the pinned pnpm version.
- Consumer-facing install instructions in README.md and
docs/get-started/ deliberately stay as `npm install` — end
users of the published package use whichever package
manager they prefer.
[1] npm/cli#4828
[2] ably/realtime#8261
[3] https://github.com/pnpm/only-allow
[4] pnpm/pnpm#11307
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…kageManager match (#12287) When `package.json` sets both `packageManager` and `devEngines.packageManager` to the same pnpm version with the same integrity hash pnpm prints a spurious warning on every command. For example, a `package.json` file that looks like: ```json { "packageManager": "pnpm@11.5.1+sha512.93f7b57422ea7068257235b4c16eb60762eb68e1dc23723199cc739043ea9be2c4143274a399d8c6defa2b1176226d9ca1c4b63482d6200c1a8fbaa78c1d1485", "devEngines": { "packageManager": { "name": "pnpm", "version": "11.5.1+sha512.93f7b57422ea7068257235b4c16eb60762eb68e1dc23723199cc739043ea9be2c4143274a399d8c6defa2b1176226d9ca1c4b63482d6200c1a8fbaa78c1d1485", "onFail": "ignore" }, "runtime": [ { "name": "node", "version": "26.3.0", "onFail": "ignore" } ] } } ``` Issues a warning on every `pnpm` command: > Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored. ## Root cause `getWantedPackageManager` compares the two fields to decide whether to warn, but the two sides were normalized differently: - `parsePackageManager` **strips the integrity hash** from the legacy `packageManager` field → `11.5.1` - the `devEngines.packageManager` version was compared **with its hash intact** → `11.5.1+sha512.93f7b57…` So, `"11.5.1" !== "11.5.1+sha512…"` was always true and the warning fired, even for identical specs. An earlier fix in #11307 only suppressed the warning when *neither* side carried a hash. ## Fix `parsePackageManager` now also returns the hash (via a shared `splitPackageManagerVersion`), and `getPackageManagerConflictWarning` compares the fields structurally. The warning is suppressed **only when the two specifiers are identical** (name + version + hash, both-absent counts as equal): | name | version | hash | result | |------|---------|------|--------| | same | same | both absent, or both present & equal | ✅ no warning | | same | same | present on **one side only** |⚠️ generic "Cannot use both…" | | same | same | both present & **differ** |⚠️ "…contradictory integrity hashes" | | same | **differ** | — |⚠️ "…different versions of pnpm" | | **differ** | — | — |⚠️ "…different package managers" | A hash on only one side is still a divergence — dropping the ignored `packageManager` field would lose that hash — so it warns with the original generic message. Two contradictory hashes for one version (a likely wrong-hash mistake) get a dedicated message. The generic single message is otherwise replaced by one tailored to each conflict, each ending with `"packageManager" will be ignored`. Closes #12028. --------- Signed-off-by: C. Spencer Beggs <spencer@beggs.codes> Signed-off-by: Zoltan Kochan <z@kochan.io> Co-authored-by: Zoltan Kochan <z@kochan.io>
Summary
Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignoredwarning 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 likev1.2.3, etc.) still warns.packageManager, while v11 usesdevEngines.packageManagerandnpm installstill errors) without a noisy warning — as long as the two values are kept in sync.Closes #11301
Test plan
pnpm --filter pnpm test packageManagerCheck.test.ts— added cases for same-exact-version (no warning), name mismatch (warns), and range vs. exact version (warns). All tests pass.pnpm --filter @pnpm/config.reader run lintclean.pnpm installin a project with matching"packageManager": "pnpm@X"and"devEngines.packageManager": { name: "pnpm", version: "X" }no longer prints the warning; any divergence still prints it.