fix: don't fail with ERR_PNPM_MISSING_TIME on packages that are excluded from trust checks (#10292)

* fix: don't fail with ERR_PNPM_MISSING_TIME on packages that are excluded from trust checks

close #10259

* test: add coverage for excluded packages missing time field (#10293)

* Initial plan

* test: add coverage for excluded packages missing time field

Co-authored-by: zkochan <1927579+zkochan@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zkochan <1927579+zkochan@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zkochan <1927579+zkochan@users.noreply.github.com>
This commit is contained in:
Zoltan Kochan
2025-12-08 15:21:25 +01:00
committed by GitHub
parent 19fb36dc6a
commit 2cb0657599
4 changed files with 64 additions and 4 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/npm-resolver": patch
"pnpm": patch
---
Don't fail with a `ERR_PNPM_MISSING_TIME` error if a package that is excluded from trust policy checks is missing the time field in the metadata.

View File

@@ -48,7 +48,7 @@ import {
import { fetchMetadataFromFromRegistry, type FetchMetadataFromFromRegistryOptions, RegistryResponseError } from './fetch.js'
import { workspacePrefToNpm } from './workspacePrefToNpm.js'
import { whichVersionIsPinned } from './whichVersionIsPinned.js'
import { pickVersionByVersionRange, assertMetaHasTime } from './pickPackageFromMeta.js'
import { pickVersionByVersionRange } from './pickPackageFromMeta.js'
import { failIfTrustDowngraded } from './trustChecks.js'
export interface NoMatchingVersionErrorOptions {
@@ -308,7 +308,6 @@ async function resolveNpm (
}
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry })
} else if (opts.trustPolicy === 'no-downgrade') {
assertMetaHasTime(meta)
failIfTrustDowngraded(meta, pickedPackage.version, opts.trustPolicyExclude)
}

View File

@@ -1,7 +1,8 @@
import { PnpmError } from '@pnpm/error'
import { type PackageInRegistry, type PackageMetaWithTime } from '@pnpm/registry.types'
import { type PackageInRegistry, type PackageMeta, type PackageMetaWithTime } from '@pnpm/registry.types'
import { type PackageVersionPolicy } from '@pnpm/types'
import semver from 'semver'
import { assertMetaHasTime } from './pickPackageFromMeta.js'
type TrustEvidence = 'provenance' | 'trustedPublisher'
@@ -11,7 +12,7 @@ const TRUST_RANK = {
} as const satisfies Record<TrustEvidence, number>
export function failIfTrustDowngraded (
meta: PackageMetaWithTime,
meta: PackageMeta,
version: string,
trustPolicyExclude?: PackageVersionPolicy
): void {
@@ -25,6 +26,8 @@ export function failIfTrustDowngraded (
}
}
assertMetaHasTime(meta)
const versionPublishedAt = meta.time[version]
if (!versionPublishedAt) {
throw new PnpmError(

View File

@@ -536,4 +536,56 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
failIfTrustDowngraded(meta, '3.0.0', createPackageVersionPolicy(['bar']))
}).not.toThrow()
})
test('does not fail with ERR_PNPM_MISSING_TIME when package@version is excluded and time field is missing', () => {
const meta = {
name: 'baz',
'dist-tags': { latest: '1.0.0' },
versions: {
'1.0.0': {
name: 'baz',
version: '1.0.0',
dist: {
shasum: 'abc123',
tarball: 'https://registry.example.com/baz/-/baz-1.0.0.tgz',
},
},
},
// Note: no 'time' field
}
expect(() => {
failIfTrustDowngraded(meta, '1.0.0', createPackageVersionPolicy(['baz@1.0.0']))
}).not.toThrow()
})
test('does not fail with ERR_PNPM_MISSING_TIME when package name is excluded and time field is missing', () => {
const meta = {
name: 'qux',
'dist-tags': { latest: '2.0.0' },
versions: {
'1.0.0': {
name: 'qux',
version: '1.0.0',
dist: {
shasum: 'abc123',
tarball: 'https://registry.example.com/qux/-/qux-1.0.0.tgz',
},
},
'2.0.0': {
name: 'qux',
version: '2.0.0',
dist: {
shasum: 'def456',
tarball: 'https://registry.example.com/qux/-/qux-2.0.0.tgz',
},
},
},
// Note: no 'time' field
}
expect(() => {
failIfTrustDowngraded(meta, '2.0.0', createPackageVersionPolicy(['qux']))
}).not.toThrow()
})
})