You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(config/reader): don't warn when packageManager and devEngines.packageManager 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>
No longer warn about using both `packageManager` and `devEngines.packageManager` when the two fields pin the same package manager at the same version with the same integrity hash (e.g. both `pnpm@11.5.1+sha512.…`). Previously the hash was stripped from the legacy `packageManager` field but not from `devEngines.packageManager`, so even identical specifications looked like a mismatch [#12028](https://github.com/pnpm/pnpm/issues/12028).
7
+
8
+
The warning still fires on any genuine divergence, and several cases now state the specific reason instead of a single generic message: a different package manager, a different version, or contradictory integrity hashes for the same version.
constignoredSuffix='. "packageManager" will be ignored'
906
+
constgenericWarning=`Cannot use both "packageManager" and "devEngines.packageManager" in package.json${ignoredSuffix}`
907
+
if(legacy.name!==devEngines.name){
908
+
return`"packageManager" (${sanitizeManifestValue(legacy.name)}) and "devEngines.packageManager" (${sanitizeManifestValue(devEngines.name)}) specify different package managers in package.json${ignoredSuffix}`
909
+
}
910
+
if(legacy.version!==devEngines.version){
911
+
// "different versions" only makes sense when both sides are concrete
912
+
// versions. If one side has no semver version — e.g. the legacy field is a
913
+
// URL or a bare name — fall back to the generic notice rather than claiming
return`"packageManager" and "devEngines.packageManager" specify different versions of ${sanitizeManifestValue(legacy.name)} in package.json${ignoredSuffix}`
861
917
}
918
+
if(legacy.hash!==devEngines.hash){
919
+
// Same name and version, but the integrity hashes differ. Two distinct
920
+
// hashes for one version is a likely wrong-hash mistake, so call it out
921
+
// specifically; a hash on only one side is a softer mismatch (the version
return`"packageManager" and "devEngines.packageManager" specify ${sanitizeManifestValue(legacy.name)}@${sanitizeManifestValue(legacy.version)} with different integrity hashes in package.json${ignoredSuffix}`
925
+
}
926
+
returngenericWarning
927
+
}
928
+
returnundefined
929
+
}
930
+
931
+
/**
932
+
* Renders a package.json-controlled value safe to embed in a warning printed to
933
+
* the terminal. Strips ANSI escape sequences and replaces remaining control
934
+
* characters (including newlines) with spaces so a malicious manifest cannot
expect(warnings).toContain(`"packageManager" and "devEngines.packageManager" specify pnpm@1.2.3 with different integrity hashes in package.json${IGNORED}`)
248
+
})
249
+
250
+
test.each([
251
+
{caseName: 'the legacy field is a URL reference',packageManager: 'pnpm@https://github.com/pnpm/pnpm'},
252
+
{caseName: 'the legacy field is a bare name with no version',packageManager: 'pnpm'},
253
+
])('warns generically rather than claiming a version mismatch when one side is not a concrete version: $caseName',async({ packageManager })=>{
expect(warnings).toContain(`"packageManager" (yarn) and "devEngines.packageManager" (pnpm) specify different package managers in package.json${IGNORED}`)
279
+
})
280
+
281
+
test('strips control characters from package.json values embedded in the warning',async()=>{
0 commit comments