diff --git a/.changeset/clear-minimum-release-age-error.md b/.changeset/clear-minimum-release-age-error.md new file mode 100644 index 0000000000..0fae0ac2e1 --- /dev/null +++ b/.changeset/clear-minimum-release-age-error.md @@ -0,0 +1,7 @@ +--- +"@pnpm/npm-resolver": patch +"pnpm": patch +--- + +Improve error message when a package version exists but does not meet the `minimumReleaseAge` constraint. The error now clearly states that the version exists and shows a human-readable time since release (e.g., "released 6 hours ago") [#10307](https://github.com/pnpm/pnpm/issues/10307). + diff --git a/exec/plugin-commands-script-runners/test/dlx.e2e.ts b/exec/plugin-commands-script-runners/test/dlx.e2e.ts index 2ad362d597..49c84b2a79 100644 --- a/exec/plugin-commands-script-runners/test/dlx.e2e.ts +++ b/exec/plugin-commands-script-runners/test/dlx.e2e.ts @@ -385,5 +385,5 @@ test('dlx should fail when the requested package does not meet the minimum age r default: 'https://registry.npmjs.org/', }, }, ['shx@0.3.4']) - ).rejects.toThrow('No matching version found for shx@0.3.4 published by') + ).rejects.toThrow(/Version 0\.3\.4 \(released .+\) of shx does not meet the minimumReleaseAge constraint/) }) diff --git a/pkg-manager/plugin-commands-installation/test/add.ts b/pkg-manager/plugin-commands-installation/test/add.ts index 528ad4db77..052d9713cc 100644 --- a/pkg-manager/plugin-commands-installation/test/add.ts +++ b/pkg-manager/plugin-commands-installation/test/add.ts @@ -412,7 +412,7 @@ test('minimumReleaseAge makes install fail if there is no version that was publi dir: path.resolve('project'), minimumReleaseAge, linkWorkspacePackages: false, - }, ['is-odd@0.1.1'])).rejects.toThrow(/No matching version found for is-odd@0\.1\.1.*satisfies the specs but/) + }, ['is-odd@0.1.1'])).rejects.toThrow(/Version 0\.1\.1 \(released .+\) of is-odd does not meet the minimumReleaseAge constraint/) }) describeOnLinuxOnly('filters optional dependencies based on pnpm.supportedArchitectures.libc', () => { diff --git a/resolving/npm-resolver/src/index.ts b/resolving/npm-resolver/src/index.ts index 4aa3a14fd4..c6c15d25e1 100644 --- a/resolving/npm-resolver/src/index.ts +++ b/resolving/npm-resolver/src/index.ts @@ -70,7 +70,9 @@ export class NoMatchingVersionError extends PnpmError { let errorMessage: string if (opts.publishedBy && opts.immatureVersion && opts.packageMeta.time) { const time = new Date(opts.packageMeta.time[opts.immatureVersion]) - errorMessage = `No matching version found for ${dep} published by ${opts.publishedBy.toString()} while fetching it from ${opts.registry}. Version ${opts.immatureVersion} satisfies the specs but was released at ${time.toString()}` + const releaseAgeText = formatTimeAgo(time) + const pkgName = opts.wantedDependency.alias ?? opts.packageMeta.name + errorMessage = `Version ${opts.immatureVersion} (released ${releaseAgeText}) of ${pkgName} does not meet the minimumReleaseAge constraint` } else { errorMessage = `No matching version found for ${dep} while fetching it from ${opts.registry}` } @@ -80,6 +82,28 @@ export class NoMatchingVersionError extends PnpmError { } } +function formatTimeAgo (date: Date): string { + const now = Date.now() + const diffMs = now - date.getTime() + + // Handle clock skew (future dates) and very recent releases (< 1 minute) + if (diffMs < 60 * 1000) { + return 'just now' + } + + const diffMinutes = Math.floor(diffMs / (60 * 1000)) + const diffHours = Math.floor(diffMs / (60 * 60 * 1000)) + const diffDays = Math.floor(diffMs / (24 * 60 * 60 * 1000)) + + if (diffHours >= 48) { + return `${diffDays} day${diffDays === 1 ? '' : 's'} ago` + } + if (diffMinutes >= 90) { + return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago` + } + return `${diffMinutes} minute${diffMinutes === 1 ? '' : 's'} ago` +} + export { parseBareSpecifier, workspacePrefToNpm, diff --git a/resolving/npm-resolver/test/publishedBy.test.ts b/resolving/npm-resolver/test/publishedBy.test.ts index 370a65c9f8..0f7848b9b8 100644 --- a/resolving/npm-resolver/test/publishedBy.test.ts +++ b/resolving/npm-resolver/test/publishedBy.test.ts @@ -116,7 +116,7 @@ test('do not pick version that does not satisfy the date requirement even if it }) await expect(resolveFromNpm({ alias: 'foo', bareSpecifier: '1.0.0' }, { publishedBy: new Date('2015-08-17T19:26:00.508Z'), - })).rejects.toThrow('No matching version found') + })).rejects.toThrow(/Version 1\.0\.0 \(released .+\) of foo does not meet the minimumReleaseAge constraint/) }) test('should skip time field validation for excluded packages', async () => {