fix: better error message when the installed package was unpublished (#5854)

close #5849
This commit is contained in:
Zoltan Kochan
2022-12-31 15:15:10 +02:00
committed by GitHub
parent 7eb7056c92
commit 83ba90fb83
6 changed files with 73 additions and 4 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/npm-resolver": patch
"pnpm": patch
---
Throw an accurate error message when trying to install a package that has no versions, or all of its versions are unpublished [#5849](https://github.com/pnpm/pnpm/issues/5849).

View File

@@ -20,10 +20,17 @@ export interface PackageMeta {
name: string
'dist-tags': Record<string, string>
versions: Record<string, PackageInRegistry>
time?: Record<string, string>
time?: PackageMetaTime
cachedAt?: number
}
export type PackageMetaTime = Record<string, string> & {
unpublished?: {
time: string
versions: string[]
}
}
export interface PackageMetaCache {
get: (key: string) => PackageMeta | undefined
set: (key: string, meta: PackageMeta) => void

View File

@@ -18,6 +18,14 @@ export function pickPackageFromMeta (
meta: PackageMeta,
publishedBy?: Date
): PackageInRegistry | null {
if ((!meta.versions || Object.keys(meta.versions).length === 0) && !publishedBy) {
// Unfortunately, the npm registry doesn't return the time field in the abbreviated metadata.
// So we won't always know if the package was unpublished.
if (meta.time?.unpublished?.versions?.length) {
throw new PnpmError('UNPUBLISHED_PKG', `No versions available for ${spec.name} because it was unpublished`)
}
throw new PnpmError('NO_VERSIONS', `No versions available for ${spec.name}. The package may be unpublished.`)
}
try {
let version!: string | null
switch (spec.type) {

View File

@@ -11,6 +11,7 @@ import { fixtures } from '@pnpm/test-fixtures'
import loadJsonFile from 'load-json-file'
import nock from 'nock'
import exists from 'path-exists'
import omit from 'ramda/src/omit'
import tempy from 'tempy'
const f = fixtures(__dirname)
@@ -45,6 +46,15 @@ async function retryLoadJsonFile<T> (filePath: string) {
}
}
afterEach(() => {
nock.cleanAll()
nock.disableNetConnect()
})
beforeEach(() => {
nock.enableNetConnect()
})
test('resolveFromNpm()', async () => {
nock(registry)
.get('/is-positive')
@@ -1653,17 +1663,46 @@ test('request to metadata is retried if the received JSON is broken', async () =
expect(resolveResult?.id).toBe('registry.npmjs.org/is-positive/1.0.0')
})
test('request to a package with malformed metadata', async () => {
test('request to a package with unpublished versions', async () => {
nock(registry)
.get('/code-snippet')
.reply(200, loadJsonFile.sync(f.find('malformed.json')))
.reply(200, loadJsonFile.sync(f.find('unpublished.json')))
const cacheDir = tempy.directory()
const resolve = createResolveFromNpm({ cacheDir })
await expect(resolve({ alias: 'code-snippet' }, { registry })).rejects
.toThrow(
new PnpmError('MALFORMED_METADATA', 'Received malformed metadata for "code-snippet"')
new PnpmError('NO_VERSIONS', 'No versions available for code-snippet because it was unpublished')
)
})
test('request to a package with no versions', async () => {
nock(registry)
.get('/code-snippet')
.reply(200, { name: 'code-snippet' })
const cacheDir = tempy.directory()
const resolve = createResolveFromNpm({ cacheDir })
await expect(resolve({ alias: 'code-snippet' }, { registry })).rejects
.toThrow(
new PnpmError('NO_VERSIONS', 'No versions available for code-snippet. The package may be unpublished.')
)
})
test('request to a package with no dist-tags', async () => {
const isPositiveMeta = omit(['dist-tags'], loadJsonFile.sync(f.find('is-positive.json')))
nock(registry)
.get('/is-positive')
.reply(200, isPositiveMeta)
const cacheDir = tempy.directory()
const resolve = createResolveFromNpm({ cacheDir })
await expect(resolve({ alias: 'is-positive' }, { registry })).rejects
.toThrow(
new PnpmError('MALFORMED_METADATA', 'Received malformed metadata for "is-positive"')
)
})

View File

@@ -18,6 +18,15 @@ const fetch = createFetchFromRegistry({})
const getAuthHeader = () => undefined
const createResolveFromNpm = createNpmResolver.bind(null, fetch, getAuthHeader)
afterEach(() => {
nock.cleanAll()
nock.disableNetConnect()
})
beforeEach(() => {
nock.enableNetConnect()
})
test('fall back to a newer version if there is no version published by the given date', async () => {
nock(registry)
.get('/bad-dates')