diff --git a/deps/npm/bin/npx-cli.js b/deps/npm/bin/npx-cli.js index a495090c64c784..7a3fb39837d276 100755 --- a/deps/npm/bin/npx-cli.js +++ b/deps/npm/bin/npx-cli.js @@ -10,18 +10,18 @@ process.argv.splice(2, 0, 'exec') const removedSwitches = new Set([ 'always-spawn', 'ignore-existing', - 'shell-auto-fallback' + 'shell-auto-fallback', ]) const removedOpts = new Set([ 'npm', 'node-arg', - 'n' + 'n', ]) const removed = new Set([ ...removedSwitches, - ...removedOpts + ...removedOpts, ]) const { definitions, shorthands } = require('../lib/utils/config/index.js') @@ -40,7 +40,7 @@ const switches = new Set([ 'version', 'v', 'help', - 'h' + 'h', ]) // things that do take a value @@ -55,7 +55,7 @@ const opts = new Set([ 'shell', 'npm', 'node-arg', - 'n' + 'n', ]) // break out of loop when we find a positional argument or -- @@ -65,9 +65,9 @@ let i let sawRemovedFlags = false for (i = 3; i < process.argv.length; i++) { const arg = process.argv[i] - if (arg === '--') { + if (arg === '--') break - } else if (/^-/.test(arg)) { + else if (/^-/.test(arg)) { const [key, ...v] = arg.replace(/^-+/, '').split('=') switch (key) { @@ -87,9 +87,8 @@ for (i = 3; i < process.argv.length; i++) { // resolve shorthands and run again if (shorthands[key] && !removed.has(key)) { const a = [...shorthands[key]] - if (v.length) { + if (v.length) a.push(v.join('=')) - } process.argv.splice(i, 1, ...a) i-- continue @@ -110,9 +109,8 @@ for (i = 3; i < process.argv.length; i++) { if (removed.has(key)) { // also remove the value for the cut key. process.argv.splice(i + 1, 1) - } else { + } else i++ - } } } else { // found a positional arg, put -- in front of it, and we're done @@ -121,8 +119,7 @@ for (i = 3; i < process.argv.length; i++) { } } -if (sawRemovedFlags) { +if (sawRemovedFlags) console.error('See `npm help exec` for more information') -} cli(process) diff --git a/deps/npm/docs/content/using-npm/workspaces.md b/deps/npm/docs/content/using-npm/workspaces.md index 612404a9946470..829168864abe45 100644 --- a/deps/npm/docs/content/using-npm/workspaces.md +++ b/deps/npm/docs/content/using-npm/workspaces.md @@ -36,8 +36,8 @@ Workspaces are usually defined via the `workspaces` property of the ``` Given the above `package.json` example living at a current working -directory `.` that contains a folder named `workspace-a` that disposes -of a `package.json` inside it, defining a nodejs package, e.g: +directory `.` that contains a folder named `workspace-a` that itself contains +a `package.json` inside it, defining a nodejs package, e.g: ``` . diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index ef780840c94f8a..9e35f955467094 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -159,7 +159,7 @@

Description

the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm’s source tree will show:

-
npm@7.18.1 /path/to/npm
+
npm@7.19.0 /path/to/npm
 └─┬ init-package-json@0.0.4
   └── promzard@0.1.5
 
diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 16aa3632315643..319135aa33d9c4 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -148,7 +148,7 @@

Table of contents

npm <command> [args]
 

Version

-

7.18.1

+

7.19.0

Description

npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency diff --git a/deps/npm/docs/output/using-npm/workspaces.html b/deps/npm/docs/output/using-npm/workspaces.html index d929729856cba0..55bdb32ec460d8 100644 --- a/deps/npm/docs/output/using-npm/workspaces.html +++ b/deps/npm/docs/output/using-npm/workspaces.html @@ -168,8 +168,8 @@

Defining workspaces

}

Given the above package.json example living at a current working -directory . that contains a folder named workspace-a that disposes -of a package.json inside it, defining a nodejs package, e.g:

+directory . that contains a folder named workspace-a that itself contains +a package.json inside it, defining a nodejs package, e.g:

.
 +-- package.json
 `-- workspace-a
diff --git a/deps/npm/lib/cli.js b/deps/npm/lib/cli.js
index fbceb459db97c0..9544f8451f8ae0 100644
--- a/deps/npm/lib/cli.js
+++ b/deps/npm/lib/cli.js
@@ -1,5 +1,5 @@
 // Separated out for easier unit testing
-module.exports = (process) => {
+module.exports = async (process) => {
   // set it here so that regardless of what happens later, we don't
   // leak any private CLI configs to other programs
   process.title = 'npm'
@@ -19,8 +19,8 @@ module.exports = (process) => {
   checkForUnsupportedNode()
 
   const npm = require('../lib/npm.js')
-  const errorHandler = require('../lib/utils/error-handler.js')
-  errorHandler.setNpm(npm)
+  const exitHandler = require('../lib/utils/exit-handler.js')
+  exitHandler.setNpm(npm)
 
   // if npm is called as "npmg" or "npm_g", then
   // run in global mode.
@@ -32,20 +32,18 @@ module.exports = (process) => {
   log.info('using', 'npm@%s', npm.version)
   log.info('using', 'node@%s', process.version)
 
-  process.on('uncaughtException', errorHandler)
-  process.on('unhandledRejection', errorHandler)
+  process.on('uncaughtException', exitHandler)
+  process.on('unhandledRejection', exitHandler)
 
-  // now actually fire up npm and run the command.
-  // this is how to use npm programmatically:
   const updateNotifier = require('../lib/utils/update-notifier.js')
-  npm.load(async er => {
-    if (er)
-      return errorHandler(er)
 
-    // npm --version=cli
+  // now actually fire up npm and run the command.
+  // this is how to use npm programmatically:
+  try {
+    await npm.load()
     if (npm.config.get('version', 'cli')) {
       npm.output(npm.version)
-      return errorHandler.exit(0)
+      return exitHandler()
     }
 
     // npm --versions=cli
@@ -57,22 +55,23 @@ module.exports = (process) => {
     updateNotifier(npm)
 
     const cmd = npm.argv.shift()
+    if (!cmd) {
+      npm.output(npm.usage)
+      process.exitCode = 1
+      return exitHandler()
+    }
+
     const impl = npm.commands[cmd]
-    if (impl)
-      impl(npm.argv, errorHandler)
-    else {
-      try {
-        if (cmd) {
-          const didYouMean = require('./utils/did-you-mean.js')
-          const suggestions = await didYouMean(npm, npm.localPrefix, cmd)
-          npm.output(`Unknown command: "${cmd}"${suggestions}\n\nTo see a list of supported npm commands, run:\n  npm help`)
-        } else
-          npm.output(npm.usage)
-        process.exitCode = 1
-        return errorHandler()
-      } catch (err) {
-        errorHandler(err)
-      }
+    if (!impl) {
+      const didYouMean = require('./utils/did-you-mean.js')
+      const suggestions = await didYouMean(npm, npm.localPrefix, cmd)
+      npm.output(`Unknown command: "${cmd}"${suggestions}\n\nTo see a list of supported npm commands, run:\n  npm help`)
+      process.exitCode = 1
+      return exitHandler()
     }
-  })
+
+    impl(npm.argv, exitHandler)
+  } catch (err) {
+    return exitHandler(err)
+  }
 }
diff --git a/deps/npm/lib/init.js b/deps/npm/lib/init.js
index d34f92b882b32d..e4bd20b7210e81 100644
--- a/deps/npm/lib/init.js
+++ b/deps/npm/lib/init.js
@@ -5,8 +5,8 @@ const initJson = require('init-package-json')
 const npa = require('npm-package-arg')
 const rpj = require('read-package-json-fast')
 const libexec = require('libnpmexec')
-const parseJSON = require('json-parse-even-better-errors')
 const mapWorkspaces = require('@npmcli/map-workspaces')
+const PackageJson = require('@npmcli/package-json')
 
 const getLocationMsg = require('./exec/get-workspace-location-msg.js')
 const BaseCommand = require('./base-command.js')
@@ -199,35 +199,16 @@ class Init extends BaseCommand {
       return
     }
 
-    let manifest
-    try {
-      manifest =
-        fs.readFileSync(resolve(this.npm.localPrefix, 'package.json'), 'utf-8')
-    } catch (error) {
-      throw new Error('package.json not found')
-    }
-
-    try {
-      manifest = parseJSON(manifest)
-    } catch (error) {
-      throw new Error(`Invalid package.json: ${error}`)
-    }
+    const pkgJson = await PackageJson.load(this.npm.localPrefix)
 
-    if (!manifest.workspaces)
-      manifest.workspaces = []
-
-    manifest.workspaces.push(relative(this.npm.localPrefix, workspacePath))
-
-    // format content
-    const {
-      [Symbol.for('indent')]: indent,
-      [Symbol.for('newline')]: newline,
-    } = manifest
-
-    const content = (JSON.stringify(manifest, null, indent) + '\n')
-      .replace(/\n/g, newline)
+    pkgJson.update({
+      workspaces: [
+        ...(pkgJson.content.workspaces || []),
+        relative(this.npm.localPrefix, workspacePath),
+      ],
+    })
 
-    fs.writeFileSync(resolve(this.npm.localPrefix, 'package.json'), content)
+    await pkgJson.save()
   }
 }
 
diff --git a/deps/npm/lib/ls.js b/deps/npm/lib/ls.js
index f146928a96b41d..7540692911976c 100644
--- a/deps/npm/lib/ls.js
+++ b/deps/npm/lib/ls.js
@@ -145,11 +145,9 @@ class LS extends ArboristWorkspaceCmd {
               dev,
               development,
               link,
-              node,
               prod,
               production,
               only,
-              tree,
             }))
             .map(mapEdgesToNodes({ seenPaths }))
             .concat(appendExtraneousChildren({ node, seenPaths }))
@@ -310,6 +308,9 @@ const getHumanOutputItem = (node, { args, color, global, long }) => {
   const targetLocation = node.root
     ? relative(node.root.realpath, node.realpath)
     : node.targetLocation
+  const invalid = node[_invalid]
+    ? `invalid: ${node[_invalid]}`
+    : ''
   const label =
     (
       node[_missing]
@@ -323,8 +324,8 @@ const getHumanOutputItem = (node, { args, color, global, long }) => {
         : ''
     ) +
     (
-      node[_invalid]
-        ? ' ' + (color ? chalk.red.bgBlack('invalid') : 'invalid')
+      invalid
+        ? ' ' + (color ? chalk.red.bgBlack(invalid) : invalid)
         : ''
     ) +
     (
@@ -375,7 +376,7 @@ const getJsonOutputItem = (node, { global, long }) => {
     item.extraneous = true
 
   if (node[_invalid])
-    item.invalid = true
+    item.invalid = node[_invalid]
 
   if (node[_missing] && !isOptional(node)) {
     item.required = node[_required]
@@ -392,11 +393,9 @@ const filterByEdgesTypes = ({
   dev,
   development,
   link,
-  node,
   prod,
   production,
   only,
-  tree,
 }) => {
   // filter deps by type, allows for: `npm ls --dev`, `npm ls --prod`,
   // `npm ls --link`, `npm ls --only=dev`, etc
@@ -436,9 +435,15 @@ const mapEdgesToNodes = ({ seenPaths }) => (edge) => {
   if (node.path)
     seenPaths.add(node.path)
 
-  node[_required] = edge.spec
+  node[_required] = edge.spec || '*'
   node[_type] = edge.type
-  node[_invalid] = edge.invalid
+
+  if (edge.invalid) {
+    const spec = JSON.stringify(node[_required])
+    const from = edge.from.location || 'the root project'
+    node[_invalid] = (node[_invalid] ? node[_invalid] + ', ' : '') +
+      (`${spec} from ${from}`)
+  }
 
   return node
 }
diff --git a/deps/npm/lib/npm.js b/deps/npm/lib/npm.js
index 937459501c0a5e..7046a84d0bcfa2 100644
--- a/deps/npm/lib/npm.js
+++ b/deps/npm/lib/npm.js
@@ -11,6 +11,7 @@ const Config = require('@npmcli/config')
 // Patch the global fs module here at the app level
 require('graceful-fs').gracefulify(require('fs'))
 
+// TODO make this only ever load once (or unload) in tests
 const procLogListener = require('./utils/proc-log-listener.js')
 
 const proxyCmds = new Proxy({}, {
@@ -48,6 +49,7 @@ const _title = Symbol('_title')
 const npm = module.exports = new class extends EventEmitter {
   constructor () {
     super()
+    // TODO make this only ever load once (or unload) in tests
     require('./utils/perf.js')
     this.started = Date.now()
     this.command = null
@@ -77,8 +79,8 @@ const npm = module.exports = new class extends EventEmitter {
   [_runCmd] (cmd, impl, args, cb) {
     if (!this.loaded) {
       throw new Error(
-        'Call npm.load(cb) before using this command.\n' +
-        'See the README.md or bin/npm-cli.js for example usage.'
+        'Call npm.load() before using this command.\n' +
+        'See lib/cli.js for example usage.'
       )
     }
 
@@ -96,7 +98,7 @@ const npm = module.exports = new class extends EventEmitter {
       args.filter(arg => /^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/.test(arg))
         .forEach(arg => {
           warnedNonDashArg = true
-          log.error('arg', 'Argument starts with non-ascii dash, this is probably invalid:', arg)
+          this.log.error('arg', 'Argument starts with non-ascii dash, this is probably invalid:', arg)
         })
     }
 
@@ -123,33 +125,32 @@ const npm = module.exports = new class extends EventEmitter {
     }
   }
 
-  // call with parsed CLI options and a callback when done loading
-  // XXX promisify this and stop taking a callback
   load (cb) {
-    if (!cb || typeof cb !== 'function')
-      throw new TypeError('must call as: npm.load(callback)')
-
-    this.once('load', cb)
-    if (this.loaded || this.loadErr) {
-      this.emit('load', this.loadErr)
-      return
+    if (cb && typeof cb !== 'function')
+      throw new TypeError('callback must be a function if provided')
+
+    if (!this.loadPromise) {
+      process.emit('time', 'npm:load')
+      this.log.pause()
+      this.loadPromise = new Promise((resolve, reject) => {
+        this[_load]().catch(er => er).then((er) => {
+          this.loadErr = er
+          if (!er && this.config.get('force'))
+            this.log.warn('using --force', 'Recommended protections disabled.')
+
+          process.emit('timeEnd', 'npm:load')
+          if (er)
+            return reject(er)
+          resolve()
+        })
+      })
     }
-    if (this.loading)
-      return
-
-    this.loading = true
+    if (!cb)
+      return this.loadPromise
 
-    process.emit('time', 'npm:load')
-    this.log.pause()
-    return this[_load]().catch(er => er).then((er) => {
-      this.loading = false
-      this.loadErr = er
-      if (!er && this.config.get('force'))
-        this.log.warn('using --force', 'Recommended protections disabled.')
-
-      process.emit('timeEnd', 'npm:load')
-      this.emit('load', er)
-    })
+    // loadPromise is returned here for legacy purposes, old code was allowing
+    // the mixing of callback and promise here.
+    return this.loadPromise.then(cb, cb)
   }
 
   get loaded () {
@@ -167,10 +168,15 @@ const npm = module.exports = new class extends EventEmitter {
 
   async [_load] () {
     process.emit('time', 'npm:load:whichnode')
-    const node = await which(process.argv[0]).catch(er => null)
+    let node
+    try {
+      node = which.sync(process.argv[0])
+    } catch (_) {
+      // TODO should we throw here?
+    }
     process.emit('timeEnd', 'npm:load:whichnode')
     if (node && node.toUpperCase() !== process.execPath.toUpperCase()) {
-      log.verbose('node symlink', node)
+      this.log.verbose('node symlink', node)
       process.execPath = node
       this.config.execPath = node
     }
@@ -198,10 +204,10 @@ const npm = module.exports = new class extends EventEmitter {
     process.env.COLOR = this.color ? '1' : '0'
 
     process.emit('time', 'npm:load:cleanupLog')
-    cleanUpLogFiles(this.cache, this.config.get('logs-max'), log.warn)
+    cleanUpLogFiles(this.cache, this.config.get('logs-max'), this.log.warn)
     process.emit('timeEnd', 'npm:load:cleanupLog')
 
-    log.resume()
+    this.log.resume()
 
     process.emit('time', 'npm:load:configScope')
     const configScope = this.config.get('scope')
@@ -314,9 +320,8 @@ const npm = module.exports = new class extends EventEmitter {
 // now load everything required by the class methods
 
 const log = require('npmlog')
-const { promisify } = require('util')
 
-const which = promisify(require('which'))
+const which = require('which')
 
 const deref = require('./utils/deref-command.js')
 const setupLog = require('./utils/setup-log.js')
diff --git a/deps/npm/lib/set-script.js b/deps/npm/lib/set-script.js
index cd01e28b56b066..24e4d8f20f6663 100644
--- a/deps/npm/lib/set-script.js
+++ b/deps/npm/lib/set-script.js
@@ -1,8 +1,7 @@
+const { resolve } = require('path')
 const log = require('npmlog')
-const fs = require('fs')
-const parseJSON = require('json-parse-even-better-errors')
 const rpj = require('read-package-json-fast')
-const { resolve } = require('path')
+const PackageJson = require('@npmcli/package-json')
 
 const BaseCommand = require('./base-command.js')
 class SetScript extends BaseCommand {
@@ -51,7 +50,7 @@ class SetScript extends BaseCommand {
 
   async setScript (args) {
     this.validate(args)
-    const warn = this.doSetScript(this.npm.localPrefix, args[0], args[1])
+    const warn = await this.doSetScript(this.npm.localPrefix, args[0], args[1])
     if (warn)
       log.warn('set-script', `Script "${args[0]}" was overwritten`)
   }
@@ -66,7 +65,7 @@ class SetScript extends BaseCommand {
 
     for (const [name, path] of this.workspaces) {
       try {
-        const warn = this.doSetScript(path, args[0], args[1])
+        const warn = await this.doSetScript(path, args[0], args[1])
         if (warn) {
           log.warn('set-script', `Script "${args[0]}" was overwritten`)
           log.warn(`  in workspace: ${name}`)
@@ -84,39 +83,28 @@ class SetScript extends BaseCommand {
   // returns a Boolean that will be true if
   // the requested script was overwritten
   // and false if it was set as a new script
-  doSetScript (path, name, value) {
-    // Set the script
-    let manifest
+  async doSetScript (path, name, value) {
     let warn = false
 
-    try {
-      manifest = fs.readFileSync(resolve(path, 'package.json'), 'utf-8')
-    } catch (error) {
-      throw new Error('package.json not found')
-    }
-
-    try {
-      manifest = parseJSON(manifest)
-    } catch (error) {
-      throw new Error(`Invalid package.json: ${error}`)
-    }
+    const pkgJson = await PackageJson.load(path)
+    const { scripts } = pkgJson.content
 
-    if (!manifest.scripts)
-      manifest.scripts = {}
+    const overwriting =
+      scripts
+        && scripts[name]
+        && scripts[name] !== value
 
-    if (manifest.scripts[name] && manifest.scripts[name] !== value)
+    if (overwriting)
       warn = true
-    manifest.scripts[name] = value
 
-    // format content
-    const {
-      [Symbol.for('indent')]: indent,
-      [Symbol.for('newline')]: newline,
-    } = manifest
+    pkgJson.update({
+      scripts: {
+        ...scripts,
+        [name]: value,
+      },
+    })
 
-    const content = (JSON.stringify(manifest, null, indent) + '\n')
-      .replace(/\n/g, newline)
-    fs.writeFileSync(resolve(path, 'package.json'), content)
+    await pkgJson.save()
 
     return warn
   }
diff --git a/deps/npm/lib/utils/error-handler.js b/deps/npm/lib/utils/exit-handler.js
similarity index 71%
rename from deps/npm/lib/utils/error-handler.js
rename to deps/npm/lib/utils/exit-handler.js
index f40e1f04fb1802..dc499f526a2085 100644
--- a/deps/npm/lib/utils/error-handler.js
+++ b/deps/npm/lib/utils/exit-handler.js
@@ -1,17 +1,20 @@
-let npm // set by the cli
-let cbCalled = false
 const log = require('npmlog')
-let itWorked = false
+const os = require('os')
 const path = require('path')
 const writeFileAtomic = require('write-file-atomic')
 const mkdirp = require('mkdirp-infer-owner')
 const fs = require('graceful-fs')
-let wroteLogFile = false
-let exitCode = 0
+
 const errorMessage = require('./error-message.js')
 const replaceInfo = require('./replace-info.js')
 
+let exitHandlerCalled = false
 let logFileName
+let npm // set by the cli
+let wroteLogFile = false
+
+const timings = {}
+
 const getLogFile = () => {
   // we call this multiple times, so we need to treat it as a singleton because
   // the date is part of the name
@@ -21,7 +24,6 @@ const getLogFile = () => {
   return logFileName
 }
 
-const timings = {}
 process.on('timing', (name, value) => {
   if (timings[name])
     timings[name] += value
@@ -53,22 +55,20 @@ process.on('exit', code => {
     }
   }
 
-  if (code)
-    itWorked = false
-  if (itWorked)
+  if (!code)
     log.info('ok')
   else {
-    if (!cbCalled) {
-      log.error('', 'cb() never called!')
+    if (!exitHandlerCalled) {
+      log.error('', 'Exit handler never called!')
       console.error('')
       log.error('', 'This is an error with npm itself. Please report this error at:')
       log.error('', '    ')
+      // TODO this doesn't have an npm.config.loaded guard
       writeLogFile()
     }
-
-    if (code)
-      log.verbose('code', code)
+    log.verbose('code', code)
   }
+  // In timing mode we always write the log file
   if (npm.config && npm.config.loaded && npm.config.get('timing') && !wroteLogFile)
     writeLogFile()
   if (wroteLogFile) {
@@ -83,52 +83,46 @@ process.on('exit', code => {
         '    ' + getLogFile(),
       ].join('\n')
     )
-    wroteLogFile = false
   }
 
-  // actually exit.
-  if (exitCode === 0 && !itWorked)
-    exitCode = 1
+  // these are needed for the tests to have a clean slate in each test case
+  exitHandlerCalled = false
+  wroteLogFile = false
 
-  if (exitCode !== 0)
-    process.exit(exitCode)
+  // actually exit.
+  process.exit(code)
 })
 
 const exit = (code, noLog) => {
-  exitCode = exitCode || process.exitCode || code
-
-  log.verbose('exit', code)
+  log.verbose('exit', code || 0)
   if (log.level === 'silent')
     noLog = true
 
-  const reallyExit = () => {
-    itWorked = !code
-
-    // Exit directly -- nothing in the CLI should still be running in the
-    // background at this point, and this makes sure anything left dangling
-    // for whatever reason gets thrown away, instead of leaving the CLI open
-    //
-    // Commands that expect long-running actions should just delay `cb()`
-    process.stdout.write('', () => {
-      process.exit(code)
-    })
-  }
-
+  // noLog is true if there was an error, including if config wasn't loaded, so
+  // this doesn't need a config.loaded guard
   if (code && !noLog)
     writeLogFile()
-  reallyExit()
+
+  // Exit directly -- nothing in the CLI should still be running in the
+  // background at this point, and this makes sure anything left dangling
+  // for whatever reason gets thrown away, instead of leaving the CLI open
+  process.stdout.write('', () => {
+    // `|| process.exitCode` supports a single use case, where we set the exit
+    // code to 1 if npm is called with no arguments
+    process.exit(code)
+  })
 }
 
-const errorHandler = (er) => {
+const exitHandler = (err) => {
   log.disableProgress()
   if (!npm.config || !npm.config.loaded) {
     // logging won't work unless we pretend that it's ready
-    er = er || new Error('Exit prior to config file resolving.')
-    console.error(er.stack || er.message)
+    err = err || new Error('Exit prior to config file resolving.')
+    console.error(err.stack || err.message)
   }
 
-  if (cbCalled)
-    er = er || new Error('Callback called more than once.')
+  if (exitHandlerCalled)
+    err = err || new Error('Exit handler called more than once.')
 
   // only show the notification if it finished before the other stuff we
   // were doing.  no need to hang on `npm -v` or something.
@@ -139,40 +133,39 @@ const errorHandler = (er) => {
     log.level = level
   }
 
-  cbCalled = true
-  if (!er)
-    return exit(0)
+  exitHandlerCalled = true
+  if (!err)
+    return exit()
 
   // if we got a command that just shells out to something else, then it
   // will presumably print its own errors and exit with a proper status
   // code if there's a problem.  If we got an error with a code=0, then...
   // something else went wrong along the way, so maybe an npm problem?
   const isShellout = npm.shelloutCommands.includes(npm.command)
-  const quietShellout = isShellout && typeof er.code === 'number' && er.code
+  const quietShellout = isShellout && typeof err.code === 'number' && err.code
   if (quietShellout)
-    return exit(er.code, true)
-  else if (typeof er === 'string') {
-    log.error('', er)
+    return exit(err.code, true)
+  else if (typeof err === 'string') {
+    log.error('', err)
     return exit(1, true)
-  } else if (!(er instanceof Error)) {
-    log.error('weird error', er)
+  } else if (!(err instanceof Error)) {
+    log.error('weird error', err)
     return exit(1, true)
   }
 
-  if (!er.code) {
-    const matchErrorCode = er.message.match(/^(?:Error: )?(E[A-Z]+)/)
-    er.code = matchErrorCode && matchErrorCode[1]
+  if (!err.code) {
+    const matchErrorCode = err.message.match(/^(?:Error: )?(E[A-Z]+)/)
+    err.code = matchErrorCode && matchErrorCode[1]
   }
 
   for (const k of ['type', 'stack', 'statusCode', 'pkgid']) {
-    const v = er[k]
+    const v = err[k]
     if (v)
       log.verbose(k, replaceInfo(v))
   }
 
   log.verbose('cwd', process.cwd())
 
-  const os = require('os')
   const args = replaceInfo(process.argv)
   log.verbose('', os.type() + ' ' + os.release())
   log.verbose('argv', args.map(JSON.stringify).join(' '))
@@ -180,19 +173,19 @@ const errorHandler = (er) => {
   log.verbose('npm ', 'v' + npm.version)
 
   for (const k of ['code', 'syscall', 'file', 'path', 'dest', 'errno']) {
-    const v = er[k]
+    const v = err[k]
     if (v)
       log.error(k, v)
   }
 
-  const msg = errorMessage(er, npm)
+  const msg = errorMessage(err, npm)
   for (const errline of [...msg.summary, ...msg.detail])
     log.error(...errline)
 
   if (npm.config && npm.config.get('json')) {
     const error = {
       error: {
-        code: er.code,
+        code: err.code,
         summary: messageText(msg.summary),
         detail: messageText(msg.detail),
       },
@@ -200,7 +193,7 @@ const errorHandler = (er) => {
     console.error(JSON.stringify(error, null, 2))
   }
 
-  exit(typeof er.errno === 'number' ? er.errno : typeof er.code === 'number' ? er.code : 1)
+  exit(typeof err.errno === 'number' ? err.errno : typeof err.code === 'number' ? err.code : 1)
 }
 
 const messageText = msg => msg.map(line => line.slice(1).join(' ')).join('\n')
@@ -209,8 +202,6 @@ const writeLogFile = () => {
   if (wroteLogFile)
     return
 
-  const os = require('os')
-
   try {
     let logOutput = ''
     log.record.forEach(m => {
@@ -243,8 +234,7 @@ const writeLogFile = () => {
   }
 }
 
-module.exports = errorHandler
-module.exports.exit = exit
+module.exports = exitHandler
 module.exports.setNpm = (n) => {
   npm = n
 }
diff --git a/deps/npm/lib/utils/explain-eresolve.js b/deps/npm/lib/utils/explain-eresolve.js
index b35a32c6f935d1..fa3c6bda52293f 100644
--- a/deps/npm/lib/utils/explain-eresolve.js
+++ b/deps/npm/lib/utils/explain-eresolve.js
@@ -1,4 +1,4 @@
-// this is called when an ERESOLVE error is caught in the error-handler,
+// this is called when an ERESOLVE error is caught in the exit-handler,
 // or when there's a log.warn('eresolve', msg, explanation), to turn it
 // into a human-intelligible explanation of what's wrong and how to fix.
 const { writeFileSync } = require('fs')
diff --git a/deps/npm/lib/utils/perf.js b/deps/npm/lib/utils/perf.js
index 3f81ee4b049e48..4961054d909ad6 100644
--- a/deps/npm/lib/utils/perf.js
+++ b/deps/npm/lib/utils/perf.js
@@ -14,3 +14,10 @@ process.on('timeEnd', (name) => {
   } else
     log.silly('timing', "Tried to end timer that doesn't exist:", name)
 })
+
+// for tests
+/* istanbul ignore next */
+exports.reset = () => {
+  process.removeAllListeners('time')
+  process.removeAllListeners('timeEnd')
+}
diff --git a/deps/npm/lib/utils/proc-log-listener.js b/deps/npm/lib/utils/proc-log-listener.js
index 1dc4b4399eaea1..2cfe94ecb0cf24 100644
--- a/deps/npm/lib/utils/proc-log-listener.js
+++ b/deps/npm/lib/utils/proc-log-listener.js
@@ -14,3 +14,9 @@ module.exports = () => {
     }
   })
 }
+
+// for tests
+/* istanbul ignore next */
+module.exports.reset = () => {
+  process.removeAllListeners('log')
+}
diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1
index 9bba449fbb1343..4cbea890fb8e96 100644
--- a/deps/npm/man/man1/npm-ls.1
+++ b/deps/npm/man/man1/npm-ls.1
@@ -26,7 +26,7 @@ example, running \fBnpm ls promzard\fP in npm's source tree will show:
 .P
 .RS 2
 .nf
-npm@7\.18\.1 /path/to/npm
+npm@7\.19\.0 /path/to/npm
 └─┬ init\-package\-json@0\.0\.4
   └── promzard@0\.1\.5
 .fi
diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1
index 9940f12ade1b8b..7090350c7b6432 100644
--- a/deps/npm/man/man1/npm.1
+++ b/deps/npm/man/man1/npm.1
@@ -10,7 +10,7 @@ npm  [args]
 .RE
 .SS Version
 .P
-7\.18\.1
+7\.19\.0
 .SS Description
 .P
 npm is the package manager for the Node JavaScript platform\.  It puts
diff --git a/deps/npm/man/man7/workspaces.7 b/deps/npm/man/man7/workspaces.7
index 9a1a417c24d290..70d808ce884606 100644
--- a/deps/npm/man/man7/workspaces.7
+++ b/deps/npm/man/man7/workspaces.7
@@ -34,8 +34,8 @@ npm help \fBpackage\.json\fP file, e\.g:
 .RE
 .P
 Given the above \fBpackage\.json\fP example living at a current working
-directory \fB\|\.\fP that contains a folder named \fBworkspace\-a\fP that disposes
-of a \fBpackage\.json\fP inside it, defining a nodejs package, e\.g:
+directory \fB\|\.\fP that contains a folder named \fBworkspace\-a\fP that itself contains
+a \fBpackage\.json\fP inside it, defining a nodejs package, e\.g:
 .P
 .RS 2
 .nf
diff --git a/deps/npm/node_modules/@npmcli/arborist/bin/lib/timers.js b/deps/npm/node_modules/@npmcli/arborist/bin/lib/timers.js
index e72217c1e4ed9c..b516af92c5b57f 100644
--- a/deps/npm/node_modules/@npmcli/arborist/bin/lib/timers.js
+++ b/deps/npm/node_modules/@npmcli/arborist/bin/lib/timers.js
@@ -1,5 +1,6 @@
 const timers = Object.create(null)
 const { format } = require('util')
+const options = require('./options.js')
 
 process.on('time', name => {
   if (timers[name])
@@ -15,7 +16,8 @@ process.on('timeEnd', name => {
   const res = process.hrtime(timers[name])
   delete timers[name]
   const msg = format(`${process.pid} ${name}`, res[0] * 1e3 + res[1] / 1e6)
-  console.error(dim(msg))
+  if (options.timers !== false)
+    console.error(dim(msg))
 })
 
 process.on('exit', () => {
diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js
index 55360538b901a0..f259a69b548e10 100644
--- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js
+++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js
@@ -15,6 +15,7 @@ const mkdirp = require('mkdirp-infer-owner')
 const justMkdirp = require('mkdirp')
 const moveFile = require('@npmcli/move-file')
 const rimraf = promisify(require('rimraf'))
+const PackageJson = require('@npmcli/package-json')
 const packageContents = require('@npmcli/installed-package-contents')
 const { checkEngine, checkPlatform } = require('npm-install-checks')
 
@@ -24,7 +25,6 @@ const Diff = require('../diff.js')
 const retirePath = require('../retire-path.js')
 const promiseAllRejectLate = require('promise-all-reject-late')
 const optionalSet = require('../optional-set.js')
-const updateRootPackageJson = require('../update-root-package-json.js')
 const calcDepFlags = require('../calc-dep-flags.js')
 const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
 
@@ -1029,6 +1029,25 @@ module.exports = cls => class Reifier extends cls {
 
     const promises = [this[_saveLockFile](saveOpt)]
 
+    const updatePackageJson = async (tree) => {
+      const pkgJson = await PackageJson.load(tree.path)
+        .catch(() => new PackageJson(tree.path))
+      const {
+        dependencies = {},
+        devDependencies = {},
+        optionalDependencies = {},
+        peerDependencies = {},
+      } = tree.package
+
+      pkgJson.update({
+        dependencies,
+        devDependencies,
+        optionalDependencies,
+        peerDependencies,
+      })
+      await pkgJson.save()
+    }
+
     // grab any from explicitRequests that had deps removed
     for (const { from: tree } of this.explicitRequests)
       updatedTrees.add(tree)
@@ -1036,7 +1055,7 @@ module.exports = cls => class Reifier extends cls {
     for (const tree of updatedTrees) {
       // refresh the edges so they have the correct specs
       tree.package = tree.package
-      promises.push(updateRootPackageJson(tree))
+      promises.push(updatePackageJson(tree))
     }
 
     await Promise.all(promises)
diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/diff.js b/deps/npm/node_modules/@npmcli/arborist/lib/diff.js
index dac7c81f8ecfbf..1f8eff0f0c4d9c 100644
--- a/deps/npm/node_modules/@npmcli/arborist/lib/diff.js
+++ b/deps/npm/node_modules/@npmcli/arborist/lib/diff.js
@@ -145,9 +145,9 @@ const allChildren = node => {
   if (!node)
     return new Map()
 
-  // if the node is a global root, and also a link, then what we really
+  // if the node is root, and also a link, then what we really
   // want is to traverse the target's children
-  if (node.global && node.isRoot && node.isLink)
+  if (node.isRoot && node.isLink)
     return allChildren(node.target)
 
   const kids = new Map()
diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/deps/npm/node_modules/@npmcli/arborist/lib/shrinkwrap.js
index 9fb0528db497c8..b251539a94c902 100644
--- a/deps/npm/node_modules/@npmcli/arborist/lib/shrinkwrap.js
+++ b/deps/npm/node_modules/@npmcli/arborist/lib/shrinkwrap.js
@@ -349,6 +349,7 @@ class Shrinkwrap {
   reset () {
     this.tree = null
     this[_awaitingUpdate] = new Map()
+    this.originalLockfileVersion = lockfileVersion
     this.data = {
       lockfileVersion,
       requires: true,
diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/update-root-package-json.js b/deps/npm/node_modules/@npmcli/arborist/lib/update-root-package-json.js
deleted file mode 100644
index 57ec414248756c..00000000000000
--- a/deps/npm/node_modules/@npmcli/arborist/lib/update-root-package-json.js
+++ /dev/null
@@ -1,95 +0,0 @@
-const fs = require('fs')
-const promisify = require('util').promisify
-const readFile = promisify(fs.readFile)
-const writeFile = promisify(fs.writeFile)
-const {resolve} = require('path')
-
-const parseJSON = require('json-parse-even-better-errors')
-
-const depTypes = new Set([
-  'dependencies',
-  'optionalDependencies',
-  'devDependencies',
-  'peerDependencies',
-])
-
-// sort alphabetically all types of deps for a given package
-const orderDeps = (pkg) => {
-  for (const type of depTypes) {
-    if (pkg && pkg[type]) {
-      pkg[type] = Object.keys(pkg[type])
-        .sort((a, b) => a.localeCompare(b, 'en'))
-        .reduce((res, key) => {
-          res[key] = pkg[type][key]
-          return res
-        }, {})
-    }
-  }
-  return pkg
-}
-const parseJsonSafe = json => {
-  try {
-    return parseJSON(json)
-  } catch (er) {
-    return null
-  }
-}
-
-const updateRootPackageJson = async tree => {
-  const filename = resolve(tree.path, 'package.json')
-  const originalJson = await readFile(filename, 'utf8').catch(() => null)
-  const originalContent = parseJsonSafe(originalJson)
-
-  const depsData = orderDeps({
-    ...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
-  // clone the object though, so we can still refer to what it originally was
-  const packageJsonContent = !originalContent ? depsData
-    : Object.assign({}, originalContent)
-
-  // loop through all types of dependencies and update package json content
-  for (const type of depTypes)
-    packageJsonContent[type] = depsData[type]
-
-  // if original package.json had dep in peerDeps AND deps, preserve that.
-  const { dependencies: origProd, peerDependencies: origPeer } =
-    originalContent || {}
-  const { peerDependencies: newPeer } = packageJsonContent
-  if (origProd && origPeer && newPeer) {
-    // we have original prod/peer deps, and new peer deps
-    // copy over any that were in both in the original
-    for (const name of Object.keys(origPeer)) {
-      if (origProd[name] !== undefined && newPeer[name] !== undefined) {
-        packageJsonContent.dependencies = packageJsonContent.dependencies || {}
-        packageJsonContent.dependencies[name] = newPeer[name]
-      }
-    }
-  }
-
-  // format content
-  const {
-    [Symbol.for('indent')]: indent,
-    [Symbol.for('newline')]: newline,
-  } = tree.package
-  const format = indent === undefined ? '  ' : indent
-  const eol = newline === undefined ? '\n' : newline
-  const content = (JSON.stringify(packageJsonContent, null, format) + '\n')
-    .replace(/\n/g, eol)
-
-  if (content !== originalJson)
-    return writeFile(filename, content)
-}
-
-module.exports = updateRootPackageJson
diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json
index bd27e4bbffa20a..138d6ec25b4c2d 100644
--- a/deps/npm/node_modules/@npmcli/arborist/package.json
+++ b/deps/npm/node_modules/@npmcli/arborist/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@npmcli/arborist",
-  "version": "2.6.3",
+  "version": "2.6.4",
   "description": "Manage node_modules trees",
   "dependencies": {
     "@npmcli/installed-package-contents": "^1.0.7",
@@ -9,6 +9,7 @@
     "@npmcli/move-file": "^1.1.0",
     "@npmcli/name-from-folder": "^1.0.1",
     "@npmcli/node-gyp": "^1.0.1",
+    "@npmcli/package-json": "^1.0.1",
     "@npmcli/run-script": "^1.8.2",
     "bin-links": "^2.2.1",
     "cacache": "^15.0.3",
diff --git a/deps/npm/node_modules/@npmcli/package-json/LICENSE b/deps/npm/node_modules/@npmcli/package-json/LICENSE
new file mode 100644
index 00000000000000..6a1f3708f6d70e
--- /dev/null
+++ b/deps/npm/node_modules/@npmcli/package-json/LICENSE
@@ -0,0 +1,18 @@
+ISC License
+
+Copyright GitHub Inc.
+
+Permission to use, copy, modify, and/or distribute this
+software for any purpose with or without fee is hereby
+granted, provided that the above copyright notice and this
+permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND NPM DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
+EVENT SHALL NPM BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/deps/npm/node_modules/@npmcli/package-json/lib/index.js b/deps/npm/node_modules/@npmcli/package-json/lib/index.js
new file mode 100644
index 00000000000000..87c3a630936886
--- /dev/null
+++ b/deps/npm/node_modules/@npmcli/package-json/lib/index.js
@@ -0,0 +1,106 @@
+const fs = require('fs')
+const promisify = require('util').promisify
+const readFile = promisify(fs.readFile)
+const writeFile = promisify(fs.writeFile)
+const { resolve } = require('path')
+const updateDeps = require('./update-dependencies.js')
+const updateScripts = require('./update-scripts.js')
+const updateWorkspaces = require('./update-workspaces.js')
+
+const parseJSON = require('json-parse-even-better-errors')
+
+const _filename = Symbol('filename')
+const _manifest = Symbol('manifest')
+const _readFileContent = Symbol('readFileContent')
+
+// a list of handy specialized helper functions that take
+// care of special cases that are handled by the npm cli
+const knownSteps = new Set([
+  updateDeps,
+  updateScripts,
+  updateWorkspaces,
+])
+
+// list of all keys that are handled by "knownSteps" helpers
+const knownKeys = new Set([
+  ...updateDeps.knownKeys,
+  'scripts',
+  'workspaces',
+])
+
+class PackageJson {
+  static async load (path) {
+    return await new PackageJson(path).load()
+  }
+
+  constructor (path) {
+    this[_filename] = resolve(path, 'package.json')
+    this[_manifest] = {}
+    this[_readFileContent] = ''
+  }
+
+  async load () {
+    try {
+      this[_readFileContent] =
+        await readFile(this[_filename], 'utf8')
+    } catch (err) {
+      throw new Error('package.json not found')
+    }
+
+    try {
+      this[_manifest] =
+        parseJSON(this[_readFileContent])
+    } catch (err) {
+      throw new Error(`Invalid package.json: ${err}`)
+    }
+
+    return this
+  }
+
+  get content () {
+    return this[_manifest]
+  }
+
+  update (content) {
+    // validates both current manifest and content param
+    const invalidContent =
+      typeof this[_manifest] !== 'object'
+        || typeof content !== 'object'
+    if (invalidContent) {
+      throw Object.assign(
+        new Error(`Can't update invalid package.json data`),
+        { code: 'EPACKAGEJSONUPDATE' }
+      )
+    }
+
+    for (const step of knownSteps)
+      this[_manifest] = step({ content, originalContent: this[_manifest] })
+
+    // unknown properties will just be overwitten
+    for (const [key, value] of Object.entries(content)) {
+      if (!knownKeys.has(key))
+        this[_manifest][key] = value
+    }
+
+    return this
+  }
+
+  async save () {
+    const {
+      [Symbol.for('indent')]: indent,
+      [Symbol.for('newline')]: newline,
+    } = this[_manifest]
+
+    const format = indent === undefined ? '  ' : indent
+    const eol = newline === undefined ? '\n' : newline
+    const fileContent = `${
+      JSON.stringify(this[_manifest], null, format)
+    }\n`
+      .replace(/\n/g, eol)
+
+    if (fileContent.trim() !== this[_readFileContent].trim())
+      return await writeFile(this[_filename], fileContent)
+  }
+}
+
+module.exports = PackageJson
diff --git a/deps/npm/node_modules/@npmcli/package-json/lib/update-dependencies.js b/deps/npm/node_modules/@npmcli/package-json/lib/update-dependencies.js
new file mode 100644
index 00000000000000..dac45a8bed7bf9
--- /dev/null
+++ b/deps/npm/node_modules/@npmcli/package-json/lib/update-dependencies.js
@@ -0,0 +1,72 @@
+const depTypes = new Set([
+  'dependencies',
+  'optionalDependencies',
+  'devDependencies',
+  'peerDependencies',
+])
+
+// sort alphabetically all types of deps for a given package
+const orderDeps = (content) => {
+  for (const type of depTypes) {
+    if (content && content[type]) {
+      content[type] = Object.keys(content[type])
+        .sort((a, b) => a.localeCompare(b, 'en'))
+        .reduce((res, key) => {
+          res[key] = content[type][key]
+          return res
+        }, {})
+    }
+  }
+  return content
+}
+
+const updateDependencies = ({ content, originalContent }) => {
+  const pkg = orderDeps({
+    ...content,
+  })
+
+  // optionalDependencies don't need to be repeated in two places
+  if (pkg.dependencies) {
+    if (pkg.optionalDependencies) {
+      for (const name of Object.keys(pkg.optionalDependencies))
+        delete pkg.dependencies[name]
+    }
+  }
+
+  const result = { ...originalContent }
+
+  // loop through all types of dependencies and update package json pkg
+  for (const type of depTypes) {
+    if (pkg[type])
+      result[type] = pkg[type]
+
+    // prune empty type props from resulting object
+    const emptyDepType =
+      pkg[type]
+      && typeof pkg === 'object'
+      && Object.keys(pkg[type]).length === 0
+    if (emptyDepType)
+      delete result[type]
+  }
+
+  // if original package.json had dep in peerDeps AND deps, preserve that.
+  const { dependencies: origProd, peerDependencies: origPeer } =
+    originalContent || {}
+  const { peerDependencies: newPeer } = result
+  if (origProd && origPeer && newPeer) {
+    // we have original prod/peer deps, and new peer deps
+    // copy over any that were in both in the original
+    for (const name of Object.keys(origPeer)) {
+      if (origProd[name] !== undefined && newPeer[name] !== undefined) {
+        result.dependencies = result.dependencies || {}
+        result.dependencies[name] = newPeer[name]
+      }
+    }
+  }
+
+  return result
+}
+
+updateDependencies.knownKeys = depTypes
+
+module.exports = updateDependencies
diff --git a/deps/npm/node_modules/@npmcli/package-json/lib/update-scripts.js b/deps/npm/node_modules/@npmcli/package-json/lib/update-scripts.js
new file mode 100644
index 00000000000000..3a88d3e9a17a80
--- /dev/null
+++ b/deps/npm/node_modules/@npmcli/package-json/lib/update-scripts.js
@@ -0,0 +1,28 @@
+const updateScripts = ({ content, originalContent = {} }) => {
+  const newScripts = content.scripts
+
+  if (!newScripts)
+    return originalContent
+
+  // validate scripts content being appended
+  const hasInvalidScripts = () =>
+    Object.entries(newScripts)
+      .some(([key, value]) =>
+        typeof key !== 'string' || typeof value !== 'string')
+  if (hasInvalidScripts()) {
+    throw Object.assign(
+      new TypeError(
+        'package.json scripts should be a key-value pair of strings.'),
+      { code: 'ESCRIPTSINVALID' }
+    )
+  }
+
+  return {
+    ...originalContent,
+    scripts: {
+      ...newScripts,
+    },
+  }
+}
+
+module.exports = updateScripts
diff --git a/deps/npm/node_modules/@npmcli/package-json/lib/update-workspaces.js b/deps/npm/node_modules/@npmcli/package-json/lib/update-workspaces.js
new file mode 100644
index 00000000000000..207dd94a236d79
--- /dev/null
+++ b/deps/npm/node_modules/@npmcli/package-json/lib/update-workspaces.js
@@ -0,0 +1,25 @@
+const updateWorkspaces = ({ content, originalContent = {} }) => {
+  const newWorkspaces = content.workspaces
+
+  if (!newWorkspaces)
+    return originalContent
+
+  // validate workspaces content being appended
+  const hasInvalidWorkspaces = () =>
+    newWorkspaces.some(w => !(typeof w === 'string'))
+  if (!newWorkspaces.length || hasInvalidWorkspaces()) {
+    throw Object.assign(
+      new TypeError('workspaces should be an array of strings.'),
+      { code: 'EWORKSPACESINVALID' }
+    )
+  }
+
+  return {
+    ...originalContent,
+    workspaces: [
+      ...newWorkspaces,
+    ],
+  }
+}
+
+module.exports = updateWorkspaces
diff --git a/deps/npm/node_modules/@npmcli/package-json/package.json b/deps/npm/node_modules/@npmcli/package-json/package.json
new file mode 100644
index 00000000000000..8708ec5eb6fb1b
--- /dev/null
+++ b/deps/npm/node_modules/@npmcli/package-json/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "@npmcli/package-json",
+  "version": "1.0.1",
+  "description": "Programmatic API to update package.json",
+  "main": "lib/index.js",
+  "files": [
+    "lib"
+  ],
+  "scripts": {
+    "preversion": "npm test",
+    "postversion": "npm publish",
+    "prepublishOnly": "git push origin --follow-tags",
+    "snap": "tap",
+    "test": "tap",
+    "npmclilint": "npmcli-lint",
+    "lint": "npm run npmclilint -- \"lib/*.*js\" \"test/*.*js\"",
+    "lintfix": "npm run lint -- --fix",
+    "posttest": "npm run lint --",
+    "postsnap": "npm run lintfix --"
+  },
+  "keywords": [
+    "npm",
+    "oss"
+  ],
+  "author": "GitHub Inc.",
+  "license": "ISC",
+  "devDependencies": {
+    "@npmcli/lint": "^1.0.1",
+    "tap": "^15.0.9"
+  },
+  "dependencies": {
+    "json-parse-even-better-errors": "^2.3.1"
+  }
+}
diff --git a/deps/npm/package.json b/deps/npm/package.json
index 58ac5d7f8c6c82..035f30b2be988b 100644
--- a/deps/npm/package.json
+++ b/deps/npm/package.json
@@ -1,5 +1,5 @@
 {
-  "version": "7.18.1",
+  "version": "7.19.0",
   "name": "npm",
   "description": "a package manager for JavaScript",
   "workspaces": [
@@ -53,9 +53,10 @@
     "./package.json": "./package.json"
   },
   "dependencies": {
-    "@npmcli/arborist": "^2.6.3",
+    "@npmcli/arborist": "^2.6.4",
     "@npmcli/ci-detect": "^1.2.0",
     "@npmcli/config": "^2.2.0",
+    "@npmcli/package-json": "^1.0.1",
     "@npmcli/run-script": "^1.8.5",
     "abbrev": "~1.1.1",
     "ansicolors": "~0.3.2",
@@ -125,6 +126,7 @@
     "@npmcli/arborist",
     "@npmcli/ci-detect",
     "@npmcli/config",
+    "@npmcli/package-json",
     "@npmcli/run-script",
     "abbrev",
     "ansicolors",
@@ -212,7 +214,7 @@
     "sudotest:nocleanup": "sudo NO_TEST_CLEANUP=1 npm run test --",
     "posttest": "npm run lint",
     "eslint": "eslint",
-    "lint": "npm run eslint -- test/lib test/bin lib scripts docs smoke-tests",
+    "lint": "npm run eslint -- test/lib test/bin bin lib scripts docs smoke-tests",
     "lintfix": "npm run lint -- --fix",
     "prelint": "rimraf test/npm_cache*",
     "resetdeps": "bash scripts/resetdeps.sh",
diff --git a/deps/npm/tap-snapshots/test/lib/ls.js.test.cjs b/deps/npm/tap-snapshots/test/lib/ls.js.test.cjs
index 3d56d1f432731a..c3d0a87648edbb 100644
--- a/deps/npm/tap-snapshots/test/lib/ls.js.test.cjs
+++ b/deps/npm/tap-snapshots/test/lib/ls.js.test.cjs
@@ -32,16 +32,16 @@ exports[`test/lib/ls.js TAP ignore missing optional deps human output > ls resul
 test-npm-ls-ignore-missing-optional@1.2.3 {project}
 +-- unmet optional dependency optional-missing@1
 +-- optional-ok@1.2.3
-+-- optional-wrong@3.2.1 invalid
++-- optional-wrong@3.2.1 invalid: "1" from the root project
 +-- unmet dependency peer-missing@1
 +-- peer-ok@1.2.3
 +-- unmet optional dependency peer-optional-missing@1
 +-- peer-optional-ok@1.2.3
-+-- peer-optional-wrong@3.2.1 invalid
-+-- peer-wrong@3.2.1 invalid
++-- peer-optional-wrong@3.2.1 invalid: "1" from the root project
++-- peer-wrong@3.2.1 invalid: "1" from the root project
 +-- unmet dependency prod-missing@1
 +-- prod-ok@1.2.3
-\`-- prod-wrong@3.2.1 invalid
+\`-- prod-wrong@3.2.1 invalid: "1" from the root project
 
 `
 
@@ -356,7 +356,7 @@ npm-broken-resolved-field-test@1.0.0 {CWD}/tap-testdir-ls-ls-broken-resolved-fie
 exports[`test/lib/ls.js TAP ls colored output > should output tree containing color info 1`] = `
 test-npm-ls@1.0.0 {CWD}/tap-testdir-ls-ls-colored-output
 +-- chai@1.0.0 extraneous
-+-- foo@1.0.0 invalid
++-- foo@1.0.0 invalid: "^2.0.0" from the root project
 | \`-- dog@1.0.0
 \`-- UNMET DEPENDENCY ipsum@^1.0.0
 
@@ -454,8 +454,8 @@ exports[`test/lib/ls.js TAP ls global > should print tree and not mark top-level
 exports[`test/lib/ls.js TAP ls invalid deduped dep > should output tree signaling mismatching peer dep in problems 1`] = `
 invalid-deduped-dep@1.0.0 {CWD}/tap-testdir-ls-ls-invalid-deduped-dep
 +-- a@1.0.0
-| \`-- b@1.0.0 deduped invalid
-\`-- b@1.0.0 invalid
+| \`-- b@1.0.0 deduped invalid: "^2.0.0" from the root project, "^2.0.0" from node_modules/a
+\`-- b@1.0.0 invalid: "^2.0.0" from the root project, "^2.0.0" from node_modules/a
 
 `
 
@@ -466,7 +466,7 @@ test-npm-ls@1.0.0 {CWD}/tap-testdir-ls-ls-invalid-peer-dep
 | \`-- foo@1.0.0
 |   \`-- dog@1.0.0
 +-- optional-dep@1.0.0
-+-- peer-dep@1.0.0 invalid
++-- peer-dep@1.0.0 invalid: "^2.0.0" from the root project
 \`-- prod-dep@1.0.0
   \`-- dog@2.0.0
 
@@ -567,7 +567,7 @@ exports[`test/lib/ls.js TAP ls missing package.json > should output tree missing
 exports[`test/lib/ls.js TAP ls missing/invalid/extraneous > should output tree containing missing, invalid, extraneous labels 1`] = `
 test-npm-ls@1.0.0 {CWD}/tap-testdir-ls-ls-missing-invalid-extraneous
 +-- chai@1.0.0 extraneous
-+-- foo@1.0.0 invalid
++-- foo@1.0.0 invalid: "^2.0.0" from the root project
 | \`-- dog@1.0.0
 \`-- UNMET DEPENDENCY ipsum@^1.0.0
 
@@ -602,7 +602,7 @@ exports[`test/lib/ls.js TAP ls unmet optional dep > should output tree with empt
 | \`-- foo@1.0.0
 |   \`-- dog@1.0.0
 +-- UNMET OPTIONAL DEPENDENCY missing-optional-dep@^1.0.0
-+-- optional-dep@1.0.0 invalid
++-- optional-dep@1.0.0 invalid: "^2.0.0" from the root project
 +-- peer-dep@1.0.0
 \`-- prod-dep@1.0.0
   \`-- dog@2.0.0
@@ -691,3 +691,14 @@ dedupe-entries@1.0.0 {CWD}/tap-testdir-ls-ls-with-no-args-dedupe-entries-and-not
 \`-- @npmcli/c@1.0.0
 
 `
+
+exports[`test/lib/ls.js TAP show multiple invalid reasons > ls result 1`] = `
+test-npm-ls@1.0.0 {cwd}/tap-testdir-ls-show-multiple-invalid-reasons
++-- cat@1.0.0 invalid: "^2.0.0" from the root project
+| \`-- dog@1.0.0 deduped invalid: "^1.2.3" from the root project, "^2.0.0" from node_modules/cat
++-- chai@1.0.0 extraneous
+| \`-- dog@1.0.0 deduped invalid: "^1.2.3" from the root project, "^2.0.0" from node_modules/cat, "2.x" from node_modules/chai
+\`-- dog@1.0.0 invalid: "^1.2.3" from the root project, "^2.0.0" from node_modules/cat, "2.x" from node_modules/chai
+  \`-- cat@1.0.0 deduped invalid: "^2.0.0" from the root project
+
+`
diff --git a/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs b/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs
index 35942fea646834..12df9ec89f6f72 100644
--- a/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs
+++ b/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs
@@ -155,3 +155,1696 @@ Array [
 exports[`test/lib/utils/config/definitions.js TAP > all config keys that are shared to flatOptions 1`] = `
 Array []
 `
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for _auth 1`] = `
+#### \`_auth\`
+
+* Default: null
+* Type: null or String
+
+A basic-auth string to use when authenticating against the npm registry.
+
+Warning: This should generally not be set via a command-line option. It is
+safer to use a registry-provided authentication bearer token stored in the
+~/.npmrc file by running \`npm login\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for access 1`] = `
+#### \`access\`
+
+* Default: 'restricted' for scoped packages, 'public' for unscoped packages
+* Type: null, "restricted", or "public"
+
+When publishing scoped packages, the access level defaults to \`restricted\`.
+If you want your scoped package to be publicly viewable (and installable)
+set \`--access=public\`. The only valid values for \`access\` are \`public\` and
+\`restricted\`. Unscoped packages _always_ have an access level of \`public\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for all 1`] = `
+#### \`all\`
+
+* Default: false
+* Type: Boolean
+
+When running \`npm outdated\` and \`npm ls\`, setting \`--all\` will show all
+outdated or installed packages, rather than only those directly depended
+upon by the current project.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for allow-same-version 1`] = `
+#### \`allow-same-version\`
+
+* Default: false
+* Type: Boolean
+
+Prevents throwing an error when \`npm version\` is used to set the new version
+to the same value as the current version.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for also 1`] = `
+#### \`also\`
+
+* Default: null
+* Type: null, "dev", or "development"
+* DEPRECATED: Please use --include=dev instead.
+
+When set to \`dev\` or \`development\`, this is an alias for \`--include=dev\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for audit 1`] = `
+#### \`audit\`
+
+* Default: true
+* Type: Boolean
+
+When "true" submit audit reports alongside \`npm install\` runs to the default
+registry and all registries configured for scopes. See the documentation for
+[\`npm audit\`](/commands/npm-audit) for details on what is submitted.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for audit-level 1`] = `
+#### \`audit-level\`
+
+* Default: null
+* Type: null, "info", "low", "moderate", "high", "critical", or "none"
+
+The minimum level of vulnerability for \`npm audit\` to exit with a non-zero
+exit code.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for auth-type 1`] = `
+#### \`auth-type\`
+
+* Default: "legacy"
+* Type: "legacy", "sso", "saml", or "oauth"
+* DEPRECATED: This method of SSO/SAML/OAuth is deprecated and will be removed
+  in a future version of npm in favor of web-based login.
+
+What authentication strategy to use with \`adduser\`/\`login\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for before 1`] = `
+#### \`before\`
+
+* Default: null
+* Type: null or Date
+
+If passed to \`npm install\`, will rebuild the npm tree such that only
+versions that were available **on or before** the \`--before\` time get
+installed. If there's no versions available for the current set of direct
+dependencies, the command will error.
+
+If the requested version is a \`dist-tag\` and the given tag does not pass the
+\`--before\` filter, the most recent version less than or equal to that tag
+will be used. For example, \`foo@latest\` might install \`foo@1.2\` even though
+\`latest\` is \`2.0\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for bin-links 1`] = `
+#### \`bin-links\`
+
+* Default: true
+* Type: Boolean
+
+Tells npm to create symlinks (or \`.cmd\` shims on Windows) for package
+executables.
+
+Set to false to have it not do this. This can be used to work around the
+fact that some file systems don't support symlinks, even on ostensibly Unix
+systems.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for browser 1`] = `
+#### \`browser\`
+
+* Default: OS X: \`"open"\`, Windows: \`"start"\`, Others: \`"xdg-open"\`
+* Type: null, Boolean, or String
+
+The browser that is called by npm commands to open websites.
+
+Set to \`false\` to suppress browser behavior and instead print urls to
+terminal.
+
+Set to \`true\` to use default system URL opener.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for ca 1`] = `
+#### \`ca\`
+
+* Default: null
+* Type: null or String (can be set multiple times)
+
+The Certificate Authority signing certificate that is trusted for SSL
+connections to the registry. Values should be in PEM format (Windows calls
+it "Base-64 encoded X.509 (.CER)") with newlines replaced by the string
+"\\n". For example:
+
+\`\`\`ini
+ca="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----"
+\`\`\`
+
+Set to \`null\` to only allow "known" registrars, or to a specific CA cert to
+trust only that specific signing authority.
+
+Multiple CAs can be trusted by specifying an array of certificates:
+
+\`\`\`ini
+ca[]="..."
+ca[]="..."
+\`\`\`
+
+See also the \`strict-ssl\` config.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for cache 1`] = `
+#### \`cache\`
+
+* Default: Windows: \`%LocalAppData%\\npm-cache\`, Posix: \`~/.npm\`
+* Type: Path
+
+The location of npm's cache directory. See [\`npm
+cache\`](/commands/npm-cache)
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for cache-max 1`] = `
+#### \`cache-max\`
+
+* Default: Infinity
+* Type: Number
+* DEPRECATED: This option has been deprecated in favor of \`--prefer-online\`
+
+\`--cache-max=0\` is an alias for \`--prefer-online\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for cache-min 1`] = `
+#### \`cache-min\`
+
+* Default: 0
+* Type: Number
+* DEPRECATED: This option has been deprecated in favor of \`--prefer-offline\`.
+
+\`--cache-min=9999 (or bigger)\` is an alias for \`--prefer-offline\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for cafile 1`] = `
+#### \`cafile\`
+
+* Default: null
+* Type: Path
+
+A path to a file containing one or multiple Certificate Authority signing
+certificates. Similar to the \`ca\` setting, but allows for multiple CA's, as
+well as for the CA information to be stored in a file on disk.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for call 1`] = `
+#### \`call\`
+
+* Default: ""
+* Type: String
+
+Optional companion option for \`npm exec\`, \`npx\` that allows for specifying a
+custom command to be run along with the installed packages.
+
+\`\`\`bash
+npm exec --package yo --package generator-node --call "yo node"
+\`\`\`
+
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for cert 1`] = `
+#### \`cert\`
+
+* Default: null
+* Type: null or String
+
+A client certificate to pass when accessing the registry. Values should be
+in PEM format (Windows calls it "Base-64 encoded X.509 (.CER)") with
+newlines replaced by the string "\\n". For example:
+
+\`\`\`ini
+cert="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----"
+\`\`\`
+
+It is _not_ the path to a certificate file (and there is no "certfile"
+option).
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for ci-name 1`] = `
+#### \`ci-name\`
+
+* Default: The name of the current CI system, or \`null\` when not on a known CI
+  platform.
+* Type: null or String
+
+The name of a continuous integration system. If not set explicitly, npm will
+detect the current CI environment using the
+[\`@npmcli/ci-detect\`](http://npm.im/@npmcli/ci-detect) module.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for cidr 1`] = `
+#### \`cidr\`
+
+* Default: null
+* Type: null or String (can be set multiple times)
+
+This is a list of CIDR address to be used when configuring limited access
+tokens with the \`npm token create\` command.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for color 1`] = `
+#### \`color\`
+
+* Default: true unless the NO_COLOR environ is set to something other than '0'
+* Type: "always" or Boolean
+
+If false, never shows colors. If \`"always"\` then always shows colors. If
+true, then only prints color codes for tty file descriptors.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for commit-hooks 1`] = `
+#### \`commit-hooks\`
+
+* Default: true
+* Type: Boolean
+
+Run git commit hooks when using the \`npm version\` command.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for depth 1`] = `
+#### \`depth\`
+
+* Default: \`Infinity\` if \`--all\` is set, otherwise \`1\`
+* Type: null or Number
+
+The depth to go when recursing packages for \`npm ls\`.
+
+If not set, \`npm ls\` will show only the immediate dependencies of the root
+project. If \`--all\` is set, then npm will show all dependencies by default.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for description 1`] = `
+#### \`description\`
+
+* Default: true
+* Type: Boolean
+
+Show the description in \`npm search\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for dev 1`] = `
+#### \`dev\`
+
+* Default: false
+* Type: Boolean
+* DEPRECATED: Please use --include=dev instead.
+
+Alias for \`--include=dev\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff 1`] = `
+#### \`diff\`
+
+* Default:
+* Type: String (can be set multiple times)
+
+Define arguments to compare in \`npm diff\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-dst-prefix 1`] = `
+#### \`diff-dst-prefix\`
+
+* Default: "b/"
+* Type: String
+
+Destination prefix to be used in \`npm diff\` output.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-ignore-all-space 1`] = `
+#### \`diff-ignore-all-space\`
+
+* Default: false
+* Type: Boolean
+
+Ignore whitespace when comparing lines in \`npm diff\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-name-only 1`] = `
+#### \`diff-name-only\`
+
+* Default: false
+* Type: Boolean
+
+Prints only filenames when using \`npm diff\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-no-prefix 1`] = `
+#### \`diff-no-prefix\`
+
+* Default: false
+* Type: Boolean
+
+Do not show any source or destination prefix in \`npm diff\` output.
+
+Note: this causes \`npm diff\` to ignore the \`--diff-src-prefix\` and
+\`--diff-dst-prefix\` configs.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-src-prefix 1`] = `
+#### \`diff-src-prefix\`
+
+* Default: "a/"
+* Type: String
+
+Source prefix to be used in \`npm diff\` output.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-text 1`] = `
+#### \`diff-text\`
+
+* Default: false
+* Type: Boolean
+
+Treat all files as text in \`npm diff\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for diff-unified 1`] = `
+#### \`diff-unified\`
+
+* Default: 3
+* Type: Number
+
+The number of lines of context to print in \`npm diff\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for dry-run 1`] = `
+#### \`dry-run\`
+
+* Default: false
+* Type: Boolean
+
+Indicates that you don't want npm to make any changes and that it should
+only report what it would have done. This can be passed into any of the
+commands that modify your local installation, eg, \`install\`, \`update\`,
+\`dedupe\`, \`uninstall\`, as well as \`pack\` and \`publish\`.
+
+Note: This is NOT honored by other network related commands, eg \`dist-tags\`,
+\`owner\`, etc.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for editor 1`] = `
+#### \`editor\`
+
+* Default: The EDITOR or VISUAL environment variables, or 'notepad.exe' on
+  Windows, or 'vim' on Unix systems
+* Type: String
+
+The command to run for \`npm edit\` and \`npm config edit\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for engine-strict 1`] = `
+#### \`engine-strict\`
+
+* Default: false
+* Type: Boolean
+
+If set to true, then npm will stubbornly refuse to install (or even consider
+installing) any package that claims to not be compatible with the current
+Node.js version.
+
+This can be overridden by setting the \`--force\` flag.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for fetch-retries 1`] = `
+#### \`fetch-retries\`
+
+* Default: 2
+* Type: Number
+
+The "retries" config for the \`retry\` module to use when fetching packages
+from the registry.
+
+npm will retry idempotent read requests to the registry in the case of
+network failures or 5xx HTTP errors.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for fetch-retry-factor 1`] = `
+#### \`fetch-retry-factor\`
+
+* Default: 10
+* Type: Number
+
+The "factor" config for the \`retry\` module to use when fetching packages.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for fetch-retry-maxtimeout 1`] = `
+#### \`fetch-retry-maxtimeout\`
+
+* Default: 60000 (1 minute)
+* Type: Number
+
+The "maxTimeout" config for the \`retry\` module to use when fetching
+packages.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for fetch-retry-mintimeout 1`] = `
+#### \`fetch-retry-mintimeout\`
+
+* Default: 10000 (10 seconds)
+* Type: Number
+
+The "minTimeout" config for the \`retry\` module to use when fetching
+packages.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for fetch-timeout 1`] = `
+#### \`fetch-timeout\`
+
+* Default: 300000 (5 minutes)
+* Type: Number
+
+The maximum amount of time to wait for HTTP requests to complete.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for force 1`] = `
+#### \`force\`
+
+* Default: false
+* Type: Boolean
+
+Removes various protections against unfortunate side effects, common
+mistakes, unnecessary performance degradation, and malicious input.
+
+* Allow clobbering non-npm files in global installs.
+* Allow the \`npm version\` command to work on an unclean git repository.
+* Allow deleting the cache folder with \`npm cache clean\`.
+* Allow installing packages that have an \`engines\` declaration requiring a
+  different version of npm.
+* Allow installing packages that have an \`engines\` declaration requiring a
+  different version of \`node\`, even if \`--engine-strict\` is enabled.
+* Allow \`npm audit fix\` to install modules outside your stated dependency
+  range (including SemVer-major changes).
+* Allow unpublishing all versions of a published package.
+* Allow conflicting peerDependencies to be installed in the root project.
+* Implicitly set \`--yes\` during \`npm init\`.
+
+If you don't have a clear idea of what you want to do, it is strongly
+recommended that you do not use this option!
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for foreground-scripts 1`] = `
+#### \`foreground-scripts\`
+
+* Default: false
+* Type: Boolean
+
+Run all build scripts (ie, \`preinstall\`, \`install\`, and \`postinstall\`)
+scripts for installed packages in the foreground process, sharing standard
+input, output, and error with the main npm process.
+
+Note that this will generally make installs run slower, and be much noisier,
+but can be useful for debugging.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for format-package-lock 1`] = `
+#### \`format-package-lock\`
+
+* Default: true
+* Type: Boolean
+
+Format \`package-lock.json\` or \`npm-shrinkwrap.json\` as a human readable
+file.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for fund 1`] = `
+#### \`fund\`
+
+* Default: true
+* Type: Boolean
+
+When "true" displays the message at the end of each \`npm install\`
+acknowledging the number of dependencies looking for funding. See [\`npm
+fund\`](/commands/npm-fund) for details.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for git 1`] = `
+#### \`git\`
+
+* Default: "git"
+* Type: String
+
+The command to use for git commands. If git is installed on the computer,
+but is not in the \`PATH\`, then set this to the full path to the git binary.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for git-tag-version 1`] = `
+#### \`git-tag-version\`
+
+* Default: true
+* Type: Boolean
+
+Tag the commit when using the \`npm version\` command.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for global 1`] = `
+#### \`global\`
+
+* Default: false
+* Type: Boolean
+
+Operates in "global" mode, so that packages are installed into the \`prefix\`
+folder instead of the current working directory. See
+[folders](/configuring-npm/folders) for more on the differences in behavior.
+
+* packages are installed into the \`{prefix}/lib/node_modules\` folder, instead
+  of the current working directory.
+* bin files are linked to \`{prefix}/bin\`
+* man pages are linked to \`{prefix}/share/man\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for global-style 1`] = `
+#### \`global-style\`
+
+* Default: false
+* Type: Boolean
+
+Causes npm to install the package into your local \`node_modules\` folder with
+the same layout it uses with the global \`node_modules\` folder. Only your
+direct dependencies will show in \`node_modules\` and everything they depend
+on will be flattened in their \`node_modules\` folders. This obviously will
+eliminate some deduping. If used with \`legacy-bundling\`, \`legacy-bundling\`
+will be preferred.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for globalconfig 1`] = `
+#### \`globalconfig\`
+
+* Default: The global --prefix setting plus 'etc/npmrc'. For example,
+  '/usr/local/etc/npmrc'
+* Type: Path
+
+The config file to read for global config options.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for heading 1`] = `
+#### \`heading\`
+
+* Default: "npm"
+* Type: String
+
+The string that starts all the debugging log output.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for https-proxy 1`] = `
+#### \`https-proxy\`
+
+* Default: null
+* Type: null or URL
+
+A proxy to use for outgoing https requests. If the \`HTTPS_PROXY\` or
+\`https_proxy\` or \`HTTP_PROXY\` or \`http_proxy\` environment variables are set,
+proxy settings will be honored by the underlying \`make-fetch-happen\`
+library.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for if-present 1`] = `
+#### \`if-present\`
+
+* Default: false
+* Type: Boolean
+
+If true, npm will not exit with an error code when \`run-script\` is invoked
+for a script that isn't defined in the \`scripts\` section of \`package.json\`.
+This option can be used when it's desirable to optionally run a script when
+it's present and fail if the script fails. This is useful, for example, when
+running scripts that may only apply for some builds in an otherwise generic
+CI setup.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for ignore-scripts 1`] = `
+#### \`ignore-scripts\`
+
+* Default: false
+* Type: Boolean
+
+If true, npm does not run scripts specified in package.json files.
+
+Note that commands explicitly intended to run a particular script, such as
+\`npm start\`, \`npm stop\`, \`npm restart\`, \`npm test\`, and \`npm run-script\`
+will still run their intended script if \`ignore-scripts\` is set, but they
+will *not* run any pre- or post-scripts.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for include 1`] = `
+#### \`include\`
+
+* Default:
+* Type: "prod", "dev", "optional", or "peer" (can be set multiple times)
+
+Option that allows for defining which types of dependencies to install.
+
+This is the inverse of \`--omit=\`.
+
+Dependency types specified in \`--include\` will not be omitted, regardless of
+the order in which omit/include are specified on the command-line.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for include-staged 1`] = `
+#### \`include-staged\`
+
+* Default: false
+* Type: Boolean
+
+Allow installing "staged" published packages, as defined by [npm RFC PR
+#92](https://github.com/npm/rfcs/pull/92).
+
+This is experimental, and not implemented by the npm public registry.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init-author-email 1`] = `
+#### \`init-author-email\`
+
+* Default: ""
+* Type: String
+
+The value \`npm init\` should use by default for the package author's email.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init-author-name 1`] = `
+#### \`init-author-name\`
+
+* Default: ""
+* Type: String
+
+The value \`npm init\` should use by default for the package author's name.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init-author-url 1`] = `
+#### \`init-author-url\`
+
+* Default: ""
+* Type: "" or URL
+
+The value \`npm init\` should use by default for the package author's
+homepage.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init-license 1`] = `
+#### \`init-license\`
+
+* Default: "ISC"
+* Type: String
+
+The value \`npm init\` should use by default for the package license.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init-module 1`] = `
+#### \`init-module\`
+
+* Default: "~/.npm-init.js"
+* Type: Path
+
+A module that will be loaded by the \`npm init\` command. See the
+documentation for the
+[init-package-json](https://github.com/npm/init-package-json) module for
+more information, or [npm init](/commands/npm-init).
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init-version 1`] = `
+#### \`init-version\`
+
+* Default: "1.0.0"
+* Type: SemVer string
+
+The value that \`npm init\` should use by default for the package version
+number, if not already set in package.json.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init.author.email 1`] = `
+#### \`init.author.email\`
+
+* Default: ""
+* Type: String
+* DEPRECATED: Use \`--init-author-email\` instead.
+
+Alias for \`--init-author-email\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init.author.name 1`] = `
+#### \`init.author.name\`
+
+* Default: ""
+* Type: String
+* DEPRECATED: Use \`--init-author-name\` instead.
+
+Alias for \`--init-author-name\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init.author.url 1`] = `
+#### \`init.author.url\`
+
+* Default: ""
+* Type: "" or URL
+* DEPRECATED: Use \`--init-author-url\` instead.
+
+Alias for \`--init-author-url\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init.license 1`] = `
+#### \`init.license\`
+
+* Default: "ISC"
+* Type: String
+* DEPRECATED: Use \`--init-license\` instead.
+
+Alias for \`--init-license\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init.module 1`] = `
+#### \`init.module\`
+
+* Default: "~/.npm-init.js"
+* Type: Path
+* DEPRECATED: Use \`--init-module\` instead.
+
+Alias for \`--init-module\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for init.version 1`] = `
+#### \`init.version\`
+
+* Default: "1.0.0"
+* Type: SemVer string
+* DEPRECATED: Use \`--init-version\` instead.
+
+Alias for \`--init-version\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for json 1`] = `
+#### \`json\`
+
+* Default: false
+* Type: Boolean
+
+Whether or not to output JSON data, rather than the normal output.
+
+Not supported by all npm commands.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for key 1`] = `
+#### \`key\`
+
+* Default: null
+* Type: null or String
+
+A client key to pass when accessing the registry. Values should be in PEM
+format with newlines replaced by the string "\\n". For example:
+
+\`\`\`ini
+key="-----BEGIN PRIVATE KEY-----\\nXXXX\\nXXXX\\n-----END PRIVATE KEY-----"
+\`\`\`
+
+It is _not_ the path to a key file (and there is no "keyfile" option).
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for legacy-bundling 1`] = `
+#### \`legacy-bundling\`
+
+* Default: false
+* Type: Boolean
+
+Causes npm to install the package such that versions of npm prior to 1.4,
+such as the one included with node 0.8, can install the package. This
+eliminates all automatic deduping. If used with \`global-style\` this option
+will be preferred.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for legacy-peer-deps 1`] = `
+#### \`legacy-peer-deps\`
+
+* Default: false
+* Type: Boolean
+
+Causes npm to completely ignore \`peerDependencies\` when building a package
+tree, as in npm versions 3 through 6.
+
+If a package cannot be installed because of overly strict \`peerDependencies\`
+that collide, it provides a way to move forward resolving the situation.
+
+This differs from \`--omit=peer\`, in that \`--omit=peer\` will avoid unpacking
+\`peerDependencies\` on disk, but will still design a tree such that
+\`peerDependencies\` _could_ be unpacked in a correct place.
+
+Use of \`legacy-peer-deps\` is not recommended, as it will not enforce the
+\`peerDependencies\` contract that meta-dependencies may rely on.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for link 1`] = `
+#### \`link\`
+
+* Default: false
+* Type: Boolean
+
+Used with \`npm ls\`, limiting output to only those packages that are linked.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for local-address 1`] = `
+#### \`local-address\`
+
+* Default: null
+* Type: IP Address
+
+The IP address of the local interface to use when making connections to the
+npm registry. Must be IPv4 in versions of Node prior to 0.12.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for loglevel 1`] = `
+#### \`loglevel\`
+
+* Default: "notice"
+* Type: "silent", "error", "warn", "notice", "http", "timing", "info",
+  "verbose", or "silly"
+
+What level of logs to report. On failure, *all* logs are written to
+\`npm-debug.log\` in the current working directory.
+
+Any logs of a higher level than the setting are shown. The default is
+"notice".
+
+See also the \`foreground-scripts\` config.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for logs-max 1`] = `
+#### \`logs-max\`
+
+* Default: 10
+* Type: Number
+
+The maximum number of log files to store.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for long 1`] = `
+#### \`long\`
+
+* Default: false
+* Type: Boolean
+
+Show extended information in \`ls\`, \`search\`, and \`help-search\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for maxsockets 1`] = `
+#### \`maxsockets\`
+
+* Default: 15
+* Type: Number
+
+The maximum number of connections to use per origin (protocol/host/port
+combination).
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for message 1`] = `
+#### \`message\`
+
+* Default: "%s"
+* Type: String
+
+Commit message which is used by \`npm version\` when creating version commit.
+
+Any "%s" in the message will be replaced with the version number.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for node-options 1`] = `
+#### \`node-options\`
+
+* Default: null
+* Type: null or String
+
+Options to pass through to Node.js via the \`NODE_OPTIONS\` environment
+variable. This does not impact how npm itself is executed but it does impact
+how lifecycle scripts are called.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for node-version 1`] = `
+#### \`node-version\`
+
+* Default: Node.js \`process.version\` value
+* Type: SemVer string
+
+The node version to use when checking a package's \`engines\` setting.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for noproxy 1`] = `
+#### \`noproxy\`
+
+* Default: The value of the NO_PROXY environment variable
+* Type: String (can be set multiple times)
+
+Domain extensions that should bypass any proxies.
+
+Also accepts a comma-delimited string.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for npm-version 1`] = `
+#### \`npm-version\`
+
+* Default: Output of \`npm --version\`
+* Type: SemVer string
+
+The npm version to use when checking a package's \`engines\` setting.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for offline 1`] = `
+#### \`offline\`
+
+* Default: false
+* Type: Boolean
+
+Force offline mode: no network requests will be done during install. To
+allow the CLI to fill in missing cache data, see \`--prefer-offline\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for omit 1`] = `
+#### \`omit\`
+
+* Default: 'dev' if the \`NODE_ENV\` environment variable is set to
+  'production', otherwise empty.
+* Type: "dev", "optional", or "peer" (can be set multiple times)
+
+Dependency types to omit from the installation tree on disk.
+
+Note that these dependencies _are_ still resolved and added to the
+\`package-lock.json\` or \`npm-shrinkwrap.json\` file. They are just not
+physically installed on disk.
+
+If a package type appears in both the \`--include\` and \`--omit\` lists, then
+it will be included.
+
+If the resulting omit list includes \`'dev'\`, then the \`NODE_ENV\` environment
+variable will be set to \`'production'\` for all lifecycle scripts.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for only 1`] = `
+#### \`only\`
+
+* Default: null
+* Type: null, "prod", or "production"
+* DEPRECATED: Use \`--omit=dev\` to omit dev dependencies from the install.
+
+When set to \`prod\` or \`production\`, this is an alias for \`--omit=dev\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for optional 1`] = `
+#### \`optional\`
+
+* Default: null
+* Type: null or Boolean
+* DEPRECATED: Use \`--omit=optional\` to exclude optional dependencies, or
+  \`--include=optional\` to include them.
+
+Default value does install optional deps unless otherwise omitted.
+
+Alias for --include=optional or --omit=optional
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for otp 1`] = `
+#### \`otp\`
+
+* Default: null
+* Type: null or String
+
+This is a one-time password from a two-factor authenticator. It's needed
+when publishing or changing package permissions with \`npm access\`.
+
+If not set, and a registry response fails with a challenge for a one-time
+password, npm will prompt on the command line for one.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for pack-destination 1`] = `
+#### \`pack-destination\`
+
+* Default: "."
+* Type: String
+
+Directory in which \`npm pack\` will save tarballs.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for package 1`] = `
+#### \`package\`
+
+* Default:
+* Type: String (can be set multiple times)
+
+The package to install for [\`npm exec\`](/commands/npm-exec)
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for package-lock 1`] = `
+#### \`package-lock\`
+
+* Default: true
+* Type: Boolean
+
+If set to false, then ignore \`package-lock.json\` files when installing. This
+will also prevent _writing_ \`package-lock.json\` if \`save\` is true.
+
+When package package-locks are disabled, automatic pruning of extraneous
+modules will also be disabled. To remove extraneous modules with
+package-locks disabled use \`npm prune\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for package-lock-only 1`] = `
+#### \`package-lock-only\`
+
+* Default: false
+* Type: Boolean
+
+If set to true, the current operation will only use the \`package-lock.json\`,
+ignoring \`node_modules\`.
+
+For \`update\` this means only the \`package-lock.json\` will be updated,
+instead of checking \`node_modules\` and downloading dependencies.
+
+For \`list\` this means the output will be based on the tree described by the
+\`package-lock.json\`, rather than the contents of \`node_modules\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for parseable 1`] = `
+#### \`parseable\`
+
+* Default: false
+* Type: Boolean
+
+Output parseable results from commands that write to standard output. For
+\`npm search\`, this will be tab-separated table format.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for prefer-offline 1`] = `
+#### \`prefer-offline\`
+
+* Default: false
+* Type: Boolean
+
+If true, staleness checks for cached data will be bypassed, but missing data
+will be requested from the server. To force full offline mode, use
+\`--offline\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for prefer-online 1`] = `
+#### \`prefer-online\`
+
+* Default: false
+* Type: Boolean
+
+If true, staleness checks for cached data will be forced, making the CLI
+look for updates immediately even for fresh package data.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for prefix 1`] = `
+#### \`prefix\`
+
+* Default: In global mode, the folder where the node executable is installed.
+  In local mode, the nearest parent folder containing either a package.json
+  file or a node_modules folder.
+* Type: Path
+
+The location to install global items. If set on the command line, then it
+forces non-global commands to run in the specified folder.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for preid 1`] = `
+#### \`preid\`
+
+* Default: ""
+* Type: String
+
+The "prerelease identifier" to use as a prefix for the "prerelease" part of
+a semver. Like the \`rc\` in \`1.2.0-rc.8\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for production 1`] = `
+#### \`production\`
+
+* Default: null
+* Type: null or Boolean
+* DEPRECATED: Use \`--omit=dev\` instead.
+
+Alias for \`--omit=dev\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for progress 1`] = `
+#### \`progress\`
+
+* Default: \`true\` unless running in a known CI system
+* Type: Boolean
+
+When set to \`true\`, npm will display a progress bar during time intensive
+operations, if \`process.stderr\` is a TTY.
+
+Set to \`false\` to suppress the progress bar.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for proxy 1`] = `
+#### \`proxy\`
+
+* Default: null
+* Type: null, false, or URL
+
+A proxy to use for outgoing http requests. If the \`HTTP_PROXY\` or
+\`http_proxy\` environment variables are set, proxy settings will be honored
+by the underlying \`request\` library.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for read-only 1`] = `
+#### \`read-only\`
+
+* Default: false
+* Type: Boolean
+
+This is used to mark a token as unable to publish when configuring limited
+access tokens with the \`npm token create\` command.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for rebuild-bundle 1`] = `
+#### \`rebuild-bundle\`
+
+* Default: true
+* Type: Boolean
+
+Rebuild bundled dependencies after installation.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for registry 1`] = `
+#### \`registry\`
+
+* Default: "https://registry.npmjs.org/"
+* Type: URL
+
+The base URL of the npm registry.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save 1`] = `
+#### \`save\`
+
+* Default: true
+* Type: Boolean
+
+Save installed packages to a package.json file as dependencies.
+
+When used with the \`npm rm\` command, removes the dependency from
+package.json.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-bundle 1`] = `
+#### \`save-bundle\`
+
+* Default: false
+* Type: Boolean
+
+If a package would be saved at install time by the use of \`--save\`,
+\`--save-dev\`, or \`--save-optional\`, then also put it in the
+\`bundleDependencies\` list.
+
+Ignore if \`--save-peer\` is set, since peerDependencies cannot be bundled.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-dev 1`] = `
+#### \`save-dev\`
+
+* Default: false
+* Type: Boolean
+
+Save installed packages to a package.json file as \`devDependencies\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-exact 1`] = `
+#### \`save-exact\`
+
+* Default: false
+* Type: Boolean
+
+Dependencies saved to package.json will be configured with an exact version
+rather than using npm's default semver range operator.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-optional 1`] = `
+#### \`save-optional\`
+
+* Default: false
+* Type: Boolean
+
+Save installed packages to a package.json file as \`optionalDependencies\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-peer 1`] = `
+#### \`save-peer\`
+
+* Default: false
+* Type: Boolean
+
+Save installed packages. to a package.json file as \`peerDependencies\`
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-prefix 1`] = `
+#### \`save-prefix\`
+
+* Default: "^"
+* Type: String
+
+Configure how versions of packages installed to a package.json file via
+\`--save\` or \`--save-dev\` get prefixed.
+
+For example if a package has version \`1.2.3\`, by default its version is set
+to \`^1.2.3\` which allows minor upgrades for that package, but after \`npm
+config set save-prefix='~'\` it would be set to \`~1.2.3\` which only allows
+patch upgrades.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for save-prod 1`] = `
+#### \`save-prod\`
+
+* Default: false
+* Type: Boolean
+
+Save installed packages into \`dependencies\` specifically. This is useful if
+a package already exists in \`devDependencies\` or \`optionalDependencies\`, but
+you want to move it to be a non-optional production dependency.
+
+This is the default behavior if \`--save\` is true, and neither \`--save-dev\`
+or \`--save-optional\` are true.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for scope 1`] = `
+#### \`scope\`
+
+* Default: the scope of the current project, if any, or ""
+* Type: String
+
+Associate an operation with a scope for a scoped registry.
+
+Useful when logging in to or out of a private registry:
+
+\`\`\`
+# log in, linking the scope to the custom registry
+npm login --scope=@mycorp --registry=https://registry.mycorp.com
+
+# log out, removing the link and the auth token
+npm logout --scope=@mycorp
+\`\`\`
+
+This will cause \`@mycorp\` to be mapped to the registry for future
+installation of packages specified according to the pattern
+\`@mycorp/package\`.
+
+This will also cause \`npm init\` to create a scoped package.
+
+\`\`\`
+# accept all defaults, and create a package named "@foo/whatever",
+# instead of just named "whatever"
+npm init --scope=@foo --yes
+\`\`\`
+
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for script-shell 1`] = `
+#### \`script-shell\`
+
+* Default: '/bin/sh' on POSIX systems, 'cmd.exe' on Windows
+* Type: null or String
+
+The shell to use for scripts run with the \`npm exec\`, \`npm run\` and \`npm
+init \` commands.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for searchexclude 1`] = `
+#### \`searchexclude\`
+
+* Default: ""
+* Type: String
+
+Space-separated options that limit the results from search.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for searchlimit 1`] = `
+#### \`searchlimit\`
+
+* Default: 20
+* Type: Number
+
+Number of items to limit search results to. Will not apply at all to legacy
+searches.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for searchopts 1`] = `
+#### \`searchopts\`
+
+* Default: ""
+* Type: String
+
+Space-separated options that are always passed to search.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for searchstaleness 1`] = `
+#### \`searchstaleness\`
+
+* Default: 900
+* Type: Number
+
+The age of the cache, in seconds, before another registry request is made if
+using legacy search endpoint.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for shell 1`] = `
+#### \`shell\`
+
+* Default: SHELL environment variable, or "bash" on Posix, or "cmd.exe" on
+  Windows
+* Type: String
+
+The shell to run for the \`npm explore\` command.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for shrinkwrap 1`] = `
+#### \`shrinkwrap\`
+
+* Default: true
+* Type: Boolean
+* DEPRECATED: Use the --package-lock setting instead.
+
+Alias for --package-lock
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for sign-git-commit 1`] = `
+#### \`sign-git-commit\`
+
+* Default: false
+* Type: Boolean
+
+If set to true, then the \`npm version\` command will commit the new package
+version using \`-S\` to add a signature.
+
+Note that git requires you to have set up GPG keys in your git configs for
+this to work properly.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for sign-git-tag 1`] = `
+#### \`sign-git-tag\`
+
+* Default: false
+* Type: Boolean
+
+If set to true, then the \`npm version\` command will tag the version using
+\`-s\` to add a signature.
+
+Note that git requires you to have set up GPG keys in your git configs for
+this to work properly.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for sso-poll-frequency 1`] = `
+#### \`sso-poll-frequency\`
+
+* Default: 500
+* Type: Number
+* DEPRECATED: The --auth-type method of SSO/SAML/OAuth will be removed in a
+  future version of npm in favor of web-based login.
+
+When used with SSO-enabled \`auth-type\`s, configures how regularly the
+registry should be polled while the user is completing authentication.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for sso-type 1`] = `
+#### \`sso-type\`
+
+* Default: "oauth"
+* Type: null, "oauth", or "saml"
+* DEPRECATED: The --auth-type method of SSO/SAML/OAuth will be removed in a
+  future version of npm in favor of web-based login.
+
+If \`--auth-type=sso\`, the type of SSO type to use.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for strict-peer-deps 1`] = `
+#### \`strict-peer-deps\`
+
+* Default: false
+* Type: Boolean
+
+If set to \`true\`, and \`--legacy-peer-deps\` is not set, then _any_
+conflicting \`peerDependencies\` will be treated as an install failure, even
+if npm could reasonably guess the appropriate resolution based on non-peer
+dependency relationships.
+
+By default, conflicting \`peerDependencies\` deep in the dependency graph will
+be resolved using the nearest non-peer dependency specification, even if
+doing so will result in some packages receiving a peer dependency outside
+the range set in their package's \`peerDependencies\` object.
+
+When such and override is performed, a warning is printed, explaining the
+conflict and the packages involved. If \`--strict-peer-deps\` is set, then
+this warning is treated as a failure.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for strict-ssl 1`] = `
+#### \`strict-ssl\`
+
+* Default: true
+* Type: Boolean
+
+Whether or not to do SSL key validation when making requests to the registry
+via https.
+
+See also the \`ca\` config.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for tag 1`] = `
+#### \`tag\`
+
+* Default: "latest"
+* Type: String
+
+If you ask npm to install a package and don't tell it a specific version,
+then it will install the specified tag.
+
+Also the tag that is added to the package@version specified by the \`npm tag\`
+command, if no explicit tag is given.
+
+When used by the \`npm diff\` command, this is the tag used to fetch the
+tarball that will be compared with the local files by default.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for tag-version-prefix 1`] = `
+#### \`tag-version-prefix\`
+
+* Default: "v"
+* Type: String
+
+If set, alters the prefix used when tagging a new version when performing a
+version increment using \`npm-version\`. To remove the prefix altogether, set
+it to the empty string: \`""\`.
+
+Because other tools may rely on the convention that npm version tags look
+like \`v1.0.0\`, _only use this property if it is absolutely necessary_. In
+particular, use care when overriding this setting for public packages.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for timing 1`] = `
+#### \`timing\`
+
+* Default: false
+* Type: Boolean
+
+If true, writes an \`npm-debug\` log to \`_logs\` and timing information to
+\`_timing.json\`, both in your cache, even if the command completes
+successfully. \`_timing.json\` is a newline delimited list of JSON objects.
+
+You can quickly view it with this [json](https://npm.im/json) command line:
+\`npm exec -- json -g < ~/.npm/_timing.json\`.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for tmp 1`] = `
+#### \`tmp\`
+
+* Default: The value returned by the Node.js \`os.tmpdir()\` method
+  
+* Type: Path
+* DEPRECATED: This setting is no longer used. npm stores temporary files in a
+  special location in the cache, and they are managed by
+  [\`cacache\`](http://npm.im/cacache).
+
+Historically, the location where temporary files were stored. No longer
+relevant.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for umask 1`] = `
+#### \`umask\`
+
+* Default: 0
+* Type: Octal numeric string in range 0000..0777 (0..511)
+
+The "umask" value to use when setting the file creation mode on files and
+folders.
+
+Folders and executables are given a mode which is \`0o777\` masked against
+this value. Other files are given a mode which is \`0o666\` masked against
+this value.
+
+Note that the underlying system will _also_ apply its own umask value to
+files and folders that are created, and npm does not circumvent this, but
+rather adds the \`--umask\` config to it.
+
+Thus, the effective default umask value on most POSIX systems is 0o22,
+meaning that folders and executables are created with a mode of 0o755 and
+other files are created with a mode of 0o644.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for unicode 1`] = `
+#### \`unicode\`
+
+* Default: false on windows, true on mac/unix systems with a unicode locale,
+  as defined by the \`LC_ALL\`, \`LC_CTYPE\`, or \`LANG\` environment variables.
+* Type: Boolean
+
+When set to true, npm uses unicode characters in the tree output. When
+false, it uses ascii characters instead of unicode glyphs.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for update-notifier 1`] = `
+#### \`update-notifier\`
+
+* Default: true
+* Type: Boolean
+
+Set to false to suppress the update notification when using an older version
+of npm than the latest.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for usage 1`] = `
+#### \`usage\`
+
+* Default: false
+* Type: Boolean
+
+Show short usage output about the command specified.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for user-agent 1`] = `
+#### \`user-agent\`
+
+* Default: "npm/{npm-version} node/{node-version} {platform} {arch}
+  workspaces/{workspaces} {ci}"
+* Type: String
+
+Sets the User-Agent request header. The following fields are replaced with
+their actual counterparts:
+
+* \`{npm-version}\` - The npm version in use
+* \`{node-version}\` - The Node.js version in use
+* \`{platform}\` - The value of \`process.platform\`
+* \`{arch}\` - The value of \`process.arch\`
+* \`{workspaces}\` - Set to \`true\` if the \`workspaces\` or \`workspace\` options
+  are set.
+* \`{ci}\` - The value of the \`ci-name\` config, if set, prefixed with \`ci/\`, or
+  an empty string if \`ci-name\` is empty.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for userconfig 1`] = `
+#### \`userconfig\`
+
+* Default: "~/.npmrc"
+* Type: Path
+
+The location of user-level configuration settings.
+
+This may be overridden by the \`npm_config_userconfig\` environment variable
+or the \`--userconfig\` command line option, but may _not_ be overridden by
+settings in the \`globalconfig\` file.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for version 1`] = `
+#### \`version\`
+
+* Default: false
+* Type: Boolean
+
+If true, output the npm version and exit successfully.
+
+Only relevant when specified explicitly on the command line.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for versions 1`] = `
+#### \`versions\`
+
+* Default: false
+* Type: Boolean
+
+If true, output the npm version as well as node's \`process.versions\` map and
+the version in the current working directory's \`package.json\` file if one
+exists, and exit successfully.
+
+Only relevant when specified explicitly on the command line.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for viewer 1`] = `
+#### \`viewer\`
+
+* Default: "man" on Posix, "browser" on Windows
+* Type: String
+
+The program to use to view help content.
+
+Set to \`"browser"\` to view html help content in the default web browser.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for which 1`] = `
+#### \`which\`
+
+* Default: null
+* Type: null or Number
+
+If there are multiple funding sources, which 1-indexed source URL to open.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for workspace 1`] = `
+#### \`workspace\`
+
+* Default:
+* Type: String (can be set multiple times)
+
+Enable running a command in the context of the configured workspaces of the
+current project while filtering by running only the workspaces defined by
+this configuration option.
+
+Valid values for the \`workspace\` config are either:
+
+* Workspace names
+* Path to a workspace directory
+* Path to a parent workspace directory (will result to selecting all of the
+  nested workspaces)
+
+When set for the \`npm init\` command, this may be set to the folder of a
+workspace which does not yet exist, to create the folder and set it up as a
+brand new workspace within the project.
+
+This value is not exported to the environment for child processes.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for workspaces 1`] = `
+#### \`workspaces\`
+
+* Default: false
+* Type: Boolean
+
+Enable running a command in the context of **all** the configured
+workspaces.
+
+This value is not exported to the environment for child processes.
+`
+
+exports[`test/lib/utils/config/definitions.js TAP > config description for yes 1`] = `
+#### \`yes\`
+
+* Default: null
+* Type: null or Boolean
+
+Automatically answer "yes" to any prompts that npm might print on the
+command line.
+`
diff --git a/deps/npm/tap-snapshots/test/lib/utils/error-handler.js.test.cjs b/deps/npm/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs
similarity index 66%
rename from deps/npm/tap-snapshots/test/lib/utils/error-handler.js.test.cjs
rename to deps/npm/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs
index 78a9eef217f353..0a5ed59a1157ce 100644
--- a/deps/npm/tap-snapshots/test/lib/utils/error-handler.js.test.cjs
+++ b/deps/npm/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs
@@ -5,14 +5,14 @@
  * Make sure to inspect the output below.  Do not ignore changes!
  */
 'use strict'
-exports[`test/lib/utils/error-handler.js TAP handles unknown error > should have expected log contents for unknown error 1`] = `
+exports[`test/lib/utils/exit-handler.js TAP handles unknown error > should have expected log contents for unknown error 1`] = `
 0 verbose code 1
 1 error foo A complete log of this run can be found in:
-1 error foo     {CWD}/test/lib/utils/tap-testdir-error-handler/_logs/expecteddate-debug.log
+1 error foo     {CWD}/test/lib/utils/tap-testdir-exit-handler/_logs/expecteddate-debug.log
 2 verbose stack Error: ERROR
 3 verbose cwd {CWD}
 4 verbose Foo 1.0.0
-5 verbose argv "/node" "{CWD}/test/lib/utils/error-handler.js"
+5 verbose argv "/node" "{CWD}/test/lib/utils/exit-handler.js"
 6 verbose node v1.0.0
 7 verbose npm  v1.0.0
 8 error foo code ERROR
diff --git a/deps/npm/test/fixtures/mock-npm.js b/deps/npm/test/fixtures/mock-npm.js
index c972c35b318611..1de080eb10b4a8 100644
--- a/deps/npm/test/fixtures/mock-npm.js
+++ b/deps/npm/test/fixtures/mock-npm.js
@@ -1,9 +1,52 @@
-// Basic npm fixture that you can give a config object that acts like
-// npm.config You still need a separate flatOptions but this is the first step
-// to eventually just using npm itself
+const npmlog = require('npmlog')
+const perf = require('../../lib/utils/perf.js')
+perf.reset()
+const procLog = require('../../lib/utils/proc-log-listener.js')
+procLog.reset()
+
+const realLog = {}
+for (const level of ['silly', 'verbose', 'timing', 'notice', 'warn', 'error'])
+  realLog[level] = npmlog[level]
+
+const { title, execPath } = process
+
+const RealMockNpm = (t, otherMocks = {}) => {
+  t.teardown(() => {
+    for (const level of ['silly', 'verbose', 'timing', 'notice', 'warn', 'error'])
+      npmlog[level] = realLog[level]
+    perf.reset()
+    procLog.reset()
+    process.title = title
+    process.execPath = execPath
+    delete process.env.npm_command
+    delete process.env.COLOR
+  })
+  const logs = []
+  const outputs = []
+  const npm = t.mock('../../lib/npm.js', otherMocks)
+  const command = async (command, args = []) => {
+    return new Promise((resolve, reject) => {
+      npm.commands[command](args, err => {
+        if (err)
+          return reject(err)
+        return resolve()
+      })
+    })
+  }
+  for (const level of ['silly', 'verbose', 'timing', 'notice', 'warn', 'error']) {
+    npmlog[level] = (...msg) => {
+      logs.push([level, ...msg])
+    }
+  }
+  npm.output = (...msg) => outputs.push(msg)
+  return { npm, logs, outputs, command }
+}
 
 const realConfig = require('../../lib/utils/config')
 
+// Basic npm fixture that you can give a config object that acts like
+// npm.config You still need a separate flatOptions. Tests should migrate to
+// using the real npm mock above
 class MockNpm {
   constructor (base = {}) {
     this._mockOutputs = []
@@ -51,7 +94,11 @@ class MockNpm {
   }
 }
 
-// TODO export MockNpm, and change tests to use new MockNpm()
-module.exports = (base = {}) => {
-  return new MockNpm(base)
+const FakeMockNpm = (base = {}) => {
+    return new MockNpm(base)
+}
+
+module.exports = {
+  fake: FakeMockNpm,
+  real: RealMockNpm
 }
diff --git a/deps/npm/test/lib/audit.js b/deps/npm/test/lib/audit.js
index bb6f06debc51f7..561765a0270b55 100644
--- a/deps/npm/test/lib/audit.js
+++ b/deps/npm/test/lib/audit.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 t.test('should audit using Arborist', t => {
   let ARB_ARGS = null
diff --git a/deps/npm/test/lib/bin.js b/deps/npm/test/lib/bin.js
index 898e7ba439b88e..8ceca8280f52d4 100644
--- a/deps/npm/test/lib/bin.js
+++ b/deps/npm/test/lib/bin.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 t.test('bin', (t) => {
   t.plan(4)
diff --git a/deps/npm/test/lib/birthday.js b/deps/npm/test/lib/birthday.js
index 0589be7a8eedbf..05660d6fa3f205 100644
--- a/deps/npm/test/lib/birthday.js
+++ b/deps/npm/test/lib/birthday.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const config = {
   yes: false,
diff --git a/deps/npm/test/lib/cache.js b/deps/npm/test/lib/cache.js
index bad0ede89e1013..d3d6f5b8845def 100644
--- a/deps/npm/test/lib/cache.js
+++ b/deps/npm/test/lib/cache.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm.js')
 const path = require('path')
 
 const usageUtil = () => 'usage instructions'
diff --git a/deps/npm/test/lib/ci.js b/deps/npm/test/lib/ci.js
index b60375c2898427..b6b2af9c111db6 100644
--- a/deps/npm/test/lib/ci.js
+++ b/deps/npm/test/lib/ci.js
@@ -4,7 +4,7 @@ const readdir = util.promisify(fs.readdir)
 
 const t = require('tap')
 
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 t.test('should ignore scripts with --ignore-scripts', (t) => {
   const SCRIPTS = []
diff --git a/deps/npm/test/lib/cli.js b/deps/npm/test/lib/cli.js
index d0a9e0bd492008..b85c981cd008bb 100644
--- a/deps/npm/test/lib/cli.js
+++ b/deps/npm/test/lib/cli.js
@@ -1,43 +1,22 @@
 const t = require('tap')
 
-let LOAD_ERROR = null
-const npmOutputs = []
-const npmock = {
-  log: { level: 'silent' },
-  output: (...msg) => npmOutputs.push(msg),
-  usage: 'npm usage test example',
-  version: '99.99.99',
-  load: cb => cb(LOAD_ERROR),
-  argv: [],
-  config: {
-    settings: {},
-    get: (k) => npmock.config.settings[k],
-    set: (k, v) => {
-      npmock.config.settings[k] = v
-    },
-  },
-  commands: {},
-}
+const { real: mockNpm } = require('../fixtures/mock-npm.js')
 
 const unsupportedMock = {
   checkForBrokenNode: () => {},
   checkForUnsupportedNode: () => {},
 }
 
-let errorHandlerCalled = null
-let errorHandlerNpm = null
-let errorHandlerCb
-const errorHandlerMock = (...args) => {
-  errorHandlerCalled = args
-  if (errorHandlerCb)
-    errorHandlerCb()
-}
-let errorHandlerExitCalled = null
-errorHandlerMock.exit = code => {
-  errorHandlerExitCalled = code
+let exitHandlerCalled = null
+let exitHandlerNpm = null
+let exitHandlerCb
+const exitHandlerMock = (...args) => {
+  exitHandlerCalled = args
+  if (exitHandlerCb)
+    exitHandlerCb()
 }
-errorHandlerMock.setNpm = npm => {
-  errorHandlerNpm = npm
+exitHandlerMock.setNpm = npm => {
+  exitHandlerNpm = npm
 }
 
 const logs = []
@@ -47,188 +26,122 @@ const npmlogMock = {
   info: (...msg) => logs.push(['info', ...msg]),
 }
 
-const cli = t.mock('../../lib/cli.js', {
-  '../../lib/npm.js': npmock,
+const cliMock = (npm) => t.mock('../../lib/cli.js', {
+  '../../lib/npm.js': npm,
   '../../lib/utils/update-notifier.js': async () => null,
-  '../../lib/utils/did-you-mean.js': () => '\ntest did you mean',
   '../../lib/utils/unsupported.js': unsupportedMock,
-  '../../lib/utils/error-handler.js': errorHandlerMock,
+  '../../lib/utils/exit-handler.js': exitHandlerMock,
   npmlog: npmlogMock,
 })
 
-t.test('print the version, and treat npm_g to npm -g', t => {
-  t.teardown(() => {
-    delete npmock.config.settings.version
-    process.argv = argv
-    npmock.argv.length = 0
-    proc.argv.length = 0
-    logs.length = 0
-    npmOutputs.length = 0
-    errorHandlerExitCalled = null
-  })
-
-  const { argv } = process
-  const proc = {
-    argv: ['node', 'npm_g', '-v'],
-    version: '420.69.lol',
+const processMock = (proc) => {
+  const mocked = {
+    ...process,
     on: () => {},
+    ...proc,
   }
-  process.argv = proc.argv
-  npmock.config.settings.version = true
+  // nopt looks at process directly
+  process.argv = mocked.argv
+  return mocked
+}
+
+const { argv } = process
+
+t.afterEach(() => {
+  logs.length = 0
+  process.argv = argv
+  exitHandlerCalled = null
+  exitHandlerNpm = null
+})
+
+t.test('print the version, and treat npm_g as npm -g', async t => {
+  const proc = processMock({
+    argv: ['node', 'npm_g', '-v'],
+    version: process.version,
+  })
 
-  cli(proc)
+  const { npm, outputs } = mockNpm(t)
+  const cli = cliMock(npm)
+  await cli(proc)
 
-  t.strictSame(npmock.argv, [])
-  t.strictSame(proc.argv, ['node', 'npm', '-g', '-v'])
+  t.strictSame(proc.argv, ['node', 'npm', '-g', '-v'], 'npm process.argv was rewritten')
+  t.strictSame(process.argv, ['node', 'npm', '-g', '-v'], 'system process.argv was rewritten')
   t.strictSame(logs, [
     'pause',
-    ['verbose', 'cli', ['node', 'npm', '-g', '-v']],
-    ['info', 'using', 'npm@%s', '99.99.99'],
-    ['info', 'using', 'node@%s', '420.69.lol'],
+    ['verbose', 'cli', proc.argv],
+    ['info', 'using', 'npm@%s', npm.version],
+    ['info', 'using', 'node@%s', process.version],
   ])
-  t.strictSame(npmOutputs, [['99.99.99']])
-  t.strictSame(errorHandlerExitCalled, 0)
-
-  t.end()
+  t.strictSame(outputs, [[npm.version]])
+  t.strictSame(exitHandlerCalled, [])
 })
 
-t.test('calling with --versions calls npm version with no args', t => {
-  const processArgv = process.argv
-  const proc = {
+t.test('calling with --versions calls npm version with no args', async t => {
+  const proc = processMock({
     argv: ['node', 'npm', 'install', 'or', 'whatever', '--versions'],
-    on: () => {},
-  }
-  process.argv = proc.argv
-  npmock.config.set('versions', true)
-
-  t.teardown(() => {
-    delete npmock.config.settings.versions
-    process.argv = processArgv
-    npmock.argv.length = 0
-    proc.argv.length = 0
-    logs.length = 0
-    npmOutputs.length = 0
-    errorHandlerExitCalled = null
-    delete npmock.commands.version
   })
+  const { npm, outputs } = mockNpm(t)
+  const cli = cliMock(npm)
 
-  npmock.commands.version = (args, cb) => {
-    t.equal(proc.title, 'npm')
-    t.strictSame(npmock.argv, [])
-    t.strictSame(proc.argv, ['node', 'npm', 'install', 'or', 'whatever', '--versions'])
-    t.strictSame(logs, [
-      'pause',
-      ['verbose', 'cli', ['node', 'npm', 'install', 'or', 'whatever', '--versions']],
-      ['info', 'using', 'npm@%s', '99.99.99'],
-      ['info', 'using', 'node@%s', undefined],
-    ])
-
-    t.strictSame(npmOutputs, [])
-    t.strictSame(errorHandlerExitCalled, null)
-
-    t.strictSame(args, [])
-    t.end()
+  let versionArgs
+  npm.commands.version = (args, cb) => {
+    versionArgs = args
+    cb()
   }
 
-  cli(proc)
-})
+  await cli(proc)
+  t.strictSame(versionArgs, [])
+  t.equal(proc.title, 'npm')
+  t.strictSame(npm.argv, [])
+  t.strictSame(logs, [
+    'pause',
+    ['verbose', 'cli', proc.argv],
+    ['info', 'using', 'npm@%s', npm.version],
+    ['info', 'using', 'node@%s', process.version],
+  ])
 
-t.test('print usage if no params provided', t => {
-  const { output } = npmock
-  t.teardown(() => {
-    npmock.output = output
-  })
-  const proc = {
-    argv: ['node', 'npm'],
-    on: () => {},
-  }
-  npmock.argv = []
-  npmock.output = (msg) => {
-    if (msg) {
-      t.match(msg, 'npm usage test example', 'outputs npm usage')
-      t.end()
-    }
-  }
-  cli(proc)
+  t.strictSame(outputs, [])
+  t.strictSame(exitHandlerCalled, [])
 })
 
-t.test('print usage if non-command param provided', t => {
-  const { output } = npmock
-  t.teardown(() => {
-    npmock.output = output
+t.test('print usage if no params provided', async t => {
+  const proc = processMock({
+    argv: ['node', 'npm'],
   })
-  const proc = {
-    argv: ['node', 'npm', 'asdf'],
-    on: () => {},
-  }
-  npmock.argv = ['asdf']
-  npmock.output = (msg) => {
-    if (msg) {
-      t.match(msg, 'Unknown command: "asdf"\ntest did you mean', 'outputs did you mean')
-      t.end()
-    }
-  }
-  cli(proc)
-})
 
-t.test('gracefully handles error printing usage', t => {
-  const { output } = npmock
-  t.teardown(() => {
-    npmock.output = output
-    errorHandlerCb = null
-    errorHandlerCalled = null
-  })
-  const proc = {
-    argv: ['node', 'npm'],
-    on: () => {},
-  }
-  npmock.argv = []
-  errorHandlerCb = () => {
-    t.match(errorHandlerCalled, [], 'should call errorHandler with no args')
-    t.match(errorHandlerNpm, npmock, 'errorHandler npm is set')
-    t.end()
-  }
-  cli(proc)
+  const { npm, outputs } = mockNpm(t)
+  const cli = cliMock(npm)
+  await cli(proc)
+  t.match(outputs[0][0], 'Usage:', 'outputs npm usage')
+  t.match(exitHandlerCalled, [], 'should call exitHandler with no args')
+  t.ok(exitHandlerNpm, 'exitHandler npm is set')
+  t.match(proc.exitCode, 1)
 })
 
-t.test('handles output error', t => {
-  const { output } = npmock
-  t.teardown(() => {
-    npmock.output = output
-    errorHandlerCb = null
-    errorHandlerCalled = null
+t.test('print usage if non-command param provided', async t => {
+  const proc = processMock({
+    argv: ['node', 'npm', 'tset'],
   })
-  const proc = {
-    argv: ['node', 'npm'],
-    on: () => {},
-  }
-  npmock.argv = []
-  npmock.output = () => {
-    throw new Error('ERR')
-  }
-  errorHandlerCb = () => {
-    t.match(errorHandlerCalled, /ERR/, 'should call errorHandler with error')
-    t.end()
-  }
-  cli(proc)
+
+  const { npm, outputs } = mockNpm(t)
+  const cli = cliMock(npm)
+  await cli(proc)
+  t.match(outputs[0][0], 'Unknown command: "tset"')
+  t.match(outputs[0][0], 'Did you mean this?')
+  t.match(exitHandlerCalled, [], 'should call exitHandler with no args')
+  t.ok(exitHandlerNpm, 'exitHandler npm is set')
+  t.match(proc.exitCode, 1)
 })
 
-t.test('load error calls error handler', t => {
-  t.teardown(() => {
-    errorHandlerCb = null
-    errorHandlerCalled = null
-    LOAD_ERROR = null
+t.test('load error calls error handler', async t => {
+  const proc = processMock({
+    argv: ['node', 'npm', 'asdf'],
   })
 
+  const { npm } = mockNpm(t)
+  const cli = cliMock(npm)
   const er = new Error('test load error')
-  LOAD_ERROR = er
-  const proc = {
-    argv: ['node', 'npm', 'asdf'],
-    on: () => {},
-  }
-  errorHandlerCb = () => {
-    t.strictSame(errorHandlerCalled, [er])
-    t.end()
-  }
-  cli(proc)
+  npm.load = () => Promise.reject(er)
+  await cli(proc)
+  t.strictSame(exitHandlerCalled, [er])
 })
diff --git a/deps/npm/test/lib/dedupe.js b/deps/npm/test/lib/dedupe.js
index 801e3c96de3cf3..30f8a380e8ea35 100644
--- a/deps/npm/test/lib/dedupe.js
+++ b/deps/npm/test/lib/dedupe.js
@@ -1,21 +1,20 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { real: mockNpm } = require('../fixtures/mock-npm')
 
-t.test('should throw in global mode', (t) => {
-  const Dedupe = t.mock('../../lib/dedupe.js')
-  const npm = mockNpm({
-    config: { 'dry-run': false, global: true },
-  })
-  const dedupe = new Dedupe(npm)
-
-  dedupe.exec([], er => {
-    t.match(er, { code: 'EDEDUPEGLOBAL' }, 'throws EDEDUPEGLOBAL')
-    t.end()
-  })
+t.test('should throw in global mode', async (t) => {
+  const { npm, command } = mockNpm(t)
+  await npm.load()
+  npm.config.set('global', true)
+  t.rejects(
+    command('dedupe'),
+    { code: 'EDEDUPEGLOBAL' },
+    'throws EDEDUPEGLOBALE'
+  )
 })
 
-t.test('should remove dupes using Arborist', (t) => {
-  const Dedupe = t.mock('../../lib/dedupe.js', {
+t.test('should remove dupes using Arborist', async (t) => {
+  t.plan(5)
+  const { npm, command } = mockNpm(t, {
     '@npmcli/arborist': function (args) {
       t.ok(args, 'gets options object')
       t.ok(args.path, 'gets path option')
@@ -28,37 +27,24 @@ t.test('should remove dupes using Arborist', (t) => {
       t.ok(arb, 'gets arborist tree')
     },
   })
-  const npm = mockNpm({
-    prefix: 'foo',
-    config: {
-      'dry-run': 'true',
-    },
-  })
-  const dedupe = new Dedupe(npm)
-  dedupe.exec([], er => {
-    if (er)
-      throw er
-    t.ok(true, 'callback is called')
-    t.end()
-  })
+  await npm.load()
+  npm.config.set('prefix', 'foo')
+  npm.config.set('dry-run', 'true')
+  await command('dedupe')
 })
 
-t.test('should remove dupes using Arborist - no arguments', (t) => {
-  const Dedupe = t.mock('../../lib/dedupe.js', {
+t.test('should remove dupes using Arborist - no arguments', async (t) => {
+  t.plan(1)
+  const { npm, command } = mockNpm(t, {
     '@npmcli/arborist': function (args) {
       t.ok(args.dryRun, 'gets dryRun from config')
       this.dedupe = () => {}
     },
     '../../lib/utils/reify-output.js': () => {},
+    '../../lib/utils/reify-finish.js': () => {},
   })
-  const npm = mockNpm({
-    prefix: 'foo',
-    config: {
-      'dry-run': 'true',
-    },
-  })
-  const dedupe = new Dedupe(npm)
-  dedupe.exec(null, () => {
-    t.end()
-  })
+  await npm.load()
+  npm.config.set('prefix', 'foo')
+  npm.config.set('dry-run', true)
+  await command('dedupe')
 })
diff --git a/deps/npm/test/lib/diff.js b/deps/npm/test/lib/diff.js
index 2fb38c9b127e44..fcba802d93b87e 100644
--- a/deps/npm/test/lib/diff.js
+++ b/deps/npm/test/lib/diff.js
@@ -1,6 +1,6 @@
-const { resolve, join } = require('path')
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { resolve, join } = require('path')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const noop = () => null
 let libnpmdiff = noop
diff --git a/deps/npm/test/lib/dist-tag.js b/deps/npm/test/lib/dist-tag.js
index 9af90c309c77ca..1fb5cb3b6ee625 100644
--- a/deps/npm/test/lib/dist-tag.js
+++ b/deps/npm/test/lib/dist-tag.js
@@ -1,5 +1,5 @@
-const mockNpm = require('../fixtures/mock-npm')
 const t = require('tap')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let result = ''
 let log = ''
diff --git a/deps/npm/test/lib/docs.js b/deps/npm/test/lib/docs.js
index 0da070d9a7d137..fbd75842012473 100644
--- a/deps/npm/test/lib/docs.js
+++ b/deps/npm/test/lib/docs.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm.js')
+const { fake: mockNpm } = require('../fixtures/mock-npm.js')
 const { join, sep } = require('path')
 
 const pkgDirs = t.testdir({
diff --git a/deps/npm/test/lib/exec.js b/deps/npm/test/lib/exec.js
index dff067619f2ce8..6d99c7959d88e8 100644
--- a/deps/npm/test/lib/exec.js
+++ b/deps/npm/test/lib/exec.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 const { resolve, delimiter } = require('path')
 
 const ARB_CTOR = []
diff --git a/deps/npm/test/lib/explore.js b/deps/npm/test/lib/explore.js
index b49c885cbcb269..fd9949e73fc4c7 100644
--- a/deps/npm/test/lib/explore.js
+++ b/deps/npm/test/lib/explore.js
@@ -43,15 +43,11 @@ const mockRunScript = ({ pkg, banner, path, event, stdio }) => {
 }
 
 const output = []
-let ERROR_HANDLER_CALLED = null
 const logs = []
 const getExplore = (windows) => {
   const Explore = t.mock('../../lib/explore.js', {
     '../../lib/utils/is-windows.js': windows,
     path: require('path')[windows ? 'win32' : 'posix'],
-    '../../lib/utils/error-handler.js': er => {
-      ERROR_HANDLER_CALLED = er
-    },
     'read-package-json-fast': mockRPJ,
     '@npmcli/run-script': mockRunScript,
   })
@@ -83,11 +79,9 @@ t.test('basic interactive', t => {
       throw er
 
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: 'c:\\npm\\dir\\pkg\\package.json',
       RUN_SCRIPT_EXEC: 'shell-command',
     })
@@ -102,11 +96,9 @@ t.test('basic interactive', t => {
       throw er
 
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: '/npm/dir/pkg/package.json',
       RUN_SCRIPT_EXEC: 'shell-command',
     })
@@ -136,11 +128,9 @@ t.test('interactive tracks exit code', t => {
       throw er
 
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: 'c:\\npm\\dir\\pkg\\package.json',
       RUN_SCRIPT_EXEC: 'shell-command',
     })
@@ -156,11 +146,9 @@ t.test('interactive tracks exit code', t => {
       throw er
 
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: '/npm/dir/pkg/package.json',
       RUN_SCRIPT_EXEC: 'shell-command',
     })
@@ -224,11 +212,9 @@ t.test('basic non-interactive', t => {
       throw er
 
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: 'c:\\npm\\dir\\pkg\\package.json',
       RUN_SCRIPT_EXEC: 'ls',
     })
@@ -241,11 +227,9 @@ t.test('basic non-interactive', t => {
       throw er
 
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: '/npm/dir/pkg/package.json',
       RUN_SCRIPT_EXEC: 'ls',
     })
@@ -310,7 +294,6 @@ t.test('signal fails non-interactive', t => {
 t.test('usage if no pkg provided', t => {
   t.teardown(() => {
     output.length = 0
-    ERROR_HANDLER_CALLED = null
   })
   const noPkg = [
     [],
@@ -326,11 +309,9 @@ t.test('usage if no pkg provided', t => {
       posixExplore.exec(args, er => {
         t.match(er, 'Usage:')
         t.strictSame({
-          ERROR_HANDLER_CALLED: null,
           RPJ_CALLED,
           RUN_SCRIPT_EXEC,
         }, {
-          ERROR_HANDLER_CALLED: null,
           RPJ_CALLED: '/npm/dir/pkg/package.json',
           RUN_SCRIPT_EXEC: 'ls',
         })
@@ -345,11 +326,9 @@ t.test('pkg not installed', t => {
 
   posixExplore.exec(['pkg', 'ls'], er => {
     t.strictSame({
-      ERROR_HANDLER_CALLED,
       RPJ_CALLED,
       RUN_SCRIPT_EXEC,
     }, {
-      ERROR_HANDLER_CALLED: null,
       RPJ_CALLED: '/npm/dir/pkg/package.json',
       RUN_SCRIPT_EXEC: 'ls',
     })
diff --git a/deps/npm/test/lib/fund.js b/deps/npm/test/lib/fund.js
index 65778fca50bd71..784989827edc16 100644
--- a/deps/npm/test/lib/fund.js
+++ b/deps/npm/test/lib/fund.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const version = '1.0.0'
 const funding = {
diff --git a/deps/npm/test/lib/help-search.js b/deps/npm/test/lib/help-search.js
index 845b3873e3cf9d..2df862d4fc570d 100644
--- a/deps/npm/test/lib/help-search.js
+++ b/deps/npm/test/lib/help-search.js
@@ -1,6 +1,6 @@
 const t = require('tap')
 const { join } = require('path')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 const ansicolors = require('ansicolors')
 
 const OUTPUT = []
diff --git a/deps/npm/test/lib/init.js b/deps/npm/test/lib/init.js
index 44a2af5bcc02b8..1cefb1fc9c8fd7 100644
--- a/deps/npm/test/lib/init.js
+++ b/deps/npm/test/lib/init.js
@@ -1,7 +1,7 @@
+const t = require('tap')
 const fs = require('fs')
 const { resolve } = require('path')
-const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const npmLog = {
   disableProgress: () => null,
@@ -431,8 +431,10 @@ t.test('workspaces', t => {
 
     const Init = t.mock('../../lib/init.js', {
       ...mocks,
-      'json-parse-even-better-errors': () => {
-        throw new Error('ERR')
+      '@npmcli/package-json': {
+        async load () {
+          throw new Error('ERR')
+        },
       },
     })
     const init = new Init(npm)
@@ -440,7 +442,7 @@ t.test('workspaces', t => {
     init.execWorkspaces([], ['a'], err => {
       t.match(
         err,
-        /Invalid package.json: Error: ERR/,
+        /ERR/,
         'should exit with error'
       )
       t.end()
@@ -452,30 +454,16 @@ t.test('workspaces', t => {
     // this avoids poluting test output with those logs
     console.log = noop
 
-    npm.localPrefix = t.testdir({
-      'package.json': JSON.stringify({
-        name: 'top-level',
-      }),
-    })
+    npm.localPrefix = t.testdir({})
 
-    const Init = t.mock('../../lib/init.js', {
-      ...mocks,
-      fs: {
-        statSync () {
-          return true
-        },
-        readFileSync () {
-          throw new Error('ERR')
-        },
-      },
-    })
+    const Init = require('../../lib/init.js')
     const init = new Init(npm)
 
     init.execWorkspaces([], ['a'], err => {
       t.match(
         err,
-        /package.json not found/,
-        'should exit with error'
+        { code: 'ENOENT' },
+        'should exit with missing package.json file error'
       )
       t.end()
     })
diff --git a/deps/npm/test/lib/install.js b/deps/npm/test/lib/install.js
index b7929bddafdba3..6412b34c16f251 100644
--- a/deps/npm/test/lib/install.js
+++ b/deps/npm/test/lib/install.js
@@ -1,7 +1,7 @@
 const t = require('tap')
 
 const Install = require('../../lib/install.js')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 t.test('should install using Arborist', (t) => {
   const SCRIPTS = []
diff --git a/deps/npm/test/lib/link.js b/deps/npm/test/lib/link.js
index 64375cfc13c2c2..736d18cab99068 100644
--- a/deps/npm/test/lib/link.js
+++ b/deps/npm/test/lib/link.js
@@ -1,9 +1,9 @@
+const t = require('tap')
 const { resolve } = require('path')
 const fs = require('fs')
 
 const Arborist = require('@npmcli/arborist')
-const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const redactCwd = (path) => {
   const normalizePath = p => p
diff --git a/deps/npm/test/lib/load-all-commands.js b/deps/npm/test/lib/load-all-commands.js
index b3b2012d23aa2e..e5f10099cf365c 100644
--- a/deps/npm/test/lib/load-all-commands.js
+++ b/deps/npm/test/lib/load-all-commands.js
@@ -3,17 +3,16 @@
 // name, a description, and if it has completion it is a function.  That it
 // renders also ensures that any params we've defined in our commands work.
 const t = require('tap')
-const npm = t.mock('../../lib/npm.js')
+const { real: mockNpm } = require('../fixtures/mock-npm.js')
 const { cmdList } = require('../../lib/utils/cmd-list.js')
 
-let npmOutput = []
-npm.output = (msg) => {
-  npmOutput = msg
-}
+const { npm, outputs } = mockNpm(t)
+
 t.test('load each command', t => {
-  t.plan(cmdList.length + 1)
+  t.plan(cmdList.length)
   npm.load((er) => {
-    t.notOk(er)
+    if (er)
+      throw er
     npm.config.set('usage', true)
     for (const cmd of cmdList.sort((a, b) => a.localeCompare(b, 'en'))) {
       t.test(cmd, t => {
@@ -26,13 +25,14 @@ t.test('load each command', t => {
         t.match(impl.usage, cmd, 'usage contains the command')
         impl([], (err) => {
           t.notOk(err)
-          t.match(npmOutput, impl.usage, 'usage is what is output')
+          t.match(outputs[0][0], impl.usage, 'usage is what is output')
           // This ties usage to a snapshot so we have to re-run snap if usage
           // changes, which rebuilds the man pages
-          t.matchSnapshot(npmOutput)
+          t.matchSnapshot(outputs[0][0])
           t.end()
         })
       })
+      outputs.length = 0
     }
   })
 })
diff --git a/deps/npm/test/lib/load-all.js b/deps/npm/test/lib/load-all.js
index 4a975d49a490e5..e6e407805346d5 100644
--- a/deps/npm/test/lib/load-all.js
+++ b/deps/npm/test/lib/load-all.js
@@ -22,10 +22,10 @@ else {
     t.end()
   })
 
-  t.test('call the error handle so we dont freak out', t => {
-    const errorHandler = require('../../lib/utils/error-handler.js')
-    errorHandler.setNpm(npm)
-    errorHandler()
+  t.test('call the exit handler so we dont freak out', t => {
+    const exitHandler = require('../../lib/utils/exit-handler.js')
+    exitHandler.setNpm(npm)
+    exitHandler()
     t.end()
   })
 }
diff --git a/deps/npm/test/lib/logout.js b/deps/npm/test/lib/logout.js
index b130d439c88ca6..7cb5c2790d6216 100644
--- a/deps/npm/test/lib/logout.js
+++ b/deps/npm/test/lib/logout.js
@@ -1,5 +1,5 @@
-const mockNpm = require('../fixtures/mock-npm')
 const t = require('tap')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const config = {
   registry: 'https://registry.npmjs.org/',
diff --git a/deps/npm/test/lib/ls.js b/deps/npm/test/lib/ls.js
index 64ece6bd8a3fd6..5f196501e55d1a 100644
--- a/deps/npm/test/lib/ls.js
+++ b/deps/npm/test/lib/ls.js
@@ -3,7 +3,7 @@
 // of them contain the tap testdir folders, which are auto-generated and
 // may change when node-tap is updated.
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm.js')
 
 const { resolve } = require('path')
 const { utimesSync } = require('fs')
@@ -123,7 +123,23 @@ const ls = new LS(npm)
 const redactCwd = res =>
   res && res.replace(/\\+/g, '/').replace(new RegExp(__dirname.replace(/\\+/g, '/'), 'gi'), '{CWD}')
 
-const jsonParse = res => JSON.parse(redactCwd(res))
+const redactCwdObj = obj => {
+  if (Array.isArray(obj))
+    return obj.map(o => redactCwdObj(o))
+  else if (typeof obj === 'string')
+    return redactCwd(obj)
+  else if (!obj)
+    return obj
+  else if (typeof obj === 'object') {
+    return Object.keys(obj).reduce((o, k) => {
+      o[k] = redactCwdObj(obj[k])
+      return o
+    }, {})
+  } else
+    return obj
+}
+
+const jsonParse = res => redactCwdObj(JSON.parse(res))
 
 const cleanUpResult = () => result = ''
 
@@ -3060,7 +3076,7 @@ t.test('ls --json', (t) => {
           dependencies: {
             foo: {
               version: '1.0.0',
-              invalid: true,
+              invalid: '"^2.0.0" from the root project',
               problems: [
                 'invalid: foo@1.0.0 {CWD}/tap-testdir-ls-ls---json-missing-invalid-extraneous/node_modules/foo',
               ],
@@ -3756,7 +3772,7 @@ t.test('ls --json', (t) => {
           dependencies: {
             'peer-dep': {
               version: '1.0.0',
-              invalid: true,
+              invalid: '"^2.0.0" from the root project',
               problems: [
                 'invalid: peer-dep@1.0.0 {CWD}/tap-testdir-ls-ls---json-unmet-peer-dep/node_modules/peer-dep',
               ],
@@ -3817,7 +3833,7 @@ t.test('ls --json', (t) => {
           dependencies: {
             'optional-dep': {
               version: '1.0.0',
-              invalid: true,
+              invalid: '"^2.0.0" from the root project',
               problems: [
                 'invalid: optional-dep@1.0.0 {CWD}/tap-testdir-ls-ls---json-unmet-optional-dep/node_modules/optional-dep',
               ],
@@ -4175,6 +4191,59 @@ t.test('ls --json', (t) => {
   t.end()
 })
 
+t.test('show multiple invalid reasons', (t) => {
+  config.json = false
+  config.all = true
+  config.depth = Infinity
+  npm.prefix = t.testdir({
+    'package.json': JSON.stringify({
+      name: 'test-npm-ls',
+      version: '1.0.0',
+      dependencies: {
+        cat: '^2.0.0',
+        dog: '^1.2.3',
+      },
+    }),
+    node_modules: {
+      cat: {
+        'package.json': JSON.stringify({
+          name: 'cat',
+          version: '1.0.0',
+          dependencies: {
+            dog: '^2.0.0',
+          },
+        }),
+      },
+      dog: {
+        'package.json': JSON.stringify({
+          name: 'dog',
+          version: '1.0.0',
+          dependencies: {
+            cat: '',
+          },
+        }),
+      },
+      chai: {
+        'package.json': JSON.stringify({
+          name: 'chai',
+          version: '1.0.0',
+          dependencies: {
+            dog: '2.x',
+          },
+        }),
+      },
+    },
+  })
+
+  const cleanupPaths = str =>
+    redactCwd(str).toLowerCase().replace(/\\/g, '/')
+  ls.exec([], (err) => {
+    t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems')
+    t.matchSnapshot(cleanupPaths(result), 'ls result')
+    t.end()
+  })
+})
+
 t.test('ls --package-lock-only', (t) => {
   config['package-lock-only'] = true
   t.test('ls --package-lock-only --json', (t) => {
@@ -4751,7 +4820,7 @@ t.test('ls --package-lock-only', (t) => {
             dependencies: {
               foo: {
                 version: '1.0.0',
-                invalid: true,
+                invalid: '"^2.0.0" from the root project',
                 problems: [
                   'invalid: foo@1.0.0 {CWD}/tap-testdir-ls-ls---package-lock-only-ls---package-lock-only---json-missing-invalid-extraneous/node_modules/foo',
                 ],
diff --git a/deps/npm/test/lib/npm.js b/deps/npm/test/lib/npm.js
index 6909c43e4ff0e0..291a58955ceedd 100644
--- a/deps/npm/test/lib/npm.js
+++ b/deps/npm/test/lib/npm.js
@@ -1,8 +1,12 @@
 const t = require('tap')
 
+const npmlog = require('npmlog')
+const { real: mockNpm } = require('../fixtures/mock-npm.js')
+
 // delete this so that we don't have configs from the fact that it
 // is being run by 'npm test'
 const event = process.env.npm_lifecycle_event
+
 for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) {
   if (env === 'npm_command') {
     // should only be running this in the 'test' or 'run-script' command!
@@ -23,52 +27,36 @@ for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) {
 const { resolve, dirname } = require('path')
 
 const actualPlatform = process.platform
-
 const beWindows = () => {
   Object.defineProperty(process, 'platform', {
     value: 'win32',
     configurable: true,
   })
 }
-
 const bePosix = () => {
   Object.defineProperty(process, 'platform', {
     value: 'posix',
     configurable: true,
   })
 }
+const argv = [...process.argv]
 
-const npmlog = require('npmlog')
-
-const npmPath = resolve(__dirname, '..', '..')
-const Config = require('@npmcli/config')
-const { definitions, shorthands, flatten } = require('../../lib/utils/config')
-const freshConfig = (opts = {}) => {
+t.afterEach(() => {
   for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e)))
     delete process.env[env]
-
   process.env.npm_config_cache = CACHE
-
-  npm.config = new Config({
-    definitions,
-    shorthands,
-    npmPath,
-    log: npmlog,
-    ...opts,
-    flatten,
+  process.argv = argv
+  Object.defineProperty(process, 'platform', {
+    value: actualPlatform,
+    configurable: true,
   })
-}
-
-const logs = []
-for (const level of ['silly', 'verbose', 'timing', 'notice', 'warn', 'error'])
-  npmlog[level] = (...msg) => logs.push([level, ...msg])
-
-const npm = require('../../lib/npm.js')
+})
 
 const CACHE = t.testdir()
 process.env.npm_config_cache = CACHE
 
 t.test('not yet loaded', t => {
+  const { npm, logs } = mockNpm(t)
   t.match(npm, {
     started: Number,
     command: null,
@@ -89,160 +77,125 @@ t.test('not yet loaded', t => {
   t.equal(npm.commands.asdfasdf, undefined)
   t.equal(npm.deref('list'), 'ls')
   t.same(logs, [])
-  logs.length = 0
   t.end()
 })
 
 t.test('npm.load', t => {
-  t.test('must be called with proper args', t => {
-    const er = new TypeError('must call as: npm.load(callback)')
-    t.throws(() => npm.load(), er)
+  t.test('callback must be a function', t => {
+    const { npm, logs } = mockNpm(t)
+    const er = new TypeError('callback must be a function if provided')
     t.throws(() => npm.load({}), er)
     t.same(logs, [])
-    logs.length = 0
     t.end()
   })
 
-  t.test('load error', t => {
-    const { load } = npm.config
+  t.test('callback style', t => {
+    const { npm } = mockNpm(t)
+    npm.load((err) => {
+      if (err)
+        throw err
+      t.ok(npm.loaded)
+      t.end()
+    })
+  })
+
+  t.test('load error', async t => {
+    const { npm } = mockNpm(t)
     const loadError = new Error('load error')
     npm.config.load = async () => {
       throw loadError
     }
-    npm.load(er => {
+    await npm.load().catch(er => {
       t.equal(er, loadError)
       t.equal(npm.loadErr, loadError)
-      npm.config.load = load
-      // loading again just returns the same error
-      npm.load(er => {
-        t.equal(er, loadError)
-        t.equal(npm.loadErr, loadError)
-        npm.loadErr = null
-        t.end()
-      })
+    })
+    npm.config.load = async () => {
+      throw new Error('new load error')
+    }
+    await npm.load().catch(er => {
+      t.equal(er, loadError, 'loading again returns the original error')
+      t.equal(npm.loadErr, loadError)
     })
   })
 
-  t.test('basic loading', t => {
+  t.test('basic loading', async t => {
+    const { npm, logs } = mockNpm(t)
     const dir = t.testdir({
       node_modules: {},
     })
-    let firstCalled = false
-    const first = (er) => {
-      if (er)
-        throw er
-
-      firstCalled = true
-      t.equal(npm.loaded, true)
-      t.equal(npm.config.loaded, true)
-      t.equal(npm.config.get('force'), false)
-    }
-
-    let secondCalled = false
-    const second = () => {
-      secondCalled = true
-    }
-
-    t.equal(npm.loading, false, 'not loading yet')
-    const p = npm.load(first).then(() => {
-      t.ok(npm.usage, 'has usage')
-      npm.config.set('prefix', dir)
-      t.match(npm, {
-        loaded: true,
-        loading: false,
-        flatOptions: {},
-      })
-      t.equal(firstCalled, true, 'first callback got called')
-      t.equal(secondCalled, true, 'second callback got called')
-      let thirdCalled = false
-      const third = () => {
-        thirdCalled = true
-      }
-      npm.load(third)
-      t.equal(thirdCalled, true, 'third callbback got called')
-      t.match(logs, [
-        ['timing', 'npm:load', /Completed in [0-9.]+ms/],
-      ])
-      logs.length = 0
-
-      bePosix()
-      t.equal(resolve(npm.cache), resolve(CACHE), 'cache is cache')
-      const newCache = t.testdir()
-      npm.cache = newCache
-      t.equal(npm.config.get('cache'), newCache, 'cache setter sets config')
-      t.equal(npm.cache, newCache, 'cache getter gets new config')
-      t.equal(npm.log, npmlog, 'npmlog getter')
-      t.equal(npm.lockfileVersion, 2, 'lockfileVersion getter')
-      t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix')
-      t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix')
-      npm.globalPrefix = npm.prefix
-      t.equal(npm.prefix, npm.globalPrefix, 'globalPrefix setter')
-      npm.localPrefix = dir + '/extra/prefix'
-      t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix after localPrefix setter')
-      t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix after localPrefix setter')
-
-      npm.prefix = dir + '/some/prefix'
-      t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix after prefix setter')
-      t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix after prefix setter')
-      t.equal(npm.bin, npm.localBin, 'bin is local bin after prefix setter')
-      t.not(npm.bin, npm.globalBin, 'bin is not global bin after prefix setter')
-      t.equal(npm.dir, npm.localDir, 'dir is local dir after prefix setter')
-      t.not(npm.dir, npm.globalDir, 'dir is not global dir after prefix setter')
-
-      npm.config.set('global', true)
-      t.equal(npm.prefix, npm.globalPrefix, 'prefix is global prefix after setting global')
-      t.not(npm.prefix, npm.localPrefix, 'prefix is not local prefix after setting global')
-      t.equal(npm.bin, npm.globalBin, 'bin is global bin after setting global')
-      t.not(npm.bin, npm.localBin, 'bin is not local bin after setting global')
-      t.equal(npm.dir, npm.globalDir, 'dir is global dir after setting global')
-      t.not(npm.dir, npm.localDir, 'dir is not local dir after setting global')
-
-      npm.prefix = dir + '/new/global/prefix'
-      t.equal(npm.prefix, npm.globalPrefix, 'prefix is global prefix after prefix setter')
-      t.not(npm.prefix, npm.localPrefix, 'prefix is not local prefix after prefix setter')
-      t.equal(npm.bin, npm.globalBin, 'bin is global bin after prefix setter')
-      t.not(npm.bin, npm.localBin, 'bin is not local bin after prefix setter')
-
-      beWindows()
-      t.equal(npm.bin, npm.globalBin, 'bin is global bin in windows mode')
-      t.equal(npm.dir, npm.globalDir, 'dir is global dir in windows mode')
-      bePosix()
-
-      const tmp = npm.tmp
-      t.match(tmp, String, 'npm.tmp is a string')
-      t.equal(tmp, npm.tmp, 'getter only generates it once')
+    await npm.load()
+    t.equal(npm.loaded, true)
+    t.equal(npm.config.loaded, true)
+    t.equal(npm.config.get('force'), false)
+    t.ok(npm.usage, 'has usage')
+    npm.config.set('prefix', dir)
+
+    t.match(npm, {
+      flatOptions: {},
     })
-
-    t.equal(npm.loaded, false, 'not loaded yet')
-    t.equal(npm.loading, true, 'working on it tho')
-    t.type(p, Promise, 'npm.load() returned a Promise first time')
-    t.equal(npm.load(second), undefined,
-      'npm.load() returns nothing second time')
-
-    return p
+    t.match(logs, [
+      ['timing', 'npm:load', /Completed in [0-9.]+ms/],
+    ])
+
+    bePosix()
+    t.equal(resolve(npm.cache), resolve(CACHE), 'cache is cache')
+    const newCache = t.testdir()
+    npm.cache = newCache
+    t.equal(npm.config.get('cache'), newCache, 'cache setter sets config')
+    t.equal(npm.cache, newCache, 'cache getter gets new config')
+    t.equal(npm.log, npmlog, 'npmlog getter')
+    t.equal(npm.lockfileVersion, 2, 'lockfileVersion getter')
+    t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix')
+    t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix')
+    npm.globalPrefix = npm.prefix
+    t.equal(npm.prefix, npm.globalPrefix, 'globalPrefix setter')
+    npm.localPrefix = dir + '/extra/prefix'
+    t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix after localPrefix setter')
+    t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix after localPrefix setter')
+
+    npm.prefix = dir + '/some/prefix'
+    t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix after prefix setter')
+    t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix after prefix setter')
+    t.equal(npm.bin, npm.localBin, 'bin is local bin after prefix setter')
+    t.not(npm.bin, npm.globalBin, 'bin is not global bin after prefix setter')
+    t.equal(npm.dir, npm.localDir, 'dir is local dir after prefix setter')
+    t.not(npm.dir, npm.globalDir, 'dir is not global dir after prefix setter')
+
+    npm.config.set('global', true)
+    t.equal(npm.prefix, npm.globalPrefix, 'prefix is global prefix after setting global')
+    t.not(npm.prefix, npm.localPrefix, 'prefix is not local prefix after setting global')
+    t.equal(npm.bin, npm.globalBin, 'bin is global bin after setting global')
+    t.not(npm.bin, npm.localBin, 'bin is not local bin after setting global')
+    t.equal(npm.dir, npm.globalDir, 'dir is global dir after setting global')
+    t.not(npm.dir, npm.localDir, 'dir is not local dir after setting global')
+
+    npm.prefix = dir + '/new/global/prefix'
+    t.equal(npm.prefix, npm.globalPrefix, 'prefix is global prefix after prefix setter')
+    t.not(npm.prefix, npm.localPrefix, 'prefix is not local prefix after prefix setter')
+    t.equal(npm.bin, npm.globalBin, 'bin is global bin after prefix setter')
+    t.not(npm.bin, npm.localBin, 'bin is not local bin after prefix setter')
+
+    beWindows()
+    t.equal(npm.bin, npm.globalBin, 'bin is global bin in windows mode')
+    t.equal(npm.dir, npm.globalDir, 'dir is global dir in windows mode')
+    bePosix()
+
+    const tmp = npm.tmp
+    t.match(tmp, String, 'npm.tmp is a string')
+    t.equal(tmp, npm.tmp, 'getter only generates it once')
   })
 
-  t.test('forceful loading', t => {
-    // also, don't get thrown off if argv[0] isn't found for some reason
-    const [argv0] = process.argv
-    t.teardown(() => {
-      process.argv[0] = argv0
-    })
-    freshConfig({ argv: [...process.argv, '--force', '--color', 'always'] })
-    process.argv[0] = 'this exe does not exist or else this test will fail'
-    return npm.load(er => {
-      if (er)
-        throw er
-
-      t.match(logs.filter(l => l[0] !== 'timing'), [
-        [
-          'warn',
-          'using --force',
-          'Recommended protections disabled.',
-        ],
-      ])
-      logs.length = 0
-    })
+  t.test('forceful loading', async t => {
+    process.argv = [...process.argv, '--force', '--color', 'always']
+    const { npm, logs } = mockNpm(t)
+    await npm.load()
+    t.match(logs.filter(l => l[0] !== 'timing'), [
+      [
+        'warn',
+        'using --force',
+        'Recommended protections disabled.',
+      ],
+    ])
   })
 
   t.test('node is a symlink', async t => {
@@ -254,7 +207,6 @@ t.test('npm.load', t => {
 
     const PATH = process.env.PATH || process.env.Path
     process.env.PATH = resolve(dir, 'bin')
-    const { execPath, argv: processArgv } = process
     process.argv = [
       node,
       process.argv[1],
@@ -267,48 +219,33 @@ t.test('npm.load', t => {
       'blergggg',
     ]
 
-    freshConfig()
-    const { log } = console
-    const consoleLogs = []
-    console.log = (...msg) => consoleLogs.push(msg)
-
     t.teardown(() => {
-      console.log = log
       process.env.PATH = PATH
-      process.argv = processArgv
-      freshConfig()
-      logs.length = 0
-      process.execPath = execPath
     })
 
-    logs.length = 0
-
-    await npm.load(er => {
-      if (er)
-        throw er
-
-      t.equal(npm.config.get('scope'), '@foo', 'added the @ sign to scope')
-      t.match(logs.filter(l => l[0] !== 'timing' || !/^config:/.test(l[1])), [
-        [
-          'timing',
-          'npm:load:whichnode',
-          /Completed in [0-9.]+ms/,
-        ],
-        [
-          'verbose',
-          'node symlink',
-          resolve(dir, 'bin', node),
-        ],
-        [
-          'timing',
-          'npm:load',
-          /Completed in [0-9.]+ms/,
-        ],
-      ])
-      logs.length = 0
-      t.equal(process.execPath, resolve(dir, 'bin', node))
-    })
+    const { npm, logs, outputs } = mockNpm(t)
+    await npm.load()
+    t.equal(npm.config.get('scope'), '@foo', 'added the @ sign to scope')
+    t.match(logs.filter(l => l[0] !== 'timing' || !/^config:/.test(l[1])), [
+      [
+        'timing',
+        'npm:load:whichnode',
+        /Completed in [0-9.]+ms/,
+      ],
+      [
+        'verbose',
+        'node symlink',
+        resolve(dir, 'bin', node),
+      ],
+      [
+        'timing',
+        'npm:load',
+        /Completed in [0-9.]+ms/,
+      ],
+    ])
+    t.equal(process.execPath, resolve(dir, 'bin', node))
 
+    outputs.length = 0
     await npm.commands.ll([], (er) => {
       if (er)
         throw er
@@ -316,13 +253,13 @@ t.test('npm.load', t => {
       t.equal(npm.command, 'll', 'command set to first npm command')
       t.equal(npm.flatOptions.npmCommand, 'll', 'npmCommand flatOption set')
 
-      t.same(consoleLogs, [[npm.commands.ll.usage]], 'print usage')
-      consoleLogs.length = 0
+      t.same(outputs, [[npm.commands.ll.usage]], 'print usage')
       npm.config.set('usage', false)
       t.equal(npm.commands.ll, npm.commands.ll, 'same command, different name')
-      logs.length = 0
     })
 
+    outputs.length = 0
+    logs.length = 0
     await npm.commands.get(['scope', '\u2010not-a-dash'], (er) => {
       if (er)
         throw er
@@ -348,7 +285,7 @@ t.test('npm.load', t => {
           /Completed in [0-9.]+ms/,
         ],
       ])
-      t.same(consoleLogs, [['scope=@foo\n\u2010not-a-dash=undefined']])
+      t.same(outputs, [['scope=@foo\n\u2010not-a-dash=undefined']])
     })
 
     // need this here or node 10 will improperly end the promise ahead of time
@@ -381,33 +318,19 @@ t.test('npm.load', t => {
       '.npmrc': '',
     })
 
-    const { log } = console
-    const consoleLogs = []
-    console.log = (...msg) => consoleLogs.push(msg)
-
-    const { execPath } = process
-    t.teardown(() => {
-      console.log = log
-    })
-
-    freshConfig({
-      argv: [
-        execPath,
-        process.argv[1],
-        '--userconfig',
-        resolve(dir, '.npmrc'),
-        '--color',
-        'false',
-        '--workspaces',
-        'true',
-      ],
-    })
-
-    await npm.load(er => {
-      if (er)
-        throw er
-    })
+    process.argv = [
+      process.execPath,
+      process.argv[1],
+      '--userconfig',
+      resolve(dir, '.npmrc'),
+      '--color',
+      'false',
+      '--workspaces',
+      'true',
+    ]
 
+    const { npm, outputs } = mockNpm(t)
+    await npm.load()
     npm.localPrefix = dir
 
     await new Promise((res, rej) => {
@@ -421,7 +344,7 @@ t.test('npm.load', t => {
         t.equal(npm.command, 'run-script', 'npm.command set to canonical name')
 
         t.match(
-          consoleLogs,
+          outputs,
           [
             ['Lifecycle scripts included in a@1.0.0:'],
             ['  test\n    echo test a'],
@@ -462,24 +385,19 @@ t.test('npm.load', t => {
         workspaces: ['./packages/*'],
       }),
     })
-    const { execPath } = process
-    freshConfig({
-      argv: [
-        execPath,
-        process.argv[1],
-        '--userconfig',
-        resolve(dir, '.npmrc'),
-        '--color',
-        'false',
-        '--workspaces',
-        '--global',
-        'true',
-      ],
-    })
-    await npm.load(er => {
-      if (er)
-        throw er
-    })
+    process.argv = [
+      process.execPath,
+      process.argv[1],
+      '--userconfig',
+      resolve(dir, '.npmrc'),
+      '--color',
+      'false',
+      '--workspaces',
+      '--global',
+      'true',
+    ]
+    const { npm } = mockNpm(t)
+    await npm.load()
     npm.localPrefix = dir
     await new Promise((res, rej) => {
       // verify that calling the command with a short name still sets
@@ -491,7 +409,6 @@ t.test('npm.load', t => {
       })
     })
   })
-
   t.end()
 })
 
@@ -512,82 +429,49 @@ t.test('loading as main will load the cli', t => {
 })
 
 t.test('set process.title', t => {
-  const { argv: processArgv } = process
-  const { log } = console
-  const titleDesc = Object.getOwnPropertyDescriptor(process, 'title')
-  Object.defineProperty(process, 'title', {
-    value: '',
-    settable: true,
-    enumerable: true,
-    configurable: true,
-  })
-  const consoleLogs = []
-  console.log = (...msg) => consoleLogs.push(msg)
-
-  t.teardown(() => {
-    console.log = log
-    process.argv = processArgv
-    Object.defineProperty(process, 'title', titleDesc)
-    freshConfig()
-  })
-
-  t.afterEach(() => consoleLogs.length = 0)
-
   t.test('basic title setting', async t => {
-    freshConfig({
-      argv: [
-        process.execPath,
-        process.argv[1],
-        '--usage',
-        '--scope=foo',
-        'ls',
-      ],
-    })
-    await npm.load(er => {
-      if (er)
-        throw er
-      t.equal(npm.title, 'npm ls')
-      t.equal(process.title, 'npm ls')
-    })
+    process.argv = [
+      process.execPath,
+      process.argv[1],
+      '--usage',
+      '--scope=foo',
+      'ls',
+    ]
+    const { npm } = mockNpm(t)
+    await npm.load()
+    t.equal(npm.title, 'npm ls')
+    t.equal(process.title, 'npm ls')
   })
 
   t.test('do not expose token being revoked', async t => {
-    freshConfig({
-      argv: [
-        process.execPath,
-        process.argv[1],
-        '--usage',
-        '--scope=foo',
-        'token',
-        'revoke',
-        'deadbeefcafebad',
-      ],
-    })
-    await npm.load(er => {
-      if (er)
-        throw er
-      t.equal(npm.title, 'npm token revoke ***')
-      t.equal(process.title, 'npm token revoke ***')
-    })
+    process.argv = [
+      process.execPath,
+      process.argv[1],
+      '--usage',
+      '--scope=foo',
+      'token',
+      'revoke',
+      'deadbeefcafebad',
+    ]
+    const { npm } = mockNpm(t)
+    await npm.load()
+    t.equal(npm.title, 'npm token revoke ***')
+    t.equal(process.title, 'npm token revoke ***')
   })
 
   t.test('do show *** unless a token is actually being revoked', async t => {
-    freshConfig({
-      argv: [
-        process.execPath,
-        process.argv[1],
-        '--usage',
-        '--scope=foo',
-        'token',
-        'revoke',
-      ],
-    })
-    await npm.load(er => {
-      if (er)
-        throw er
-      t.equal(npm.title, 'npm token revoke')
-      t.equal(process.title, 'npm token revoke')
-    })
+    process.argv = [
+      process.execPath,
+      process.argv[1],
+      '--usage',
+      '--scope=foo',
+      'token',
+      'revoke',
+    ]
+    const { npm } = mockNpm(t)
+    await npm.load()
+    t.equal(npm.title, 'npm token revoke')
+    t.equal(process.title, 'npm token revoke')
   })
 
   t.end()
diff --git a/deps/npm/test/lib/outdated.js b/deps/npm/test/lib/outdated.js
index 462ec0fc62b6ca..34a0aa6c9e03e8 100644
--- a/deps/npm/test/lib/outdated.js
+++ b/deps/npm/test/lib/outdated.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const packument = spec => {
   const mocks = {
diff --git a/deps/npm/test/lib/owner.js b/deps/npm/test/lib/owner.js
index 10ceb03030a5ac..32944a84edbc40 100644
--- a/deps/npm/test/lib/owner.js
+++ b/deps/npm/test/lib/owner.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm.js')
+const { fake: mockNpm } = require('../fixtures/mock-npm.js')
 
 let result = ''
 let readPackageNamePrefix = null
diff --git a/deps/npm/test/lib/pack.js b/deps/npm/test/lib/pack.js
index 523ba5d6b535d7..3d61abdaf74ca5 100644
--- a/deps/npm/test/lib/pack.js
+++ b/deps/npm/test/lib/pack.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 const pacote = require('pacote')
 const path = require('path')
 
diff --git a/deps/npm/test/lib/ping.js b/deps/npm/test/lib/ping.js
index c2d53efef5a7e4..f0a10718c46d02 100644
--- a/deps/npm/test/lib/ping.js
+++ b/deps/npm/test/lib/ping.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 t.test('pings', (t) => {
   t.plan(8)
diff --git a/deps/npm/test/lib/profile.js b/deps/npm/test/lib/profile.js
index 17bcf99b9b6539..112aa5c3b75e19 100644
--- a/deps/npm/test/lib/profile.js
+++ b/deps/npm/test/lib/profile.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let result = ''
 const config = {
diff --git a/deps/npm/test/lib/publish.js b/deps/npm/test/lib/publish.js
index e34f00b477ee13..56590478fc1ae4 100644
--- a/deps/npm/test/lib/publish.js
+++ b/deps/npm/test/lib/publish.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 const fs = require('fs')
 
 // The way we set loglevel is kind of convoluted, and there is no way to affect
diff --git a/deps/npm/test/lib/rebuild.js b/deps/npm/test/lib/rebuild.js
index e686b6a32ce53a..81768a21fb3b7a 100644
--- a/deps/npm/test/lib/rebuild.js
+++ b/deps/npm/test/lib/rebuild.js
@@ -1,7 +1,7 @@
+const t = require('tap')
 const fs = require('fs')
 const { resolve } = require('path')
-const mockNpm = require('../fixtures/mock-npm')
-const t = require('tap')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let result = ''
 
diff --git a/deps/npm/test/lib/repo.js b/deps/npm/test/lib/repo.js
index 0701750b58c63c..e1ac90b1e7577b 100644
--- a/deps/npm/test/lib/repo.js
+++ b/deps/npm/test/lib/repo.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm.js')
+const { fake: mockNpm } = require('../fixtures/mock-npm.js')
 const { join, sep } = require('path')
 
 const pkgDirs = t.testdir({
diff --git a/deps/npm/test/lib/run-script.js b/deps/npm/test/lib/run-script.js
index 897c80817e2549..a3f04ea6790fa4 100644
--- a/deps/npm/test/lib/run-script.js
+++ b/deps/npm/test/lib/run-script.js
@@ -1,6 +1,6 @@
-const { resolve } = require('path')
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { resolve } = require('path')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const normalizePath = p => p
   .replace(/\\+/g, '/')
diff --git a/deps/npm/test/lib/search.js b/deps/npm/test/lib/search.js
index ac894a9e3369da..510a470f48088e 100644
--- a/deps/npm/test/lib/search.js
+++ b/deps/npm/test/lib/search.js
@@ -1,6 +1,6 @@
-const Minipass = require('minipass')
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const Minipass = require('minipass')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 const libnpmsearchResultFixture =
   require('../fixtures/libnpmsearch-stream-result.js')
 
diff --git a/deps/npm/test/lib/set-script.js b/deps/npm/test/lib/set-script.js
index 96b455785c5095..37ba9a1cc71a20 100644
--- a/deps/npm/test/lib/set-script.js
+++ b/deps/npm/test/lib/set-script.js
@@ -1,7 +1,7 @@
 const t = require('tap')
 const fs = require('fs')
 const parseJSON = require('json-parse-even-better-errors')
-const mockNpm = require('../fixtures/mock-npm.js')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 const { resolve } = require('path')
 
 const flatOptions = {}
@@ -121,25 +121,6 @@ t.test('warns when overwriting', (t) => {
   })
 })
 
-t.test('provided indentation and eol is used', (t) => {
-  npm.localPrefix = t.testdir({
-    'package.json': JSON.stringify({
-      name: 'foo',
-    }, null, ' '.repeat(6)).replace(/\n/g, '\r\n'),
-  })
-
-  t.plan(3)
-  setScript.exec(['arg1', 'arg2'], (error) => {
-    t.equal(error, undefined)
-    // rather than checking every line's content
-    // we parse the result and verify the symbols match
-    const contents = fs.readFileSync(resolve(npm.localPrefix, 'package.json'))
-    const data = parseJSON(contents)
-    t.equal(data[Symbol.for('indent')], ' '.repeat(6), 'keeps indenting')
-    t.equal(data[Symbol.for('newline')], '\r\n', 'keeps newlines')
-  })
-})
-
 t.test('workspaces', (t) => {
   ERROR_OUTPUT.length = 0
   WARN_OUTPUT.length = 0
@@ -153,7 +134,7 @@ t.test('workspaces', (t) => {
       'package.json': '{}',
     },
     'workspace-b': {
-      'package.json': '"notjson"',
+      'package.json': '"notajsonobject"',
     },
     'workspace-c': {
       'package.json': JSON.stringify({
@@ -176,8 +157,8 @@ t.test('workspaces', (t) => {
     t.hasStrict(dataA, { scripts: { arg1: 'arg2' } }, 'defined the script')
 
     // workspace-b logged an error
-    t.match(ERROR_OUTPUT, [
-      ['set-script', `Cannot create property 'scripts' on string 'notjson'`],
+    t.strictSame(ERROR_OUTPUT, [
+      ['set-script', `Can't update invalid package.json data`],
       ['  in workspace: workspace-b'],
       [`  at location: ${resolve(npm.localPrefix, 'workspace-b')}`],
     ], 'logged workspace-b error')
diff --git a/deps/npm/test/lib/shrinkwrap.js b/deps/npm/test/lib/shrinkwrap.js
index 8bd18f7fbd7c64..ab3b8d0ffe447e 100644
--- a/deps/npm/test/lib/shrinkwrap.js
+++ b/deps/npm/test/lib/shrinkwrap.js
@@ -1,6 +1,6 @@
 const t = require('tap')
 const fs = require('fs')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const config = {
   global: false,
diff --git a/deps/npm/test/lib/star.js b/deps/npm/test/lib/star.js
index 0c584e4a3f0cf8..8820d6e9cfb0b1 100644
--- a/deps/npm/test/lib/star.js
+++ b/deps/npm/test/lib/star.js
@@ -1,5 +1,5 @@
-const mockNpm = require('../fixtures/mock-npm')
 const t = require('tap')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let result = ''
 
diff --git a/deps/npm/test/lib/uninstall.js b/deps/npm/test/lib/uninstall.js
index 8cf35bd428d3b7..272adb86836028 100644
--- a/deps/npm/test/lib/uninstall.js
+++ b/deps/npm/test/lib/uninstall.js
@@ -1,7 +1,7 @@
+const t = require('tap')
 const fs = require('fs')
 const { resolve } = require('path')
-const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const npm = mockNpm({
   globalDir: '',
diff --git a/deps/npm/test/lib/unpublish.js b/deps/npm/test/lib/unpublish.js
index 6ad45e958c22f7..9199b8aed94420 100644
--- a/deps/npm/test/lib/unpublish.js
+++ b/deps/npm/test/lib/unpublish.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let result = ''
 const noop = () => null
diff --git a/deps/npm/test/lib/update.js b/deps/npm/test/lib/update.js
index 411d07592a155b..487b12e5fa2970 100644
--- a/deps/npm/test/lib/update.js
+++ b/deps/npm/test/lib/update.js
@@ -1,6 +1,6 @@
-const { resolve } = require('path')
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { resolve } = require('path')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 const config = {
   depth: 0,
diff --git a/deps/npm/test/lib/utils/config/definitions.js b/deps/npm/test/lib/utils/config/definitions.js
index a87698c1813594..8724f0e3bd3ebd 100644
--- a/deps/npm/test/lib/utils/config/definitions.js
+++ b/deps/npm/test/lib/utils/config/definitions.js
@@ -17,6 +17,11 @@ const defpath = '../../../../lib/utils/config/definitions.js'
 delete process.env.NODE_ENV
 const definitions = require(defpath)
 
+// Tie the definitions to a snapshot so that if they change we are forced to
+// update snapshots, which rebuilds the docs
+for (const key of Object.keys(definitions))
+  t.matchSnapshot(definitions[key].describe(), `config description for ${key}`)
+
 const isWin = '../../../../lib/utils/is-windows.js'
 
 // snapshot these just so we note when they change
diff --git a/deps/npm/test/lib/utils/error-handler.js b/deps/npm/test/lib/utils/exit-handler.js
similarity index 86%
rename from deps/npm/test/lib/utils/error-handler.js
rename to deps/npm/test/lib/utils/exit-handler.js
index 9a681e52ce5db0..72f94bfded7b04 100644
--- a/deps/npm/test/lib/utils/error-handler.js
+++ b/deps/npm/test/lib/utils/exit-handler.js
@@ -121,8 +121,8 @@ const mocks = {
   }),
 }
 
-let errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks)
-errorHandler.setNpm(npm)
+let exitHandler = t.mock('../../../lib/utils/exit-handler.js', mocks)
+exitHandler.setNpm(npm)
 
 t.test('default exit code', (t) => {
   t.plan(1)
@@ -165,7 +165,7 @@ t.test('handles unknown error', (t) => {
   writeFileAtomic.sync = (filename, content) => {
     t.equal(
       redactCwd(filename),
-      '{CWD}/test/lib/utils/tap-testdir-error-handler/_logs/expecteddate-debug.log',
+      '{CWD}/test/lib/utils/tap-testdir-exit-handler/_logs/expecteddate-debug.log',
       'should use expected log filename'
     )
     t.matchSnapshot(
@@ -174,7 +174,7 @@ t.test('handles unknown error', (t) => {
     )
   }
 
-  errorHandler(err)
+  exitHandler(err)
 
   t.teardown(() => {
     writeFileAtomic.sync = sync
@@ -197,7 +197,7 @@ t.test('npm.config not ready', (t) => {
     )
   }
 
-  errorHandler()
+  exitHandler()
 
   t.teardown(() => {
     console.error = _error
@@ -219,7 +219,7 @@ t.test('fail to write logfile', (t) => {
   })
 
   t.doesNotThrow(
-    () => errorHandler(err),
+    () => exitHandler(err),
     'should not throw on cache write failure'
   )
 })
@@ -244,7 +244,7 @@ t.test('console.log output using --json', (t) => {
     )
   }
 
-  errorHandler(new Error('Error: EBADTHING Something happened'))
+  exitHandler(new Error('Error: EBADTHING Something happened'))
 
   t.teardown(() => {
     console.error = _error
@@ -271,7 +271,7 @@ t.test('throw a non-error obj', (t) => {
     t.equal(code, 1, 'should exit with code 1')
   }
 
-  errorHandler(weirdError)
+  exitHandler(weirdError)
 
   t.teardown(() => {
     process.exit = _exit
@@ -295,7 +295,7 @@ t.test('throw a string error', (t) => {
     t.equal(code, 1, 'should exit with code 1')
   }
 
-  errorHandler(error)
+  exitHandler(error)
 
   t.teardown(() => {
     process.exit = _exit
@@ -315,7 +315,7 @@ t.test('update notification', (t) => {
     t.equal(msg, updateMsg, 'should show update message')
   }
 
-  errorHandler(err)
+  exitHandler(err)
 
   t.teardown(() => {
     npmlog.notice = _notice
@@ -355,7 +355,7 @@ t.test('it worked', (t) => {
   const _exit = process.exit
   process.exit = (code) => {
     process.exit = _exit
-    t.equal(code, 0, 'should exit with code 0')
+    t.notOk(code, 'should exit with no code')
 
     const _info = npmlog.info
     npmlog.info = (msg) => {
@@ -371,14 +371,14 @@ t.test('it worked', (t) => {
     config.values.timing = true
   })
 
-  errorHandler.exit(0)
+  exitHandler()
 })
 
 t.test('uses code from errno', (t) => {
   t.plan(1)
 
-  errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks)
-  errorHandler.setNpm(npm)
+  exitHandler = t.mock('../../../lib/utils/exit-handler.js', mocks)
+  exitHandler.setNpm(npm)
 
   npmlog.level = 'silent'
   const _exit = process.exit
@@ -386,7 +386,7 @@ t.test('uses code from errno', (t) => {
     t.equal(code, 127, 'should use set errno')
   }
 
-  errorHandler(Object.assign(
+  exitHandler(Object.assign(
     new Error('Error with errno'),
     {
       errno: 127,
@@ -402,8 +402,8 @@ t.test('uses code from errno', (t) => {
 t.test('uses exitCode as code if using a number', (t) => {
   t.plan(1)
 
-  errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks)
-  errorHandler.setNpm(npm)
+  exitHandler = t.mock('../../../lib/utils/exit-handler.js', mocks)
+  exitHandler.setNpm(npm)
 
   npmlog.level = 'silent'
   const _exit = process.exit
@@ -411,7 +411,7 @@ t.test('uses exitCode as code if using a number', (t) => {
     t.equal(code, 404, 'should use code if a number')
   }
 
-  errorHandler(Object.assign(
+  exitHandler(Object.assign(
     new Error('Error with code type number'),
     {
       code: 404,
@@ -424,25 +424,25 @@ t.test('uses exitCode as code if using a number', (t) => {
   })
 })
 
-t.test('call errorHandler with no error', (t) => {
+t.test('call exitHandler with no error', (t) => {
   t.plan(1)
 
-  errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks)
-  errorHandler.setNpm(npm)
+  exitHandler = t.mock('../../../lib/utils/exit-handler.js', mocks)
+  exitHandler.setNpm(npm)
 
   const _exit = process.exit
   process.exit = (code) => {
-    t.equal(code, 0, 'should exit with code 0')
+    t.equal(code, undefined, 'should exit with code undefined')
   }
 
   t.teardown(() => {
     process.exit = _exit
   })
 
-  errorHandler()
+  exitHandler()
 })
 
-t.test('callback called twice', (t) => {
+t.test('exit handler called twice', (t) => {
   t.plan(2)
 
   const _verbose = npmlog.verbose
@@ -450,13 +450,13 @@ t.test('callback called twice', (t) => {
     t.equal(key, 'stack', 'should log stack in verbose level')
     t.match(
       value,
-      /Error: Callback called more than once./,
+      /Error: Exit handler called more than once./,
       'should have expected error msg'
     )
     npmlog.verbose = _verbose
   }
 
-  errorHandler()
+  exitHandler()
 })
 
 t.test('defaults to log error msg if stack is missing', (t) => {
@@ -480,35 +480,17 @@ t.test('defaults to log error msg if stack is missing', (t) => {
     t.equal(msg, 'Error with no stack', 'should use error msg')
   }
 
-  errorHandler(noStackErr)
+  exitHandler(noStackErr)
 })
 
-t.test('set it worked', (t) => {
-  t.plan(1)
-
-  errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks)
-  errorHandler.setNpm(npm)
-
-  const _exit = process.exit
-  process.exit = () => {
-    t.ok('ok')
-  }
-
-  t.teardown(() => {
-    process.exit = _exit
-  })
-
-  errorHandler.exit(0, true)
-})
-
-t.test('use exitCode when emitting exit event', (t) => {
+t.test('exits cleanly when emitting exit event', (t) => {
   t.plan(1)
 
   npmlog.level = 'silent'
   const _exit = process.exit
   process.exit = (code) => {
     process.exit = _exit
-    t.equal(code, 1, 'should exit with code 1')
+    t.same(code, null, 'should exit with code null')
   }
 
   t.teardown(() => {
@@ -549,7 +531,7 @@ t.test('do no fancy handling for shellouts', t => {
 
   t.test('shellout with a numeric error code', t => {
     EXPECT_EXIT = 5
-    errorHandler(Object.assign(new Error(), { code: 5 }))
+    exitHandler(Object.assign(new Error(), { code: 5 }))
     t.equal(EXPECT_EXIT, 0, 'called process.exit')
     // should log no warnings or errors, verbose/silly is fine.
     t.strictSame(loudNoises(), [], 'no noisy warnings')
@@ -558,7 +540,7 @@ t.test('do no fancy handling for shellouts', t => {
 
   t.test('shellout without a numeric error code (something in npm)', t => {
     EXPECT_EXIT = 1
-    errorHandler(Object.assign(new Error(), { code: 'banana stand' }))
+    exitHandler(Object.assign(new Error(), { code: 'banana stand' }))
     t.equal(EXPECT_EXIT, 0, 'called process.exit')
     // should log some warnings and errors, because something weird happened
     t.strictNotSame(loudNoises(), [], 'bring the noise')
@@ -567,7 +549,7 @@ t.test('do no fancy handling for shellouts', t => {
 
   t.test('shellout with code=0 (extra weird?)', t => {
     EXPECT_EXIT = 1
-    errorHandler(Object.assign(new Error(), { code: 0 }))
+    exitHandler(Object.assign(new Error(), { code: 0 }))
     t.equal(EXPECT_EXIT, 0, 'called process.exit')
     // should log some warnings and errors, because something weird happened
     t.strictNotSame(loudNoises(), [], 'bring the noise')
diff --git a/deps/npm/test/lib/utils/read-package-name.js b/deps/npm/test/lib/utils/read-package-name.js
index c8f88bacd4b840..a1a1b4a1504dce 100644
--- a/deps/npm/test/lib/utils/read-package-name.js
+++ b/deps/npm/test/lib/utils/read-package-name.js
@@ -1,17 +1,15 @@
 const t = require('tap')
-const mockNpm = require('../../fixtures/mock-npm')
-const npm = mockNpm()
 
 const readPackageName = require('../../../lib/utils/read-package-name.js')
 
 t.test('read local package.json', async (t) => {
-  npm.prefix = t.testdir({
+  const prefix = t.testdir({
     'package.json': JSON.stringify({
       name: 'my-local-package',
       version: '1.0.0',
     }),
   })
-  const packageName = await readPackageName(npm.prefix)
+  const packageName = await readPackageName(prefix)
   t.equal(
     packageName,
     'my-local-package',
@@ -20,13 +18,13 @@ t.test('read local package.json', async (t) => {
 })
 
 t.test('read local scoped-package.json', async (t) => {
-  npm.prefix = t.testdir({
+  const prefix = t.testdir({
     'package.json': JSON.stringify({
       name: '@my-scope/my-local-package',
       version: '1.0.0',
     }),
   })
-  const packageName = await readPackageName(npm.prefix)
+  const packageName = await readPackageName(prefix)
   t.equal(
     packageName,
     '@my-scope/my-local-package',
diff --git a/deps/npm/test/lib/version.js b/deps/npm/test/lib/version.js
index 6a83375b0025c9..df6d0dd797d0a5 100644
--- a/deps/npm/test/lib/version.js
+++ b/deps/npm/test/lib/version.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let result = []
 
diff --git a/deps/npm/test/lib/view.js b/deps/npm/test/lib/view.js
index 4544d7d5d1a94a..5f2e5a8add5e9d 100644
--- a/deps/npm/test/lib/view.js
+++ b/deps/npm/test/lib/view.js
@@ -3,7 +3,7 @@ const t = require('tap')
 // run the same as tap does when running directly with node
 process.stdout.columns = undefined
 
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 let logs
 const cleanLogs = () => {
diff --git a/deps/npm/test/lib/whoami.js b/deps/npm/test/lib/whoami.js
index 5e350a32ebfb4c..9190e3858b137a 100644
--- a/deps/npm/test/lib/whoami.js
+++ b/deps/npm/test/lib/whoami.js
@@ -1,5 +1,5 @@
 const t = require('tap')
-const mockNpm = require('../fixtures/mock-npm')
+const { fake: mockNpm } = require('../fixtures/mock-npm')
 
 t.test('whoami', (t) => {
   t.plan(3)