mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-01 12:41:16 -04:00
fix(config): apply pmOnFail default to devEngines.packageManager (singular) (#11682)
* fix(config): apply pmOnFail default to devEngines.packageManager (singular)
The pnpm v11 release notes document the `pmOnFail` default as `download`
(via the migration table that maps `managePackageManagerVersions: true` →
`pmOnFail: download (default)`). The legacy `packageManager` field already
gets that default applied at the central onFail-resolution site, but the
singular form of `devEngines.packageManager` short-circuited it by setting
`onFail = 'error'` inside `parseDevEnginesPackageManager`, so projects that
pinned a different pnpm via `devEngines.packageManager` saw a hard version
mismatch instead of an auto-download.
Drop that local `?? 'error'` and let the central default apply. The array
form of `devEngines.packageManager` keeps its own per-element defaults
('error' for the last entry, 'ignore' for the rest) — those reflect
explicit prioritisation by the user, not a system-wide fallback. Explicit
`onFail` values are still honored everywhere.
Closes #11676.
* chore: fix spelling (prioritisation → prioritization)
cspell flagged the British spelling at pre-push.
---------
Co-authored-by: Damon <damon@deeplearning.ai>
This commit is contained in:
8
.changeset/pmonfail-default-devengines-11676.md
Normal file
8
.changeset/pmonfail-default-devengines-11676.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/config.reader": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fix `devEngines.packageManager` (singular form, without `onFail`) defaulting to `onFail: "error"` instead of the documented `pmOnFail: "download"`. As a result, a project that pinned a different pnpm version via `devEngines.packageManager` and ran `pnpm install` from a mismatched pnpm version failed with a hard error, even though the migration table from `managePackageManagerVersions: true` to `pmOnFail: download (default)` promises the install would auto-download the wanted version [#11676](https://github.com/pnpm/pnpm/issues/11676).
|
||||
|
||||
The array form of `devEngines.packageManager` keeps its existing per-element defaults (`error` for the last entry, `ignore` for the rest), since those reflect explicit prioritization by the user. Explicit `onFail` values continue to win.
|
||||
@@ -657,9 +657,11 @@ 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, the legacy
|
||||
// `packageManager` field defaults to `download` — `devEngines.packageManager`
|
||||
// already has onFail set during parsing.
|
||||
// a specific behavior without editing the manifest. Otherwise, both the
|
||||
// legacy `packageManager` field and singular `devEngines.packageManager`
|
||||
// fall through to `download` (the documented default for `pmOnFail`); the
|
||||
// array form of `devEngines.packageManager` already has its own per-element
|
||||
// defaults applied during parsing.
|
||||
if (pnpmConfig.wantedPackageManager) {
|
||||
if (pnpmConfig.pmOnFail) {
|
||||
pnpmConfig.wantedPackageManager.onFail = pnpmConfig.pmOnFail
|
||||
@@ -764,7 +766,7 @@ export function parsePackageManager (packageManager: string): { name: string, ve
|
||||
function parseDevEnginesPackageManager (devEngines?: DevEngines): EngineDependency | undefined {
|
||||
if (!devEngines?.packageManager) return undefined
|
||||
let pmEngine: EngineDependency | undefined
|
||||
let onFail: 'ignore' | 'warn' | 'error' | 'download'
|
||||
let onFail: 'ignore' | 'warn' | 'error' | 'download' | undefined
|
||||
if (Array.isArray(devEngines.packageManager)) {
|
||||
const engines = devEngines.packageManager
|
||||
if (engines.length === 0) return undefined
|
||||
@@ -781,7 +783,11 @@ function parseDevEnginesPackageManager (devEngines?: DevEngines): EngineDependen
|
||||
}
|
||||
} else {
|
||||
pmEngine = devEngines.packageManager
|
||||
onFail = pmEngine.onFail ?? 'error'
|
||||
// Singular form: leave onFail undefined when the user did not set it, so
|
||||
// the central pmOnFail default ('download') applies. The array form keeps
|
||||
// its own per-element defaults ('error' for the last entry, 'ignore' for
|
||||
// the rest) because those reflect explicit prioritization by the user.
|
||||
onFail = pmEngine.onFail
|
||||
}
|
||||
if (!pmEngine?.name) return undefined
|
||||
return {
|
||||
|
||||
@@ -166,6 +166,47 @@ test('runtimeOnFail=ignore overrides an existing onFail=download and removes nod
|
||||
expect(context.rootProjectManifest?.devDependencies?.node).toBeUndefined()
|
||||
})
|
||||
|
||||
test('devEngines.packageManager without onFail resolves to the documented pmOnFail default "download" (#11676)', async () => {
|
||||
prepare({
|
||||
devEngines: {
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '11.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const { context } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: { name: 'pnpm', version: '11.0.0' },
|
||||
})
|
||||
|
||||
expect(context.wantedPackageManager).toMatchObject({
|
||||
name: 'pnpm',
|
||||
version: '11.0.0',
|
||||
onFail: 'download',
|
||||
})
|
||||
})
|
||||
|
||||
test('devEngines.packageManager with explicit onFail is respected (regression guard for #11676)', async () => {
|
||||
prepare({
|
||||
devEngines: {
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '11.0.0',
|
||||
onFail: 'error',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const { context } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: { name: 'pnpm', version: '11.0.0' },
|
||||
})
|
||||
|
||||
expect(context.wantedPackageManager?.onFail).toBe('error')
|
||||
})
|
||||
|
||||
test('throw error if --link-workspace-packages is used with --global', async () => {
|
||||
await expect(getConfig({
|
||||
cliOptions: {
|
||||
|
||||
@@ -143,7 +143,7 @@ test('devEngines.packageManager with onFail=ignore should not check version', as
|
||||
expect(stderr.toString()).not.toContain('0.0.1')
|
||||
})
|
||||
|
||||
test('devEngines.packageManager defaults to onFail=error', async () => {
|
||||
test('devEngines.packageManager defaults to onFail=download (#11676)', async () => {
|
||||
prepare({
|
||||
devEngines: {
|
||||
packageManager: {
|
||||
@@ -153,10 +153,19 @@ test('devEngines.packageManager defaults to onFail=error', async () => {
|
||||
},
|
||||
})
|
||||
|
||||
const { status, stderr } = execPnpmSync(['install'])
|
||||
// The documented `pmOnFail` default is `download`. Run under COREPACK_ROOT
|
||||
// to short-circuit the actual version switch (corepack owns version
|
||||
// selection there), so the test exercises the resolved default without a
|
||||
// network round-trip. Pre-fix, devEngines.packageManager defaulted to
|
||||
// `error` and the corepack-specific download-fallthrough hint did NOT
|
||||
// appear. Asserting on that hint pins the new behavior.
|
||||
const { status, stderr } = execPnpmSync(['install'], {
|
||||
env: { COREPACK_ROOT: '/fake/corepack' },
|
||||
})
|
||||
|
||||
expect(status).toBe(1)
|
||||
expect(stderr.toString()).toContain('This project is configured to use 0.0.1 of pnpm')
|
||||
expect(stderr.toString()).toContain('does not switch versions when running under corepack')
|
||||
})
|
||||
|
||||
test('devEngines.packageManager with a different PM name should fail with onFail=error', async () => {
|
||||
|
||||
Reference in New Issue
Block a user