diff --git a/CHANGELOG.md b/CHANGELOG.md index 888c799bee253..77322b9f587ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +## v7.5.3 (2021-02-08) + +### BUG FIXES + +* [`df596bf4c`](https://github.com/npm/cli/commit/df596bf4c10d6917672579cc38800f5e846002bc) + fix(publish): follow all configs for registry auth check + [#2602](https://github.com/npm/cli/issues/2602) + ([@wraithgar](https://github.com/wraithgar)) +* [`6d7afb03c`](https://github.com/npm/cli/commit/6d7afb03cd7602b60e709516711a2f94cd61ff25) + [#2613](https://github.com/npm/cli/issues/2613) + install script: pass -q to curl calls to disable user .curlrc files + ([@nlf](https://github.com/nlf)) + +### DEPENDENCIES + +* [`3294fed6f`](https://github.com/npm/cli/commit/3294fed6f18626516978c21fac5f28ecfdb58124) + `pacote@11.2.5` + * prevent infinite recursion in git dep preparation +* [`0f7a3a87c`](https://github.com/npm/cli/commit/0f7a3a87c12e30bdd2cdab596ca6511de787c969) + `read-package-json-fast@2.0.1` + * avoid duplicating optionalDependencies as dependencies in package.json +* [`6f46b0f7f`](https://github.com/npm/cli/commit/6f46b0f7fef9891e6de4af3547c70a67cb3a7a13) + `init-package-json@2.0.2` +* [`df4f65acc`](https://github.com/npm/cli/commit/df4f65acc4ceaf15db4c227670e80f94584c055c) + `@npmcli/arborist@2.2.0` +* [`7038c2ff4`](https://github.com/npm/cli/commit/7038c2ff49022f8babd495d1b831b5c82d6aed05) + `@npmcli/run-script@1.8.2` +* [`54cd4c87a`](https://github.com/npm/cli/commit/54cd4c87a71c9381519d8ac52e306096066dc92e) + `libnpmversion@1.0.8` +* [`9ab36aae4`](https://github.com/npm/cli/commit/9ab36aae429784df754211d5f086a515012b9bdd) + `graceful-fs@4.2.5` +* [`e1822cf27`](https://github.com/npm/cli/commit/e1822cf277336728f1d5696ffe0db3ea6e700d9e) + `@npmcli/installed-package-contents@1.0.7` + ## v7.5.2 (2021-02-02) ### BUG FIXES diff --git a/README.md b/README.md index 9350087f62cd1..3b6b30513db5d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can download & install **`npm`** directly from [**npmjs**.com](https://npmjs.com/) using our custom `install.sh` script: ```bash -curl -L https://www.npmjs.com/install.sh | sh +curl -qL https://www.npmjs.com/install.sh | sh ``` #### Node Version Managers @@ -50,4 +50,4 @@ npm ### Acknowledgments * `npm` is configured to use the **npm Public Registry** at [https://registry.npmjs.org](https://registry.npmjs.org) by default; Usage of this registry is subject to **Terms of Use** available at [https://npmjs.com/policies/terms](https://npmjs.com/policies/terms) -* You can configure `npm` to use any other compatible registry you prefer. You can read more about configuring third-party registries [here](https://docs.npmjs.com/cli/v7/using-npm/registry) \ No newline at end of file +* You can configure `npm` to use any other compatible registry you prefer. You can read more about configuring third-party registries [here](https://docs.npmjs.com/cli/v7/using-npm/registry) diff --git a/lib/help.js b/lib/help.js index 171c52704df6c..f6996166542f9 100644 --- a/lib/help.js +++ b/lib/help.js @@ -8,22 +8,22 @@ help.completion = function (opts, cb) { } const npmUsage = require('./utils/npm-usage.js') -var path = require('path') -var spawn = require('./utils/spawn') -var npm = require('./npm.js') -var log = require('npmlog') -var openUrl = require('./utils/open-url') -var glob = require('glob') -var output = require('./utils/output.js') +const { spawn } = require('child_process') +const path = require('path') +const npm = require('./npm.js') +const log = require('npmlog') +const openUrl = require('./utils/open-url') +const glob = require('glob') +const output = require('./utils/output.js') const usage = require('./utils/usage.js') help.usage = usage('help', 'npm help []') function help (args, cb) { - var argv = npm.config.parsedArgv.cooked + const argv = npm.config.parsedArgv.cooked - var argnum = 0 + let argnum = 0 if (args.length === 2 && ~~args[0]) argnum = ~~args.shift() @@ -34,7 +34,7 @@ function help (args, cb) { const affordances = { 'find-dupes': 'dedupe', } - var section = affordances[args[0]] || npm.deref(args[0]) || args[0] + let section = affordances[args[0]] || npm.deref(args[0]) || args[0] // npm help : show basic usage if (!section) { @@ -52,15 +52,12 @@ function help (args, cb) { return cb() } - var pref = [1, 5, 7] - if (argnum) { - pref = [argnum].concat(pref.filter(function (n) { - return n !== argnum - })) - } + let pref = [1, 5, 7] + if (argnum) + pref = [argnum].concat(pref.filter(n => n !== argnum)) // npm help
: Try to find the path - var manroot = path.resolve(__dirname, '..', 'man') + const manroot = path.resolve(__dirname, '..', 'man') // legacy if (section === 'global') @@ -71,18 +68,18 @@ function help (args, cb) { // find either /section.n or /npm-section.n // The glob is used in the glob. The regexp is used much // further down. Globs and regexps are different - var compextglob = '.+(gz|bz2|lzma|[FYzZ]|xz)' - var compextre = '\\.(gz|bz2|lzma|[FYzZ]|xz)$' - var f = '+(npm-' + section + '|' + section + ').[0-9]?(' + compextglob + ')' - return glob(manroot + '/*/' + f, function (er, mans) { + const compextglob = '.+(gz|bz2|lzma|[FYzZ]|xz)' + const compextre = '\\.(gz|bz2|lzma|[FYzZ]|xz)$' + const f = '+(npm-' + section + '|' + section + ').[0-9]?(' + compextglob + ')' + return glob(manroot + '/*/' + f, (er, mans) => { if (er) return cb(er) if (!mans.length) return npm.commands['help-search'](args, cb) - mans = mans.map(function (man) { - var ext = path.extname(man) + mans = mans.map((man) => { + const ext = path.extname(man) if (man.match(new RegExp(compextre))) man = path.basename(man, ext) @@ -94,14 +91,12 @@ function help (args, cb) { } function pickMan (mans, pref_) { - var nre = /([0-9]+)$/ - var pref = {} - pref_.forEach(function (sect, i) { - pref[sect] = i - }) - mans = mans.sort(function (a, b) { - var an = a.match(nre)[1] - var bn = b.match(nre)[1] + const nre = /([0-9]+)$/ + const pref = {} + pref_.forEach((sect, i) => pref[sect] = i) + mans = mans.sort((a, b) => { + const an = a.match(nre)[1] + const bn = b.match(nre)[1] return an === bn ? (a > b ? -1 : 1) : pref[an] < pref[bn] ? -1 : 1 @@ -110,48 +105,61 @@ function pickMan (mans, pref_) { } function viewMan (man, cb) { - var nre = /([0-9]+)$/ - var num = man.match(nre)[1] - var section = path.basename(man, '.' + num) + const nre = /([0-9]+)$/ + const num = man.match(nre)[1] + const section = path.basename(man, '.' + num) // at this point, we know that the specified man page exists - var manpath = path.join(__dirname, '..', 'man') - var env = {} + const manpath = path.join(__dirname, '..', 'man') + const env = {} Object.keys(process.env).forEach(function (i) { env[i] = process.env[i] }) env.MANPATH = manpath - var viewer = npm.config.get('viewer') + const viewer = npm.config.get('viewer') + + const opts = { + env, + stdio: 'inherit', + } - var conf + let bin = 'man' + const args = [] switch (viewer) { case 'woman': - var a = ['-e', '(woman-find-file \'' + man + '\')'] - conf = { env: env, stdio: 'inherit' } - var woman = spawn('emacsclient', a, conf) - woman.on('close', cb) + bin = 'emacsclient' + args.push('-e', `(woman-find-file '${man}')`) break case 'browser': + bin = false try { - var url = htmlMan(man) + const url = htmlMan(man) + openUrl(url, 'help available at the following URL', cb) } catch (err) { return cb(err) } - openUrl(url, 'help available at the following URL', cb) break default: - conf = { env: env, stdio: 'inherit' } - var manProcess = spawn('man', [num, section], conf) - manProcess.on('close', cb) + args.push(num, section) break } + + if (bin) { + const proc = spawn(bin, args, opts) + proc.on('exit', (code) => { + if (code) + return cb(new Error(`help process exited with code: ${code}`)) + + return cb() + }) + } } function htmlMan (man) { - var sect = +man.match(/([0-9]+)$/)[1] - var f = path.basename(man).replace(/[.]([0-9]+)$/, '') + let sect = +man.match(/([0-9]+)$/)[1] + const f = path.basename(man).replace(/[.]([0-9]+)$/, '') switch (sect) { case 1: sect = 'commands' @@ -169,7 +177,7 @@ function htmlMan (man) { } function getSections (cb) { - var g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]') + const g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]') glob(g, function (er, files) { if (er) return cb(er) diff --git a/lib/ls.js b/lib/ls.js index 153759d83815e..603c3b412ddc5 100644 --- a/lib/ls.js +++ b/lib/ls.js @@ -163,7 +163,10 @@ const getJsonOutputItem = (node, { global, long }) => { Object.assign(item, packageInfo) item.extraneous = false item.path = node.path - item._dependencies = node.package.dependencies || {} + item._dependencies = { + ...node.package.dependencies, + ...node.package.optionalDependencies, + } item.devDependencies = node.package.devDependencies || {} item.peerDependencies = node.package.peerDependencies || {} } diff --git a/lib/publish.js b/lib/publish.js index 49b2088070e7a..190d381a8aeeb 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -6,6 +6,7 @@ const libpub = require('libnpmpublish').publish const runScript = require('@npmcli/run-script') const pacote = require('pacote') const npa = require('npm-package-arg') +const npmFetch = require('npm-registry-fetch') const npm = require('./npm.js') const output = require('./utils/output.js') @@ -71,27 +72,12 @@ const publish_ = async (arg, opts) => { // you can publish name@version, ./foo.tgz, etc. // even though the default is the 'file:.' cwd. const spec = npa(arg) - const manifest = await getManifest(spec, opts) + + let manifest = await getManifest(spec, opts) if (manifest.publishConfig) Object.assign(opts, publishConfigToOpts(manifest.publishConfig)) - const { registry } = opts - if (!registry) { - throw Object.assign(new Error('No registry specified.'), { - code: 'ENOREGISTRY', - }) - } - - if (!dryRun) { - const creds = npm.config.getCredentialsByURI(registry) - if (!creds.token && !creds.username) { - throw Object.assign(new Error('This command requires you to be logged in.'), { - code: 'ENEEDAUTH', - }) - } - } - // only run scripts for directory type publishes if (spec.type === 'directory') { await runScript({ @@ -105,18 +91,27 @@ const publish_ = async (arg, opts) => { const tarballData = await pack(spec, opts) const pkgContents = await getContents(manifest, tarballData) + // The purpose of re-reading the manifest is in case it changed, + // so that we send the latest and greatest thing to the registry + // note that publishConfig might have changed as well! + manifest = await getManifest(spec, opts) + if (manifest.publishConfig) + Object.assign(opts, publishConfigToOpts(manifest.publishConfig)) + // note that logTar calls npmlog.notice(), so if we ARE in silent mode, // this will do nothing, but we still want it in the debuglog if it fails. if (!json) logTar(pkgContents, { log, unicode }) if (!dryRun) { - // The purpose of re-reading the manifest is in case it changed, - // so that we send the latest and greatest thing to the registry - // note that publishConfig might have changed as well! - const manifest = await getManifest(spec, opts) - if (manifest.publishConfig) - Object.assign(opts, publishConfigToOpts(manifest.publishConfig)) + const resolved = npa.resolve(manifest.name, manifest.version) + const registry = npmFetch.pickRegistry(resolved, opts) + const creds = npm.config.getCredentialsByURI(registry) + if (!creds.token && !creds.username) { + throw Object.assign(new Error('This command requires you to be logged in.'), { + code: 'ENEEDAUTH', + }) + } await otplease(opts, opts => libpub(manifest, tarballData, opts)) } diff --git a/lib/utils/no-progress-while-running.js b/lib/utils/no-progress-while-running.js deleted file mode 100644 index c2e6a01b2396d..0000000000000 --- a/lib/utils/no-progress-while-running.js +++ /dev/null @@ -1,25 +0,0 @@ -var log = require('npmlog') -var progressEnabled -var running = 0 - -var startRunning = exports.startRunning = function () { - if (progressEnabled == null) - progressEnabled = log.progressEnabled - if (progressEnabled) - log.disableProgress() - ++running -} - -var stopRunning = exports.stopRunning = function () { - --running - if (progressEnabled && running === 0) - log.enableProgress() -} - -exports.tillDone = function noProgressTillDone (cb) { - startRunning() - return function () { - stopRunning() - cb.apply(this, arguments) - } -} diff --git a/lib/utils/pulse-till-done.js b/lib/utils/pulse-till-done.js index 13147bae16613..a88b8aacd862b 100644 --- a/lib/utils/pulse-till-done.js +++ b/lib/utils/pulse-till-done.js @@ -1,41 +1,26 @@ const log = require('npmlog') -let pulsers = 0 -let pulse +let pulseTimer = null +const withPromise = async (promise) => { + pulseStart() + try { + return await promise + } finally { + pulseStop() + } +} -function pulseStart (prefix) { - if (++pulsers > 1) - return - pulse = setInterval(function () { - log.gauge.pulse(prefix) +const pulseStart = () => { + pulseTimer = pulseTimer || setInterval(() => { + log.gauge.pulse('') }, 150) } -function pulseStop () { - if (--pulsers > 0) - return - clearInterval(pulse) -} -module.exports = function (prefix, cb) { - if (!prefix) - prefix = 'network' - pulseStart(prefix) - return (er, ...args) => { - pulseStop() - cb(er, ...args) - } +const pulseStop = () => { + clearInterval(pulseTimer) + pulseTimer = null } -const pulseWhile = async (prefix, promise) => { - if (!promise) { - promise = prefix - prefix = '' - } - pulseStart(prefix) - try { - return await promise - } finally { - pulseStop() - } +module.exports = { + withPromise, } -module.exports.withPromise = pulseWhile diff --git a/lib/utils/read-user-info.js b/lib/utils/read-user-info.js index b0166e18c90df..e3c4a9fbe51ca 100644 --- a/lib/utils/read-user-info.js +++ b/lib/utils/read-user-info.js @@ -8,21 +8,21 @@ exports.password = readPassword exports.username = readUsername exports.email = readEmail +const otpPrompt = `This command requires a one-time password (OTP) from your authenticator app. +Enter one below. You can also pass one on the command line by appending --otp=123456. +For more information, see: +https://docs.npmjs.com/getting-started/using-two-factor-authentication +Enter OTP: ` +const passwordPrompt = 'npm password: ' +const usernamePrompt = 'npm username: ' +const emailPrompt = 'email (this IS public): ' + function read (opts) { log.clearProgress() return readAsync(opts).finally(() => log.showProgress()) } -function readOTP (msg, otp, isRetry) { - if (!msg) { - msg = [ - 'This command requires a one-time password (OTP) from your authenticator app.', - 'Enter one below. You can also pass one on the command line by appending --otp=123456.', - 'For more information, see:', - 'https://docs.npmjs.com/getting-started/using-two-factor-authentication', - 'Enter OTP: ', - ].join('\n') - } +function readOTP (msg = otpPrompt, otp, isRetry) { if (isRetry && otp && /^[\d ]+$|^[A-Fa-f0-9]{64,64}$/.test(otp)) return otp.replace(/\s+/g, '') @@ -30,9 +30,7 @@ function readOTP (msg, otp, isRetry) { .then((otp) => readOTP(msg, otp, true)) } -function readPassword (msg, password, isRetry) { - if (!msg) - msg = 'npm password: ' +function readPassword (msg = passwordPrompt, password, isRetry) { if (isRetry && password) return password @@ -40,9 +38,7 @@ function readPassword (msg, password, isRetry) { .then((password) => readPassword(msg, password, true)) } -function readUsername (msg, username, opts, isRetry) { - if (!msg) - msg = 'npm username: ' +function readUsername (msg = usernamePrompt, username, opts = {}, isRetry) { if (isRetry && username) { const error = userValidate.username(username) if (error) @@ -55,9 +51,7 @@ function readUsername (msg, username, opts, isRetry) { .then((username) => readUsername(msg, username, opts, true)) } -function readEmail (msg, email, opts, isRetry) { - if (!msg) - msg = 'email (this IS public): ' +function readEmail (msg = emailPrompt, email, opts = {}, isRetry) { if (isRetry && email) { const error = userValidate.email(email) if (error) diff --git a/lib/utils/spawn.js b/lib/utils/spawn.js deleted file mode 100644 index 3bbe18384bd3c..0000000000000 --- a/lib/utils/spawn.js +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = spawn - -var _spawn = require('child_process').spawn -var EventEmitter = require('events').EventEmitter -var npwr = require('./no-progress-while-running.js') - -function willCmdOutput (stdio) { - if (stdio === 'inherit') - return true - if (!Array.isArray(stdio)) - return false - for (var fh = 1; fh <= 2; ++fh) { - if (stdio[fh] === 'inherit') - return true - if (stdio[fh] === 1 || stdio[fh] === 2) - return true - } - return false -} - -function spawn (cmd, args, options) { - var cmdWillOutput = willCmdOutput(options && options.stdio) - - if (cmdWillOutput) - npwr.startRunning() - var raw = _spawn(cmd, args, options) - var cooked = new EventEmitter() - - raw.on('error', function (er) { - if (cmdWillOutput) - npwr.stopRunning() - er.file = cmd - cooked.emit('error', er) - }).on('close', function (code, signal) { - if (cmdWillOutput) - npwr.stopRunning() - // Create ENOENT error because Node.js v0.8 will not emit - // an `error` event if the command could not be found. - if (code === 127) { - var er = new Error('spawn ENOENT') - er.code = 'ENOENT' - er.errno = 'ENOENT' - er.syscall = 'spawn' - er.file = cmd - cooked.emit('error', er) - } else - cooked.emit('close', code, signal) - }) - - cooked.stdin = raw.stdin - cooked.stdout = raw.stdout - cooked.stderr = raw.stderr - cooked.kill = function (sig) { - return raw.kill(sig) - } - - return cooked -} diff --git a/node_modules/@npmcli/arborist/bin/actual.js b/node_modules/@npmcli/arborist/bin/actual.js new file mode 100644 index 0000000000000..ef254e1d4133d --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/actual.js @@ -0,0 +1,21 @@ +const Arborist = require('../') +const print = require('./lib/print-tree.js') +const options = require('./lib/options.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const start = process.hrtime() +new Arborist(options).loadActual(options).then(tree => { + const end = process.hrtime(start) + if (!process.argv.includes('--quiet')) + print(tree) + + console.error(`read ${tree.inventory.size} deps in ${end[0] * 1000 + end[1] / 1e6}ms`) + if (options.save) + tree.meta.save() + if (options.saveHidden) { + tree.meta.hiddenLockfile = true + tree.meta.filename = options.path + '/node_modules/.package-lock.json' + tree.meta.save() + } +}).catch(er => console.error(er)) diff --git a/node_modules/@npmcli/arborist/bin/audit.js b/node_modules/@npmcli/arborist/bin/audit.js new file mode 100644 index 0000000000000..5075724e2d471 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/audit.js @@ -0,0 +1,48 @@ +const Arborist = require('../') + +const print = require('./lib/print-tree.js') +const options = require('./lib/options.js') +require('./lib/timers.js') +require('./lib/logging.js') + +const Vuln = require('../lib/vuln.js') +const printReport = report => { + for (const vuln of report.values()) + console.log(printVuln(vuln)) + if (report.topVulns.size) { + console.log('\n# top-level vulnerabilities') + for (const vuln of report.topVulns.values()) + console.log(printVuln(vuln)) + } +} + +const printVuln = vuln => { + return { + __proto__: { constructor: Vuln }, + name: vuln.name, + issues: [...vuln.advisories].map(a => printAdvisory(a)), + range: vuln.simpleRange, + nodes: [...vuln.nodes].map(node => `${node.name} ${node.location || '#ROOT'}`), + ...(vuln.topNodes.size === 0 ? {} : { + topNodes: [...vuln.topNodes].map(node => `${node.location || '#ROOT'}`), + }), + } +} + +const printAdvisory = a => `${a.title}${a.url ? ' ' + a.url : ''}` + +const start = process.hrtime() +process.emit('time', 'audit script') +const arb = new Arborist(options) +arb.audit(options).then(tree => { + process.emit('timeEnd', 'audit script') + const end = process.hrtime(start) + if (options.fix) + print(tree) + if (!options.quiet) + printReport(arb.auditReport) + if (options.fix) + console.error(`resolved ${tree.inventory.size} deps in ${end[0] + end[1] / 1e9}s`) + if (tree.meta && options.save) + tree.meta.save() +}).catch(er => console.error(er)) diff --git a/node_modules/@npmcli/arborist/bin/funding.js b/node_modules/@npmcli/arborist/bin/funding.js new file mode 100644 index 0000000000000..fa1237e87e98a --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/funding.js @@ -0,0 +1,32 @@ +const options = require('./lib/options.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const Arborist = require('../') +const a = new Arborist(options) +const query = options._.shift() +const start = process.hrtime() +a.loadVirtual().then(tree => { + // only load the actual tree if the virtual one doesn't have modern metadata + if (!tree.meta || !(tree.meta.originalLockfileVersion >= 2)) { + console.error('old metadata, load actual') + throw 'load actual' + } else { + console.error('meta ok, return virtual tree') + return tree + } +}).catch(() => a.loadActual()).then(tree => { + const end = process.hrtime(start) + if (!query) { + for (const node of tree.inventory.values()) { + if (node.package.funding) + console.log(node.name, node.location, node.package.funding) + } + } else { + for (const node of tree.inventory.query('name', query)) { + if (node.package.funding) + console.log(node.name, node.location, node.package.funding) + } + } + console.error(`read ${tree.inventory.size} deps in ${end[0] * 1000 + end[1] / 1e6}ms`) +}) diff --git a/node_modules/@npmcli/arborist/bin/ideal.js b/node_modules/@npmcli/arborist/bin/ideal.js new file mode 100644 index 0000000000000..18a5b9eb31086 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/ideal.js @@ -0,0 +1,68 @@ +const Arborist = require('../') + +const options = require('./lib/options.js') +const print = require('./lib/print-tree.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const c = require('chalk') + +const whichIsA = (name, dependents, indent = ' ') => { + if (!dependents || dependents.length === 0) + return '' + const str = `\nfor: ` + + dependents.map(dep => { + return dep.more ? `${dep.more} more (${dep.names.join(', ')})` + : `${dep.type} dependency ` + + `${c.bold(name)}@"${c.bold(dep.spec)}"` + `\nfrom:` + + (dep.from.location ? (dep.from.name + ? ` ${c.bold(dep.from.name)}@${c.bold(dep.from.version)} ` + + c.dim(`at ${dep.from.location}`) + : ' the root project') + : ` ${c.bold(dep.from.name)}@${c.bold(dep.from.version)}`) + + whichIsA(dep.from.name, dep.from.dependents, ' ') + }).join('\nand: ') + + return str.split(/\n/).join(`\n${indent}`) +} + +const explainEresolve = ({ dep, current, peerConflict, fixWithForce }) => { + return (!dep.whileInstalling ? '' : `While resolving: ` + + `${c.bold(dep.whileInstalling.name)}@${c.bold(dep.whileInstalling.version)}\n`) + + + `Found: ` + + `${c.bold(current.name)}@${c.bold(current.version)} ` + + c.dim(`at ${current.location}`) + + `${whichIsA(current.name, current.dependents)}` + + + `\n\nCould not add conflicting dependency: ` + + `${c.bold(dep.name)}@${c.bold(dep.version)} ` + + c.dim(`at ${dep.location}`) + + `${whichIsA(dep.name, dep.dependents)}\n` + + + (!peerConflict ? '' : + `\nConflicting peer dependency: ` + + `${c.bold(peerConflict.name)}@${c.bold(peerConflict.version)} ` + + c.dim(`at ${peerConflict.location}`) + + `${whichIsA(peerConflict.name, peerConflict.dependents)}\n` + ) + + + `\nFix the upstream dependency conflict, or +run this command with --legacy-peer-deps${ + fixWithForce ? ' or --force' : ''} +to accept an incorrect (and potentially broken) dependency resolution. +` +} + +const start = process.hrtime() +new Arborist(options).buildIdealTree(options).then(tree => { + const end = process.hrtime(start) + print(tree) + console.error(`resolved ${tree.inventory.size} deps in ${end[0] + end[1] / 10e9}s`) + if (tree.meta && options.save) + tree.meta.save() +}).catch(er => { + console.error(er) + if (er.code === 'ERESOLVE') + console.error(explainEresolve(er)) +}) diff --git a/node_modules/@npmcli/arborist/bin/index.js b/node_modules/@npmcli/arborist/bin/index.js new file mode 100755 index 0000000000000..3cedc91d73565 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/index.js @@ -0,0 +1,77 @@ +#!/usr/bin/env node +const [cmd] = process.argv.splice(2, 1) + +const usage = () => `Arborist - the npm tree doctor + +Version: ${require('../package.json').version} + +# USAGE + arborist [path] [options...] + +# COMMANDS + +* reify: reify ideal tree to node_modules (install, update, rm, ...) +* ideal: generate and print the ideal tree +* actual: read and print the actual tree in node_modules +* virtual: read and print the virtual tree in the local shrinkwrap file +* shrinkwrap: load a local shrinkwrap and print its data +* audit: perform a security audit on project dependencies +* funding: query funding information in the local package tree. A second + positional argument after the path name can limit to a package name. +* license: query license information in the local package tree. A second + positional argument after the path name can limit to a license type. +* help: print this text + +# OPTIONS + +Most npm options are supported, but in camelCase rather than css-case. For +example, instead of '--dry-run', use '--dryRun'. + +Additionally: + +* --quiet will supppress the printing of package trees +* Instead of 'npm install ', use 'arborist reify --add='. + The '--add=' option can be specified multiple times. +* Instead of 'npm rm ', use 'arborist reify --rm='. + The '--rm=' option can be specified multiple times. +* Instead of 'npm update', use 'arborist reify --update-all'. +* 'npm audit fix' is 'arborist audit --fix' +` + +const help = () => console.log(usage()) + +switch (cmd) { + case 'actual': + require('./actual.js') + break + case 'virtual': + require('./virtual.js') + break + case 'ideal': + require('./ideal.js') + break + case 'reify': + require('./reify.js') + break + case 'audit': + require('./audit.js') + break + case 'funding': + require('./funding.js') + break + case 'license': + require('./license.js') + break + case 'shrinkwrap': + require('./shrinkwrap.js') + break + case 'help': + case '-h': + case '--help': + help() + break + default: + process.exitCode = 1 + console.error(usage()) + break +} diff --git a/node_modules/@npmcli/arborist/bin/lib/logging.js b/node_modules/@npmcli/arborist/bin/lib/logging.js new file mode 100644 index 0000000000000..57597b2e509e4 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/lib/logging.js @@ -0,0 +1,33 @@ +const options = require('./options.js') +const { quiet = false } = options +const { loglevel = quiet ? 'warn' : 'silly' } = options + +const levels = [ + 'silly', + 'verbose', + 'info', + 'timing', + 'http', + 'notice', + 'warn', + 'error', + 'silent', +] + +const levelMap = new Map(levels.reduce((set, level, index) => { + set.push([level, index], [index, level]) + return set +}, [])) + +const { inspect, format } = require('util') +if (loglevel !== 'silent') { + process.on('log', (level, ...args) => { + if (levelMap.get(level) < levelMap.get(loglevel)) + return + const pref = `${process.pid} ${level} ` + if (level === 'warn' && args[0] === 'ERESOLVE') + args[2] = inspect(args[2], { depth: Infinity }) + const msg = pref + format(...args).trim().split('\n').join(`\n${pref}`) + console.error(msg) + }) +} diff --git a/node_modules/@npmcli/arborist/bin/lib/options.js b/node_modules/@npmcli/arborist/bin/lib/options.js new file mode 100644 index 0000000000000..8f0dc2f120324 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/lib/options.js @@ -0,0 +1,49 @@ +const options = module.exports = { + path: undefined, + cache: `${process.env.HOME}/.npm/_cacache`, + _: [], +} + +for (const arg of process.argv.slice(2)) { + if (/^--add=/.test(arg)) { + options.add = options.add || [] + options.add.push(arg.substr('--add='.length)) + } else if (/^--rm=/.test(arg)) { + options.rm = options.rm || [] + options.rm.push(arg.substr('--rm='.length)) + } else if (arg === '--global') + options.global = true + else if (arg === '--global-style') + options.globalStyle = true + else if (arg === '--prefer-dedupe') + options.preferDedupe = true + else if (arg === '--legacy-peer-deps') + options.legacyPeerDeps = true + else if (arg === '--force') + options.force = true + else if (arg === '--update-all') { + options.update = options.update || {} + options.update.all = true + } else if (/^--update=/.test(arg)) { + options.update = options.update || {} + options.update.names = options.update.names || [] + options.update.names.push(arg.substr('--update='.length)) + } else if (/^--omit=/.test(arg)) { + options.omit = options.omit || [] + options.omit.push(arg.substr('--omit='.length)) + } else if (/^--[^=]+=/.test(arg)) { + const [key, ...v] = arg.replace(/^--/, '').split('=') + const val = v.join('=') + options[key] = val === 'false' ? false : val === 'true' ? true : val + } else if (/^--.+/.test(arg)) + options[arg.replace(/^--/, '')] = true + else if (options.path === undefined) + options.path = arg + else + options._.push(arg) +} + +if (options.path === undefined) + options.path = '.' + +console.error(options) diff --git a/node_modules/@npmcli/arborist/bin/lib/print-tree.js b/node_modules/@npmcli/arborist/bin/lib/print-tree.js new file mode 100644 index 0000000000000..1ea2a72187332 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/lib/print-tree.js @@ -0,0 +1,5 @@ +const { inspect } = require('util') +const { quiet } = require('./options.js') + +module.exports = quiet ? () => {} + : tree => console.log(inspect(tree.toJSON(), { depth: Infinity })) diff --git a/node_modules/@npmcli/arborist/bin/lib/timers.js b/node_modules/@npmcli/arborist/bin/lib/timers.js new file mode 100644 index 0000000000000..3b73c0bf6ddd3 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/lib/timers.js @@ -0,0 +1,22 @@ +const timers = Object.create(null) + +process.on('time', name => { + if (timers[name]) + throw new Error('conflicting timer! ' + name) + timers[name] = process.hrtime() +}) + +process.on('timeEnd', name => { + if (!timers[name]) + throw new Error('timer not started! ' + name) + const res = process.hrtime(timers[name]) + delete timers[name] + console.error(`${process.pid} ${name}`, res[0] * 1e3 + res[1] / 1e6) +}) + +process.on('exit', () => { + for (const name of Object.keys(timers)) { + console.error('Dangling timer: ', name) + process.exitCode = 1 + } +}) diff --git a/node_modules/@npmcli/arborist/bin/license.js b/node_modules/@npmcli/arborist/bin/license.js new file mode 100644 index 0000000000000..4083ddc695d46 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/license.js @@ -0,0 +1,34 @@ +const Arborist = require('../') +const options = require('./lib/options.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const a = new Arborist(options) +const query = options._.shift() + +a.loadVirtual().then(tree => { + // only load the actual tree if the virtual one doesn't have modern metadata + if (!tree.meta || !(tree.meta.originalLockfileVersion >= 2)) + throw 'load actual' + else + return tree +}).catch((er) => { + console.error('loading actual tree', er) + return a.loadActual() +}).then(tree => { + if (!query) { + const set = [] + for (const license of tree.inventory.query('license')) + set.push([tree.inventory.query('license', license).size, license]) + + for (const [count, license] of set.sort((a, b) => + a[1] && b[1] ? b[0] - a[0] || a[1].localeCompare(b[1]) + : a[1] ? -1 + : b[1] ? 1 + : 0)) + console.log(count, license) + } else { + for (const node of tree.inventory.query('license', query === 'undefined' ? undefined : query)) + console.log(`${node.name} ${node.location} ${node.package.description || ''}`) + } +}) diff --git a/node_modules/@npmcli/arborist/bin/reify.js b/node_modules/@npmcli/arborist/bin/reify.js new file mode 100644 index 0000000000000..d17a0e03b3286 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/reify.js @@ -0,0 +1,46 @@ +const Arborist = require('../') + +const options = require('./lib/options.js') +const print = require('./lib/print-tree.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const printDiff = diff => { + const {depth} = require('treeverse') + depth({ + tree: diff, + visit: d => { + if (d.location === '') + return + switch (d.action) { + case 'REMOVE': + console.error('REMOVE', d.actual.location) + break + case 'ADD': + console.error('ADD', d.ideal.location, d.ideal.resolved) + break + case 'CHANGE': + console.error('CHANGE', d.actual.location, { + from: d.actual.resolved, + to: d.ideal.resolved, + }) + break + } + }, + getChildren: d => d.children, + }) +} + +const start = process.hrtime() +process.emit('time', 'install') +const arb = new Arborist(options) +arb.reify(options).then(tree => { + process.emit('timeEnd', 'install') + const end = process.hrtime(start) + print(tree) + if (options.dryRun) + printDiff(arb.diff) + console.error(`resolved ${tree.inventory.size} deps in ${end[0] + end[1] / 1e9}s`) + if (tree.meta && options.save) + tree.meta.save() +}).catch(er => console.error(require('util').inspect(er, { depth: Infinity }))) diff --git a/node_modules/@npmcli/arborist/bin/shrinkwrap.js b/node_modules/@npmcli/arborist/bin/shrinkwrap.js new file mode 100644 index 0000000000000..ee5ec24557947 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/shrinkwrap.js @@ -0,0 +1,12 @@ +const Shrinkwrap = require('../lib/shrinkwrap.js') +const options = require('./lib/options.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const { quiet } = options +Shrinkwrap.load(options) + .then(s => quiet || console.log(JSON.stringify(s.data, 0, 2))) + .catch(er => { + console.error('shrinkwrap load failure', er) + process.exit(1) + }) diff --git a/node_modules/@npmcli/arborist/bin/virtual.js b/node_modules/@npmcli/arborist/bin/virtual.js new file mode 100644 index 0000000000000..7f90f20cf3817 --- /dev/null +++ b/node_modules/@npmcli/arborist/bin/virtual.js @@ -0,0 +1,15 @@ +const Arborist = require('../') + +const print = require('./lib/print-tree.js') +const options = require('./lib/options.js') +require('./lib/logging.js') +require('./lib/timers.js') + +const start = process.hrtime() +new Arborist(options).loadVirtual().then(tree => { + const end = process.hrtime(start) + print(tree) + if (options.save) + tree.meta.save() + console.error(`read ${tree.inventory.size} deps in ${end[0] * 1000 + end[1] / 1e6}ms`) +}).catch(er => console.error(er)) diff --git a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index ae92b74cefd18..4c266502101a4 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -398,6 +398,7 @@ module.exports = cls => class IdealTreeBuilder extends cls { if (this[_global] && (this[_updateAll] || this[_updateNames].length)) { const nm = resolve(this.path, 'node_modules') for (const name of await readdir(nm)) { + tree.package.dependencies = tree.package.dependencies || {} if (this[_updateAll] || this[_updateNames].includes(name)) tree.package.dependencies[name] = '*' } diff --git a/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js index 6cc129a7cc057..1dd4b4b0f1931 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -907,7 +907,7 @@ module.exports = cls => class Reifier extends cls { return Promise.all([ this[_saveLockFile](saveOpt), - updateRootPackageJson({ tree: this.idealTree }), + updateRootPackageJson(this.idealTree), ]).then(() => process.emit('timeEnd', 'reify:save')) } diff --git a/node_modules/@npmcli/arborist/lib/update-root-package-json.js b/node_modules/@npmcli/arborist/lib/update-root-package-json.js index f5d62f7a5a713..735ebd10ad16f 100644 --- a/node_modules/@npmcli/arborist/lib/update-root-package-json.js +++ b/node_modules/@npmcli/arborist/lib/update-root-package-json.js @@ -15,7 +15,7 @@ const depTypes = new Set([ 'peerDependencies', ]) -async function updateRootPackageJson ({ tree }) { +const updateRootPackageJson = async tree => { const filename = resolve(tree.path, 'package.json') const originalContent = await readFile(filename, 'utf8') .then(data => parseJSON(data)) @@ -25,6 +25,16 @@ async function updateRootPackageJson ({ tree }) { ...tree.package, }) + // optionalDependencies don't need to be repeated in two places + if (depsData.dependencies) { + if (depsData.optionalDependencies) { + for (const name of Object.keys(depsData.optionalDependencies)) + delete depsData.dependencies[name] + } + if (Object.keys(depsData.dependencies).length === 0) + delete depsData.dependencies + } + // if there's no package.json, just use internal pkg info as source of truth const packageJsonContent = originalContent || depsData diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index 2107652c6754d..6e88b9cbb57a1 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,15 +1,15 @@ { "name": "@npmcli/arborist", - "version": "2.1.1", + "version": "2.2.1", "description": "Manage node_modules trees", "dependencies": { - "@npmcli/installed-package-contents": "^1.0.5", - "@npmcli/map-workspaces": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.0.1", "@npmcli/move-file": "^1.1.0", "@npmcli/name-from-folder": "^1.0.1", "@npmcli/node-gyp": "^1.0.1", - "@npmcli/run-script": "^1.8.1", + "@npmcli/run-script": "^1.8.2", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", @@ -20,11 +20,11 @@ "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", "npm-registry-fetch": "^9.0.0", - "pacote": "^11.2.4", + "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^1.2.1", + "read-package-json-fast": "^2.0.1", "readdir-scoped-modules": "^1.1.0", "semver": "^7.3.4", "tar": "^6.1.0", @@ -55,7 +55,7 @@ "postversion": "npm publish", "prepublishOnly": "git push origin --follow-tags", "eslint": "eslint", - "lint": "npm run eslint -- \"lib/**/*.js\" \"test/arborist/*.js\" \"test/*.js\"", + "lint": "npm run eslint -- \"lib/**/*.js\" \"test/arborist/*.js\" \"test/*.js\" \"bin/**/*.js\"", "lintfix": "npm run lint -- --fix", "benchmark": "node scripts/benchmark.js", "benchclean": "rm -rf scripts/benchmark/*/" @@ -67,9 +67,13 @@ "author": "Isaac Z. Schlueter (http://blog.izs.me/)", "license": "ISC", "files": [ - "lib/**/*.js" + "lib/**/*.js", + "bin/**/*.js" ], "main": "lib/index.js", + "bin": { + "arborist": "bin/index.js" + }, "tap": { "100": true, "node-arg": [ diff --git a/node_modules/@npmcli/installed-package-contents/index.js b/node_modules/@npmcli/installed-package-contents/index.js index fa81551fed4bf..30427fe28c108 100755 --- a/node_modules/@npmcli/installed-package-contents/index.js +++ b/node_modules/@npmcli/installed-package-contents/index.js @@ -22,6 +22,7 @@ const fs = require('fs') const readFile = promisify(fs.readFile) const readdir = promisify(fs.readdir) const stat = promisify(fs.stat) +const lstat = promisify(fs.lstat) const {relative, resolve, basename, dirname} = require('path') const normalizePackageBin = require('npm-normalize-package-bin') @@ -131,6 +132,18 @@ const pkgContents = async ({ const recursePromises = [] + // if we didn't get withFileTypes support, tack that on + if (typeof dirEntries[0] === 'string') { + // use a map so we can return a promise, but we mutate dirEntries in place + // this is much slower than getting the entries from the readdir call, + // but polyfills support for node versions before 10.10 + await Promise.all(dirEntries.map(async (name, index) => { + const p = resolve(path, name) + const st = await lstat(p) + dirEntries[index] = Object.assign(st, {name}) + })) + } + for (const entry of dirEntries) { const p = resolve(path, entry.name) if (entry.isDirectory() === false) { diff --git a/node_modules/@npmcli/installed-package-contents/package.json b/node_modules/@npmcli/installed-package-contents/package.json index 5af7077b6ac98..13916308f99db 100644 --- a/node_modules/@npmcli/installed-package-contents/package.json +++ b/node_modules/@npmcli/installed-package-contents/package.json @@ -1,10 +1,12 @@ { "name": "@npmcli/installed-package-contents", - "version": "1.0.5", + "version": "1.0.7", "description": "Get the list of files installed in a package in node_modules, including bundled dependencies", "author": "Isaac Z. Schlueter (https://izs.me)", "main": "index.js", - "bin": "index.js", + "bin": { + "installed-package-contents": "index.js" + }, "license": "ISC", "scripts": { "test": "tap", @@ -14,16 +16,16 @@ "postpublish": "git push origin --follow-tags" }, "tap": { - "check-coverage": true + "check-coverage": true, + "color": true }, "devDependencies": { - "tap": "^14.10.4" + "require-inject": "^1.4.4", + "tap": "^14.11.0" }, "dependencies": { "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1", - "read-package-json-fast": "^1.1.1", - "readdir-scoped-modules": "^1.1.0" + "npm-normalize-package-bin": "^1.0.1" }, "repository": "git+https://github.com/npm/installed-package-contents", "files": [ diff --git a/node_modules/@npmcli/map-workspaces/package.json b/node_modules/@npmcli/map-workspaces/package.json index 2a66a74240d6d..df509648db0c7 100644 --- a/node_modules/@npmcli/map-workspaces/package.json +++ b/node_modules/@npmcli/map-workspaces/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/map-workspaces", - "version": "1.0.1", + "version": "1.0.2", "files": [ "index.js" ], @@ -52,6 +52,6 @@ "@npmcli/name-from-folder": "^1.0.1", "glob": "^7.1.6", "minimatch": "^3.0.4", - "read-package-json-fast": "^1.2.1" + "read-package-json-fast": "^2.0.1" } } diff --git a/node_modules/@npmcli/run-script/package.json b/node_modules/@npmcli/run-script/package.json index 7adb5c76d82cc..332f1e74df657 100644 --- a/node_modules/@npmcli/run-script/package.json +++ b/node_modules/@npmcli/run-script/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/run-script", - "version": "1.8.1", + "version": "1.8.2", "description": "Run a lifecycle script for a package (descendant of npm-lifecycle)", "author": "Isaac Z. Schlueter (https://izs.me)", "license": "ISC", @@ -18,22 +18,22 @@ "coverage-map": "map.js" }, "devDependencies": { - "eslint": "^7.10.0", + "eslint": "^7.19.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^4.0.1", + "eslint-plugin-standard": "^5.0.0", "minipass": "^3.1.1", "require-inject": "^1.4.4", - "tap": "^14.10.6" + "tap": "^14.11.0" }, "dependencies": { - "@npmcli/node-gyp": "^1.0.0", - "@npmcli/promise-spawn": "^1.3.0", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", "puka": "^1.0.1", - "read-package-json-fast": "^1.1.3" + "read-package-json-fast": "^2.0.1" }, "files": [ "lib/**/*.js", diff --git a/node_modules/graceful-fs/clone.js b/node_modules/graceful-fs/clone.js index 028356c96ed53..dff3cc8c504b4 100644 --- a/node_modules/graceful-fs/clone.js +++ b/node_modules/graceful-fs/clone.js @@ -2,12 +2,16 @@ module.exports = clone +var getPrototypeOf = Object.getPrototypeOf || function (obj) { + return obj.__proto__ +} + function clone (obj) { if (obj === null || typeof obj !== 'object') return obj if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } + var copy = { __proto__: getPrototypeOf(obj) } else var copy = Object.create(null) diff --git a/node_modules/graceful-fs/graceful-fs.js b/node_modules/graceful-fs/graceful-fs.js index de3df47fd5552..8218b1478a003 100644 --- a/node_modules/graceful-fs/graceful-fs.js +++ b/node_modules/graceful-fs/graceful-fs.js @@ -170,6 +170,21 @@ function patch (fs) { } } + var fs$copyFile = fs.copyFile + if (fs$copyFile) + fs.copyFile = copyFile + function copyFile (src, dest, cb) { + return fs$copyFile(src, dest, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([fs$copyFile, [src, dest, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + var fs$readdir = fs.readdir fs.readdir = readdir function readdir (path, options, cb) { diff --git a/node_modules/graceful-fs/package.json b/node_modules/graceful-fs/package.json index 0a56eb73f3751..8eca6d66ff8d6 100644 --- a/node_modules/graceful-fs/package.json +++ b/node_modules/graceful-fs/package.json @@ -1,7 +1,7 @@ { "name": "graceful-fs", "description": "A drop-in replacement for fs, making various improvements.", - "version": "4.2.4", + "version": "4.2.5", "repository": { "type": "git", "url": "https://github.com/isaacs/node-graceful-fs" @@ -14,7 +14,8 @@ "preversion": "npm test", "postversion": "npm publish", "postpublish": "git push origin --follow-tags", - "test": "node test.js | tap -" + "test": "nyc --silent node test.js | tap -", + "posttest": "nyc report" }, "keywords": [ "fs", diff --git a/node_modules/graceful-fs/polyfills.js b/node_modules/graceful-fs/polyfills.js index a5808d23f132e..56d08d180017e 100644 --- a/node_modules/graceful-fs/polyfills.js +++ b/node_modules/graceful-fs/polyfills.js @@ -19,6 +19,7 @@ process.chdir = function(d) { cwd = null chdir.call(process, d) } +if (Object.setPrototypeOf) Object.setPrototypeOf(process.chdir, chdir) module.exports = patch @@ -132,7 +133,7 @@ function patch (fs) { } // This ensures `util.promisify` works as it does for native `fs.read`. - read.__proto__ = fs$read + if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read) return read })(fs.read) diff --git a/node_modules/init-package-json/README.md b/node_modules/init-package-json/README.md index bd64c1230986f..528acf355158a 100644 --- a/node_modules/init-package-json/README.md +++ b/node_modules/init-package-json/README.md @@ -23,7 +23,7 @@ var dir = process.cwd() var configData = { some: 'extra stuff' } // Any existing stuff from the package.json file is also exposed in the -// PromZard module as the `package` object. There will also be free +// PromZard module as the `package` object. There will also be three // vars for: // * `filename` path to the package.json file // * `basename` the tip of the package dir diff --git a/node_modules/init-package-json/init-package-json.js b/node_modules/init-package-json/init-package-json.js index 5b2889e55da6b..83e7342d0aa4f 100644 --- a/node_modules/init-package-json/init-package-json.js +++ b/node_modules/init-package-json/init-package-json.js @@ -103,7 +103,7 @@ function init (dir, input, config, cb) { if (!pkg.description) pkg.description = data.description - var d = JSON.stringify(pkg, null, 2) + '\n' + var d = JSON.stringify(updateDeps(pkg), null, 2) + '\n' function write (yes) { fs.writeFile(packageFile, d, 'utf8', function (er) { if (!er && yes && !config.get('silent')) { @@ -132,6 +132,20 @@ function init (dir, input, config, cb) { } +function updateDeps(depsData) { + // optionalDependencies don't need to be repeated in two places + if (depsData.dependencies) { + if (depsData.optionalDependencies) { + for (const name of Object.keys(depsData.optionalDependencies)) + delete depsData.dependencies[name] + } + if (Object.keys(depsData.dependencies).length === 0) + delete depsData.dependencies + } + + return depsData +} + // turn the objects into somewhat more humane strings. function unParsePeople (data) { if (data.author) data.author = unParsePerson(data.author) diff --git a/node_modules/init-package-json/package.json b/node_modules/init-package-json/package.json index abf06969264e4..91c6bfba82049 100644 --- a/node_modules/init-package-json/package.json +++ b/node_modules/init-package-json/package.json @@ -1,6 +1,6 @@ { "name": "init-package-json", - "version": "2.0.1", + "version": "2.0.2", "main": "init-package-json.js", "scripts": { "test": "tap", diff --git a/node_modules/libnpmversion/package.json b/node_modules/libnpmversion/package.json index d7e8d5fa58647..b19edd84171f1 100644 --- a/node_modules/libnpmversion/package.json +++ b/node_modules/libnpmversion/package.json @@ -1,6 +1,6 @@ { "name": "libnpmversion", - "version": "1.0.7", + "version": "1.0.8", "main": "lib/index.js", "files": [ "lib/*.js" @@ -25,13 +25,13 @@ }, "devDependencies": { "require-inject": "^1.4.4", - "tap": "^14.10.6" + "tap": "^14.11.0" }, "dependencies": { - "@npmcli/git": "^2.0.1", - "@npmcli/run-script": "^1.2.1", - "read-package-json-fast": "^1.2.1", - "semver": "^7.1.3", + "@npmcli/git": "^2.0.4", + "@npmcli/run-script": "^1.8.2", + "read-package-json-fast": "^2.0.1", + "semver": "^7.3.4", "stringify-package": "^1.0.1" } } diff --git a/node_modules/pacote/lib/fetcher.js b/node_modules/pacote/lib/fetcher.js index ad3cacec89bf4..c9a3201f0ae4a 100644 --- a/node_modules/pacote/lib/fetcher.js +++ b/node_modules/pacote/lib/fetcher.js @@ -110,7 +110,7 @@ class FetcherBase { // going to be packing in the context of a publish, which may set // a dist-tag, but certainly wants to keep defaulting to latest. this.npmCliConfig = opts.npmCliConfig || [ - `--cache=${this.cache}`, + `--cache=${dirname(this.cache)}`, `--prefer-offline=${!!this.preferOffline}`, `--prefer-online=${!!this.preferOnline}`, `--offline=${!!this.offline}`, diff --git a/node_modules/pacote/lib/git.js b/node_modules/pacote/lib/git.js index 406ab5c600221..14d8a833659ce 100644 --- a/node_modules/pacote/lib/git.js +++ b/node_modules/pacote/lib/git.js @@ -161,12 +161,28 @@ class GitFetcher extends Fetcher { scripts.prepare)) return + // to avoid cases where we have an cycle of git deps that depend + // on one another, we only ever do preparation for one instance + // of a given git dep along the chain of installations. + // Note that this does mean that a dependency MAY in theory end up + // trying to run its prepare script using a dependency that has not + // been properly prepared itself, but that edge case is smaller + // and less hazardous than a fork bomb of npm and git commands. + const noPrepare = !process.env._PACOTE_NO_PREPARE_ ? [] + : process.env._PACOTE_NO_PREPARE_.split('\n') + if (noPrepare.includes(this.resolved)) { + this.log.info('prepare', 'skip prepare, already seen', this.resolved) + return + } + noPrepare.push(this.resolved) + // the DirFetcher will do its own preparation to run the prepare scripts // All we have to do is put the deps in place so that it can succeed. return npm( this.npmBin, [].concat(this.npmInstallCmd).concat(this.npmCliConfig), dir, + { ...process.env, _PACOTE_NO_PREPARE_: noPrepare.join('\n') }, { message: 'git dep preparation failed' } ) }) diff --git a/node_modules/pacote/lib/util/cache-dir.js b/node_modules/pacote/lib/util/cache-dir.js index d5c0bf28fb81e..abd2453232027 100644 --- a/node_modules/pacote/lib/util/cache-dir.js +++ b/node_modules/pacote/lib/util/cache-dir.js @@ -7,6 +7,6 @@ module.exports = (fakePlatform = false) => { const home = os.homedir() || resolve(temp, 'npm-' + uidOrPid) const platform = fakePlatform || process.platform const cacheExtra = platform === 'win32' ? 'npm-cache' : '.npm' - const cacheRoot = (platform === 'win32' && process.env.APPDATA) || home - return resolve(cacheRoot, cacheExtra) + const cacheRoot = (platform === 'win32' && process.env.LOCALAPPDATA) || home + return resolve(cacheRoot, cacheExtra, '_cacache') } diff --git a/node_modules/pacote/lib/util/npm.js b/node_modules/pacote/lib/util/npm.js index 293695525c726..f2f29bd0acbd1 100644 --- a/node_modules/pacote/lib/util/npm.js +++ b/node_modules/pacote/lib/util/npm.js @@ -1,9 +1,15 @@ // run an npm command const spawn = require('@npmcli/promise-spawn') +const {dirname} = require('path') -module.exports = (npmBin, npmCommand, cwd, extra) => { +module.exports = (npmBin, npmCommand, cwd, env, extra) => { const isJS = npmBin.endsWith('.js') const cmd = isJS ? process.execPath : npmBin const args = (isJS ? [npmBin] : []).concat(npmCommand) - return spawn(cmd, args, { cwd, stdioString: true }, extra) + // when installing to run the `prepare` script for a git dep, we need + // to ensure that we don't run into a cycle of checking out packages + // in temp directories. this lets us link previously-seen repos that + // are also being prepared. + + return spawn(cmd, args, { cwd, stdioString: true, env }, extra) } diff --git a/node_modules/pacote/node_modules/err-code/.editorconfig b/node_modules/pacote/node_modules/err-code/.editorconfig new file mode 100644 index 0000000000000..829280bee1ac3 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[package.json] +indent_size = 2 diff --git a/node_modules/pacote/node_modules/err-code/.eslintrc.json b/node_modules/pacote/node_modules/err-code/.eslintrc.json new file mode 100644 index 0000000000000..4829595a424ed --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "root": true, + "extends": [ + "@satazor/eslint-config/es6", + "@satazor/eslint-config/addons/node" + ] +} \ No newline at end of file diff --git a/node_modules/pacote/node_modules/err-code/.travis.yml b/node_modules/pacote/node_modules/err-code/.travis.yml new file mode 100644 index 0000000000000..b29cf66a2b3b3 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "4" + - "6" diff --git a/node_modules/pacote/node_modules/err-code/README.md b/node_modules/pacote/node_modules/err-code/README.md new file mode 100644 index 0000000000000..5afdab00c9348 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/README.md @@ -0,0 +1,70 @@ +# err-code + +[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url] [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url] + +[npm-url]:https://npmjs.org/package/err-code +[downloads-image]:http://img.shields.io/npm/dm/err-code.svg +[npm-image]:http://img.shields.io/npm/v/err-code.svg +[travis-url]:https://travis-ci.org/IndigoUnited/js-err-code +[travis-image]:http://img.shields.io/travis/IndigoUnited/js-err-code/master.svg +[david-dm-url]:https://david-dm.org/IndigoUnited/js-err-code +[david-dm-image]:https://img.shields.io/david/IndigoUnited/js-err-code.svg +[david-dm-dev-url]:https://david-dm.org/IndigoUnited/js-err-code?type=dev +[david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/js-err-code.svg +[greenkeeper-image]:https://badges.greenkeeper.io/IndigoUnited/js-err-code.svg +[greenkeeper-url]:https://greenkeeper.io/ + +Create new error instances with a code and additional properties. + + +## Installation + +```console +$ npm install err-code +// or +$ bower install err-code +``` + +The browser file is named index.umd.js which supports CommonJS, AMD and globals (errCode). + + +## Why + +I find myself doing this repeatedly: + +```js +var err = new Error('My message'); +err.code = 'SOMECODE'; +err.detail = 'Additional information about the error'; +throw err; +``` + + +## Usage + +Simple usage. + +```js +var errcode = require('err-code'); + +// fill error with message + code +throw errcode(new Error('My message'), 'ESOMECODE'); +// fill error with message + code + props +throw errcode(new Error('My message'), 'ESOMECODE', { detail: 'Additional information about the error' }); +// fill error with message + props +throw errcode(new Error('My message'), { detail: 'Additional information about the error' }); +``` + +## Pre-existing fields + +If the passed `Error` already has a `.code` field, or fields specified in the third argument to `errcode` they will be overwritten, unless the fields are read only or otherwise throw during assignment in which case a new object will be created that shares a prototype chain with the original `Error`. The `.stack` and `.message` properties will be carried over from the original error and `.code` or any passed properties will be set on it. + + +## Tests + +`$ npm test` + + +## License + +Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php). diff --git a/node_modules/pacote/node_modules/err-code/bower.json b/node_modules/pacote/node_modules/err-code/bower.json new file mode 100644 index 0000000000000..a39cb702cedb2 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/bower.json @@ -0,0 +1,30 @@ +{ + "name": "err-code", + "version": "1.1.1", + "description": "Create new error instances with a code and additional properties", + "main": "index.umd.js", + "homepage": "https://github.com/IndigoUnited/js-err-code", + "authors": [ + "IndigoUnited (http://indigounited.com)" + ], + "moduleType": [ + "amd", + "globals", + "node" + ], + "keywords": [ + "error", + "err", + "code", + "properties", + "property" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/node_modules/pacote/node_modules/err-code/index.js b/node_modules/pacote/node_modules/err-code/index.js new file mode 100644 index 0000000000000..9ff3e9c5de4c2 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/index.js @@ -0,0 +1,47 @@ +'use strict'; + +function assign(obj, props) { + for (const key in props) { + Object.defineProperty(obj, key, { + value: props[key], + enumerable: true, + configurable: true, + }); + } + + return obj; +} + +function createError(err, code, props) { + if (!err || typeof err === 'string') { + throw new TypeError('Please pass an Error to err-code'); + } + + if (!props) { + props = {}; + } + + if (typeof code === 'object') { + props = code; + code = undefined; + } + + if (code != null) { + props.code = code; + } + + try { + return assign(err, props); + } catch (_) { + props.message = err.message; + props.stack = err.stack; + + const ErrClass = function () {}; + + ErrClass.prototype = Object.create(Object.getPrototypeOf(err)); + + return assign(new ErrClass(), props); + } +} + +module.exports = createError; diff --git a/node_modules/pacote/node_modules/err-code/index.umd.js b/node_modules/pacote/node_modules/err-code/index.umd.js new file mode 100644 index 0000000000000..41007269d3d03 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/index.umd.js @@ -0,0 +1,51 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.errCode = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i index.umd.js" + }, + "bugs": { + "url": "https://github.com/IndigoUnited/js-err-code/issues/" + }, + "repository": { + "type": "git", + "url": "git://github.com/IndigoUnited/js-err-code.git" + }, + "keywords": [ + "error", + "err", + "code", + "properties", + "property" + ], + "author": "IndigoUnited (http://indigounited.com)", + "license": "MIT", + "devDependencies": { + "@satazor/eslint-config": "^3.0.0", + "browserify": "^16.5.1", + "eslint": "^7.2.0", + "expect.js": "^0.3.1", + "mocha": "^8.0.1" + } +} diff --git a/node_modules/pacote/node_modules/err-code/test/.eslintrc.json b/node_modules/pacote/node_modules/err-code/test/.eslintrc.json new file mode 100644 index 0000000000000..f9fbb2d6ce6ab --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/test/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "env": { + "mocha": true + } +} \ No newline at end of file diff --git a/node_modules/pacote/node_modules/err-code/test/test.js b/node_modules/pacote/node_modules/err-code/test/test.js new file mode 100644 index 0000000000000..22ba0a8a1a8c1 --- /dev/null +++ b/node_modules/pacote/node_modules/err-code/test/test.js @@ -0,0 +1,159 @@ +'use strict'; + +const errcode = require('../index'); +const expect = require('expect.js'); + +describe('errcode', () => { + describe('string as first argument', () => { + it('should throw an error', () => { + expect(() => { errcode('my message'); }).to.throwError((err) => { + expect(err).to.be.a(TypeError); + }); + }); + }); + + describe('error as first argument', () => { + it('should accept an error and do nothing', () => { + const myErr = new Error('my message'); + const err = errcode(myErr); + + expect(err).to.be(myErr); + expect(err.hasOwnProperty(err.code)).to.be(false); + }); + + it('should accept an error and add a code', () => { + const myErr = new Error('my message'); + const err = errcode(myErr, 'ESOME'); + + expect(err).to.be(myErr); + expect(err.code).to.be('ESOME'); + }); + + it('should accept an error object and add code & properties', () => { + const myErr = new Error('my message'); + const err = errcode(myErr, 'ESOME', { foo: 'bar', bar: 'foo' }); + + expect(err).to.be.an(Error); + expect(err.code).to.be('ESOME'); + expect(err.foo).to.be('bar'); + expect(err.bar).to.be('foo'); + }); + + it('should create an error object without code but with properties', () => { + const myErr = new Error('my message'); + const err = errcode(myErr, { foo: 'bar', bar: 'foo' }); + + expect(err).to.be.an(Error); + expect(err.code).to.be(undefined); + expect(err.foo).to.be('bar'); + expect(err.bar).to.be('foo'); + }); + + it('should set a non-writable field', () => { + const myErr = new Error('my message'); + + Object.defineProperty(myErr, 'code', { + value: 'derp', + writable: false, + }); + const err = errcode(myErr, 'ERR_WAT'); + + expect(err).to.be.an(Error); + expect(err.stack).to.equal(myErr.stack); + expect(err.code).to.be('ERR_WAT'); + }); + + it('should add a code to frozen object', () => { + const myErr = new Error('my message'); + const err = errcode(Object.freeze(myErr), 'ERR_WAT'); + + expect(err).to.be.an(Error); + expect(err.stack).to.equal(myErr.stack); + expect(err.code).to.be('ERR_WAT'); + }); + + it('should to set a field that throws at assignment time', () => { + const myErr = new Error('my message'); + + Object.defineProperty(myErr, 'code', { + enumerable: true, + set() { + throw new Error('Nope!'); + }, + get() { + return 'derp'; + }, + }); + const err = errcode(myErr, 'ERR_WAT'); + + expect(err).to.be.an(Error); + expect(err.stack).to.equal(myErr.stack); + expect(err.code).to.be('ERR_WAT'); + }); + + it('should retain error type', () => { + const myErr = new TypeError('my message'); + + Object.defineProperty(myErr, 'code', { + value: 'derp', + writable: false, + }); + const err = errcode(myErr, 'ERR_WAT'); + + expect(err).to.be.a(TypeError); + expect(err.stack).to.equal(myErr.stack); + expect(err.code).to.be('ERR_WAT'); + }); + + it('should add a code to a class that extends Error', () => { + class CustomError extends Error { + set code(val) { + throw new Error('Nope!'); + } + } + + const myErr = new CustomError('my message'); + + Object.defineProperty(myErr, 'code', { + value: 'derp', + writable: false, + configurable: false, + }); + const err = errcode(myErr, 'ERR_WAT'); + + expect(err).to.be.a(CustomError); + expect(err.stack).to.equal(myErr.stack); + expect(err.code).to.be('ERR_WAT'); + + // original prototype chain should be intact + expect(() => { + const otherErr = new CustomError('my message'); + + otherErr.code = 'derp'; + }).to.throwError(); + }); + + it('should support errors that are not Errors', () => { + const err = errcode({ + message: 'Oh noes!', + }, 'ERR_WAT'); + + expect(err.message).to.be('Oh noes!'); + expect(err.code).to.be('ERR_WAT'); + }); + }); + + describe('falsy first arguments', () => { + it('should not allow passing null as the first argument', () => { + expect(() => { errcode(null); }).to.throwError((err) => { + expect(err).to.be.a(TypeError); + }); + }); + + it('should not allow passing undefined as the first argument', () => { + expect(() => { errcode(undefined); }).to.throwError((err) => { + expect(err).to.be.a(TypeError); + }); + }); + }); +}); diff --git a/node_modules/pacote/node_modules/promise-retry/.editorconfig b/node_modules/pacote/node_modules/promise-retry/.editorconfig new file mode 100644 index 0000000000000..8bc4f108d549f --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[package.json] +indent_size = 2 diff --git a/node_modules/pacote/node_modules/promise-retry/.travis.yml b/node_modules/pacote/node_modules/promise-retry/.travis.yml new file mode 100644 index 0000000000000..e2d26a9cad62b --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "10" + - "12" diff --git a/node_modules/pacote/node_modules/promise-retry/LICENSE b/node_modules/pacote/node_modules/promise-retry/LICENSE new file mode 100644 index 0000000000000..db5e914de1f58 --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 IndigoUnited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/pacote/node_modules/promise-retry/README.md b/node_modules/pacote/node_modules/promise-retry/README.md new file mode 100644 index 0000000000000..587de5c0b1841 --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/README.md @@ -0,0 +1,94 @@ +# node-promise-retry + +[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url] [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url] + +[npm-url]:https://npmjs.org/package/promise-retry +[downloads-image]:http://img.shields.io/npm/dm/promise-retry.svg +[npm-image]:http://img.shields.io/npm/v/promise-retry.svg +[travis-url]:https://travis-ci.org/IndigoUnited/node-promise-retry +[travis-image]:http://img.shields.io/travis/IndigoUnited/node-promise-retry/master.svg +[david-dm-url]:https://david-dm.org/IndigoUnited/node-promise-retry +[david-dm-image]:https://img.shields.io/david/IndigoUnited/node-promise-retry.svg +[david-dm-dev-url]:https://david-dm.org/IndigoUnited/node-promise-retry?type=dev +[david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/node-promise-retry.svg +[greenkeeper-image]:https://badges.greenkeeper.io/IndigoUnited/node-promise-retry.svg +[greenkeeper-url]:https://greenkeeper.io/ + +Retries a function that returns a promise, leveraging the power of the [retry](https://github.com/tim-kos/node-retry) module to the promises world. + +There's already some modules that are able to retry functions that return promises but +they were rather difficult to use or do not offer an easy way to do conditional retries. + + +## Installation + +`$ npm install promise-retry` + + +## Usage + +### promiseRetry(fn, [options]) + +Calls `fn` until the returned promise ends up fulfilled or rejected with an error different than +a `retry` error. +The `options` argument is an object which maps to the [retry](https://github.com/tim-kos/node-retry) module options: + +- `retries`: The maximum amount of times to retry the operation. Default is `10`. +- `factor`: The exponential factor to use. Default is `2`. +- `minTimeout`: The number of milliseconds before starting the first retry. Default is `1000`. +- `maxTimeout`: The maximum number of milliseconds between two retries. Default is `Infinity`. +- `randomize`: Randomizes the timeouts by multiplying with a factor between `1` to `2`. Default is `false`. + + +The `fn` function will receive a `retry` function as its first argument that should be called with an error whenever you want to retry `fn`. The `retry` function will always throw an error. +If there are retries left, it will throw a special `retry` error that will be handled internally to call `fn` again. +If there are no retries left, it will throw the actual error passed to it. + +If you prefer, you can pass the options first using the alternative function signature `promiseRetry([options], fn)`. + +## Example +```js +var promiseRetry = require('promise-retry'); + +// Simple example +promiseRetry(function (retry, number) { + console.log('attempt number', number); + + return doSomething() + .catch(retry); +}) +.then(function (value) { + // .. +}, function (err) { + // .. +}); + +// Conditional example +promiseRetry(function (retry, number) { + console.log('attempt number', number); + + return doSomething() + .catch(function (err) { + if (err.code === 'ETIMEDOUT') { + retry(err); + } + + throw err; + }); +}) +.then(function (value) { + // .. +}, function (err) { + // .. +}); +``` + + +## Tests + +`$ npm test` + + +## License + +Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php). diff --git a/node_modules/pacote/node_modules/promise-retry/index.js b/node_modules/pacote/node_modules/promise-retry/index.js new file mode 100644 index 0000000000000..5df48ae91602d --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/index.js @@ -0,0 +1,52 @@ +'use strict'; + +var errcode = require('err-code'); +var retry = require('retry'); + +var hasOwn = Object.prototype.hasOwnProperty; + +function isRetryError(err) { + return err && err.code === 'EPROMISERETRY' && hasOwn.call(err, 'retried'); +} + +function promiseRetry(fn, options) { + var temp; + var operation; + + if (typeof fn === 'object' && typeof options === 'function') { + // Swap options and fn when using alternate signature (options, fn) + temp = options; + options = fn; + fn = temp; + } + + operation = retry.operation(options); + + return new Promise(function (resolve, reject) { + operation.attempt(function (number) { + Promise.resolve() + .then(function () { + return fn(function (err) { + if (isRetryError(err)) { + err = err.retried; + } + + throw errcode(new Error('Retrying'), 'EPROMISERETRY', { retried: err }); + }, number); + }) + .then(resolve, function (err) { + if (isRetryError(err)) { + err = err.retried; + + if (operation.retry(err || new Error())) { + return; + } + } + + reject(err); + }); + }); + }); +} + +module.exports = promiseRetry; diff --git a/node_modules/pacote/node_modules/promise-retry/package.json b/node_modules/pacote/node_modules/promise-retry/package.json new file mode 100644 index 0000000000000..6842de823fd19 --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/package.json @@ -0,0 +1,37 @@ +{ + "name": "promise-retry", + "version": "2.0.1", + "description": "Retries a function that returns a promise, leveraging the power of the retry module.", + "main": "index.js", + "scripts": { + "test": "mocha --bail -t 10000" + }, + "bugs": { + "url": "https://github.com/IndigoUnited/node-promise-retry/issues/" + }, + "repository": { + "type": "git", + "url": "git://github.com/IndigoUnited/node-promise-retry.git" + }, + "keywords": [ + "retry", + "promise", + "backoff", + "repeat", + "replay" + ], + "author": "IndigoUnited (http://indigounited.com)", + "license": "MIT", + "devDependencies": { + "expect.js": "^0.3.1", + "mocha": "^8.0.1", + "sleep-promise": "^8.0.1" + }, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } +} diff --git a/node_modules/pacote/node_modules/promise-retry/test/test.js b/node_modules/pacote/node_modules/promise-retry/test/test.js new file mode 100644 index 0000000000000..466b0991e0f55 --- /dev/null +++ b/node_modules/pacote/node_modules/promise-retry/test/test.js @@ -0,0 +1,263 @@ +'use strict'; + +var expect = require('expect.js'); +var promiseRetry = require('../'); +var promiseDelay = require('sleep-promise'); + +describe('promise-retry', function () { + it('should call fn again if retry was called', function () { + var count = 0; + + return promiseRetry(function (retry) { + count += 1; + + return promiseDelay(10) + .then(function () { + if (count <= 2) { + retry(new Error('foo')); + } + + return 'final'; + }); + }, { factor: 1 }) + .then(function (value) { + expect(value).to.be('final'); + expect(count).to.be(3); + }, function () { + throw new Error('should not fail'); + }); + }); + + it('should call fn with the attempt number', function () { + var count = 0; + + return promiseRetry(function (retry, number) { + count += 1; + expect(count).to.equal(number); + + return promiseDelay(10) + .then(function () { + if (count <= 2) { + retry(new Error('foo')); + } + + return 'final'; + }); + }, { factor: 1 }) + .then(function (value) { + expect(value).to.be('final'); + expect(count).to.be(3); + }, function () { + throw new Error('should not fail'); + }); + }); + + it('should not retry on fulfillment if retry was not called', function () { + var count = 0; + + return promiseRetry(function () { + count += 1; + + return promiseDelay(10) + .then(function () { + return 'final'; + }); + }) + .then(function (value) { + expect(value).to.be('final'); + expect(count).to.be(1); + }, function () { + throw new Error('should not fail'); + }); + }); + + it('should not retry on rejection if retry was not called', function () { + var count = 0; + + return promiseRetry(function () { + count += 1; + + return promiseDelay(10) + .then(function () { + throw new Error('foo'); + }); + }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err.message).to.be('foo'); + expect(count).to.be(1); + }); + }); + + it('should not retry on rejection if nr of retries is 0', function () { + var count = 0; + + return promiseRetry(function (retry) { + count += 1; + + return promiseDelay(10) + .then(function () { + throw new Error('foo'); + }) + .catch(retry); + }, { retries : 0 }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err.message).to.be('foo'); + expect(count).to.be(1); + }); + }); + + it('should reject the promise if the retries were exceeded', function () { + var count = 0; + + return promiseRetry(function (retry) { + count += 1; + + return promiseDelay(10) + .then(function () { + throw new Error('foo'); + }) + .catch(retry); + }, { retries: 2, factor: 1 }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err.message).to.be('foo'); + expect(count).to.be(3); + }); + }); + + it('should pass options to the underlying retry module', function () { + var count = 0; + + return promiseRetry(function (retry) { + return promiseDelay(10) + .then(function () { + if (count < 2) { + count += 1; + retry(new Error('foo')); + } + + return 'final'; + }); + }, { retries: 1, factor: 1 }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err.message).to.be('foo'); + }); + }); + + it('should convert direct fulfillments into promises', function () { + return promiseRetry(function () { + return 'final'; + }, { factor: 1 }) + .then(function (value) { + expect(value).to.be('final'); + }, function () { + throw new Error('should not fail'); + }); + }); + + it('should convert direct rejections into promises', function () { + promiseRetry(function () { + throw new Error('foo'); + }, { retries: 1, factor: 1 }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err.message).to.be('foo'); + }); + }); + + it('should not crash on undefined rejections', function () { + return promiseRetry(function () { + throw undefined; + }, { retries: 1, factor: 1 }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err).to.be(undefined); + }) + .then(function () { + return promiseRetry(function (retry) { + retry(); + }, { retries: 1, factor: 1 }); + }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err).to.be(undefined); + }); + }); + + it('should retry if retry() was called with undefined', function () { + var count = 0; + + return promiseRetry(function (retry) { + count += 1; + + return promiseDelay(10) + .then(function () { + if (count <= 2) { + retry(); + } + + return 'final'; + }); + }, { factor: 1 }) + .then(function (value) { + expect(value).to.be('final'); + expect(count).to.be(3); + }, function () { + throw new Error('should not fail'); + }); + }); + + it('should work with several retries in the same chain', function () { + var count = 0; + + return promiseRetry(function (retry) { + count += 1; + + return promiseDelay(10) + .then(function () { + retry(new Error('foo')); + }) + .catch(function (err) { + retry(err); + }); + }, { retries: 1, factor: 1 }) + .then(function () { + throw new Error('should not succeed'); + }, function (err) { + expect(err.message).to.be('foo'); + expect(count).to.be(2); + }); + }); + + it('should allow options to be passed first', function () { + var count = 0; + + return promiseRetry({ factor: 1 }, function (retry) { + count += 1; + + return promiseDelay(10) + .then(function () { + if (count <= 2) { + retry(new Error('foo')); + } + + return 'final'; + }); + }).then(function (value) { + expect(value).to.be('final'); + expect(count).to.be(3); + }, function () { + throw new Error('should not fail'); + }); + }); +}); diff --git a/node_modules/pacote/node_modules/retry/.npmignore b/node_modules/pacote/node_modules/retry/.npmignore new file mode 100644 index 0000000000000..432f2855d6839 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/.npmignore @@ -0,0 +1,3 @@ +/node_modules/* +npm-debug.log +coverage diff --git a/node_modules/pacote/node_modules/retry/.travis.yml b/node_modules/pacote/node_modules/retry/.travis.yml new file mode 100644 index 0000000000000..bcde2122b9006 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/.travis.yml @@ -0,0 +1,15 @@ +language: node_js +node_js: + - "4" +before_install: + - pip install --user codecov +after_success: + - codecov --file coverage/lcov.info --disable search +# travis encrypt [subdomain]:[api token]@[room id] +# notifications: +# email: false +# campfire: +# rooms: +# secure: xyz +# on_failure: always +# on_success: always diff --git a/node_modules/pacote/node_modules/retry/License b/node_modules/pacote/node_modules/retry/License new file mode 100644 index 0000000000000..0b58de379fb30 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/License @@ -0,0 +1,21 @@ +Copyright (c) 2011: +Tim Koschützki (tim@debuggable.com) +Felix Geisendörfer (felix@debuggable.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/node_modules/pacote/node_modules/retry/Makefile b/node_modules/pacote/node_modules/retry/Makefile new file mode 100644 index 0000000000000..1968d8ff8b07b --- /dev/null +++ b/node_modules/pacote/node_modules/retry/Makefile @@ -0,0 +1,18 @@ +SHELL := /bin/bash + +release-major: test + npm version major -m "Release %s" + git push + npm publish + +release-minor: test + npm version minor -m "Release %s" + git push + npm publish + +release-patch: test + npm version patch -m "Release %s" + git push + npm publish + +.PHONY: test release-major release-minor release-patch diff --git a/node_modules/pacote/node_modules/retry/README.md b/node_modules/pacote/node_modules/retry/README.md new file mode 100644 index 0000000000000..16e28ec267d6d --- /dev/null +++ b/node_modules/pacote/node_modules/retry/README.md @@ -0,0 +1,227 @@ + +[![Build Status](https://secure.travis-ci.org/tim-kos/node-retry.png?branch=master)](http://travis-ci.org/tim-kos/node-retry "Check this project's build status on TravisCI") +[![codecov](https://codecov.io/gh/tim-kos/node-retry/branch/master/graph/badge.svg)](https://codecov.io/gh/tim-kos/node-retry) + + +# retry + +Abstraction for exponential and custom retry strategies for failed operations. + +## Installation + + npm install retry + +## Current Status + +This module has been tested and is ready to be used. + +## Tutorial + +The example below will retry a potentially failing `dns.resolve` operation +`10` times using an exponential backoff strategy. With the default settings, this +means the last attempt is made after `17 minutes and 3 seconds`. + +``` javascript +var dns = require('dns'); +var retry = require('retry'); + +function faultTolerantResolve(address, cb) { + var operation = retry.operation(); + + operation.attempt(function(currentAttempt) { + dns.resolve(address, function(err, addresses) { + if (operation.retry(err)) { + return; + } + + cb(err ? operation.mainError() : null, addresses); + }); + }); +} + +faultTolerantResolve('nodejs.org', function(err, addresses) { + console.log(err, addresses); +}); +``` + +Of course you can also configure the factors that go into the exponential +backoff. See the API documentation below for all available settings. +currentAttempt is an int representing the number of attempts so far. + +``` javascript +var operation = retry.operation({ + retries: 5, + factor: 3, + minTimeout: 1 * 1000, + maxTimeout: 60 * 1000, + randomize: true, +}); +``` + +## API + +### retry.operation([options]) + +Creates a new `RetryOperation` object. `options` is the same as `retry.timeouts()`'s `options`, with two additions: + +* `forever`: Whether to retry forever, defaults to `false`. +* `unref`: Whether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`. +* `maxRetryTime`: The maximum time (in milliseconds) that the retried operation is allowed to run. Default is `Infinity`. + +### retry.timeouts([options]) + +Returns an array of timeouts. All time `options` and return values are in +milliseconds. If `options` is an array, a copy of that array is returned. + +`options` is a JS object that can contain any of the following keys: + +* `retries`: The maximum amount of times to retry the operation. Default is `10`. Seting this to `1` means `do it once, then retry it once`. +* `factor`: The exponential factor to use. Default is `2`. +* `minTimeout`: The number of milliseconds before starting the first retry. Default is `1000`. +* `maxTimeout`: The maximum number of milliseconds between two retries. Default is `Infinity`. +* `randomize`: Randomizes the timeouts by multiplying with a factor between `1` to `2`. Default is `false`. + +The formula used to calculate the individual timeouts is: + +``` +Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout) +``` + +Have a look at [this article][article] for a better explanation of approach. + +If you want to tune your `factor` / `times` settings to attempt the last retry +after a certain amount of time, you can use wolfram alpha. For example in order +to tune for `10` attempts in `5 minutes`, you can use this equation: + +![screenshot](https://github.com/tim-kos/node-retry/raw/master/equation.gif) + +Explaining the various values from left to right: + +* `k = 0 ... 9`: The `retries` value (10) +* `1000`: The `minTimeout` value in ms (1000) +* `x^k`: No need to change this, `x` will be your resulting factor +* `5 * 60 * 1000`: The desired total amount of time for retrying in ms (5 minutes) + +To make this a little easier for you, use wolfram alpha to do the calculations: + + + +[article]: http://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html + +### retry.createTimeout(attempt, opts) + +Returns a new `timeout` (integer in milliseconds) based on the given parameters. + +`attempt` is an integer representing for which retry the timeout should be calculated. If your retry operation was executed 4 times you had one attempt and 3 retries. If you then want to calculate a new timeout, you should set `attempt` to 4 (attempts are zero-indexed). + +`opts` can include `factor`, `minTimeout`, `randomize` (boolean) and `maxTimeout`. They are documented above. + +`retry.createTimeout()` is used internally by `retry.timeouts()` and is public for you to be able to create your own timeouts for reinserting an item, see [issue #13](https://github.com/tim-kos/node-retry/issues/13). + +### retry.wrap(obj, [options], [methodNames]) + +Wrap all functions of the `obj` with retry. Optionally you can pass operation options and +an array of method names which need to be wrapped. + +``` +retry.wrap(obj) + +retry.wrap(obj, ['method1', 'method2']) + +retry.wrap(obj, {retries: 3}) + +retry.wrap(obj, {retries: 3}, ['method1', 'method2']) +``` +The `options` object can take any options that the usual call to `retry.operation` can take. + +### new RetryOperation(timeouts, [options]) + +Creates a new `RetryOperation` where `timeouts` is an array where each value is +a timeout given in milliseconds. + +Available options: +* `forever`: Whether to retry forever, defaults to `false`. +* `unref`: Wether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`. + +If `forever` is true, the following changes happen: +* `RetryOperation.errors()` will only output an array of one item: the last error. +* `RetryOperation` will repeatedly use the `timeouts` array. Once all of its timeouts have been used up, it restarts with the first timeout, then uses the second and so on. + +#### retryOperation.errors() + +Returns an array of all errors that have been passed to `retryOperation.retry()` so far. The +returning array has the errors ordered chronologically based on when they were passed to +`retryOperation.retry()`, which means the first passed error is at index zero and the last is +at the last index. + +#### retryOperation.mainError() + +A reference to the error object that occured most frequently. Errors are +compared using the `error.message` property. + +If multiple error messages occured the same amount of time, the last error +object with that message is returned. + +If no errors occured so far, the value is `null`. + +#### retryOperation.attempt(fn, timeoutOps) + +Defines the function `fn` that is to be retried and executes it for the first +time right away. The `fn` function can receive an optional `currentAttempt` callback that represents the number of attempts to execute `fn` so far. + +Optionally defines `timeoutOps` which is an object having a property `timeout` in miliseconds and a property `cb` callback function. +Whenever your retry operation takes longer than `timeout` to execute, the timeout callback function `cb` is called. + + +#### retryOperation.try(fn) + +This is an alias for `retryOperation.attempt(fn)`. This is deprecated. Please use `retryOperation.attempt(fn)` instead. + +#### retryOperation.start(fn) + +This is an alias for `retryOperation.attempt(fn)`. This is deprecated. Please use `retryOperation.attempt(fn)` instead. + +#### retryOperation.retry(error) + +Returns `false` when no `error` value is given, or the maximum amount of retries +has been reached. + +Otherwise it returns `true`, and retries the operation after the timeout for +the current attempt number. + +#### retryOperation.stop() + +Allows you to stop the operation being retried. Useful for aborting the operation on a fatal error etc. + +#### retryOperation.reset() + +Resets the internal state of the operation object, so that you can call `attempt()` again as if this was a new operation object. + +#### retryOperation.attempts() + +Returns an int representing the number of attempts it took to call `fn` before it was successful. + +## License + +retry is licensed under the MIT license. + + +# Changelog + +0.10.0 Adding `stop` functionality, thanks to @maxnachlinger. + +0.9.0 Adding `unref` functionality, thanks to @satazor. + +0.8.0 Implementing retry.wrap. + +0.7.0 Some bug fixes and made retry.createTimeout() public. Fixed issues [#10](https://github.com/tim-kos/node-retry/issues/10), [#12](https://github.com/tim-kos/node-retry/issues/12), and [#13](https://github.com/tim-kos/node-retry/issues/13). + +0.6.0 Introduced optional timeOps parameter for the attempt() function which is an object having a property timeout in milliseconds and a property cb callback function. Whenever your retry operation takes longer than timeout to execute, the timeout callback function cb is called. + +0.5.0 Some minor refactoring. + +0.4.0 Changed retryOperation.try() to retryOperation.attempt(). Deprecated the aliases start() and try() for it. + +0.3.0 Added retryOperation.start() which is an alias for retryOperation.try(). + +0.2.0 Added attempts() function and parameter to retryOperation.try() representing the number of attempts it took to call fn(). diff --git a/node_modules/pacote/node_modules/retry/equation.gif b/node_modules/pacote/node_modules/retry/equation.gif new file mode 100644 index 0000000000000..97107237ba19f Binary files /dev/null and b/node_modules/pacote/node_modules/retry/equation.gif differ diff --git a/node_modules/pacote/node_modules/retry/example/dns.js b/node_modules/pacote/node_modules/retry/example/dns.js new file mode 100644 index 0000000000000..446729b6f9af6 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/example/dns.js @@ -0,0 +1,31 @@ +var dns = require('dns'); +var retry = require('../lib/retry'); + +function faultTolerantResolve(address, cb) { + var opts = { + retries: 2, + factor: 2, + minTimeout: 1 * 1000, + maxTimeout: 2 * 1000, + randomize: true + }; + var operation = retry.operation(opts); + + operation.attempt(function(currentAttempt) { + dns.resolve(address, function(err, addresses) { + if (operation.retry(err)) { + return; + } + + cb(operation.mainError(), operation.errors(), addresses); + }); + }); +} + +faultTolerantResolve('nodejs.org', function(err, errors, addresses) { + console.warn('err:'); + console.log(err); + + console.warn('addresses:'); + console.log(addresses); +}); \ No newline at end of file diff --git a/node_modules/pacote/node_modules/retry/example/stop.js b/node_modules/pacote/node_modules/retry/example/stop.js new file mode 100644 index 0000000000000..e1ceafeebafc5 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/example/stop.js @@ -0,0 +1,40 @@ +var retry = require('../lib/retry'); + +function attemptAsyncOperation(someInput, cb) { + var opts = { + retries: 2, + factor: 2, + minTimeout: 1 * 1000, + maxTimeout: 2 * 1000, + randomize: true + }; + var operation = retry.operation(opts); + + operation.attempt(function(currentAttempt) { + failingAsyncOperation(someInput, function(err, result) { + + if (err && err.message === 'A fatal error') { + operation.stop(); + return cb(err); + } + + if (operation.retry(err)) { + return; + } + + cb(operation.mainError(), operation.errors(), result); + }); + }); +} + +attemptAsyncOperation('test input', function(err, errors, result) { + console.warn('err:'); + console.log(err); + + console.warn('result:'); + console.log(result); +}); + +function failingAsyncOperation(input, cb) { + return setImmediate(cb.bind(null, new Error('A fatal error'))); +} diff --git a/node_modules/pacote/node_modules/retry/index.js b/node_modules/pacote/node_modules/retry/index.js new file mode 100644 index 0000000000000..ee62f3a112c28 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/retry'); \ No newline at end of file diff --git a/node_modules/pacote/node_modules/retry/lib/retry.js b/node_modules/pacote/node_modules/retry/lib/retry.js new file mode 100644 index 0000000000000..dcb5768072794 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/lib/retry.js @@ -0,0 +1,100 @@ +var RetryOperation = require('./retry_operation'); + +exports.operation = function(options) { + var timeouts = exports.timeouts(options); + return new RetryOperation(timeouts, { + forever: options && options.forever, + unref: options && options.unref, + maxRetryTime: options && options.maxRetryTime + }); +}; + +exports.timeouts = function(options) { + if (options instanceof Array) { + return [].concat(options); + } + + var opts = { + retries: 10, + factor: 2, + minTimeout: 1 * 1000, + maxTimeout: Infinity, + randomize: false + }; + for (var key in options) { + opts[key] = options[key]; + } + + if (opts.minTimeout > opts.maxTimeout) { + throw new Error('minTimeout is greater than maxTimeout'); + } + + var timeouts = []; + for (var i = 0; i < opts.retries; i++) { + timeouts.push(this.createTimeout(i, opts)); + } + + if (options && options.forever && !timeouts.length) { + timeouts.push(this.createTimeout(i, opts)); + } + + // sort the array numerically ascending + timeouts.sort(function(a,b) { + return a - b; + }); + + return timeouts; +}; + +exports.createTimeout = function(attempt, opts) { + var random = (opts.randomize) + ? (Math.random() + 1) + : 1; + + var timeout = Math.round(random * opts.minTimeout * Math.pow(opts.factor, attempt)); + timeout = Math.min(timeout, opts.maxTimeout); + + return timeout; +}; + +exports.wrap = function(obj, options, methods) { + if (options instanceof Array) { + methods = options; + options = null; + } + + if (!methods) { + methods = []; + for (var key in obj) { + if (typeof obj[key] === 'function') { + methods.push(key); + } + } + } + + for (var i = 0; i < methods.length; i++) { + var method = methods[i]; + var original = obj[method]; + + obj[method] = function retryWrapper(original) { + var op = exports.operation(options); + var args = Array.prototype.slice.call(arguments, 1); + var callback = args.pop(); + + args.push(function(err) { + if (op.retry(err)) { + return; + } + if (err) { + arguments[0] = op.mainError(); + } + callback.apply(this, arguments); + }); + + op.attempt(function() { + original.apply(obj, args); + }); + }.bind(obj, original); + obj[method].options = options; + } +}; diff --git a/node_modules/pacote/node_modules/retry/lib/retry_operation.js b/node_modules/pacote/node_modules/retry/lib/retry_operation.js new file mode 100644 index 0000000000000..1e564696fe7e0 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/lib/retry_operation.js @@ -0,0 +1,158 @@ +function RetryOperation(timeouts, options) { + // Compatibility for the old (timeouts, retryForever) signature + if (typeof options === 'boolean') { + options = { forever: options }; + } + + this._originalTimeouts = JSON.parse(JSON.stringify(timeouts)); + this._timeouts = timeouts; + this._options = options || {}; + this._maxRetryTime = options && options.maxRetryTime || Infinity; + this._fn = null; + this._errors = []; + this._attempts = 1; + this._operationTimeout = null; + this._operationTimeoutCb = null; + this._timeout = null; + this._operationStart = null; + + if (this._options.forever) { + this._cachedTimeouts = this._timeouts.slice(0); + } +} +module.exports = RetryOperation; + +RetryOperation.prototype.reset = function() { + this._attempts = 1; + this._timeouts = this._originalTimeouts; +} + +RetryOperation.prototype.stop = function() { + if (this._timeout) { + clearTimeout(this._timeout); + } + + this._timeouts = []; + this._cachedTimeouts = null; +}; + +RetryOperation.prototype.retry = function(err) { + if (this._timeout) { + clearTimeout(this._timeout); + } + + if (!err) { + return false; + } + var currentTime = new Date().getTime(); + if (err && currentTime - this._operationStart >= this._maxRetryTime) { + this._errors.unshift(new Error('RetryOperation timeout occurred')); + return false; + } + + this._errors.push(err); + + var timeout = this._timeouts.shift(); + if (timeout === undefined) { + if (this._cachedTimeouts) { + // retry forever, only keep last error + this._errors.splice(this._errors.length - 1, this._errors.length); + this._timeouts = this._cachedTimeouts.slice(0); + timeout = this._timeouts.shift(); + } else { + return false; + } + } + + var self = this; + var timer = setTimeout(function() { + self._attempts++; + + if (self._operationTimeoutCb) { + self._timeout = setTimeout(function() { + self._operationTimeoutCb(self._attempts); + }, self._operationTimeout); + + if (self._options.unref) { + self._timeout.unref(); + } + } + + self._fn(self._attempts); + }, timeout); + + if (this._options.unref) { + timer.unref(); + } + + return true; +}; + +RetryOperation.prototype.attempt = function(fn, timeoutOps) { + this._fn = fn; + + if (timeoutOps) { + if (timeoutOps.timeout) { + this._operationTimeout = timeoutOps.timeout; + } + if (timeoutOps.cb) { + this._operationTimeoutCb = timeoutOps.cb; + } + } + + var self = this; + if (this._operationTimeoutCb) { + this._timeout = setTimeout(function() { + self._operationTimeoutCb(); + }, self._operationTimeout); + } + + this._operationStart = new Date().getTime(); + + this._fn(this._attempts); +}; + +RetryOperation.prototype.try = function(fn) { + console.log('Using RetryOperation.try() is deprecated'); + this.attempt(fn); +}; + +RetryOperation.prototype.start = function(fn) { + console.log('Using RetryOperation.start() is deprecated'); + this.attempt(fn); +}; + +RetryOperation.prototype.start = RetryOperation.prototype.try; + +RetryOperation.prototype.errors = function() { + return this._errors; +}; + +RetryOperation.prototype.attempts = function() { + return this._attempts; +}; + +RetryOperation.prototype.mainError = function() { + if (this._errors.length === 0) { + return null; + } + + var counts = {}; + var mainError = null; + var mainErrorCount = 0; + + for (var i = 0; i < this._errors.length; i++) { + var error = this._errors[i]; + var message = error.message; + var count = (counts[message] || 0) + 1; + + counts[message] = count; + + if (count >= mainErrorCount) { + mainError = error; + mainErrorCount = count; + } + } + + return mainError; +}; diff --git a/node_modules/pacote/node_modules/retry/package.json b/node_modules/pacote/node_modules/retry/package.json new file mode 100644 index 0000000000000..73c7259707aee --- /dev/null +++ b/node_modules/pacote/node_modules/retry/package.json @@ -0,0 +1,32 @@ +{ + "author": "Tim Koschützki (http://debuggable.com/)", + "name": "retry", + "description": "Abstraction for exponential and custom retry strategies for failed operations.", + "license": "MIT", + "version": "0.12.0", + "homepage": "https://github.com/tim-kos/node-retry", + "repository": { + "type": "git", + "url": "git://github.com/tim-kos/node-retry.git" + }, + "directories": { + "lib": "./lib" + }, + "main": "index", + "engines": { + "node": ">= 4" + }, + "dependencies": {}, + "devDependencies": { + "fake": "0.2.0", + "istanbul": "^0.4.5", + "tape": "^4.8.0" + }, + "scripts": { + "test": "./node_modules/.bin/istanbul cover ./node_modules/tape/bin/tape ./test/integration/*.js", + "release:major": "env SEMANTIC=major npm run release", + "release:minor": "env SEMANTIC=minor npm run release", + "release:patch": "env SEMANTIC=patch npm run release", + "release": "npm version ${SEMANTIC:-patch} -m \"Release %s\" && git push && git push --tags && npm publish" + } +} diff --git a/node_modules/pacote/node_modules/retry/test/common.js b/node_modules/pacote/node_modules/retry/test/common.js new file mode 100644 index 0000000000000..224720696ebac --- /dev/null +++ b/node_modules/pacote/node_modules/retry/test/common.js @@ -0,0 +1,10 @@ +var common = module.exports; +var path = require('path'); + +var rootDir = path.join(__dirname, '..'); +common.dir = { + lib: rootDir + '/lib' +}; + +common.assert = require('assert'); +common.fake = require('fake'); \ No newline at end of file diff --git a/node_modules/pacote/node_modules/retry/test/integration/test-forever.js b/node_modules/pacote/node_modules/retry/test/integration/test-forever.js new file mode 100644 index 0000000000000..b41307cb529f1 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/test/integration/test-forever.js @@ -0,0 +1,24 @@ +var common = require('../common'); +var assert = common.assert; +var retry = require(common.dir.lib + '/retry'); + +(function testForeverUsesFirstTimeout() { + var operation = retry.operation({ + retries: 0, + minTimeout: 100, + maxTimeout: 100, + forever: true + }); + + operation.attempt(function(numAttempt) { + console.log('>numAttempt', numAttempt); + var err = new Error("foo"); + if (numAttempt == 10) { + operation.stop(); + } + + if (operation.retry(err)) { + return; + } + }); +})(); diff --git a/node_modules/pacote/node_modules/retry/test/integration/test-retry-operation.js b/node_modules/pacote/node_modules/retry/test/integration/test-retry-operation.js new file mode 100644 index 0000000000000..e351bb683ed44 --- /dev/null +++ b/node_modules/pacote/node_modules/retry/test/integration/test-retry-operation.js @@ -0,0 +1,258 @@ +var common = require('../common'); +var assert = common.assert; +var fake = common.fake.create(); +var retry = require(common.dir.lib + '/retry'); + +(function testReset() { + var error = new Error('some error'); + var operation = retry.operation([1, 2, 3]); + var attempts = 0; + + var finalCallback = fake.callback('finalCallback'); + fake.expectAnytime(finalCallback); + + var expectedFinishes = 1; + var finishes = 0; + + var fn = function() { + operation.attempt(function(currentAttempt) { + attempts++; + assert.equal(currentAttempt, attempts); + if (operation.retry(error)) { + return; + } + + finishes++ + assert.equal(expectedFinishes, finishes); + assert.strictEqual(attempts, 4); + assert.strictEqual(operation.attempts(), attempts); + assert.strictEqual(operation.mainError(), error); + + if (finishes < 2) { + attempts = 0; + expectedFinishes++; + operation.reset(); + fn() + } else { + finalCallback(); + } + }); + }; + + fn(); +})(); + +(function testErrors() { + var operation = retry.operation(); + + var error = new Error('some error'); + var error2 = new Error('some other error'); + operation._errors.push(error); + operation._errors.push(error2); + + assert.deepEqual(operation.errors(), [error, error2]); +})(); + +(function testMainErrorReturnsMostFrequentError() { + var operation = retry.operation(); + var error = new Error('some error'); + var error2 = new Error('some other error'); + + operation._errors.push(error); + operation._errors.push(error2); + operation._errors.push(error); + + assert.strictEqual(operation.mainError(), error); +})(); + +(function testMainErrorReturnsLastErrorOnEqualCount() { + var operation = retry.operation(); + var error = new Error('some error'); + var error2 = new Error('some other error'); + + operation._errors.push(error); + operation._errors.push(error2); + + assert.strictEqual(operation.mainError(), error2); +})(); + +(function testAttempt() { + var operation = retry.operation(); + var fn = new Function(); + + var timeoutOpts = { + timeout: 1, + cb: function() {} + }; + operation.attempt(fn, timeoutOpts); + + assert.strictEqual(fn, operation._fn); + assert.strictEqual(timeoutOpts.timeout, operation._operationTimeout); + assert.strictEqual(timeoutOpts.cb, operation._operationTimeoutCb); +})(); + +(function testRetry() { + var error = new Error('some error'); + var operation = retry.operation([1, 2, 3]); + var attempts = 0; + + var finalCallback = fake.callback('finalCallback'); + fake.expectAnytime(finalCallback); + + var fn = function() { + operation.attempt(function(currentAttempt) { + attempts++; + assert.equal(currentAttempt, attempts); + if (operation.retry(error)) { + return; + } + + assert.strictEqual(attempts, 4); + assert.strictEqual(operation.attempts(), attempts); + assert.strictEqual(operation.mainError(), error); + finalCallback(); + }); + }; + + fn(); +})(); + +(function testRetryForever() { + var error = new Error('some error'); + var operation = retry.operation({ retries: 3, forever: true }); + var attempts = 0; + + var finalCallback = fake.callback('finalCallback'); + fake.expectAnytime(finalCallback); + + var fn = function() { + operation.attempt(function(currentAttempt) { + attempts++; + assert.equal(currentAttempt, attempts); + if (attempts !== 6 && operation.retry(error)) { + return; + } + + assert.strictEqual(attempts, 6); + assert.strictEqual(operation.attempts(), attempts); + assert.strictEqual(operation.mainError(), error); + finalCallback(); + }); + }; + + fn(); +})(); + +(function testRetryForeverNoRetries() { + var error = new Error('some error'); + var delay = 50 + var operation = retry.operation({ + retries: null, + forever: true, + minTimeout: delay, + maxTimeout: delay + }); + + var attempts = 0; + var startTime = new Date().getTime(); + + var finalCallback = fake.callback('finalCallback'); + fake.expectAnytime(finalCallback); + + var fn = function() { + operation.attempt(function(currentAttempt) { + attempts++; + assert.equal(currentAttempt, attempts); + if (attempts !== 4 && operation.retry(error)) { + return; + } + + var endTime = new Date().getTime(); + var minTime = startTime + (delay * 3); + var maxTime = minTime + 20 // add a little headroom for code execution time + assert(endTime >= minTime) + assert(endTime < maxTime) + assert.strictEqual(attempts, 4); + assert.strictEqual(operation.attempts(), attempts); + assert.strictEqual(operation.mainError(), error); + finalCallback(); + }); + }; + + fn(); +})(); + +(function testStop() { + var error = new Error('some error'); + var operation = retry.operation([1, 2, 3]); + var attempts = 0; + + var finalCallback = fake.callback('finalCallback'); + fake.expectAnytime(finalCallback); + + var fn = function() { + operation.attempt(function(currentAttempt) { + attempts++; + assert.equal(currentAttempt, attempts); + + if (attempts === 2) { + operation.stop(); + + assert.strictEqual(attempts, 2); + assert.strictEqual(operation.attempts(), attempts); + assert.strictEqual(operation.mainError(), error); + finalCallback(); + } + + if (operation.retry(error)) { + return; + } + }); + }; + + fn(); +})(); + +(function testMaxRetryTime() { + var error = new Error('some error'); + var maxRetryTime = 30; + var operation = retry.operation({ + minTimeout: 1, + maxRetryTime: maxRetryTime + }); + var attempts = 0; + + var finalCallback = fake.callback('finalCallback'); + fake.expectAnytime(finalCallback); + + var longAsyncFunction = function (wait, callback){ + setTimeout(callback, wait); + }; + + var fn = function() { + var startTime = new Date().getTime(); + operation.attempt(function(currentAttempt) { + attempts++; + assert.equal(currentAttempt, attempts); + + if (attempts !== 2) { + if (operation.retry(error)) { + return; + } + } else { + var curTime = new Date().getTime(); + longAsyncFunction(maxRetryTime - (curTime - startTime - 1), function(){ + if (operation.retry(error)) { + assert.fail('timeout should be occurred'); + return; + } + + assert.strictEqual(operation.mainError(), error); + finalCallback(); + }); + } + }); + }; + + fn(); +})(); diff --git a/node_modules/pacote/node_modules/retry/test/integration/test-retry-wrap.js b/node_modules/pacote/node_modules/retry/test/integration/test-retry-wrap.js new file mode 100644 index 0000000000000..3d2b6bfa6436d --- /dev/null +++ b/node_modules/pacote/node_modules/retry/test/integration/test-retry-wrap.js @@ -0,0 +1,101 @@ +var common = require('../common'); +var assert = common.assert; +var fake = common.fake.create(); +var retry = require(common.dir.lib + '/retry'); + +function getLib() { + return { + fn1: function() {}, + fn2: function() {}, + fn3: function() {} + }; +} + +(function wrapAll() { + var lib = getLib(); + retry.wrap(lib); + assert.equal(lib.fn1.name, 'bound retryWrapper'); + assert.equal(lib.fn2.name, 'bound retryWrapper'); + assert.equal(lib.fn3.name, 'bound retryWrapper'); +}()); + +(function wrapAllPassOptions() { + var lib = getLib(); + retry.wrap(lib, {retries: 2}); + assert.equal(lib.fn1.name, 'bound retryWrapper'); + assert.equal(lib.fn2.name, 'bound retryWrapper'); + assert.equal(lib.fn3.name, 'bound retryWrapper'); + assert.equal(lib.fn1.options.retries, 2); + assert.equal(lib.fn2.options.retries, 2); + assert.equal(lib.fn3.options.retries, 2); +}()); + +(function wrapDefined() { + var lib = getLib(); + retry.wrap(lib, ['fn2', 'fn3']); + assert.notEqual(lib.fn1.name, 'bound retryWrapper'); + assert.equal(lib.fn2.name, 'bound retryWrapper'); + assert.equal(lib.fn3.name, 'bound retryWrapper'); +}()); + +(function wrapDefinedAndPassOptions() { + var lib = getLib(); + retry.wrap(lib, {retries: 2}, ['fn2', 'fn3']); + assert.notEqual(lib.fn1.name, 'bound retryWrapper'); + assert.equal(lib.fn2.name, 'bound retryWrapper'); + assert.equal(lib.fn3.name, 'bound retryWrapper'); + assert.equal(lib.fn2.options.retries, 2); + assert.equal(lib.fn3.options.retries, 2); +}()); + +(function runWrappedWithoutError() { + var callbackCalled; + var lib = {method: function(a, b, callback) { + assert.equal(a, 1); + assert.equal(b, 2); + assert.equal(typeof callback, 'function'); + callback(); + }}; + retry.wrap(lib); + lib.method(1, 2, function() { + callbackCalled = true; + }); + assert.ok(callbackCalled); +}()); + +(function runWrappedSeveralWithoutError() { + var callbacksCalled = 0; + var lib = { + fn1: function (a, callback) { + assert.equal(a, 1); + assert.equal(typeof callback, 'function'); + callback(); + }, + fn2: function (a, callback) { + assert.equal(a, 2); + assert.equal(typeof callback, 'function'); + callback(); + } + }; + retry.wrap(lib, {}, ['fn1', 'fn2']); + lib.fn1(1, function() { + callbacksCalled++; + }); + lib.fn2(2, function() { + callbacksCalled++; + }); + assert.equal(callbacksCalled, 2); +}()); + +(function runWrappedWithError() { + var callbackCalled; + var lib = {method: function(callback) { + callback(new Error('Some error')); + }}; + retry.wrap(lib, {retries: 1}); + lib.method(function(err) { + callbackCalled = true; + assert.ok(err instanceof Error); + }); + assert.ok(!callbackCalled); +}()); diff --git a/node_modules/pacote/node_modules/retry/test/integration/test-timeouts.js b/node_modules/pacote/node_modules/retry/test/integration/test-timeouts.js new file mode 100644 index 0000000000000..7206b0fb0b01d --- /dev/null +++ b/node_modules/pacote/node_modules/retry/test/integration/test-timeouts.js @@ -0,0 +1,69 @@ +var common = require('../common'); +var assert = common.assert; +var retry = require(common.dir.lib + '/retry'); + +(function testDefaultValues() { + var timeouts = retry.timeouts(); + + assert.equal(timeouts.length, 10); + assert.equal(timeouts[0], 1000); + assert.equal(timeouts[1], 2000); + assert.equal(timeouts[2], 4000); +})(); + +(function testDefaultValuesWithRandomize() { + var minTimeout = 5000; + var timeouts = retry.timeouts({ + minTimeout: minTimeout, + randomize: true + }); + + assert.equal(timeouts.length, 10); + assert.ok(timeouts[0] > minTimeout); + assert.ok(timeouts[1] > timeouts[0]); + assert.ok(timeouts[2] > timeouts[1]); +})(); + +(function testPassedTimeoutsAreUsed() { + var timeoutsArray = [1000, 2000, 3000]; + var timeouts = retry.timeouts(timeoutsArray); + assert.deepEqual(timeouts, timeoutsArray); + assert.notStrictEqual(timeouts, timeoutsArray); +})(); + +(function testTimeoutsAreWithinBoundaries() { + var minTimeout = 1000; + var maxTimeout = 10000; + var timeouts = retry.timeouts({ + minTimeout: minTimeout, + maxTimeout: maxTimeout + }); + for (var i = 0; i < timeouts; i++) { + assert.ok(timeouts[i] >= minTimeout); + assert.ok(timeouts[i] <= maxTimeout); + } +})(); + +(function testTimeoutsAreIncremental() { + var timeouts = retry.timeouts(); + var lastTimeout = timeouts[0]; + for (var i = 0; i < timeouts; i++) { + assert.ok(timeouts[i] > lastTimeout); + lastTimeout = timeouts[i]; + } +})(); + +(function testTimeoutsAreIncrementalForFactorsLessThanOne() { + var timeouts = retry.timeouts({ + retries: 3, + factor: 0.5 + }); + + var expected = [250, 500, 1000]; + assert.deepEqual(expected, timeouts); +})(); + +(function testRetries() { + var timeouts = retry.timeouts({retries: 2}); + assert.strictEqual(timeouts.length, 2); +})(); diff --git a/node_modules/pacote/package.json b/node_modules/pacote/package.json index 959cb1ec48831..8c25e68330bdc 100644 --- a/node_modules/pacote/package.json +++ b/node_modules/pacote/package.json @@ -1,6 +1,6 @@ { "name": "pacote", - "version": "11.2.4", + "version": "11.2.6", "description": "JavaScript package downloader", "author": "Isaac Z. Schlueter (https://izs.me)", "bin": { @@ -25,7 +25,7 @@ "mutate-fs": "^2.1.1", "npm-registry-mock": "^1.3.1", "require-inject": "^1.4.4", - "tap": "^14.10.8" + "tap": "^14.11.0" }, "files": [ "lib/**/*.js" @@ -37,9 +37,9 @@ ], "dependencies": { "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.5", + "@npmcli/installed-package-contents": "^1.0.6", "@npmcli/promise-spawn": "^1.2.0", - "@npmcli/run-script": "^1.3.0", + "@npmcli/run-script": "^1.8.2", "cacache": "^15.0.5", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", @@ -50,10 +50,10 @@ "npm-packlist": "^2.1.4", "npm-pick-manifest": "^6.0.0", "npm-registry-fetch": "^9.0.0", - "promise-retry": "^1.1.1", - "read-package-json-fast": "^1.1.3", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", "rimraf": "^3.0.2", - "ssri": "^8.0.0", + "ssri": "^8.0.1", "tar": "^6.1.0" }, "engines": { diff --git a/node_modules/read-package-json-fast/index.js b/node_modules/read-package-json-fast/index.js index bfef5d6abcacc..cf373029ddf68 100644 --- a/node_modules/read-package-json-fast/index.js +++ b/node_modules/read-package-json-fast/index.js @@ -13,7 +13,7 @@ const normalizePackageBin = require('npm-normalize-package-bin') const normalize = data => { add_id(data) fixBundled(data) - foldinOptionalDeps(data) + pruneRepeatedOptionals(data) fixScripts(data) fixFunding(data) normalizePackageBin(data) @@ -28,14 +28,20 @@ const add_id = data => { return data } -const foldinOptionalDeps = data => { +// it was once common practice to list deps both in optionalDependencies +// and in dependencies, to support npm versions that did not know abbout +// optionalDependencies. This is no longer a relevant need, so duplicating +// the deps in two places is unnecessary and excessive. +const pruneRepeatedOptionals = data => { const od = data.optionalDependencies + const dd = data.dependencies || {} if (od && typeof od === 'object') { - data.dependencies = data.dependencies || {} - for (const [name, spec] of Object.entries(od)) { - data.dependencies[name] = spec + for (const name of Object.keys(od)) { + delete dd[name] } } + if (Object.keys(dd).length === 0) + delete data.dependencies return data } diff --git a/node_modules/read-package-json-fast/package.json b/node_modules/read-package-json-fast/package.json index a59a3b2e86e9b..aa5f5d87007b8 100644 --- a/node_modules/read-package-json-fast/package.json +++ b/node_modules/read-package-json-fast/package.json @@ -1,6 +1,6 @@ { "name": "read-package-json-fast", - "version": "1.2.1", + "version": "2.0.1", "description": "Like read-package-json, but faster", "author": "Isaac Z. Schlueter (https://izs.me)", "license": "ISC", @@ -11,6 +11,9 @@ "postversion": "npm publish", "postpublish": "git push origin --follow-tags" }, + "engines": { + "node": ">=10" + }, "tap": { "check-coverage": true }, diff --git a/package-lock.json b/package-lock.json index eecef9bdf2af7..3f5aec15eafa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "npm", - "version": "7.5.2", + "version": "7.5.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "npm", - "version": "7.5.2", + "version": "7.5.3", "bundleDependencies": [ "@npmcli/arborist", "@npmcli/ci-detect", @@ -358,10 +358,11 @@ ], "license": "Artistic-2.0", "dependencies": { - "@npmcli/arborist": "^2.1.1", + "@npmcli/arborist": "^2.2.1", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.9", - "@npmcli/run-script": "^1.8.1", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/run-script": "^1.8.2", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", @@ -374,10 +375,10 @@ "cli-table3": "^0.6.0", "columnify": "~1.5.4", "glob": "^7.1.4", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.5", "hosted-git-info": "^3.0.8", "ini": "^2.0.0", - "init-package-json": "^2.0.1", + "init-package-json": "^2.0.2", "is-cidr": "^4.0.2", "json-parse-even-better-errors": "^2.3.1", "leven": "^3.1.0", @@ -390,7 +391,7 @@ "libnpmpublish": "^4.0.0", "libnpmsearch": "^3.1.0", "libnpmteam": "^2.0.2", - "libnpmversion": "^1.0.7", + "libnpmversion": "^1.0.8", "make-fetch-happen": "^8.0.13", "minipass": "^3.1.3", "minipass-pipeline": "^1.2.4", @@ -407,12 +408,12 @@ "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "opener": "^1.5.2", - "pacote": "^11.2.3", + "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", "qrcode-terminal": "^0.12.0", "read": "~1.0.7", "read-package-json": "^3.0.0", - "read-package-json-fast": "^1.2.1", + "read-package-json-fast": "^2.0.1", "readdir-scoped-modules": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.4", @@ -432,7 +433,7 @@ }, "devDependencies": { "cmark-gfm": "^0.8.5", - "eslint": "^7.18.0", + "eslint": "^7.19.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", @@ -686,18 +687,18 @@ } }, "node_modules/@npmcli/arborist": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.1.1.tgz", - "integrity": "sha512-zt+dabNvSuhQMlmJL4H0YV4mGujylxgxeXPWSSjMjMoZI3laniHUB+oGOhJi/k68FVoZ/o/Aevi4rWDClfm5ZQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.2.1.tgz", + "integrity": "sha512-J76nY+TYhxNLFAnWy1HqfjszC6dHy5zxHHFt1LJ2pgBDcb00ipNAbTX0qtyv6FPTF67hPErmPKePaKtFr5KvEA==", "inBundle": true, "dependencies": { - "@npmcli/installed-package-contents": "^1.0.5", - "@npmcli/map-workspaces": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.0.1", "@npmcli/move-file": "^1.1.0", "@npmcli/name-from-folder": "^1.0.1", "@npmcli/node-gyp": "^1.0.1", - "@npmcli/run-script": "^1.8.1", + "@npmcli/run-script": "^1.8.2", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", @@ -708,16 +709,19 @@ "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", "npm-registry-fetch": "^9.0.0", - "pacote": "^11.2.4", + "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^1.2.1", + "read-package-json-fast": "^2.0.1", "readdir-scoped-modules": "^1.1.0", "semver": "^7.3.4", "tar": "^6.1.0", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" } }, "node_modules/@npmcli/ci-detect": { @@ -772,15 +776,13 @@ } }, "node_modules/@npmcli/installed-package-contents": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.5.tgz", - "integrity": "sha512-aKIwguaaqb6ViwSOFytniGvLPb9SMCUm39TgM3SfUo7n0TxUMbwoXfpwyvQ4blm10lzbAwTsvjr7QZ85LvTi4A==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", "inBundle": true, "dependencies": { "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1", - "read-package-json-fast": "^1.1.1", - "readdir-scoped-modules": "^1.1.0" + "npm-normalize-package-bin": "^1.0.1" }, "bin": { "installed-package-contents": "index.js" @@ -790,15 +792,15 @@ } }, "node_modules/@npmcli/map-workspaces": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-1.0.1.tgz", - "integrity": "sha512-w6mVyJ2ngWV7O7f9zPjLrQzRDv7leFmNLVnewNuhouV1MYxXz61DXn2ja3AQj6xlnIp9Z/0GdV0/Ut14eVT8Vw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-1.0.2.tgz", + "integrity": "sha512-12nBSZ0EI/jRtCCjjQXF+1Twvj+ecxtBXFRomrTXR0xWn8oppc/OM53aPpPG5EnQWrKAAnOS/hEIATm6kxKz/A==", "inBundle": true, "dependencies": { "@npmcli/name-from-folder": "^1.0.1", "glob": "^7.1.6", "minimatch": "^3.0.4", - "read-package-json-fast": "^1.2.1" + "read-package-json-fast": "^2.0.1" }, "engines": { "node": ">=10" @@ -850,17 +852,17 @@ } }, "node_modules/@npmcli/run-script": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.1.tgz", - "integrity": "sha512-G8c86g9cQHyRINosIcpovzv0BkXQc3urhL1ORf3KTe4TS4UBsg2O4Z2feca/W3pfzdHEJzc83ETBW4aKbb3SaA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.2.tgz", + "integrity": "sha512-iwKq152Q62zG2rz/zRqT/OLDKcF1nBGTGmFdHRkTV8JRte6bUt18vPG4vOr/uoECecrIuJe1SSyvuUF32yt5BA==", "inBundle": true, "dependencies": { - "@npmcli/node-gyp": "^1.0.0", - "@npmcli/promise-spawn": "^1.3.0", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", "puka": "^1.0.1", - "read-package-json-fast": "^1.1.3" + "read-package-json-fast": "^2.0.1" } }, "node_modules/@tootallnate/once": { @@ -1496,7 +1498,6 @@ "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -1559,7 +1560,6 @@ "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", "inBundle": true, "dependencies": { - "colors": "^1.1.2", "object-assign": "^4.1.0", "string-width": "^4.2.0" }, @@ -2258,8 +2258,7 @@ "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "optionator": "^0.8.1" }, "bin": { "escodegen": "bin/escodegen.js", @@ -2324,9 +2323,9 @@ } }, "node_modules/eslint": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", - "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz", + "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==", "dev": true, "inBundle": true, "dependencies": { @@ -3284,9 +3283,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz", + "integrity": "sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw==", "inBundle": true }, "node_modules/har-schema": { @@ -3570,9 +3569,9 @@ } }, "node_modules/init-package-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.1.tgz", - "integrity": "sha512-UsbZSZZipVfK8HsWoOIdCJYKdhqe5J7A2qxPOcTQjP9jNYKAbHiDyPqjugJsLFJR5GmVHuY/Iyha3Dp9pNdF9g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.2.tgz", + "integrity": "sha512-PO64kVeArePvhX7Ff0jVWkpnE1DfGRvaWcStYrPugcJz9twQGYibagKJuIMHCX7ENcp0M6LJlcjLBuLD5KeJMg==", "inBundle": true, "dependencies": { "glob": "^7.1.1", @@ -4395,15 +4394,15 @@ } }, "node_modules/libnpmversion": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.0.7.tgz", - "integrity": "sha512-WNJOnu7pqXv66Szz8pBBf7xFdPobd6fRjf1n2wBjmhy1bsQ5Ifkdfsn0UaQE7JffKs5geoAe7JiBQO2hHSQN7A==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.0.8.tgz", + "integrity": "sha512-WrLLHx+y+0or9IycspDOWVCMde/NGo1AU97CHidjB7DeOjtbfjCOGwqem8z+WsgCnLHjwcvMaP63l7cJG2i9pg==", "inBundle": true, "dependencies": { - "@npmcli/git": "^2.0.1", - "@npmcli/run-script": "^1.2.1", - "read-package-json-fast": "^1.2.1", - "semver": "^7.1.3", + "@npmcli/git": "^2.0.4", + "@npmcli/run-script": "^1.8.2", + "read-package-json-fast": "^2.0.1", + "semver": "^7.3.4", "stringify-package": "^1.0.1" } }, @@ -4668,7 +4667,6 @@ "integrity": "sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ==", "inBundle": true, "dependencies": { - "encoding": "^0.1.12", "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" @@ -5380,15 +5378,15 @@ } }, "node_modules/pacote": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.2.4.tgz", - "integrity": "sha512-GfTeVQGJ6WyBQbQD4t3ocHbyOmTQLmWjkCKSZPmKiGFKYKNUaM5U2gbLzUW8WG1XmS9yQFnsTFA0k3o1+q4klQ==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.2.6.tgz", + "integrity": "sha512-xCl++Hb3aBC7LaWMimbO4xUqZVsEbKDVc6KKDIIyAeBYrmMwY1yJC2nES/lsGd8sdQLUosgBxQyuVNncZ2Ru0w==", "inBundle": true, "dependencies": { "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.5", + "@npmcli/installed-package-contents": "^1.0.6", "@npmcli/promise-spawn": "^1.2.0", - "@npmcli/run-script": "^1.3.0", + "@npmcli/run-script": "^1.8.2", "cacache": "^15.0.5", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", @@ -5399,10 +5397,10 @@ "npm-packlist": "^2.1.4", "npm-pick-manifest": "^6.0.0", "npm-registry-fetch": "^9.0.0", - "promise-retry": "^1.1.1", - "read-package-json-fast": "^1.1.3", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", "rimraf": "^3.0.2", - "ssri": "^8.0.0", + "ssri": "^8.0.1", "tar": "^6.1.0" }, "bin": { @@ -5412,6 +5410,34 @@ "node": ">=10" } }, + "node_modules/pacote/node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "inBundle": true + }, + "node_modules/pacote/node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "inBundle": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pacote/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "inBundle": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5810,13 +5836,16 @@ } }, "node_modules/read-package-json-fast": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-1.2.1.tgz", - "integrity": "sha512-OFbpwnHcv74Oa5YN5WvbOBfLw6yPmPcwvyJJw/tj9cWFBF7juQUDLDSZiOjEcgzfweWeeROOmbPpNN1qm4hcRg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.1.tgz", + "integrity": "sha512-bp6z0tdgLy9KzdfENDIw/53HWAolOVoQTRWXv7PUiqAo3YvvoUVeLr7RWPWq+mu7KUOu9kiT4DvxhUgNUBsvug==", "inBundle": true, "dependencies": { "json-parse-even-better-errors": "^2.3.0", "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/read-pkg": { @@ -9923,17 +9952,17 @@ } }, "@npmcli/arborist": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.1.1.tgz", - "integrity": "sha512-zt+dabNvSuhQMlmJL4H0YV4mGujylxgxeXPWSSjMjMoZI3laniHUB+oGOhJi/k68FVoZ/o/Aevi4rWDClfm5ZQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.2.1.tgz", + "integrity": "sha512-J76nY+TYhxNLFAnWy1HqfjszC6dHy5zxHHFt1LJ2pgBDcb00ipNAbTX0qtyv6FPTF67hPErmPKePaKtFr5KvEA==", "requires": { - "@npmcli/installed-package-contents": "^1.0.5", - "@npmcli/map-workspaces": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.0.1", "@npmcli/move-file": "^1.1.0", "@npmcli/name-from-folder": "^1.0.1", "@npmcli/node-gyp": "^1.0.1", - "@npmcli/run-script": "^1.8.1", + "@npmcli/run-script": "^1.8.2", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", @@ -9944,11 +9973,11 @@ "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", "npm-registry-fetch": "^9.0.0", - "pacote": "^11.2.4", + "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^1.2.1", + "read-package-json-fast": "^2.0.1", "readdir-scoped-modules": "^1.1.0", "semver": "^7.3.4", "tar": "^6.1.0", @@ -9998,25 +10027,23 @@ } }, "@npmcli/installed-package-contents": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.5.tgz", - "integrity": "sha512-aKIwguaaqb6ViwSOFytniGvLPb9SMCUm39TgM3SfUo7n0TxUMbwoXfpwyvQ4blm10lzbAwTsvjr7QZ85LvTi4A==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", "requires": { "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1", - "read-package-json-fast": "^1.1.1", - "readdir-scoped-modules": "^1.1.0" + "npm-normalize-package-bin": "^1.0.1" } }, "@npmcli/map-workspaces": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-1.0.1.tgz", - "integrity": "sha512-w6mVyJ2ngWV7O7f9zPjLrQzRDv7leFmNLVnewNuhouV1MYxXz61DXn2ja3AQj6xlnIp9Z/0GdV0/Ut14eVT8Vw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-1.0.2.tgz", + "integrity": "sha512-12nBSZ0EI/jRtCCjjQXF+1Twvj+ecxtBXFRomrTXR0xWn8oppc/OM53aPpPG5EnQWrKAAnOS/hEIATm6kxKz/A==", "requires": { "@npmcli/name-from-folder": "^1.0.1", "glob": "^7.1.6", "minimatch": "^3.0.4", - "read-package-json-fast": "^1.2.1" + "read-package-json-fast": "^2.0.1" } }, "@npmcli/metavuln-calculator": { @@ -10057,16 +10084,16 @@ } }, "@npmcli/run-script": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.1.tgz", - "integrity": "sha512-G8c86g9cQHyRINosIcpovzv0BkXQc3urhL1ORf3KTe4TS4UBsg2O4Z2feca/W3pfzdHEJzc83ETBW4aKbb3SaA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.2.tgz", + "integrity": "sha512-iwKq152Q62zG2rz/zRqT/OLDKcF1nBGTGmFdHRkTV8JRte6bUt18vPG4vOr/uoECecrIuJe1SSyvuUF32yt5BA==", "requires": { - "@npmcli/node-gyp": "^1.0.0", - "@npmcli/promise-spawn": "^1.3.0", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", "puka": "^1.0.1", - "read-package-json-fast": "^1.1.3" + "read-package-json-fast": "^2.0.1" } }, "@tootallnate/once": { @@ -11145,9 +11172,9 @@ } }, "eslint": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", - "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz", + "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -11858,9 +11885,9 @@ } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz", + "integrity": "sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw==" }, "har-schema": { "version": "2.0.0", @@ -12048,9 +12075,9 @@ "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" }, "init-package-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.1.tgz", - "integrity": "sha512-UsbZSZZipVfK8HsWoOIdCJYKdhqe5J7A2qxPOcTQjP9jNYKAbHiDyPqjugJsLFJR5GmVHuY/Iyha3Dp9pNdF9g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.2.tgz", + "integrity": "sha512-PO64kVeArePvhX7Ff0jVWkpnE1DfGRvaWcStYrPugcJz9twQGYibagKJuIMHCX7ENcp0M6LJlcjLBuLD5KeJMg==", "requires": { "glob": "^7.1.1", "npm-package-arg": "^8.1.0", @@ -12646,14 +12673,14 @@ } }, "libnpmversion": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.0.7.tgz", - "integrity": "sha512-WNJOnu7pqXv66Szz8pBBf7xFdPobd6fRjf1n2wBjmhy1bsQ5Ifkdfsn0UaQE7JffKs5geoAe7JiBQO2hHSQN7A==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.0.8.tgz", + "integrity": "sha512-WrLLHx+y+0or9IycspDOWVCMde/NGo1AU97CHidjB7DeOjtbfjCOGwqem8z+WsgCnLHjwcvMaP63l7cJG2i9pg==", "requires": { - "@npmcli/git": "^2.0.1", - "@npmcli/run-script": "^1.2.1", - "read-package-json-fast": "^1.2.1", - "semver": "^7.1.3", + "@npmcli/git": "^2.0.4", + "@npmcli/run-script": "^1.8.2", + "read-package-json-fast": "^2.0.1", + "semver": "^7.3.4", "stringify-package": "^1.0.1" } }, @@ -13371,14 +13398,14 @@ } }, "pacote": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.2.4.tgz", - "integrity": "sha512-GfTeVQGJ6WyBQbQD4t3ocHbyOmTQLmWjkCKSZPmKiGFKYKNUaM5U2gbLzUW8WG1XmS9yQFnsTFA0k3o1+q4klQ==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.2.6.tgz", + "integrity": "sha512-xCl++Hb3aBC7LaWMimbO4xUqZVsEbKDVc6KKDIIyAeBYrmMwY1yJC2nES/lsGd8sdQLUosgBxQyuVNncZ2Ru0w==", "requires": { "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.5", + "@npmcli/installed-package-contents": "^1.0.6", "@npmcli/promise-spawn": "^1.2.0", - "@npmcli/run-script": "^1.3.0", + "@npmcli/run-script": "^1.8.2", "cacache": "^15.0.5", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", @@ -13389,11 +13416,32 @@ "npm-packlist": "^2.1.4", "npm-pick-manifest": "^6.0.0", "npm-registry-fetch": "^9.0.0", - "promise-retry": "^1.1.1", - "read-package-json-fast": "^1.1.3", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", "rimraf": "^3.0.2", - "ssri": "^8.0.0", + "ssri": "^8.0.1", "tar": "^6.1.0" + }, + "dependencies": { + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + } } }, "parent-module": { @@ -13688,9 +13736,9 @@ } }, "read-package-json-fast": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-1.2.1.tgz", - "integrity": "sha512-OFbpwnHcv74Oa5YN5WvbOBfLw6yPmPcwvyJJw/tj9cWFBF7juQUDLDSZiOjEcgzfweWeeROOmbPpNN1qm4hcRg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.1.tgz", + "integrity": "sha512-bp6z0tdgLy9KzdfENDIw/53HWAolOVoQTRWXv7PUiqAo3YvvoUVeLr7RWPWq+mu7KUOu9kiT4DvxhUgNUBsvug==", "requires": { "json-parse-even-better-errors": "^2.3.0", "npm-normalize-package-bin": "^1.0.1" diff --git a/package.json b/package.json index 2eecfdbfc0456..8f88726ad1e04 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.5.2", + "version": "7.5.3", "name": "npm", "description": "a package manager for JavaScript", "keywords": [ @@ -42,10 +42,11 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^2.1.1", + "@npmcli/arborist": "^2.2.1", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.9", - "@npmcli/run-script": "^1.8.1", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/run-script": "^1.8.2", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", @@ -58,10 +59,10 @@ "cli-table3": "^0.6.0", "columnify": "~1.5.4", "glob": "^7.1.4", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.5", "hosted-git-info": "^3.0.8", "ini": "^2.0.0", - "init-package-json": "^2.0.1", + "init-package-json": "^2.0.2", "is-cidr": "^4.0.2", "json-parse-even-better-errors": "^2.3.1", "leven": "^3.1.0", @@ -74,7 +75,7 @@ "libnpmpublish": "^4.0.0", "libnpmsearch": "^3.1.0", "libnpmteam": "^2.0.2", - "libnpmversion": "^1.0.7", + "libnpmversion": "^1.0.8", "make-fetch-happen": "^8.0.13", "minipass": "^3.1.3", "minipass-pipeline": "^1.2.4", @@ -91,12 +92,12 @@ "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "opener": "^1.5.2", - "pacote": "^11.2.3", + "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", "qrcode-terminal": "^0.12.0", "read": "~1.0.7", "read-package-json": "^3.0.0", - "read-package-json-fast": "^1.2.1", + "read-package-json-fast": "^2.0.1", "readdir-scoped-modules": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.4", @@ -181,7 +182,7 @@ ], "devDependencies": { "cmark-gfm": "^0.8.5", - "eslint": "^7.18.0", + "eslint": "^7.19.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", diff --git a/scripts/install.sh b/scripts/install.sh index 4458de87faefb..8c0ba3de72f12 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -18,7 +18,7 @@ if [ "x$0" = "xsh" ]; then # which is a bit cuter. But on others, &1 is already closed, # so catting to another script file won't do anything. # Follow Location: headers, and fail on errors - curl -f -L -s https://www.npmjs.org/install.sh > npm-install-$$.sh + curl -q -f -L -s https://www.npmjs.org/install.sh > npm-install-$$.sh ret=$? if [ $ret -eq 0 ]; then (exit 0) @@ -134,7 +134,7 @@ fi # need to echo "" after, because Posix sed doesn't treat EOF # as an implied end of line. -url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \ +url=`(curl -qSsL https://registry.npmjs.org/npm/$t; echo "") \ | sed -e 's/^.*tarball":"//' \ | sed -e 's/".*$//'` @@ -142,7 +142,7 @@ ret=$? if [ "x$url" = "x" ]; then ret=125 # try without the -e arg to sed. - url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \ + url=`(curl -qSsL https://registry.npmjs.org/npm/$t; echo "") \ | sed 's/^.*tarball":"//' \ | sed 's/".*$//'` ret=$? @@ -159,7 +159,7 @@ fi echo "fetching: $url" >&2 cd "$TMP" \ - && curl -SsL -o npm.tgz "$url" \ + && curl -qSsL -o npm.tgz "$url" \ && $tar -xzf npm.tgz \ && cd "$TMP"/package \ && echo "removing existing npm" \ diff --git a/tap-snapshots/test-lib-utils-open-url.js-TAP.test.js b/tap-snapshots/test-lib-utils-open-url.js-TAP.test.js new file mode 100644 index 0000000000000..8c8159ebcfc04 --- /dev/null +++ b/tap-snapshots/test-lib-utils-open-url.js-TAP.test.js @@ -0,0 +1,25 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/lib/utils/open-url.js TAP prints where to go when browser is disabled > printed expected message 1`] = ` +npm home: + https://www.npmjs.com + +` + +exports[`test/lib/utils/open-url.js TAP prints where to go when browser is disabled and json is enabled > printed expected message 1`] = ` +{ + "title": "npm home", + "url": "https://www.npmjs.com" +} +` + +exports[`test/lib/utils/open-url.js TAP prints where to go when given browser does not exist > printed expected message 1`] = ` +npm home: + https://www.npmjs.com + +` diff --git a/test/lib/help.js b/test/lib/help.js index 17018acc61620..40a0354210b92 100644 --- a/test/lib/help.js +++ b/test/lib/help.js @@ -55,12 +55,13 @@ const glob = (p, cb) => { let spawnBin = null let spawnArgs = null +let spawnCode = 0 const spawn = (bin, args) => { spawnBin = bin spawnArgs = args const spawnEmitter = new EventEmitter() process.nextTick(() => { - spawnEmitter.emit('close', 0) + spawnEmitter.emit('exit', spawnCode) }) return spawnEmitter } @@ -76,7 +77,9 @@ const help = requireInject('../../lib/help.js', { '../../lib/utils/npm-usage.js': npmUsage, '../../lib/utils/open-url.js': openUrl, '../../lib/utils/output.js': output, - '../../lib/utils/spawn.js': spawn, + child_process: { + spawn, + }, glob, }) @@ -339,6 +342,29 @@ test('npm help ?(un)star', t => { }) }) +test('npm help - woman viewer propagates errors', t => { + npmConfig.viewer = 'woman' + spawnCode = 1 + globResult = [ + '/root/man/man1/npm-star.1', + '/root/man/man1/npm-unstar.1', + ] + t.teardown(() => { + npmConfig.viewer = undefined + spawnCode = 0 + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['?(un)star'], (err) => { + t.match(err, /help process exited with code: 1/, 'received the correct error') + t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly') + t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-unstar.1')`], 'passes the correct arguments') + t.end() + }) +}) + test('npm help un*', t => { globResult = [ '/root/man/man1/npm-unstar.1', @@ -360,3 +386,25 @@ test('npm help un*', t => { t.end() }) }) + +test('npm help - man viewer propagates errors', t => { + spawnCode = 1 + globResult = [ + '/root/man/man1/npm-unstar.1', + '/root/man/man1/npm-uninstall.1', + '/root/man/man1/npm-unpublish.1', + ] + t.teardown(() => { + spawnCode = 0 + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['un*'], (err) => { + t.match(err, /help process exited with code: 1/, 'received correct error') + t.equal(spawnBin, 'man', 'calls man by default') + t.strictSame(spawnArgs, ['1', 'npm-unstar'], 'passes the correct arguments') + t.end() + }) +}) diff --git a/test/lib/publish.js b/test/lib/publish.js index f0ce0b966533c..6d5cebf540698 100644 --- a/test/lib/publish.js +++ b/test/lib/publish.js @@ -4,19 +4,40 @@ const requireInject = require('require-inject') // mock config const {defaults} = require('../../lib/utils/config.js') const credentials = { - token: 'asdfasdf', - alwaysAuth: false, + 'https://unauthed.registry': { + email: 'me@example.com', + }, + 'https://scope.specific.registry': { + token: 'some.registry.token', + alwaysAuth: false, + }, + 'https://some.registry': { + token: 'some.registry.token', + alwaysAuth: false, + }, + 'https://registry.npmjs.org/': { + token: 'npmjs.registry.token', + alwaysAuth: false, + }, } const config = { list: [defaults], - getCredentialsByURI: () => credentials, } + +const registryCredentials = (t, registry) => { + return (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return credentials[uri] + } +} + const fs = require('fs') t.test('should publish with libnpmpublish, respecting publishConfig', (t) => { - t.plan(5) + t.plan(6) - const publishConfig = { registry: 'https://some.registry' } + const registry = 'https://some.registry' + const publishConfig = { registry } const testDir = t.testdir({ 'package.json': JSON.stringify({ name: 'my-cool-pkg', @@ -30,9 +51,12 @@ t.test('should publish with libnpmpublish, respecting publishConfig', (t) => { flatOptions: { json: true, defaultTag: 'latest', - registry: 'https://registry.npmjs.org', + registry, + }, + config: { + ...config, + getCredentialsByURI: registryCredentials(t, registry), }, - config, }, '../../lib/utils/tar.js': { getContents: () => ({ @@ -71,8 +95,9 @@ t.test('should publish with libnpmpublish, respecting publishConfig', (t) => { }) t.test('re-loads publishConfig if added during script process', (t) => { - t.plan(5) - const publishConfig = { registry: 'https://some.registry' } + t.plan(6) + const registry = 'https://some.registry' + const publishConfig = { registry } const testDir = t.testdir({ 'package.json': JSON.stringify({ name: 'my-cool-pkg', @@ -87,7 +112,10 @@ t.test('re-loads publishConfig if added during script process', (t) => { defaultTag: 'latest', registry: 'https://registry.npmjs.org/', }, - config, + config: { + ...config, + getCredentialsByURI: registryCredentials(t, registry), + }, }, '../../lib/utils/tar.js': { getContents: () => ({ @@ -112,7 +140,7 @@ t.test('re-loads publishConfig if added during script process', (t) => { t.match(manifest, { name: 'my-cool-pkg', version: '1.0.0' }, 'gets manifest') t.isa(tarData, Buffer, 'tarData is a buffer') t.ok(opts, 'gets opts object') - t.same(opts.registry, publishConfig.registry, 'publishConfig is passed through') + t.same(opts.registry, registry, 'publishConfig is passed through') }, }, }) @@ -124,9 +152,10 @@ t.test('re-loads publishConfig if added during script process', (t) => { }) }) -t.test('should not log if silent', (t) => { +t.test('should not log if silent (dry run)', (t) => { t.plan(2) + const registry = 'https://registry.npmjs.org' const testDir = t.testdir({ 'package.json': JSON.stringify({ name: 'my-cool-pkg', @@ -140,9 +169,14 @@ t.test('should not log if silent', (t) => { json: false, defaultTag: 'latest', dryRun: true, - registry: 'https://registry.npmjs.org/', + registry, + }, + config: { + ...config, + getCredentialsByURI: () => { + throw new Error('should not call getCredentialsByURI in dry run') + }, }, - config, }, '../../lib/utils/tar.js': { getContents: () => ({}), @@ -164,7 +198,7 @@ t.test('should not log if silent', (t) => { libnpmpack: async () => '', libnpmpublish: { publish: (manifest, tarData, opts) => { - throw new Error('should not call libnpmpublish!') + throw new Error('should not call libnpmpublish in dry run') }, }, }) @@ -176,8 +210,10 @@ t.test('should not log if silent', (t) => { }) }) -t.test('should log tarball contents', (t) => { +t.test('should log tarball contents (dry run)', (t) => { t.plan(3) + + const registry = 'https://registry.npmjs.org' const testDir = t.testdir({ 'package.json': JSON.stringify({ name: 'my-cool-pkg', @@ -191,12 +227,12 @@ t.test('should log tarball contents', (t) => { json: false, defaultTag: 'latest', dryRun: true, - registry: 'https://registry.npmjs.org/', + registry, }, config: { ...config, getCredentialsByURI: () => { - throw new Error('should not call getCredentialsByURI!') + throw new Error('should not call getCredentialsByURI in dry run') }}, }, '../../lib/utils/tar.js': { @@ -216,7 +252,7 @@ t.test('should log tarball contents', (t) => { libnpmpack: async () => '', libnpmpublish: { publish: () => { - throw new Error('should not call libnpmpublish!') + throw new Error('should not call libnpmpublish in dry run') }, }, }) @@ -246,12 +282,15 @@ t.test('shows usage with wrong set of arguments', (t) => { t.test('throws when invalid tag', (t) => { t.plan(1) + + const registry = 'https://registry.npmjs.org' + const publish = requireInject('../../lib/publish.js', { '../../lib/npm.js': { flatOptions: { json: false, defaultTag: '0.0.13', - registry: 'https://registry.npmjs.org/', + registry, }, config, }, @@ -265,7 +304,9 @@ t.test('throws when invalid tag', (t) => { }) t.test('can publish a tarball', t => { - t.plan(3) + t.plan(4) + + const registry = 'https://registry.npmjs.org/' const testDir = t.testdir({ package: { 'package.json': JSON.stringify({ @@ -291,9 +332,12 @@ t.test('can publish a tarball', t => { flatOptions: { json: true, defaultTag: 'latest', - registry: 'https://registry.npmjs.org/', + registry, + }, + config: { + ...config, + getCredentialsByURI: registryCredentials(t, registry), }, - config, }, '../../lib/utils/tar.js': { getContents: () => ({ @@ -323,39 +367,25 @@ t.test('can publish a tarball', t => { }) }) -t.test('throw if no registry', async t => { - t.plan(1) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - json: false, - registry: null, - }, - config, - }, - }) - - return publish([], (err) => { - t.match(err, { - message: 'No registry specified.', - code: 'ENOREGISTRY', - }, 'throws when registry unset') - }) -}) - t.test('throw if not logged in', async t => { - t.plan(1) + t.plan(2) + const registry = 'https://unauthed.registry' + const publish = requireInject('../../lib/publish.js', { + '../../lib/utils/tar.js': { + getContents: () => ({ + id: 'someid', + }), + logTar: () => {}, + }, '../../lib/npm.js': { flatOptions: { json: false, - registry: 'https://registry.npmjs.org/', + registry, }, config: { ...config, - getCredentialsByURI: () => ({ - email: 'me@example.com', - }), + getCredentialsByURI: registryCredentials(t, registry), }, }, }) @@ -369,9 +399,10 @@ t.test('throw if not logged in', async t => { }) t.test('read registry only from publishConfig', t => { - t.plan(3) + t.plan(4) - const publishConfig = { registry: 'https://some.registry' } + const registry = 'https://some.registry' + const publishConfig = { registry } const testDir = t.testdir({ 'package.json': JSON.stringify({ name: 'my-cool-pkg', @@ -385,7 +416,10 @@ t.test('read registry only from publishConfig', t => { flatOptions: { json: false, }, - config, + config: { + ...config, + getCredentialsByURI: registryCredentials(t, registry), + }, }, '../../lib/utils/tar.js': { getContents: () => ({ @@ -397,7 +431,7 @@ t.test('read registry only from publishConfig', t => { libnpmpublish: { publish: (manifest, tarData, opts) => { t.match(manifest, { name: 'my-cool-pkg', version: '1.0.0' }, 'gets manifest') - t.same(opts.registry, publishConfig.registry, 'publishConfig is passed through') + t.same(opts.registry, registry, 'publishConfig is passed through') }, }, }) @@ -408,3 +442,44 @@ t.test('read registry only from publishConfig', t => { t.pass('got to callback') }) }) + +t.test('should check auth for scope specific registry', t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: '@npm/my-cool-pkg', + version: '1.0.0', + }, null, 2), + }) + + const registry = 'https://scope.specific.registry' + const publish = requireInject('../../lib/publish.js', { + '../../lib/npm.js': { + flatOptions: { + json: false, + '@npm:registry': registry, + }, + config: { + ...config, + getCredentialsByURI: registryCredentials(t, registry), + }, + }, + '../../lib/utils/tar.js': { + getContents: () => ({ + id: 'someid', + }), + logTar: () => {}, + }, + '../../lib/utils/output.js': () => {}, + '../../lib/utils/otplease.js': (opts, fn) => { + return Promise.resolve().then(() => fn(opts)) + }, + libnpmpublish: { + publish: () => '', + }, + }) + return publish([testDir], (er) => { + if (er) + throw er + t.pass('got to callback') + }) +}) diff --git a/test/lib/utils/open-url.js b/test/lib/utils/open-url.js new file mode 100644 index 0000000000000..ce1783dadcd7b --- /dev/null +++ b/test/lib/utils/open-url.js @@ -0,0 +1,165 @@ +const { test } = require('tap') +const requireInject = require('require-inject') + +const npm = { + _config: { + json: false, + browser: true, + }, + config: { + get: (k) => npm._config[k], + set: (k, v) => { + npm._config[k] = v + }, + }, +} + +const OUTPUT = [] +const output = (...args) => OUTPUT.push(args) + +let openerUrl = null +let openerOpts = null +let openerResult = null +const opener = (url, opts, cb) => { + openerUrl = url + openerOpts = opts + return cb(openerResult) +} + +const openUrl = requireInject('../../../lib/utils/open-url.js', { + '../../../lib/npm.js': npm, + '../../../lib/utils/output.js': output, + opener, +}) + +test('opens a url', (t) => { + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + }) + openUrl('https://www.npmjs.com', 'npm home', (err) => { + if (err) + throw err + + t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') + t.same(openerOpts, { command: null }, 'passed command as null (the default)') + t.same(OUTPUT, [], 'printed no output') + t.done() + }) +}) + +test('returns error for non-https and non-file url', (t) => { + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + }) + openUrl('ftp://www.npmjs.com', 'npm home', (err) => { + t.match(err, /Invalid URL/, 'got the correct error') + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.same(OUTPUT, [], 'printed no output') + t.done() + }) +}) + +test('returns error for non-parseable url', (t) => { + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + }) + openUrl('git+ssh://user@host:repo.git', 'npm home', (err) => { + t.match(err, /Invalid URL/, 'got the correct error') + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.same(OUTPUT, [], 'printed no output') + t.done() + }) +}) + +test('opens a url with the given browser', (t) => { + npm.config.set('browser', 'chrome') + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + npm.config.set('browser', true) + }) + openUrl('https://www.npmjs.com', 'npm home', (err) => { + if (err) + throw err + + t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') + t.same(openerOpts, { command: 'chrome' }, 'passed the given browser as command') + t.same(OUTPUT, [], 'printed no output') + t.done() + }) +}) + +test('prints where to go when browser is disabled', (t) => { + npm.config.set('browser', false) + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + npm.config.set('browser', true) + }) + openUrl('https://www.npmjs.com', 'npm home', (err) => { + if (err) + throw err + + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.equal(OUTPUT.length, 1, 'got one logged message') + t.equal(OUTPUT[0].length, 1, 'logged message had one value') + t.matchSnapshot(OUTPUT[0][0], 'printed expected message') + t.done() + }) +}) + +test('prints where to go when browser is disabled and json is enabled', (t) => { + npm.config.set('browser', false) + npm.config.set('json', true) + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + npm.config.set('browser', true) + npm.config.set('json', false) + }) + openUrl('https://www.npmjs.com', 'npm home', (err) => { + if (err) + throw err + + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.equal(OUTPUT.length, 1, 'got one logged message') + t.equal(OUTPUT[0].length, 1, 'logged message had one value') + t.matchSnapshot(OUTPUT[0][0], 'printed expected message') + t.done() + }) +}) + +test('prints where to go when given browser does not exist', (t) => { + npm.config.set('browser', 'firefox') + openerResult = Object.assign(new Error('failed'), { code: 'ENOENT' }) + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + npm.config.set('browser', true) + }) + openUrl('https://www.npmjs.com', 'npm home', (err) => { + if (err) + throw err + + t.equal(openerUrl, 'https://www.npmjs.com', 'tried to open the correct url') + t.same(openerOpts, { command: 'firefox' }, 'tried to use the correct browser') + t.equal(OUTPUT.length, 1, 'got one logged message') + t.equal(OUTPUT[0].length, 1, 'logged message had one value') + t.matchSnapshot(OUTPUT[0][0], 'printed expected message') + t.done() + }) +}) diff --git a/test/lib/utils/otplease.js b/test/lib/utils/otplease.js new file mode 100644 index 0000000000000..048856b485770 --- /dev/null +++ b/test/lib/utils/otplease.js @@ -0,0 +1,94 @@ +const { test } = require('tap') +const requireInject = require('require-inject') + +const readUserInfo = { + otp: async () => '1234', +} + +const otplease = requireInject('../../../lib/utils/otplease.js', { + '../../../lib/utils/read-user-info.js': readUserInfo, +}) + +test('prompts for otp for EOTP', async (t) => { + const stdinTTY = process.stdin.isTTY + const stdoutTTY = process.stdout.isTTY + process.stdin.isTTY = true + process.stdout.isTTY = true + t.teardown(() => { + process.stdin.isTTY = stdinTTY + process.stdout.isTTY = stdoutTTY + }) + + let runs = 0 + const fn = async (opts) => { + if (++runs === 1) + throw Object.assign(new Error('nope'), { code: 'EOTP' }) + + t.equal(opts.some, 'prop', 'carried original options') + t.equal(opts.otp, '1234', 'received the otp') + t.done() + } + + await otplease({ some: 'prop' }, fn) +}) + +test('prompts for otp for 401', async (t) => { + const stdinTTY = process.stdin.isTTY + const stdoutTTY = process.stdout.isTTY + process.stdin.isTTY = true + process.stdout.isTTY = true + t.teardown(() => { + process.stdin.isTTY = stdinTTY + process.stdout.isTTY = stdoutTTY + }) + + let runs = 0 + const fn = async (opts) => { + if (++runs === 1) { + throw Object.assign(new Error('nope'), { + code: 'E401', + body: 'one-time pass required', + }) + } + + t.equal(opts.some, 'prop', 'carried original options') + t.equal(opts.otp, '1234', 'received the otp') + t.done() + } + + await otplease({ some: 'prop' }, fn) +}) + +test('does not prompt for non-otp errors', async (t) => { + const stdinTTY = process.stdin.isTTY + const stdoutTTY = process.stdout.isTTY + process.stdin.isTTY = true + process.stdout.isTTY = true + t.teardown(() => { + process.stdin.isTTY = stdinTTY + process.stdout.isTTY = stdoutTTY + }) + + const fn = async (opts) => { + throw new Error('nope') + } + + t.rejects(otplease({ some: 'prop' }, fn), { message: 'nope' }, 'rejects with the original error') +}) + +test('does not prompt if stdin or stdout is not a tty', async (t) => { + const stdinTTY = process.stdin.isTTY + const stdoutTTY = process.stdout.isTTY + process.stdin.isTTY = false + process.stdout.isTTY = false + t.teardown(() => { + process.stdin.isTTY = stdinTTY + process.stdout.isTTY = stdoutTTY + }) + + const fn = async (opts) => { + throw Object.assign(new Error('nope'), { code: 'EOTP' }) + } + + t.rejects(otplease({ some: 'prop' }, fn), { message: 'nope' }, 'rejects with the original error') +}) diff --git a/test/lib/utils/pulse-till-done.js b/test/lib/utils/pulse-till-done.js new file mode 100644 index 0000000000000..16c2d521dad08 --- /dev/null +++ b/test/lib/utils/pulse-till-done.js @@ -0,0 +1,35 @@ +const { test } = require('tap') +const requireInject = require('require-inject') + +let pulseStarted = null +const npmlog = { + gauge: { + pulse: () => { + if (pulseStarted) + pulseStarted() + }, + }, +} + +const pulseTillDone = requireInject('../../../lib/utils/pulse-till-done.js', { + npmlog, +}) + +test('pulses (with promise)', async (t) => { + t.teardown(() => { + pulseStarted = null + }) + + let resolver + const promise = new Promise(resolve => { + resolver = resolve + }) + + const result = pulseTillDone.withPromise(promise) + // wait until the gauge has fired at least once + await new Promise(resolve => { + pulseStarted = resolve + }) + resolver('value') + t.resolveMatch(result, 'value', 'returned the resolved promise') +}) diff --git a/test/lib/utils/read-user-info.js b/test/lib/utils/read-user-info.js new file mode 100644 index 0000000000000..99d85d66c4feb --- /dev/null +++ b/test/lib/utils/read-user-info.js @@ -0,0 +1,116 @@ +const { test } = require('tap') +const requireInject = require('require-inject') + +let readOpts = null +let readResult = null +const read = (opts, cb) => { + readOpts = opts + return cb(null, readResult) +} + +const npmlog = { + clearProgress: () => {}, + showProgress: () => {}, +} + +const npmUserValidate = { + username: (username) => { + if (username === 'invalid') + return new Error('invalid username') + + return null + }, + email: (email) => { + if (email.startsWith('invalid')) + return new Error('invalid email') + + return null + }, +} + +const readUserInfo = requireInject('../../../lib/utils/read-user-info.js', { + read, + npmlog, + 'npm-user-validate': npmUserValidate, +}) + +test('otp', async (t) => { + readResult = '1234' + t.teardown(() => { + readResult = null + readOpts = null + }) + const result = await readUserInfo.otp() + t.equal(result, '1234', 'received the otp') +}) + +test('password', async (t) => { + readResult = 'password' + t.teardown(() => { + readResult = null + readOpts = null + }) + const result = await readUserInfo.password() + t.equal(result, 'password', 'received the password') + t.match(readOpts, { + silent: true, + }, 'got the correct options') +}) + +test('username', async (t) => { + readResult = 'username' + t.teardown(() => { + readResult = null + readOpts = null + }) + const result = await readUserInfo.username() + t.equal(result, 'username', 'received the username') +}) + +test('username - invalid warns and retries', async (t) => { + readResult = 'invalid' + t.teardown(() => { + readResult = null + readOpts = null + }) + + let logMsg + const log = { + warn: (msg) => logMsg = msg, + } + const pResult = readUserInfo.username(null, null, { log }) + // have to swap it to a valid username after execution starts + // or it will loop forever + readResult = 'valid' + const result = await pResult + t.equal(result, 'valid', 'received the username') + t.equal(logMsg, 'invalid username') +}) + +test('email', async (t) => { + readResult = 'foo@bar.baz' + t.teardown(() => { + readResult = null + readOpts = null + }) + const result = await readUserInfo.email() + t.equal(result, 'foo@bar.baz', 'received the email') +}) + +test('email - invalid warns and retries', async (t) => { + readResult = 'invalid@bar.baz' + t.teardown(() => { + readResult = null + readOpts = null + }) + + let logMsg + const log = { + warn: (msg) => logMsg = msg, + } + const pResult = readUserInfo.email(null, null, { log }) + readResult = 'foo@bar.baz' + const result = await pResult + t.equal(result, 'foo@bar.baz', 'received the email') + t.equal(logMsg, 'invalid email') +})