mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-31 13:32:18 -04:00
fix: better error message when the installed package was unpublished (#5854)
close #5849
This commit is contained in:
6
.changeset/gorgeous-islands-shake.md
Normal file
6
.changeset/gorgeous-islands-shake.md
Normal 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).
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"')
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user