diff --git a/.gitignore b/.gitignore index ec23a805..f2b57334 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules /.nyc_output /test/cache +/coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index a25d7210..36eb6efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [7.0.0](https://github.com/npm/registry-fetch/compare/v6.0.2...v7.0.0) (2020-02-18) + + +### ⚠ BREAKING CHANGES + +* figgy pudding is now nowhere to be found. +* this removes figgy-pudding, and drops several option +aliases. + +Defaults and behavior are all the same, and this module is now using the +canonical camelCase option names that npm v7 will provide to all its +deps. + +Related to: https://github.com/npm/rfcs/pull/102 + +PR-URL: https://github.com/npm/npm-registry-fetch/pull/22 +Credit: @isaacs + +### Bug Fixes + +* Remove figgy-pudding, use canonical option names ([ede3c08](https://github.com/npm/registry-fetch/commit/ede3c087007fd1808e02b1af70562220d03b18a9)), closes [#22](https://github.com/npm/registry-fetch/issues/22) + + +* update cacache, ssri, make-fetch-happen ([57fcc88](https://github.com/npm/registry-fetch/commit/57fcc889bee03edcc0a2025d96a171039108c231)) + ### [6.0.2](https://github.com/npm/registry-fetch/compare/v6.0.1...v6.0.2) (2020-02-14) diff --git a/README.md b/README.md index 9b298868..da722827 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ to trust only that specific signing authority. Multiple CAs can be trusted by specifying an array of certificates instead of a single string. -See also [`opts.strict-ssl`](#opts-strict-ssl), [`opts.ca`](#opts-ca) and +See also [`opts.strictSSL`](#opts-strictSSL), [`opts.ca`](#opts-ca) and [`opts.key`](#opts-key) ##### `opts.cache` @@ -194,8 +194,8 @@ will be cached according to [IETF RFC 7234](https://tools.ietf.org/html/rfc7234) rules. This will speed up future requests, as well as make the cached data available offline if necessary/requested. -See also [`offline`](#opts-offline), [`prefer-offline`](#opts-prefer-offline), -and [`prefer-online`](#opts-prefer-online). +See also [`offline`](#opts-offline), [`preferOffline`](#opts-preferOffline), +and [`preferOnline`](#opts-preferOnline). ##### `opts.cert` @@ -216,7 +216,7 @@ It is _not_ the path to a certificate file (and there is no "certfile" option). See also: [`opts.ca`](#opts-ca) and [`opts.key`](#opts-key) -##### `opts.fetch-retries` +##### `opts.fetchRetries` * Type: Number * Default: 2 @@ -227,7 +227,7 @@ packages from the registry. See also [`opts.retry`](#opts-retry) to provide all retry options as a single object. -##### `opts.fetch-retry-factor` +##### `opts.fetchRetryFactor` * Type: Number * Default: 10 @@ -238,7 +238,7 @@ packages. See also [`opts.retry`](#opts-retry) to provide all retry options as a single object. -##### `opts.fetch-retry-mintimeout` +##### `opts.fetchRetryMintimeout` * Type: Number * Default: 10000 (10 seconds) @@ -249,7 +249,7 @@ packages. See also [`opts.retry`](#opts-retry) to provide all retry options as a single object. -##### `opts.fetch-retry-maxtimeout` +##### `opts.fetchRetryMaxtimeout` * Type: Number * Default: 60000 (1 minute) @@ -260,9 +260,8 @@ packages. See also [`opts.retry`](#opts-retry) to provide all retry options as a single object. -##### `opts.force-auth` +##### `opts.forceAuth` -* Alias: `opts.forceAuth` * Type: Object * Default: null @@ -288,9 +287,8 @@ Additional headers for the outgoing request. This option can also be used to override headers automatically generated by `npm-registry-fetch`, such as `Content-Type`. -##### `opts.ignore-body` +##### `opts.ignoreBody` -* Alias: `opts.ignoreBody` * Type: Boolean * Default: false @@ -317,9 +315,8 @@ previously-generated integrity hash for the saved request information, so `EINTEGRITY` errors can happen if [`opts.cache`](#opts-cache) is used, even if `opts.integrity` is not passed in. -##### `opts.is-from-ci` +##### `opts.isFromCI` -* Alias: `opts.isFromCI` * Type: Boolean * Default: Based on environment variables @@ -343,7 +340,7 @@ It is _not_ the path to a key file (and there is no "keyfile" option). See also: [`opts.ca`](#opts-ca) and [`opts.cert`](#opts-cert) -##### `opts.local-address` +##### `opts.localAddress` * Type: IP Address String * Default: null @@ -361,9 +358,8 @@ See also [`opts.proxy`](#opts-proxy) Logger object to use for logging operation details. Must have the same methods as `npmlog`. -##### `opts.map-json` +##### `opts.mapJSON` -* Alias: `mapJson`, `mapJSON` * Type: Function * Default: undefined @@ -371,9 +367,8 @@ When using `fetch.json.stream()` (NOT `fetch.json()`), this will be passed down to [`JSONStream`](https://npm.im/JSONStream) as the second argument to `JSONStream.parse`, and can be used to transform stream data before output. -##### `opts.maxsockets` +##### `opts.maxSockets` -* Alias: `opts.max-sockets` * Type: Integer * Default: 12 @@ -394,9 +389,8 @@ HTTP method to use for the outgoing request. Case-insensitive. If true, proxying will be disabled even if [`opts.proxy`](#opts-proxy) is used. -##### `opts.npm-session` +##### `opts.npmSession` -* Alias: `opts.npmSession` * Type: String * Default: null @@ -411,7 +405,7 @@ invocations of the CLI). Force offline mode: no network requests will be done during install. To allow `npm-registry-fetch` to fill in missing cache data, see -[`opts.prefer-offline`](#opts-prefer-offline). +[`opts.preferOffline`](#opts-preferOffline). This option is only really useful if you're also using [`opts.cache`](#opts-cache). @@ -448,7 +442,7 @@ That is: See also [`opts.username`](#opts-username) -##### `opts.prefer-offline` +##### `opts.preferOffline` * Type: Boolean * Default: false @@ -463,7 +457,7 @@ This option is generally only useful if you're also using This option is set to `false` when the request includes `write=true` in the query string. -##### `opts.prefer-online` +##### `opts.preferOnline` * Type: Boolean * Default: false @@ -477,9 +471,8 @@ This option is generally only useful if you're also using This option is set to `true` when the request includes `write=true` in the query string. -##### `opts.project-scope` +##### `opts.projectScope` -* Alias: `opts.projectScope` * Type: String * Default: null @@ -510,7 +503,6 @@ If the request URI already has a query string, it will be merged with ##### `opts.refer` -* Alias: `opts.referer` * Type: String * Default: null @@ -574,7 +566,7 @@ If provided, can be used to automatically configure [`opts.scope`](#opts-scope) based on a specific package name. Non-registry package specs will throw an error. -##### `opts.strict-ssl` +##### `opts.strictSSL` * Type: Boolean * Default: true @@ -607,7 +599,7 @@ Can be scoped to a registry by using a "nerf dart" for that registry. That is: } ``` -##### `opts.user-agent` +##### `opts.userAgent` * Type: String * Default: `'npm-registry-fetch@/node@+ ()'` diff --git a/auth.js b/auth.js index 3198b9c4..11c3bde6 100644 --- a/auth.js +++ b/auth.js @@ -1,17 +1,14 @@ 'use strict' -const config = require('./config.js') +const defaultOpts = require('./default-opts.js') const url = require('url') module.exports = getAuth -function getAuth (registry, opts) { +function getAuth (registry, opts_ = {}) { if (!registry) { throw new Error('registry is required') } - opts = config(opts) + const opts = opts_.forceAuth ? opts_.forceAuth : { ...defaultOpts, ...opts_ } const AUTH = {} const regKey = registry && registryKey(registry) - if (opts.forceAuth) { - opts = opts.forceAuth - } const doKey = (key, alias) => addKey(opts, AUTH, regKey, key, alias) doKey('token') doKey('_authToken', 'token') diff --git a/check-response.js b/check-response.js index 3137ea71..933c8f7c 100644 --- a/check-response.js +++ b/check-response.js @@ -1,12 +1,13 @@ 'use strict' -const config = require('./config.js') const errors = require('./errors.js') const LRU = require('lru-cache') const { Response } = require('minipass-fetch') +const defaultOpts = require('./default-opts.js') + module.exports = checkResponse -function checkResponse (method, res, registry, startTime, opts) { - opts = config(opts) +function checkResponse (method, res, registry, startTime, opts_ = {}) { + const opts = { ...defaultOpts, ...opts_ } if (res.headers.has('npm-notice') && !res.headers.has('x-local-cache')) { opts.log.notice('', res.headers.get('npm-notice')) } diff --git a/config.js b/config.js deleted file mode 100644 index d6681d09..00000000 --- a/config.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict' - -const pkg = require('./package.json') -const figgyPudding = require('figgy-pudding') -const silentLog = require('./silentlog.js') -const ciDetect = require('@npmcli/ci-detect') - -const AUTH_REGEX = /^(?:.*:)?(token|_authToken|username|_password|password|email|always-auth|_auth|otp)$/ -const SCOPE_REGISTRY_REGEX = /@.*:registry$/gi -module.exports = figgyPudding({ - agent: {}, - algorithms: {}, - body: {}, - ca: {}, - cache: {}, - cert: {}, - 'fetch-retries': {}, - 'fetch-retry-factor': {}, - 'fetch-retry-maxtimeout': {}, - 'fetch-retry-mintimeout': {}, - 'force-auth': {}, - forceAuth: 'force-auth', - gzip: {}, - headers: {}, - 'https-proxy': {}, - 'ignore-body': {}, - ignoreBody: 'ignore-body', - integrity: {}, - 'is-from-ci': 'isFromCI', - isFromCI: { - default () { - return ciDetect() - } - }, - key: {}, - 'local-address': {}, - log: { - default: silentLog - }, - 'map-json': 'mapJson', - mapJSON: 'mapJson', - mapJson: {}, - 'max-sockets': 'maxsockets', - maxsockets: { - default: 12 - }, - memoize: {}, - method: { - default: 'GET' - }, - 'no-proxy': {}, - noproxy: {}, - 'npm-session': 'npmSession', - npmSession: {}, - offline: {}, - otp: {}, - 'prefer-offline': {}, - 'prefer-online': {}, - projectScope: {}, - 'project-scope': 'projectScope', - proxy: {}, - query: {}, - refer: {}, - referer: 'refer', - registry: { - default: 'https://registry.npmjs.org/' - }, - retry: {}, - scope: {}, - spec: {}, - 'strict-ssl': {}, - timeout: { - default: 30 * 1000 - }, - 'user-agent': { - default: `${ - pkg.name - }@${ - pkg.version - }/node@${ - process.version - }+${ - process.arch - } (${ - process.platform - })` - } -}, { - other (key) { - return key.match(AUTH_REGEX) || key.match(SCOPE_REGISTRY_REGEX) - } -}) diff --git a/default-opts.js b/default-opts.js new file mode 100644 index 00000000..4d51da47 --- /dev/null +++ b/default-opts.js @@ -0,0 +1,22 @@ +const pkg = require('./package.json') +const ciDetect = require('@npmcli/ci-detect') +module.exports = { + isFromCI: ciDetect(), + log: require('./silentlog.js'), + maxSockets: 12, + method: 'GET', + registry: 'https://registry.npmjs.org/', + timeout: 30 * 1000, + strictSSL: true, + noProxy: process.env.NOPROXY, + userAgent: `${pkg.name + }@${ + pkg.version + }/node@${ + process.version + }+${ + process.arch + } (${ + process.platform + })` +} diff --git a/index.js b/index.js index 6a0b110d..2942db22 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,6 @@ 'use strict' -const Buffer = require('safe-buffer').Buffer - -const ciDetect = require('@npmcli/ci-detect') const checkResponse = require('./check-response.js') -const config = require('./config.js') const getAuth = require('./auth.js') const fetch = require('make-fetch-happen') const JSONStream = require('minipass-json-stream') @@ -14,6 +10,8 @@ const url = require('url') const zlib = require('minizlib') const Minipass = require('minipass') +const defaultOpts = require('./default-opts.js') + // WhatWG URL throws if it's not fully resolved const urlIsValid = u => { try { @@ -24,12 +22,15 @@ const urlIsValid = u => { } module.exports = regFetch -function regFetch (uri, opts) { - opts = config(opts) - const registry = ( +function regFetch (uri, /* istanbul ignore next */ opts_ = {}) { + const opts = { + ...defaultOpts, + ...opts_ + } + const registry = opts.registry = ( (opts.spec && pickRegistry(opts.spec, opts)) || opts.registry || - /* istanbul ignore next: default set in figgy pudding config */ + /* istanbul ignore next */ 'https://registry.npmjs.org/' ) @@ -41,9 +42,7 @@ function regFetch (uri, opts) { }` } - const method = opts.method || - /* istanbul ignore next: default set in figgy pudding config */ - 'GET' + const method = opts.method || 'GET' // through that takes into account the scope, the prefix of `uri`, etc const startTime = Date.now() @@ -92,11 +91,9 @@ function regFetch (uri, opts) { // do not cache, because this GET is fetching a rev that will be // used for a subsequent PUT or DELETE, so we need to conditionally // update cache. - opts = opts.concat({ - offline: false, - 'prefer-offline': false, - 'prefer-online': true - }) + opts.offline = false + opts.preferOffline = false + opts.preferOnline = true } const doFetch = (body) => fetch(uri, { @@ -110,21 +107,21 @@ function regFetch (uri, opts) { headers, integrity: opts.integrity, key: opts.key, - localAddress: opts['local-address'], - maxSockets: opts.maxsockets, + localAddress: opts.localAddress, + maxSockets: opts.maxSockets, memoize: opts.memoize, method: method, - noProxy: opts['no-proxy'] || opts.noproxy, - proxy: opts['https-proxy'] || opts.proxy, + noProxy: opts.noProxy, + proxy: opts.httpsProxy || opts.proxy, referer: opts.refer, - retry: opts.retry != null ? opts.retry : { - retries: opts['fetch-retries'], - factor: opts['fetch-retry-factor'], - minTimeout: opts['fetch-retry-mintimeout'], - maxTimeout: opts['fetch-retry-maxtimeout'] + retry: opts.retry ? opts.retry : { + retries: opts.fetchRetries, + factor: opts.fetchRetryFactor, + minTimeout: opts.fetchRetryMintimeout, + maxTimeout: opts.fetchRetryMaxtimeout }, - strictSSL: !!opts['strict-ssl'], - timeout: opts.timeout + strictSSL: opts.strictSSL, + timeout: opts.timeout || 30 * 1000 }).then(res => checkResponse( method, res, registry, startTime, opts )) @@ -138,9 +135,9 @@ function fetchJSON (uri, opts) { } module.exports.json.stream = fetchJSONStream -function fetchJSONStream (uri, jsonPath, opts) { - opts = config(opts) - const parser = JSONStream.parse(jsonPath, opts.mapJson) +function fetchJSONStream (uri, jsonPath, /* istanbul ignore next */ opts_ = {}) { + const opts = { ...defaultOpts, ...opts_ } + const parser = JSONStream.parse(jsonPath, opts.mapJSON) regFetch(uri, opts).then(res => res.body.on('error', /* istanbul ignore next: unlikely and difficult to test */ @@ -150,9 +147,8 @@ function fetchJSONStream (uri, jsonPath, opts) { } module.exports.pickRegistry = pickRegistry -function pickRegistry (spec, opts) { +function pickRegistry (spec, opts = {}) { spec = npa(spec) - opts = config(opts) let registry = spec.scope && opts[spec.scope.replace(/^@?/, '@') + ':registry'] @@ -161,9 +157,7 @@ function pickRegistry (spec, opts) { } if (!registry) { - registry = opts.registry || - /* istanbul ignore next: default set by figgy pudding config */ - 'https://registry.npmjs.org/' + registry = opts.registry || 'https://registry.npmjs.org/' } return registry @@ -172,23 +166,21 @@ function pickRegistry (spec, opts) { function getCacheMode (opts) { return opts.offline ? 'only-if-cached' - : opts['prefer-offline'] + : opts.preferOffline ? 'force-cache' - : opts['prefer-online'] + : opts.preferOnline ? 'no-cache' : 'default' } function getHeaders (registry, uri, opts) { const headers = Object.assign({ - 'npm-in-ci': !!( - opts['is-from-ci'] || ciDetect() - ), - 'npm-scope': opts['project-scope'], - 'npm-session': opts['npm-session'], - 'user-agent': opts['user-agent'], + 'npm-in-ci': !!opts.isFromCI, + 'npm-scope': opts.projectScope, + 'npm-session': opts.npmSession, + 'user-agent': opts.userAgent, referer: opts.refer - }, opts.headers) + }, opts.headers || {}) const auth = getAuth(registry, opts) // If a tarball is hosted on a different place than the manifest, only send diff --git a/package-lock.json b/package-lock.json index bd7a5838..bc4eb0f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "npm-registry-fetch", - "version": "6.0.2", + "version": "7.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -416,28 +416,34 @@ "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" }, "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", + "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", "requires": { "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "minipass": "^3.0.0", + "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", + "mkdirp": "^1.0.3", "move-concurrently": "^1.0.1", "p-map": "^3.0.0", "promise-inflight": "^1.0.1", "rimraf": "^2.7.1", - "ssri": "^7.0.0", + "ssri": "^8.0.0", + "tar": "^6.0.1", "unique-filename": "^1.1.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", + "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==" + } } }, "caching-transform": { @@ -554,9 +560,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "clean-stack": { "version": "2.2.0", @@ -1911,11 +1917,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -2145,9 +2146,9 @@ "dev": true }, "fs-minipass": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz", - "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "requires": { "minipass": "^3.0.0" } @@ -2756,9 +2757,9 @@ } }, "http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-Z2EICWNJou7Tr9Bd2M2UqDJq3A9F2ePG9w3lIpjoyuSyXFP9QbniJVu3XQYytuw5ebmG7dXSXO9PgAjJG8DDKA==" }, "http-proxy-agent": { "version": "3.0.0", @@ -3515,13 +3516,13 @@ "dev": true }, "make-fetch-happen": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-7.1.0.tgz", - "integrity": "sha512-/5ICTcpd4ApIRn76pxcl4aQhrWxdDCnRDy3y+Tu7DbRsfqde6q8OYXUm7bYhH5dSey590AMT0RH9LDFq7v5KRA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.1.tgz", + "integrity": "sha512-oiK8xz6+IxaPqmOCW+rmlH922RTZ+fi4TAULGRih8ryqIju0x6WriDR3smm7Z+8NZRxDIK/iDLM096F/gLfiWg==", "requires": { "agentkeepalive": "^4.1.0", - "cacache": "^13.0.1", - "http-cache-semantics": "^4.0.3", + "cacache": "^15.0.0", + "http-cache-semantics": "^4.0.4", "http-proxy-agent": "^3.0.0", "https-proxy-agent": "^4.0.0", "is-lambda": "^1.0.1", @@ -3533,7 +3534,7 @@ "minipass-pipeline": "^1.2.2", "promise-retry": "^1.1.1", "socks-proxy-agent": "^4.0.0", - "ssri": "^7.0.1" + "ssri": "^8.0.0" } }, "map-obj": { @@ -4776,7 +4777,8 @@ "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4983,11 +4985,10 @@ } }, "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", "requires": { - "figgy-pudding": "^3.5.1", "minipass": "^3.1.1" } }, @@ -6357,6 +6358,31 @@ "yaml": "^1.5.0" } }, + "tar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", + "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", + "requires": { + "chownr": "^1.1.3", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", + "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "tcompare": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-3.0.4.tgz", diff --git a/package.json b/package.json index b685796f..ccc8e39c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "npm-registry-fetch", - "version": "6.0.2", + "version": "7.0.0", "description": "Fetch-based http client for use with npm registry APIs", "main": "index.js", "files": [ @@ -29,25 +29,22 @@ "license": "ISC", "dependencies": { "@npmcli/ci-detect": "^1.0.0", - "figgy-pudding": "^3.4.1", "lru-cache": "^5.1.1", - "make-fetch-happen": "^7.1.0", + "make-fetch-happen": "^8.0.1", "minipass": "^3.0.0", "minipass-fetch": "^1.1.2", "minipass-json-stream": "^1.0.1", "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0", - "safe-buffer": "^5.2.0", - "semver": "^7.0.0" + "npm-package-arg": "^8.0.0" }, "devDependencies": { - "cacache": "^13.0.1", + "cacache": "^15.0.0", "mkdirp": "^0.5.1", "nock": "^11.7.0", "npmlog": "^4.1.2", "require-inject": "^1.4.4", "rimraf": "^2.6.2", - "ssri": "^7.1.0", + "ssri": "^8.0.0", "standard": "^14.3.1", "standard-version": "^7.0.1", "tap": "^14.10.4" diff --git a/test/auth.js b/test/auth.js index 7c9f333d..54a6deec 100644 --- a/test/auth.js +++ b/test/auth.js @@ -1,7 +1,5 @@ 'use strict' -const Buffer = require('safe-buffer').Buffer - const npmlog = require('npmlog') const test = require('tap').test const tnock = require('./util/tnock.js') diff --git a/test/cache.js b/test/cache.js index 97f7092c..82f10d20 100644 --- a/test/cache.js +++ b/test/cache.js @@ -2,7 +2,6 @@ const { promisify } = require('util') const statAsync = promisify(require('fs').stat) -const config = require('../config.js') const npmlog = require('npmlog') const path = require('path') const test = require('tap').test @@ -14,7 +13,7 @@ const testDir = require('./util/test-dir.js')(__filename) npmlog.level = process.env.LOGLEVEL || 'silent' const REGISTRY = 'https://mock.reg' -const OPTS = config({ +const OPTS = { log: npmlog, memoize: false, timeout: 0, @@ -26,7 +25,7 @@ const OPTS = config({ }, cache: path.join(testDir, '_cacache'), registry: REGISTRY -}) +} test('can cache GET requests', t => { tnock(t, REGISTRY) @@ -35,22 +34,22 @@ test('can cache GET requests', t => { .reply(200, { obj: 'value' }) return fetch.json('/normal', OPTS) .then(val => t.deepEqual(val, { obj: 'value' }, 'got expected response')) - .then(() => statAsync(OPTS.get('cache'))) + .then(() => statAsync(OPTS.cache)) .then(stat => t.ok(stat.isDirectory(), 'cache directory created')) .then(() => fetch.json('/normal', OPTS)) .then(val => t.deepEqual(val, { obj: 'value' }, 'response was cached')) }) -test('prefer-offline', t => { +test('preferOffline', t => { tnock(t, REGISTRY) - .get('/prefer-offline') + .get('/preferOffline') .times(1) .reply(200, { obj: 'value' }) - return fetch.json('/prefer-offline', OPTS.concat({ 'prefer-offline': true })) + return fetch.json('/preferOffline', { ...OPTS, preferOffline: true }) .then(val => t.deepEqual(val, { obj: 'value' }, 'got expected response')) - .then(() => statAsync(OPTS.get('cache'))) + .then(() => statAsync(OPTS.cache)) .then(stat => t.ok(stat.isDirectory(), 'cache directory created')) - .then(() => fetch.json('/prefer-offline', OPTS.concat({ 'prefer-offline': true }))) + .then(() => fetch.json('/preferOffline', { ...OPTS, preferOffline: true })) .then(val => t.deepEqual(val, { obj: 'value' }, 'response was cached')) }) @@ -61,24 +60,24 @@ test('offline', t => { .reply(200, { obj: 'value' }) return fetch.json('/offline', OPTS) .then(val => t.deepEqual(val, { obj: 'value' }, 'got expected response')) - .then(() => statAsync(OPTS.get('cache'))) + .then(() => statAsync(OPTS.cache)) .then(stat => t.ok(stat.isDirectory(), 'cache directory created')) - .then(() => fetch.json('/offline', OPTS.concat({ offline: true }))) + .then(() => fetch.json('/offline', { ...OPTS, offline: true })) .then(val => t.deepEqual(val, { obj: 'value' }, 'response was cached')) }) test('offline fails if not cached', t => - t.rejects(() => fetch('/offline-fails', OPTS.concat({ offline: true })))) + t.rejects(() => fetch('/offline-fails', { ...OPTS, offline: true }))) -test('prefer-online', t => { +test('preferOnline', t => { tnock(t, REGISTRY) - .get('/prefer-online') + .get('/preferOnline') .times(2) .reply(200, { obj: 'value' }) - return fetch.json('/prefer-online', OPTS) + return fetch.json('/preferOnline', OPTS) .then(val => t.deepEqual(val, { obj: 'value' }, 'got expected response')) - .then(() => statAsync(OPTS.get('cache'))) + .then(() => statAsync(OPTS.cache)) .then(stat => t.ok(stat.isDirectory(), 'cache directory created')) - .then(() => fetch.json('/prefer-online', OPTS.concat({ 'prefer-online': true }))) + .then(() => fetch.json('/preferOnline', { ...OPTS, preferOnline: true })) .then(val => t.deepEqual(val, { obj: 'value' }, 'response was refetched')) }) diff --git a/test/check-response.js b/test/check-response.js index 1be14360..95566088 100644 --- a/test/check-response.js +++ b/test/check-response.js @@ -1,7 +1,6 @@ const { Readable } = require('stream') const test = require('tap').test -const config = require('../config.js') const checkResponse = require('../check-response.js') const errors = require('./errors.js') const silentLog = require('../silentlog.js') @@ -27,7 +26,16 @@ test('any response error should be silent', t => { status: 400 }) t.rejects(checkResponse('get', res, 'registry', Date.now(), { ignoreBody: true }), errors.HttpErrorGeneral) - t.done() + t.end() +}) + +test('all checks are ok, nothing to report', t => { + const res = Object.assign({}, mockFetchRes, { + buffer: () => Promise.resolve(Buffer.from('ok')), + status: 400 + }) + t.rejects(checkResponse('get', res, 'registry', Date.now()), errors.HttpErrorGeneral) + t.end() }) test('log x-fetch-attempts header value', t => { @@ -37,13 +45,13 @@ test('log x-fetch-attempts header value', t => { headers, status: 400 }) - t.rejects(checkResponse('get', res, 'registry', Date.now(), config({ + t.rejects(checkResponse('get', res, 'registry', Date.now(), { log: Object.assign({}, silentLog, { http (header, msg) { t.ok(msg.endsWith('attempt #3'), 'should log correct number of attempts') } }) - }))) + })) t.plan(2) }) @@ -56,12 +64,12 @@ test('bad-formatted warning headers', t => { const res = Object.assign({}, mockFetchRes, { headers }) - t.ok(checkResponse('get', res, 'registry', Date.now(), config({ + t.ok(checkResponse('get', res, 'registry', Date.now(), { log: Object.assign({}, silentLog, { warn (header, msg) { t.fail('should not log warnings') } }) - }))) + })) t.plan(1) }) diff --git a/test/config.js b/test/config.js deleted file mode 100644 index c64a295d..00000000 --- a/test/config.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict' - -const requireInject = require('require-inject') -const config = requireInject('../config.js', { - '@npmcli/ci-detect': () => false -}) -const npmlog = require('npmlog') -const { test } = require('tap') - -npmlog.level = process.env.LOGLEVEL || 'silent' - -test('isFromCI config option', t => { - const OPTS = config({ - log: npmlog, - timeout: 0, - registry: 'https://mock.reg/' - }) - t.equal(OPTS.isFromCI, false, 'should be false if not on a CI env') - t.end() -}) - -test('default timeout', t => { - const DEFAULT_OPTS = config({}) - t.equal(DEFAULT_OPTS.timeout, 30 * 1000, 'default timeout is 30s') - const SPECIFIED_OPTS = config({ - timeout: 15 * 1000 - }) - t.equal(SPECIFIED_OPTS.timeout, 15 * 1000, 'default timeout can be overridden') - t.end() -}) diff --git a/test/errors.js b/test/errors.js index 54d7de1a..e3ab7c5f 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,6 +1,5 @@ 'use strict' -const config = require('../config.js') const npa = require('npm-package-arg') const npmlog = require('npmlog') const test = require('tap').test @@ -9,7 +8,7 @@ const tnock = require('./util/tnock.js') const fetch = require('../index.js') npmlog.level = process.env.LOGLEVEL || 'silent' -const OPTS = config({ +const OPTS = { log: npmlog, timeout: 0, retry: { @@ -19,7 +18,7 @@ const OPTS = config({ maxTimeout: 10 }, registry: 'https://mock.reg/' -}) +} test('generic request errors', t => { tnock(t, OPTS.registry) @@ -69,9 +68,10 @@ test('pkgid with `opts.spec`', t => { tnock(t, OPTS.registry) .get('/ohno/_rewrite/ohyeah') .reply(400, 'failwhale!') - return fetch('/ohno/_rewrite/ohyeah', OPTS.concat({ + return fetch('/ohno/_rewrite/ohyeah', { + ...OPTS, spec: npa('foo@1.2.3') - })) + }) .then( () => { throw new Error('should not have succeeded!') }, err => t.equal(err.pkgid, 'foo@1.2.3', 'opts.spec used for pkgid') diff --git a/test/index.js b/test/index.js index ea251721..e618c3a6 100644 --- a/test/index.js +++ b/test/index.js @@ -1,8 +1,5 @@ 'use strict' -const Buffer = require('safe-buffer').Buffer - -const config = require('../config.js') const Minipass = require('minipass') const npmlog = require('npmlog') const silentLog = require('../silentlog.js') @@ -14,7 +11,7 @@ const zlib = require('zlib') const fetch = require('../index.js') npmlog.level = process.env.LOGLEVEL || 'silent' -const OPTS = config({ +const OPTS = { // just to make sure we hit the second branch when // we are ACTUALLY in CI isFromCI: false, @@ -27,13 +24,16 @@ const OPTS = config({ maxTimeout: 10 }, registry: 'https://mock.reg/' -}) +} test('hello world', t => { tnock(t, OPTS.registry) .get('/hello') .reply(200, { hello: 'world' }) - return fetch('/hello', OPTS) + return fetch('/hello', { + method: false, // will fall back to GET if falsey, + ...OPTS + }) .then(res => { t.equal(res.status, 200, 'got successful response') return res.json() @@ -54,10 +54,11 @@ test('JSON body param', t => { }, 'got the JSON version of the body') return reqBody }) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'POST', body: { hello: 'world' } - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200) @@ -81,10 +82,11 @@ test('buffer body param', t => { ) return reqBody }) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'POST', body: Buffer.from('hello', 'utf8') - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200) @@ -110,10 +112,11 @@ test('stream body param', t => { }) const stream = new Minipass() setImmediate(() => stream.end(JSON.stringify({ hello: 'world' }))) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'POST', body: stream - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200) @@ -135,11 +138,12 @@ test('JSON body param', t => { .post('/hello') // NOTE: can't really test the body itself here because nock freaks out. .reply(200) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'POST', body: { hello: 'world' }, gzip: true - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200, 'request succeeded') @@ -166,11 +170,12 @@ test('gzip + buffer body param', t => { ) return reqBody }) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'POST', body: Buffer.from('hello', 'utf8'), gzip: true - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200) @@ -201,7 +206,8 @@ test('gzip + stream body param', t => { }) const stream = new Minipass() setImmediate(() => stream.end(JSON.stringify({ hello: 'world' }))) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'POST', body: stream, gzip: true, @@ -209,7 +215,7 @@ test('gzip + stream body param', t => { everything: undefined, is: undefined } - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200) @@ -222,18 +228,20 @@ test('query strings', t => { tnock(t, OPTS.registry) .get('/hello?hi=there&who=wor%20ld') .reply(200, { hello: 'world' }) - return fetch.json('/hello?hi=there', OPTS.concat({ + return fetch.json('/hello?hi=there', { + ...OPTS, query: 'who=wor ld' - })).then(json => t.equal(json.hello, 'world', 'query-string merged')) + }).then(json => t.equal(json.hello, 'world', 'query-string merged')) }) test('query strings - undefined values', t => { tnock(t, OPTS.registry) .get('/hello?who=wor%20ld') .reply(200, { ok: true }) - return fetch.json('/hello', OPTS.concat({ + return fetch.json('/hello', { + ...OPTS, query: { hi: undefined, who: 'wor ld' } - })).then(json => t.ok(json.ok, 'undefined keys not included in query string')) + }).then(json => t.ok(json.ok, 'undefined keys not included in query string')) }) test('json()', t => { @@ -246,25 +254,25 @@ test('json()', t => { test('query string with ?write=true', t => { const cache = t.testdir() - const opts = OPTS.concat({ 'prefer-offline': true, cache }) - const qsString = opts.concat({ query: { write: 'true' } }) - const qsBool = opts.concat({ query: { write: true } }) + const opts = { ...OPTS, preferOffline: true, cache } + const qsString = { ...opts, query: { write: 'true' } } + const qsBool = { ...opts, query: { write: true } } tnock(t, opts.registry) - .get('/hello?write=true') + .get('/writeTrueTest?write=true') .times(6) .reply(200, { write: 'go for it' }) - return fetch.json('/hello?write=true', opts) + return fetch.json('/writeTrueTest?write=true', opts) .then(res => t.strictSame(res, { write: 'go for it' })) - .then(() => fetch.json('/hello?write=true', opts)) + .then(() => fetch.json('/writeTrueTest?write=true', opts)) .then(res => t.strictSame(res, { write: 'go for it' })) - .then(() => fetch.json('/hello', qsString)) + .then(() => fetch.json('/writeTrueTest', qsString)) .then(res => t.strictSame(res, { write: 'go for it' })) - .then(() => fetch.json('/hello', qsString)) + .then(() => fetch.json('/writeTrueTest', qsString)) .then(res => t.strictSame(res, { write: 'go for it' })) - .then(() => fetch.json('/hello', qsBool)) + .then(() => fetch.json('/writeTrueTest', qsBool)) .then(res => t.strictSame(res, { write: 'go for it' })) - .then(() => fetch.json('/hello', qsBool)) + .then(() => fetch.json('/writeTrueTest', qsBool)) .then(res => t.strictSame(res, { write: 'go for it' })) }) @@ -283,17 +291,18 @@ test('fetch.json.stream()', t => { }) }) -test('fetch.json.stream opts.mapJson', t => { +test('fetch.json.stream opts.mapJSON', t => { tnock(t, OPTS.registry).get('/hello').reply(200, { a: 1, b: 2, c: 3 }) - return fetch.json.stream('/hello', '*', OPTS.concat({ - mapJson (value, [key]) { + return fetch.json.stream('/hello', '*', { + ...OPTS, + mapJSON (value, [key]) { return [key, value] } - })).collect().then(data => { + }).collect().then(data => { t.deepEqual(data, [ ['a', 1], ['b', 2], @@ -303,11 +312,12 @@ test('fetch.json.stream opts.mapJson', t => { }) test('fetch.json.stream gets fetch error on stream', t => { - return t.rejects(fetch.json.stream('/hello', '*', OPTS.concat({ + return t.rejects(fetch.json.stream('/hello', '*', { + ...OPTS, body: Promise.reject(new Error('no body for you')), method: 'POST', gzip: true // make sure we don't gzip the promise, lol! - })).collect(), { + }).collect(), { message: 'no body for you' }) }) @@ -316,7 +326,7 @@ test('opts.ignoreBody', t => { tnock(t, OPTS.registry) .get('/hello') .reply(200, { hello: 'world' }) - return fetch('/hello', OPTS.concat({ ignoreBody: true })) + return fetch('/hello', { ...OPTS, ignoreBody: true }) .then(res => { t.equal(res.body, null, 'body omitted') }) @@ -326,9 +336,10 @@ test('method configurable', t => { tnock(t, OPTS.registry) .delete('/hello') .reply(200) - const opts = OPTS.concat({ + const opts = { + ...OPTS, method: 'DELETE' - }) + } return fetch('/hello', opts) .then(res => { t.equal(res.status, 200, 'successfully used DELETE method') @@ -341,14 +352,15 @@ test('npm-notice header logging', t => { .reply(200, { hello: 'world' }, { 'npm-notice': 'npm <3 u' }) - const opts = OPTS.concat({ + const opts = { + ...OPTS, log: Object.assign({}, silentLog, { notice (header, msg) { t.equal(header, '', 'empty log header thing') t.equal(msg, 'npm <3 u', 'logged out npm-notice at NOTICE level') } }) - }) + } t.plan(3) return fetch('/hello', opts) .then(res => t.equal(res.status, 200, 'got successful response')) @@ -361,13 +373,13 @@ test('optionally verifies request body integrity', t => { .times(2) .reply(200, 'hello') const integrity = ssri.fromData('hello') - return fetch('/hello', OPTS.concat({ integrity })) + return fetch('/hello', { ...OPTS, integrity }) .then(res => res.buffer()) .then(buf => t.equal( buf.toString('utf8'), 'hello', 'successfully got the right data') ) .then(() => { - return fetch('/hello', OPTS.concat({ integrity: 'sha1-nope' })) + return fetch('/hello', { ...OPTS, integrity: 'sha1-nope' }) .then(res => { t.ok(res.body, 'no error until body starts getting read') return res @@ -422,18 +434,20 @@ test('pickRegistry through opts.spec', t => { .get('/pkg') .times(2) .reply(200, { source: scopedReg }) - return fetch.json('/pkg', OPTS.concat({ + return fetch.json('/pkg', { + ...OPTS, spec: 'pkg@1.2.3', '@myscope:registry': scopedReg - })).then(json => t.equal( + }).then(json => t.equal( json.source, OPTS.registry, 'request made to main registry' - )).then(() => fetch.json('/pkg', OPTS.concat({ + )).then(() => fetch.json('/pkg', { + ...OPTS, spec: 'pkg@1.2.3', '@myscope:registry': scopedReg, scope: '@myscope' - }))).then(json => t.equal( + })).then(json => t.equal( json.source, scopedReg, 'request made to scope registry using opts.scope' @@ -451,14 +465,15 @@ test('log warning header info', t => { tnock(t, OPTS.registry) .get('/hello') .reply(200, { hello: 'world' }, { Warning: '199 - "ENOTFOUND" "Wed, 21 Oct 2015 07:28:00 GMT"' }) - const opts = OPTS.concat({ + const opts = { + ...OPTS, log: Object.assign({}, silentLog, { warn (header, msg) { t.equal(header, 'registry', 'expected warn log header') t.equal(msg, `Using stale data from ${OPTS.registry} because the host is inaccessible -- are you offline?`, 'logged out at WARNING level') } }) - }) + } t.plan(3) return fetch('/hello', opts) .then(res => t.equal(res.status, 200, 'got successful response'))