fix: retry metadata download if the received JSON is broken

close #2949
PR #2971
This commit is contained in:
Zoltan Kochan
2020-11-08 15:43:07 +02:00
committed by GitHub
parent e6ac4f85e1
commit 5ff6c28fab
5 changed files with 74 additions and 12 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/npm-resolver": patch
---
Retry metadata download if the received JSON is broken.

View File

@@ -34,11 +34,13 @@
"@pnpm/logger": "^3.1.0" "@pnpm/logger": "^3.1.0"
}, },
"dependencies": { "dependencies": {
"@pnpm/core-loggers": "workspace:^5.0.2",
"@pnpm/error": "workspace:1.3.1", "@pnpm/error": "workspace:1.3.1",
"@pnpm/fetching-types": "workspace:^1.0.0", "@pnpm/fetching-types": "workspace:^1.0.0",
"@pnpm/resolve-workspace-range": "workspace:1.0.1", "@pnpm/resolve-workspace-range": "workspace:1.0.1",
"@pnpm/resolver-base": "workspace:7.0.5", "@pnpm/resolver-base": "workspace:7.0.5",
"@pnpm/types": "workspace:6.3.1", "@pnpm/types": "workspace:6.3.1",
"@zkochan/retry": "^0.2.0",
"encode-registry": "^3.0.0", "encode-registry": "^3.0.0",
"load-json-file": "^6.2.0", "load-json-file": "^6.2.0",
"lru-cache": "^6.0.0", "lru-cache": "^6.0.0",

View File

@@ -1,10 +1,12 @@
import { import { requestRetryLogger } from '@pnpm/core-loggers'
import PnpmError, {
FetchError, FetchError,
FetchErrorRequest, FetchErrorRequest,
FetchErrorResponse, FetchErrorResponse,
} from '@pnpm/error' } from '@pnpm/error'
import { FetchFromRegistry, RetryTimeoutOptions } from '@pnpm/fetching-types' import { FetchFromRegistry, RetryTimeoutOptions } from '@pnpm/fetching-types'
import { PackageMeta } from './pickPackage' import { PackageMeta } from './pickPackage'
import * as retry from '@zkochan/retry'
import url = require('url') import url = require('url')
interface RegistryResponse { interface RegistryResponse {
@@ -39,21 +41,48 @@ export class RegistryResponseError extends FetchError {
export default async function fromRegistry ( export default async function fromRegistry (
fetch: FetchFromRegistry, fetch: FetchFromRegistry,
retry: RetryTimeoutOptions, retryOpts: RetryTimeoutOptions,
pkgName: string, pkgName: string,
registry: string, registry: string,
authHeaderValue?: string authHeaderValue?: string
) { ): Promise<PackageMeta> {
const uri = toUri(pkgName, registry) const uri = toUri(pkgName, registry)
const response = await fetch(uri, { authHeaderValue, retry }) as RegistryResponse const op = retry.operation(retryOpts)
if (response.status > 400) { return new Promise((resolve, reject) =>
const request = { op.attempt(async (attempt) => {
authHeaderValue, const response = await fetch(uri, { authHeaderValue, retry: retryOpts }) as RegistryResponse
url: uri, if (response.status > 400) {
} const request = {
throw new RegistryResponseError(request, response, pkgName) authHeaderValue,
} url: uri,
return response.json() }
reject(new RegistryResponseError(request, response, pkgName))
return
}
// Here we only retry broken JSON responses.
// Other HTTP issues are retried by the @pnpm/fetch library
try {
resolve(await response.json())
} catch (error) {
const timeout = op.retry(
new PnpmError('BROKEN_METADATA_JSON', error.message)
)
if (timeout === false) {
reject(op.mainError())
return
}
requestRetryLogger.debug({
attempt,
error,
maxRetries: retryOpts.retries!,
method: 'GET',
timeout,
url: uri,
})
}
})
)
} }
function toUri (pkgName: string, registry: string) { function toUri (pkgName: string, registry: string) {

View File

@@ -1427,3 +1427,25 @@ test('resolveFromNpm() should always return the name of the package that is spec
expect(meta.versions).toBeTruthy() expect(meta.versions).toBeTruthy()
expect(meta['dist-tags']).toBeTruthy() expect(meta['dist-tags']).toBeTruthy()
}) })
test('request to metadata is retried if the received JSON is broken', async () => {
const registry = 'https://registry1.com/'
nock(registry)
.get('/is-positive')
.reply(200, '{')
nock(registry)
.get('/is-positive')
.reply(200, isPositiveMeta)
const storeDir = tempy.directory()
const resolve = createResolveFromNpm({
retry: { retries: 1 },
storeDir,
})
const resolveResult = await resolve({ alias: 'is-positive', pref: '1.0.0' }, {
registry,
})!
expect(resolveResult?.id).toBe('registry.npmjs.org/is-positive/1.0.0')
})

4
pnpm-lock.yaml generated
View File

@@ -1223,11 +1223,13 @@ importers:
socks-proxy-agent: ^5.0.0 socks-proxy-agent: ^5.0.0
packages/npm-resolver: packages/npm-resolver:
dependencies: dependencies:
'@pnpm/core-loggers': 'link:../core-loggers'
'@pnpm/error': 'link:../error' '@pnpm/error': 'link:../error'
'@pnpm/fetching-types': 'link:../fetching-types' '@pnpm/fetching-types': 'link:../fetching-types'
'@pnpm/resolve-workspace-range': 'link:../resolve-workspace-range' '@pnpm/resolve-workspace-range': 'link:../resolve-workspace-range'
'@pnpm/resolver-base': 'link:../resolver-base' '@pnpm/resolver-base': 'link:../resolver-base'
'@pnpm/types': 'link:../types' '@pnpm/types': 'link:../types'
'@zkochan/retry': 0.2.0
encode-registry: 3.0.0 encode-registry: 3.0.0
load-json-file: 6.2.0 load-json-file: 6.2.0
lru-cache: 6.0.0 lru-cache: 6.0.0
@@ -1254,6 +1256,7 @@ importers:
path-exists: 4.0.0 path-exists: 4.0.0
tempy: 1.0.0 tempy: 1.0.0
specifiers: specifiers:
'@pnpm/core-loggers': 'workspace:^5.0.2'
'@pnpm/error': 'workspace:1.3.1' '@pnpm/error': 'workspace:1.3.1'
'@pnpm/fetch': 'workspace:^2.1.7' '@pnpm/fetch': 'workspace:^2.1.7'
'@pnpm/fetching-types': 'workspace:^1.0.0' '@pnpm/fetching-types': 'workspace:^1.0.0'
@@ -1267,6 +1270,7 @@ importers:
'@types/normalize-path': ^3.0.0 '@types/normalize-path': ^3.0.0
'@types/semver': ^7.3.4 '@types/semver': ^7.3.4
'@types/ssri': ^6.0.3 '@types/ssri': ^6.0.3
'@zkochan/retry': ^0.2.0
encode-registry: ^3.0.0 encode-registry: ^3.0.0
load-json-file: ^6.2.0 load-json-file: ^6.2.0
lru-cache: ^6.0.0 lru-cache: ^6.0.0