fix(outdated): handle NO_MATCHING_VERSION when minimumReleaseAge is set (#10618)

* fix(outdated): handle NO_MATCHING_VERSION when minimumReleaseAge is set

When minimumReleaseAge filters out all versions of a package (including
the one the "latest" dist-tag points to), the resolver throws
NO_MATCHING_VERSION instead of NO_MATURE_MATCHING_VERSION. The
getManifest function in outdated only caught the latter, causing
`pnpm outdated` to crash.

Now both error codes are caught when publishedBy is set, so outdated
gracefully skips packages with no mature versions.

Fixes #10605

* test(outdated): remove redundant getManifest test

The 'handles NO_MATCHING_VERSION error gracefully' test was misleadingly
named (it actually threw ERR_PNPM_NO_MATURE_MATCHING_VERSION) and
duplicated the existing 'with minimumReleaseAge filters latest when too
new' test. It also passed without the fix, so it wasn't verifying the
actual bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Varun Chawla
2026-03-13 08:41:24 -07:00
committed by GitHub
parent 353bc16b42
commit 3417386cb7
3 changed files with 39 additions and 5 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/outdated": patch
"pnpm": patch
---
Fixed `pnpm outdated` crashing with `ERR_PNPM_NO_MATCHING_VERSION` when `minimumReleaseAge` is set and all versions of a package are newer than the threshold [#10605](https://github.com/pnpm/pnpm/issues/10605).

View File

@@ -63,8 +63,16 @@ export async function getManifest (
})
return resolution?.manifest ?? null
} catch (err) {
if ((err as { code?: string }).code === 'ERR_PNPM_NO_MATURE_MATCHING_VERSION' && opts.publishedBy) {
// No versions found that meet the minimumReleaseAge requirement
const code = (err as { code?: string }).code
if (opts.publishedBy && (
code === 'ERR_PNPM_NO_MATURE_MATCHING_VERSION' ||
code === 'ERR_PNPM_NO_MATCHING_VERSION'
)) {
// No versions found that meet the minimumReleaseAge requirement.
// This can happen when all published versions (including the one the
// "latest" dist-tag points to) are newer than the minimumReleaseAge
// threshold, causing the resolver to throw NO_MATCHING_VERSION instead
// of NO_MATURE_MATCHING_VERSION.
return null
}
throw err

View File

@@ -100,7 +100,8 @@ test('getManifest() does not convert non-latest specifiers', async () => {
expect(resolve).toHaveBeenCalledTimes(1)
})
test('getManifest() handles NO_MATCHING_VERSION error gracefully', async () => {
// https://github.com/pnpm/pnpm/issues/10605
test('getManifest() returns null for NO_MATCHING_VERSION when publishedBy is set', async () => {
const opts = {
dir: '',
lockfileDir: '',
@@ -110,17 +111,36 @@ test('getManifest() handles NO_MATCHING_VERSION error gracefully', async () => {
const publishedBy = new Date(Date.now() - 10080 * 60 * 1000)
const resolve: ResolveFunction = jest.fn(async function () {
// When all versions of a package are newer than minimumReleaseAge and the
// latest dist-tag points to a pre-release, the resolver may throw
// NO_MATCHING_VERSION instead of NO_MATURE_MATCHING_VERSION.
const error = new Error('No matching version found') as Error & { code?: string }
error.code = 'ERR_PNPM_NO_MATURE_MATCHING_VERSION'
error.code = 'ERR_PNPM_NO_MATCHING_VERSION'
throw error
})
const result = await getManifest({ ...opts, resolve, publishedBy }, 'foo', 'latest')
// Should return null when no version matches minimumReleaseAge
expect(result).toBeNull()
})
// When publishedBy is NOT set, NO_MATCHING_VERSION should still throw.
test('getManifest() throws NO_MATCHING_VERSION when publishedBy is not set', async () => {
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
}
const resolve: ResolveFunction = jest.fn(async function () {
const error = new Error('No matching version found') as Error & { code?: string }
error.code = 'ERR_PNPM_NO_MATCHING_VERSION'
throw error
})
await expect(getManifest({ ...opts, resolve }, 'foo', 'latest')).rejects.toThrow('No matching version found')
})
test('getManifest() with minimumReleaseAgeExclude', async () => {
const opts = {
dir: '',