mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
11
.changeset/cool-papayas-end.md
Normal file
11
.changeset/cool-papayas-end.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"@pnpm/plugin-commands-audit": minor
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"@pnpm/plugin-commands-outdated": minor
|
||||
"@pnpm/plugin-commands-publishing": minor
|
||||
"pnpm": minor
|
||||
"@pnpm/store-connection-manager": minor
|
||||
---
|
||||
|
||||
Add new config setting: `fetch-timeout`.
|
||||
10
.changeset/orange-ghosts-listen.md
Normal file
10
.changeset/orange-ghosts-listen.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@pnpm/fetch": minor
|
||||
"@pnpm/audit": minor
|
||||
"@pnpm/client": minor
|
||||
"@pnpm/fetching-types": minor
|
||||
"@pnpm/npm-resolver": minor
|
||||
"@pnpm/tarball-fetcher": minor
|
||||
---
|
||||
|
||||
Add new option: timeout.
|
||||
@@ -13,6 +13,7 @@ export default async function audit (
|
||||
include?: { [dependenciesField in DependenciesField]: boolean }
|
||||
registry: string
|
||||
retry?: RetryTimeoutOptions
|
||||
timeout?: number
|
||||
}
|
||||
) {
|
||||
const auditTree = lockfileToAuditTree(lockfile, { include: opts.include })
|
||||
@@ -23,6 +24,7 @@ export default async function audit (
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'post',
|
||||
retry: opts.retry,
|
||||
timeout: opts.timeout,
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
throw new PnpmError('AUDIT_BAD_RESPONSE', `The audit endpoint (at ${auditUrl}) responded with ${res.status}: ${await res.text()}`)
|
||||
|
||||
@@ -14,6 +14,7 @@ export { ResolveFunction }
|
||||
export type ClientOptions = {
|
||||
authConfig: Record<string, string>
|
||||
retry?: RetryTimeoutOptions
|
||||
timeout?: number
|
||||
userAgent?: string
|
||||
} & ResolverFactoryOptions & AgentOptions
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface Config {
|
||||
fetchRetryFactor?: number
|
||||
fetchRetryMintimeout?: number
|
||||
fetchRetryMaxtimeout?: number
|
||||
fetchTimeout?: number
|
||||
saveExact?: boolean
|
||||
savePrefix?: string
|
||||
shellEmulator?: boolean
|
||||
|
||||
@@ -37,6 +37,7 @@ export const types = Object.assign({
|
||||
dir: String,
|
||||
'enable-modules-dir': Boolean,
|
||||
'enable-pre-post-scripts': Boolean,
|
||||
'fetch-timeout': Number,
|
||||
'fetching-concurrency': Number,
|
||||
filter: [String, Array],
|
||||
'filter-prod': [String, Array],
|
||||
@@ -160,6 +161,7 @@ export default async (
|
||||
'fetch-retry-factor': 10,
|
||||
'fetch-retry-maxtimeout': 60000,
|
||||
'fetch-retry-mintimeout': 10000,
|
||||
'fetch-timeout': 60000,
|
||||
globalconfig: npmDefaults.globalconfig,
|
||||
hoist: true,
|
||||
'hoist-pattern': ['*'],
|
||||
|
||||
@@ -19,6 +19,7 @@ export type RequestInfo = string | URLLike | Request
|
||||
|
||||
export interface RequestInit extends NodeRequestInit {
|
||||
retry?: RetryTimeoutOptions
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export const isRedirect = fetch.isRedirect
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function (
|
||||
headers,
|
||||
redirect: 'manual',
|
||||
retry: opts?.retry,
|
||||
timeout: opts?.timeout ?? 60000,
|
||||
})
|
||||
if (!isRedirect(response.status) || redirects >= MAX_FOLLOWED_REDIRECTS) {
|
||||
return response
|
||||
|
||||
@@ -8,6 +8,7 @@ export type FetchFromRegistry = (
|
||||
opts?: {
|
||||
authHeaderValue?: string
|
||||
retry?: RetryTimeoutOptions
|
||||
timeout?: number
|
||||
}
|
||||
) => Promise<Response>
|
||||
|
||||
|
||||
@@ -41,16 +41,20 @@ export class RegistryResponseError extends FetchError {
|
||||
|
||||
export default async function fromRegistry (
|
||||
fetch: FetchFromRegistry,
|
||||
retryOpts: RetryTimeoutOptions,
|
||||
fetchOpts: { retry: RetryTimeoutOptions, timeout: number },
|
||||
pkgName: string,
|
||||
registry: string,
|
||||
authHeaderValue?: string
|
||||
): Promise<PackageMeta> {
|
||||
const uri = toUri(pkgName, registry)
|
||||
const op = retry.operation(retryOpts)
|
||||
const op = retry.operation(fetchOpts.retry)
|
||||
return new Promise((resolve, reject) =>
|
||||
op.attempt(async (attempt) => {
|
||||
const response = await fetch(uri, { authHeaderValue, retry: retryOpts }) as RegistryResponse
|
||||
const response = await fetch(uri, {
|
||||
authHeaderValue,
|
||||
retry: fetchOpts.retry,
|
||||
timeout: fetchOpts.timeout,
|
||||
}) as RegistryResponse
|
||||
if (response.status > 400) {
|
||||
const request = {
|
||||
authHeaderValue,
|
||||
@@ -75,7 +79,7 @@ export default async function fromRegistry (
|
||||
requestRetryLogger.debug({
|
||||
attempt,
|
||||
error,
|
||||
maxRetries: retryOpts.retries!,
|
||||
maxRetries: fetchOpts.retry.retries!,
|
||||
method: 'GET',
|
||||
timeout,
|
||||
url: uri,
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface ResolverFactoryOptions {
|
||||
offline?: boolean
|
||||
preferOffline?: boolean
|
||||
retry?: RetryTimeoutOptions
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export default function createResolver (
|
||||
@@ -69,7 +70,11 @@ export default function createResolver (
|
||||
if (typeof opts.storeDir !== 'string') { // eslint-disable-line
|
||||
throw new TypeError('`opts.storeDir` is required and needs to be a string')
|
||||
}
|
||||
const fetch = pMemoize(fromRegistry.bind(null, fetchFromRegistry, opts.retry ?? {}), {
|
||||
const fetchOpts = {
|
||||
retry: opts.retry ?? {},
|
||||
timeout: opts.timeout ?? 60000,
|
||||
}
|
||||
const fetch = pMemoize(fromRegistry.bind(null, fetchFromRegistry, fetchOpts), {
|
||||
cacheKey: (...args) => JSON.stringify(args),
|
||||
maxAge: 1000 * 20, // 20 seconds
|
||||
})
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function handler (
|
||||
json?: boolean
|
||||
lockfileDir?: string
|
||||
registries: Registries
|
||||
} & Pick<Config, 'fetchRetries' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchRetryFactor' | 'production' | 'dev' | 'optional'>
|
||||
} & Pick<Config, 'fetchRetries' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchRetryFactor' | 'fetchTimeout' | 'production' | 'dev' | 'optional'>
|
||||
) {
|
||||
const lockfile = await readWantedLockfile(opts.lockfileDir ?? opts.dir, { ignoreIncompatible: true })
|
||||
if (lockfile == null) {
|
||||
@@ -113,6 +113,7 @@ export async function handler (
|
||||
minTimeout: opts.fetchRetryMintimeout,
|
||||
retries: opts.fetchRetries,
|
||||
},
|
||||
timeout: opts.fetchTimeout,
|
||||
})
|
||||
const vulnerabilities = auditReport.metadata.vulnerabilities
|
||||
const totalVulnerabilityCount = Object.values(vulnerabilities)
|
||||
|
||||
@@ -15,6 +15,7 @@ export function rcOptionsTypes () {
|
||||
'fetch-retry-factor',
|
||||
'fetch-retry-maxtimeout',
|
||||
'fetch-retry-mintimeout',
|
||||
'fetch-timeout',
|
||||
'force',
|
||||
'global-dir',
|
||||
'global-pnpmfile',
|
||||
|
||||
@@ -17,6 +17,7 @@ export function rcOptionsTypes () {
|
||||
'fetch-retry-factor',
|
||||
'fetch-retry-maxtimeout',
|
||||
'fetch-retry-mintimeout',
|
||||
'fetch-timeout',
|
||||
'frozen-lockfile',
|
||||
'global-dir',
|
||||
'global-pnpmfile',
|
||||
|
||||
@@ -25,6 +25,7 @@ export function rcOptionsTypes () {
|
||||
'fetch-retry-factor',
|
||||
'fetch-retry-maxtimeout',
|
||||
'fetch-retry-mintimeout',
|
||||
'fetch-timeout',
|
||||
'force',
|
||||
'global-dir',
|
||||
'global-pnpmfile',
|
||||
@@ -188,6 +189,13 @@ async function interactiveUpdate (
|
||||
...opts,
|
||||
compatible: opts.latest !== true,
|
||||
include,
|
||||
retry: {
|
||||
factor: opts.fetchRetryFactor,
|
||||
maxTimeout: opts.fetchRetryMaxtimeout,
|
||||
minTimeout: opts.fetchRetryMintimeout,
|
||||
retries: opts.fetchRetries,
|
||||
},
|
||||
timeout: opts.fetchTimeout,
|
||||
})
|
||||
const choices = getUpdateChoices(R.unnest(outdatedPkgsOfProjects))
|
||||
if (choices.length === 0) {
|
||||
|
||||
@@ -132,6 +132,7 @@ export type OutdatedCommandOptions = {
|
||||
| 'fetchRetryFactor'
|
||||
| 'fetchRetryMaxtimeout'
|
||||
| 'fetchRetryMintimeout'
|
||||
| 'fetchTimeout'
|
||||
| 'global'
|
||||
| 'httpProxy'
|
||||
| 'httpsProxy'
|
||||
@@ -175,6 +176,13 @@ export async function handler (
|
||||
...opts,
|
||||
fullMetadata: opts.long,
|
||||
include,
|
||||
retry: {
|
||||
factor: opts.fetchRetryFactor,
|
||||
maxTimeout: opts.fetchRetryMaxtimeout,
|
||||
minTimeout: opts.fetchRetryMintimeout,
|
||||
retries: opts.fetchRetries,
|
||||
},
|
||||
timeout: opts.fetchTimeout,
|
||||
})
|
||||
|
||||
if (outdatedPackages.length === 0) return { output: '', exitCode: 0 }
|
||||
|
||||
@@ -49,7 +49,17 @@ export default async (
|
||||
opts: OutdatedCommandOptions & { include: IncludedDependencies }
|
||||
) => {
|
||||
const outdatedMap = {} as Record<string, OutdatedInWorkspace>
|
||||
const outdatedPackagesByProject = await outdatedDepsOfProjects(pkgs, params, { ...opts, fullMetadata: opts.long })
|
||||
const outdatedPackagesByProject = await outdatedDepsOfProjects(pkgs, params, {
|
||||
...opts,
|
||||
fullMetadata: opts.long,
|
||||
retry: {
|
||||
factor: opts.fetchRetryFactor,
|
||||
maxTimeout: opts.fetchRetryMaxtimeout,
|
||||
minTimeout: opts.fetchRetryMintimeout,
|
||||
retries: opts.fetchRetries,
|
||||
},
|
||||
timeout: opts.fetchTimeout,
|
||||
})
|
||||
for (let i = 0; i < outdatedPackagesByProject.length; i++) {
|
||||
const { dir, manifest } = pkgs[i]
|
||||
outdatedPackagesByProject[i].forEach((outdatedPkg) => {
|
||||
|
||||
@@ -20,6 +20,7 @@ Partial<Pick<Config,
|
||||
| 'tag'
|
||||
| 'ca'
|
||||
| 'cert'
|
||||
| 'fetchTimeout'
|
||||
| 'force'
|
||||
| 'dryRun'
|
||||
| 'extraBinPaths'
|
||||
@@ -52,10 +53,18 @@ export default async function (
|
||||
) {
|
||||
const pkgs = Object.values(opts.selectedProjectsGraph).map((wsPkg) => wsPkg.package)
|
||||
const storeDir = await storePath(opts.workspaceDir, opts.storeDir)
|
||||
const resolve = createResolver(Object.assign(opts, {
|
||||
const resolve = createResolver({
|
||||
...opts,
|
||||
authConfig: opts.rawConfig,
|
||||
retry: {
|
||||
factor: opts.fetchRetryFactor,
|
||||
maxTimeout: opts.fetchRetryMaxtimeout,
|
||||
minTimeout: opts.fetchRetryMintimeout,
|
||||
retries: opts.fetchRetries,
|
||||
},
|
||||
storeDir,
|
||||
})) as unknown as ResolveFunction
|
||||
timeout: opts.fetchTimeout,
|
||||
}) as unknown as ResolveFunction
|
||||
const pkgsToPublish = await pFilter(pkgs, async (pkg) => {
|
||||
if (!pkg.manifest.name || !pkg.manifest.version || pkg.manifest.private) return false
|
||||
if (opts.force) return true
|
||||
|
||||
@@ -31,7 +31,14 @@ export default async function (config: Config) {
|
||||
const resolve = createResolver({
|
||||
...config,
|
||||
authConfig: config.rawConfig,
|
||||
retry: {
|
||||
factor: config.fetchRetryFactor,
|
||||
maxTimeout: config.fetchRetryMaxtimeout,
|
||||
minTimeout: config.fetchRetryMintimeout,
|
||||
retries: config.fetchRetries,
|
||||
},
|
||||
storeDir,
|
||||
timeout: config.fetchTimeout,
|
||||
})
|
||||
const resolution = await resolve({ alias: packageManager.name, pref: 'latest' }, {
|
||||
lockfileDir: config.lockfileDir ?? config.dir,
|
||||
|
||||
@@ -434,3 +434,11 @@ test('installing in a CI environment', async () => {
|
||||
|
||||
await execPnpm(['install', '--no-prefer-frozen-lockfile'], { env: { CI: 'true' } })
|
||||
})
|
||||
|
||||
test('installation fails with a timeout error', async () => {
|
||||
prepare()
|
||||
|
||||
await expect(
|
||||
execPnpm(['add', 'typescript@2.4.2', '--fetch-timeout=1', '--fetch-retries=0'])
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ type CreateResolverOptions = Pick<Config,
|
||||
export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick<Config,
|
||||
| 'ca'
|
||||
| 'cert'
|
||||
| 'fetchTimeout'
|
||||
| 'httpProxy'
|
||||
| 'httpsProxy'
|
||||
| 'key'
|
||||
@@ -57,6 +58,7 @@ export default async (
|
||||
},
|
||||
storeDir: opts.storeDir,
|
||||
strictSSL: opts.strictSsl ?? true,
|
||||
timeout: opts.fetchTimeout,
|
||||
userAgent: opts.userAgent,
|
||||
maxSockets: opts.networkConcurrency != null
|
||||
? (opts.networkConcurrency * 3)
|
||||
|
||||
@@ -74,6 +74,7 @@ export default (
|
||||
maxTimeout?: number
|
||||
randomize?: boolean
|
||||
}
|
||||
timeout?: number
|
||||
}
|
||||
): DownloadFunction => {
|
||||
const retryOpts = {
|
||||
@@ -142,6 +143,7 @@ export default (
|
||||
// Hence, we tell fetch to not retry,
|
||||
// and we perform the retries from this function instead.
|
||||
retry: { retries: 0 },
|
||||
timeout: gotOpts.timeout,
|
||||
})
|
||||
|
||||
if (res.status !== 200) {
|
||||
|
||||
@@ -27,12 +27,14 @@ export default function (
|
||||
fetchFromRegistry: FetchFromRegistry,
|
||||
getCredentials: GetCredentials,
|
||||
opts: {
|
||||
timeout?: number
|
||||
retry?: RetryTimeoutOptions
|
||||
offline?: boolean
|
||||
}
|
||||
): { tarball: FetchFunction } {
|
||||
const download = createDownloader(fetchFromRegistry, {
|
||||
retry: opts.retry,
|
||||
timeout: opts.timeout,
|
||||
})
|
||||
return {
|
||||
tarball: fetchFromTarball.bind(null, {
|
||||
|
||||
Reference in New Issue
Block a user