From 2cb0657599dc5d7fc4d2b44c738e889e27e26ab7 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 8 Dec 2025 15:21:25 +0100 Subject: [PATCH] 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> --- .changeset/open-zoos-sniff.md | 6 +++ resolving/npm-resolver/src/index.ts | 3 +- resolving/npm-resolver/src/trustChecks.ts | 7 ++- .../npm-resolver/test/trustChecks.test.ts | 52 +++++++++++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 .changeset/open-zoos-sniff.md diff --git a/.changeset/open-zoos-sniff.md b/.changeset/open-zoos-sniff.md new file mode 100644 index 0000000000..35cb33372a --- /dev/null +++ b/.changeset/open-zoos-sniff.md @@ -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. diff --git a/resolving/npm-resolver/src/index.ts b/resolving/npm-resolver/src/index.ts index e1171830d7..6980f6119a 100644 --- a/resolving/npm-resolver/src/index.ts +++ b/resolving/npm-resolver/src/index.ts @@ -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) } diff --git a/resolving/npm-resolver/src/trustChecks.ts b/resolving/npm-resolver/src/trustChecks.ts index c01ce99626..da5dc55bb6 100644 --- a/resolving/npm-resolver/src/trustChecks.ts +++ b/resolving/npm-resolver/src/trustChecks.ts @@ -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 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( diff --git a/resolving/npm-resolver/test/trustChecks.test.ts b/resolving/npm-resolver/test/trustChecks.test.ts index 555c65ee0e..3cb8e8844a 100644 --- a/resolving/npm-resolver/test/trustChecks.test.ts +++ b/resolving/npm-resolver/test/trustChecks.test.ts @@ -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() + }) })