mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-26 16:48:10 -05:00
feat: improve error message when no mature enough matching package is found (#9974)
--------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
7
.changeset/chubby-trees-notice.md
Normal file
7
.changeset/chubby-trees-notice.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"pnpm": patch
|
||||
"@pnpm/npm-resolver": patch
|
||||
"@pnpm/default-reporter": patch
|
||||
---
|
||||
|
||||
When a version specifier cannot be resolved because the versions don't satisfy the `minimumReleaseAge` setting, print this information out in the error message [#9974](https://github.com/pnpm/pnpm/pull/9974).
|
||||
@@ -70,7 +70,7 @@ function getErrorInfo (logObj: Log, config?: Config): ErrorInfo | null {
|
||||
case 'ERR_PNPM_MISSING_TIME':
|
||||
return { title: err.message, body: 'If you cannot fix this registry issue, then set "resolution-mode" to "highest".' }
|
||||
case 'ERR_PNPM_NO_MATCHING_VERSION':
|
||||
return formatNoMatchingVersion(err, logObj as unknown as { packageMeta: PackageMeta })
|
||||
return formatNoMatchingVersion(err, logObj as unknown as { packageMeta: PackageMeta, immatureVersion?: string })
|
||||
case 'ERR_PNPM_RECURSIVE_FAIL':
|
||||
return formatRecursiveCommandSummary(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
case 'ERR_PNPM_BAD_TARBALL_SIZE':
|
||||
@@ -129,29 +129,56 @@ interface PackageMeta {
|
||||
latest: string
|
||||
}
|
||||
versions: Record<string, object>
|
||||
time?: Record<string, string>
|
||||
}
|
||||
|
||||
function formatNoMatchingVersion (err: Error, msg: { packageMeta: PackageMeta }) {
|
||||
function formatNoMatchingVersion (err: Error, msg: { packageMeta: PackageMeta, immatureVersion?: string }) {
|
||||
const meta: PackageMeta = msg.packageMeta
|
||||
let output = `The latest release of ${meta.name} is "${meta['dist-tags'].latest}".${EOL}`
|
||||
const latestVersion = meta['dist-tags'].latest
|
||||
let output = `The latest release of ${meta.name} is "${latestVersion}".`
|
||||
const latestTime = msg.packageMeta.time?.[latestVersion]
|
||||
if (latestTime) {
|
||||
output += ` Published at ${stringifyDate(latestTime)}`
|
||||
}
|
||||
output += EOL
|
||||
|
||||
if (!equals(Object.keys(meta['dist-tags']), ['latest'])) {
|
||||
output += EOL + 'Other releases are:' + EOL
|
||||
for (const tag in meta['dist-tags']) {
|
||||
if (tag !== 'latest') {
|
||||
output += ` * ${tag}: ${meta['dist-tags'][tag]}${EOL}`
|
||||
const version = meta['dist-tags'][tag]
|
||||
output += ` * ${tag}: ${version}`
|
||||
const time = msg.packageMeta.time?.[version]
|
||||
if (time) {
|
||||
output += ` published at ${stringifyDate(time)}`
|
||||
}
|
||||
output += EOL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output += `${EOL}If you need the full list of all ${Object.keys(meta.versions).length} published versions run "$ pnpm view ${meta.name} versions".`
|
||||
|
||||
if (msg.immatureVersion) {
|
||||
output += `${EOL}${EOL}If you want to install the matched version ignoring the time it was published, you can add the package name to the minimumReleaseAgeExclude setting. Read more about it: https://pnpm.io/settings#minimumreleaseageexclude`
|
||||
}
|
||||
|
||||
return {
|
||||
title: err.message,
|
||||
body: output,
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyDate (dateStr: string): string {
|
||||
const now = Date.now()
|
||||
const oneDayAgo = now - 24 * 60 * 60 * 1000
|
||||
const date = new Date(dateStr)
|
||||
if (date.getTime() < oneDayAgo) {
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
}
|
||||
|
||||
function reportUnexpectedStore (
|
||||
err: Error,
|
||||
msg: {
|
||||
|
||||
@@ -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')
|
||||
}, ['is-odd@0.1.1'])).rejects.toThrow(/No matching version found for is-odd@0\.1\.1.*satisfies the specs but/)
|
||||
})
|
||||
|
||||
describeOnLinuxOnly('filters optional dependencies based on pnpm.supportedArchitectures.libc', () => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { jest } from '@jest/globals'
|
||||
|
||||
jest.mock('enquirer', () => ({ prompt: jest.fn() }))
|
||||
|
||||
// eslint-disable-next-line
|
||||
const prompt = jest.mocked(enquirer.prompt)
|
||||
|
||||
const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}`
|
||||
|
||||
@@ -43,15 +43,33 @@ import {
|
||||
import { fromRegistry, RegistryResponseError } from './fetch.js'
|
||||
import { workspacePrefToNpm } from './workspacePrefToNpm.js'
|
||||
import { whichVersionIsPinned } from './whichVersionIsPinned.js'
|
||||
import { pickVersionByVersionRange } from './pickPackageFromMeta.js'
|
||||
|
||||
export interface NoMatchingVersionErrorOptions {
|
||||
wantedDependency: WantedDependency
|
||||
packageMeta: PackageMeta
|
||||
registry: string
|
||||
immatureVersion?: string
|
||||
publishedBy?: Date
|
||||
}
|
||||
|
||||
export class NoMatchingVersionError extends PnpmError {
|
||||
public readonly packageMeta: PackageMeta
|
||||
constructor (opts: { wantedDependency: WantedDependency, packageMeta: PackageMeta, registry: string }) {
|
||||
public readonly immatureVersion?: string
|
||||
constructor (opts: NoMatchingVersionErrorOptions) {
|
||||
const dep = opts.wantedDependency.alias
|
||||
? `${opts.wantedDependency.alias}@${opts.wantedDependency.bareSpecifier ?? ''}`
|
||||
: opts.wantedDependency.bareSpecifier!
|
||||
super('NO_MATCHING_VERSION', `No matching version found for ${dep} while fetching it from ${opts.registry}`)
|
||||
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()}`
|
||||
} else {
|
||||
errorMessage = `No matching version found for ${dep} while fetching it from ${opts.registry}`
|
||||
}
|
||||
super('NO_MATCHING_VERSION', errorMessage)
|
||||
this.packageMeta = opts.packageMeta
|
||||
this.immatureVersion = opts.immatureVersion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +276,23 @@ async function resolveNpm (
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.publishedBy) {
|
||||
const immatureVersion = pickVersionByVersionRange({
|
||||
meta,
|
||||
versionRange: spec.fetchSpec,
|
||||
preferredVersionSelectors: opts.preferredVersions?.[spec.name],
|
||||
})
|
||||
if (immatureVersion) {
|
||||
throw new NoMatchingVersionError({
|
||||
wantedDependency,
|
||||
packageMeta: meta,
|
||||
registry,
|
||||
immatureVersion,
|
||||
publishedBy: opts.publishedBy,
|
||||
})
|
||||
}
|
||||
}
|
||||
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user