diff --git a/AUTHORS b/AUTHORS index 1ee244b9f5838..135694a5872ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -784,3 +784,4 @@ Spencer Wilson <5624115+spencerwilson@users.noreply.github.com> Daniel Park Daniel Park Luke Karrys +Ivan diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7fa90cb8ad8..170a65066c782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,53 @@ -## v7.18.0 (2021-06-17) +## v7.19.0 (2021-06-24) + +### FEATURES + +* [`23ce3af19`](https://github.com/npm/cli/commit/23ce3af199c8a14ef16c63fc638a1ac21fd9a9b0) + [#3460](https://github.com/npm/cli/issues/3460) + feat(ls): report *why* something is invalid + ([@isaacs](https://github.com/isaacs)) + +### BUG FIXES + +* [`53f81af31`](https://github.com/npm/cli/commit/53f81af319f298a0fdd8f143184c3e89770f24ea) + [#3450](https://github.com/npm/cli/issues/3450) + fix(docs): Improve phrasing of workspace example + ([@lumaxis](https://github.com/lumaxis)) +* [`78da60ffe`](https://github.com/npm/cli/commit/78da60ffefcfd457a4432ce1492ee7b53d854450) + [#3454](https://github.com/npm/cli/issues/3454) + chore(linting): add bin and clean up lib/ls.js +* [`54eae3063`](https://github.com/npm/cli/commit/54eae3063eeb197225ee930525a1316e34ecf34c) + [#3416](https://github.com/npm/cli/issues/3416) + chore(errorHandler): rename to exit handler + ([@wraithgar](https://github.com/wraithgar)) +* [`d0f50b156`](https://github.com/npm/cli/commit/d0f50b156725e5b414050d9e9a59d5fad8a39a3d) + [#3451](https://github.com/npm/cli/issues/3451) + chore(refactor): async npm.load + ([@wraithgar](https://github.com/wraithgar)) +* [`87f67d9ef`](https://github.com/npm/cli/commit/87f67d9efaf6f897cf0d74e738c2625a21044109) + [#3458](https://github.com/npm/cli/issues/3458) + chore(tests): expose real mock npm object + ([@wraithgar](https://github.com/wraithgar)) +* [`f3dce0917`](https://github.com/npm/cli/commit/f3dce0917088dc37795af39e7f6b5089beff984c) + [#3459](https://github.com/npm/cli/issues/3459) + chore(config): snapshot config descriptions + ([@wraithgar](https://github.com/wraithgar)) +* [`6254b6f72`](https://github.com/npm/cli/commit/6254b6f726a301908f73b36ccfa52cd4fd6619e5) + [#3234](https://github.com/npm/cli/issues/3234) + [#3455](https://github.com/npm/cli/issues/3455) + @npmcli/package-json refactor + ([@ruyadorno](https://github.com/ruyadorno)) + +### DEPENDENCIES + +* [`fe4138381`](https://github.com/npm/cli/commit/fe4138381fd2e8c919bb9f794e20033ff049f783) + `@npmcli/arborist@2.6.4`: + * bin: allow turning off timer display with --timers=false + * fix: do not try to inflate a fresh lockfile + * fix(diff): walk target children if root is a link + * chore: @npmcli/package-json refactor + +## v7.18.1 (2021-06-17) ## BUG FIXES diff --git a/bin/npx-cli.js b/bin/npx-cli.js index a495090c64c78..7a3fb39837d27 100755 --- a/bin/npx-cli.js +++ b/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/docs/content/using-npm/workspaces.md b/docs/content/using-npm/workspaces.md index 612404a994647..829168864abe4 100644 --- a/docs/content/using-npm/workspaces.md +++ b/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/lib/cli.js b/lib/cli.js index fbceb459db97c..9544f8451f8ae 100644 --- a/lib/cli.js +++ b/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/lib/init.js b/lib/init.js index d34f92b882b32..e4bd20b7210e8 100644 --- a/lib/init.js +++ b/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/lib/ls.js b/lib/ls.js index f146928a96b41..7540692911976 100644 --- a/lib/ls.js +++ b/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/lib/npm.js b/lib/npm.js index 937459501c0a5..7046a84d0bcfa 100644 --- a/lib/npm.js +++ b/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/lib/set-script.js b/lib/set-script.js index cd01e28b56b06..24e4d8f20f666 100644 --- a/lib/set-script.js +++ b/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/lib/utils/error-handler.js b/lib/utils/exit-handler.js similarity index 71% rename from lib/utils/error-handler.js rename to lib/utils/exit-handler.js index f40e1f04fb180..dc499f526a208 100644 --- a/lib/utils/error-handler.js +++ b/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/lib/utils/explain-eresolve.js b/lib/utils/explain-eresolve.js index b35a32c6f935d..fa3c6bda52293 100644 --- a/lib/utils/explain-eresolve.js +++ b/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/lib/utils/perf.js b/lib/utils/perf.js index 3f81ee4b049e4..4961054d909ad 100644 --- a/lib/utils/perf.js +++ b/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/lib/utils/proc-log-listener.js b/lib/utils/proc-log-listener.js index 1dc4b4399eaea..2cfe94ecb0cf2 100644 --- a/lib/utils/proc-log-listener.js +++ b/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/node_modules/@npmcli/arborist/bin/lib/timers.js b/node_modules/@npmcli/arborist/bin/lib/timers.js index e72217c1e4ed9..b516af92c5b57 100644 --- a/node_modules/@npmcli/arborist/bin/lib/timers.js +++ b/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/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js index 55360538b901a..f259a69b548e1 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/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/node_modules/@npmcli/arborist/lib/diff.js b/node_modules/@npmcli/arborist/lib/diff.js index dac7c81f8ecfb..1f8eff0f0c4d9 100644 --- a/node_modules/@npmcli/arborist/lib/diff.js +++ b/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/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/node_modules/@npmcli/arborist/lib/shrinkwrap.js index 9fb0528db497c..b251539a94c90 100644 --- a/node_modules/@npmcli/arborist/lib/shrinkwrap.js +++ b/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/node_modules/@npmcli/arborist/lib/update-root-package-json.js b/node_modules/@npmcli/arborist/lib/update-root-package-json.js deleted file mode 100644 index 57ec414248756..0000000000000 --- a/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/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index bd27e4bbffa20..138d6ec25b4c2 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/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/node_modules/@npmcli/package-json/LICENSE b/node_modules/@npmcli/package-json/LICENSE new file mode 100644 index 0000000000000..6a1f3708f6d70 --- /dev/null +++ b/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/node_modules/@npmcli/package-json/lib/index.js b/node_modules/@npmcli/package-json/lib/index.js new file mode 100644 index 0000000000000..87c3a63093688 --- /dev/null +++ b/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/node_modules/@npmcli/package-json/lib/update-dependencies.js b/node_modules/@npmcli/package-json/lib/update-dependencies.js new file mode 100644 index 0000000000000..dac45a8bed7bf --- /dev/null +++ b/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/node_modules/@npmcli/package-json/lib/update-scripts.js b/node_modules/@npmcli/package-json/lib/update-scripts.js new file mode 100644 index 0000000000000..3a88d3e9a17a8 --- /dev/null +++ b/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/node_modules/@npmcli/package-json/lib/update-workspaces.js b/node_modules/@npmcli/package-json/lib/update-workspaces.js new file mode 100644 index 0000000000000..207dd94a236d7 --- /dev/null +++ b/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/node_modules/@npmcli/package-json/package.json b/node_modules/@npmcli/package-json/package.json new file mode 100644 index 0000000000000..8708ec5eb6fb1 --- /dev/null +++ b/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/package-lock.json b/package-lock.json index f54f60b5c60a2..1e86148f91f14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "npm", - "version": "7.18.1", + "version": "7.19.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "npm", - "version": "7.18.1", + "version": "7.19.0", "bundleDependencies": [ "@npmcli/arborist", "@npmcli/ci-detect", "@npmcli/config", + "@npmcli/package-json", "@npmcli/run-script", "abbrev", "ansicolors", @@ -82,9 +83,10 @@ "packages/*" ], "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", @@ -733,9 +735,9 @@ } }, "node_modules/@npmcli/arborist": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.3.tgz", - "integrity": "sha512-R8U2dZ8+jeE7go+qNU4Mt6aiXyBu3mM75iRIugNCA4P0OWlsLOpuDPPhsaRcOVbtXheOGZXrqe36qP1g+M68KQ==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.4.tgz", + "integrity": "sha512-A/pDQ/VZpdxaqsQS5XOWrhrPuC+ER7HLq+4ZkEmnO2yo/USFCWEsiUPYKhfY+sWXK3pgKjN7B7CEFmAnSoAt3g==", "inBundle": true, "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", @@ -744,6 +746,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", @@ -890,6 +893,15 @@ "integrity": "sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg==", "inBundle": true }, + "node_modules/@npmcli/package-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", + "integrity": "sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg==", + "inBundle": true, + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + } + }, "node_modules/@npmcli/promise-spawn": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", @@ -2515,9 +2527,9 @@ } }, "node_modules/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", @@ -10857,9 +10869,9 @@ "dev": true }, "@npmcli/arborist": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.3.tgz", - "integrity": "sha512-R8U2dZ8+jeE7go+qNU4Mt6aiXyBu3mM75iRIugNCA4P0OWlsLOpuDPPhsaRcOVbtXheOGZXrqe36qP1g+M68KQ==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.4.tgz", + "integrity": "sha512-A/pDQ/VZpdxaqsQS5XOWrhrPuC+ER7HLq+4ZkEmnO2yo/USFCWEsiUPYKhfY+sWXK3pgKjN7B7CEFmAnSoAt3g==", "requires": { "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", @@ -10867,6 +10879,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", @@ -10980,6 +10993,14 @@ "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.2.tgz", "integrity": "sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg==" }, + "@npmcli/package-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", + "integrity": "sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg==", + "requires": { + "json-parse-even-better-errors": "^2.3.1" + } + }, "@npmcli/promise-spawn": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", @@ -12191,9 +12212,9 @@ } }, "eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", diff --git a/package.json b/package.json index 58ac5d7f8c6c8..035f30b2be988 100644 --- a/package.json +++ b/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/tap-snapshots/test/lib/ls.js.test.cjs b/tap-snapshots/test/lib/ls.js.test.cjs index 3d56d1f432731..c3d0a87648edb 100644 --- a/tap-snapshots/test/lib/ls.js.test.cjs +++ b/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/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs b/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs index 35942fea64683..12df9ec89f6f7 100644 --- a/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs +++ b/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/tap-snapshots/test/lib/utils/error-handler.js.test.cjs b/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs similarity index 66% rename from tap-snapshots/test/lib/utils/error-handler.js.test.cjs rename to tap-snapshots/test/lib/utils/exit-handler.js.test.cjs index 78a9eef217f35..0a5ed59a1157c 100644 --- a/tap-snapshots/test/lib/utils/error-handler.js.test.cjs +++ b/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/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js index c972c35b31861..1de080eb10b4a 100644 --- a/test/fixtures/mock-npm.js +++ b/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/test/lib/audit.js b/test/lib/audit.js index bb6f06debc51f..561765a0270b5 100644 --- a/test/lib/audit.js +++ b/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/test/lib/bin.js b/test/lib/bin.js index 898e7ba439b88..8ceca8280f52d 100644 --- a/test/lib/bin.js +++ b/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/test/lib/birthday.js b/test/lib/birthday.js index 0589be7a8eedb..05660d6fa3f20 100644 --- a/test/lib/birthday.js +++ b/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/test/lib/cache.js b/test/lib/cache.js index bad0ede89e101..d3d6f5b8845de 100644 --- a/test/lib/cache.js +++ b/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/test/lib/ci.js b/test/lib/ci.js index b60375c289842..b6b2af9c111db 100644 --- a/test/lib/ci.js +++ b/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/test/lib/cli.js b/test/lib/cli.js index d0a9e0bd49200..b85c981cd008b 100644 --- a/test/lib/cli.js +++ b/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/test/lib/dedupe.js b/test/lib/dedupe.js index 801e3c96de3cf..30f8a380e8ea3 100644 --- a/test/lib/dedupe.js +++ b/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/test/lib/diff.js b/test/lib/diff.js index 2fb38c9b127e4..fcba802d93b87 100644 --- a/test/lib/diff.js +++ b/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/test/lib/dist-tag.js b/test/lib/dist-tag.js index 9af90c309c77c..1fb5cb3b6ee62 100644 --- a/test/lib/dist-tag.js +++ b/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/test/lib/docs.js b/test/lib/docs.js index 0da070d9a7d13..fbd7584201247 100644 --- a/test/lib/docs.js +++ b/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/test/lib/exec.js b/test/lib/exec.js index dff067619f2ce..6d99c7959d88e 100644 --- a/test/lib/exec.js +++ b/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/test/lib/explore.js b/test/lib/explore.js index b49c885cbcb26..fd9949e73fc4c 100644 --- a/test/lib/explore.js +++ b/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/test/lib/fund.js b/test/lib/fund.js index 65778fca50bd7..784989827edc1 100644 --- a/test/lib/fund.js +++ b/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/test/lib/help-search.js b/test/lib/help-search.js index 845b3873e3cf9..2df862d4fc570 100644 --- a/test/lib/help-search.js +++ b/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/test/lib/init.js b/test/lib/init.js index 44a2af5bcc02b..1cefb1fc9c8fd 100644 --- a/test/lib/init.js +++ b/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/test/lib/install.js b/test/lib/install.js index b7929bddafdba..6412b34c16f25 100644 --- a/test/lib/install.js +++ b/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/test/lib/link.js b/test/lib/link.js index 64375cfc13c2c..736d18cab9906 100644 --- a/test/lib/link.js +++ b/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/test/lib/load-all-commands.js b/test/lib/load-all-commands.js index b3b2012d23aa2..e5f10099cf365 100644 --- a/test/lib/load-all-commands.js +++ b/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/test/lib/load-all.js b/test/lib/load-all.js index 4a975d49a490e..e6e407805346d 100644 --- a/test/lib/load-all.js +++ b/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/test/lib/logout.js b/test/lib/logout.js index b130d439c88ca..7cb5c2790d621 100644 --- a/test/lib/logout.js +++ b/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/test/lib/ls.js b/test/lib/ls.js index 64ece6bd8a3fd..5f196501e55d1 100644 --- a/test/lib/ls.js +++ b/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/test/lib/npm.js b/test/lib/npm.js index 6909c43e4ff0e..291a58955ceed 100644 --- a/test/lib/npm.js +++ b/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/test/lib/outdated.js b/test/lib/outdated.js index 462ec0fc62b6c..34a0aa6c9e03e 100644 --- a/test/lib/outdated.js +++ b/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/test/lib/owner.js b/test/lib/owner.js index 10ceb03030a5a..32944a84edbc4 100644 --- a/test/lib/owner.js +++ b/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/test/lib/pack.js b/test/lib/pack.js index 523ba5d6b535d..3d61abdaf74ca 100644 --- a/test/lib/pack.js +++ b/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/test/lib/ping.js b/test/lib/ping.js index c2d53efef5a7e..f0a10718c46d0 100644 --- a/test/lib/ping.js +++ b/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/test/lib/profile.js b/test/lib/profile.js index 17bcf99b9b653..112aa5c3b75e1 100644 --- a/test/lib/profile.js +++ b/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/test/lib/publish.js b/test/lib/publish.js index e34f00b477ee1..56590478fc1ae 100644 --- a/test/lib/publish.js +++ b/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/test/lib/rebuild.js b/test/lib/rebuild.js index e686b6a32ce53..81768a21fb3b7 100644 --- a/test/lib/rebuild.js +++ b/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/test/lib/repo.js b/test/lib/repo.js index 0701750b58c63..e1ac90b1e7577 100644 --- a/test/lib/repo.js +++ b/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/test/lib/run-script.js b/test/lib/run-script.js index 897c80817e254..a3f04ea6790fa 100644 --- a/test/lib/run-script.js +++ b/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/test/lib/search.js b/test/lib/search.js index ac894a9e3369d..510a470f48088 100644 --- a/test/lib/search.js +++ b/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/test/lib/set-script.js b/test/lib/set-script.js index 96b455785c509..37ba9a1cc71a2 100644 --- a/test/lib/set-script.js +++ b/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/test/lib/shrinkwrap.js b/test/lib/shrinkwrap.js index 8bd18f7fbd7c6..ab3b8d0ffe447 100644 --- a/test/lib/shrinkwrap.js +++ b/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/test/lib/star.js b/test/lib/star.js index 0c584e4a3f0cf..8820d6e9cfb0b 100644 --- a/test/lib/star.js +++ b/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/test/lib/uninstall.js b/test/lib/uninstall.js index 8cf35bd428d3b..272adb8683602 100644 --- a/test/lib/uninstall.js +++ b/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/test/lib/unpublish.js b/test/lib/unpublish.js index 6ad45e958c22f..9199b8aed9442 100644 --- a/test/lib/unpublish.js +++ b/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/test/lib/update.js b/test/lib/update.js index 411d07592a155..487b12e5fa297 100644 --- a/test/lib/update.js +++ b/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/test/lib/utils/config/definitions.js b/test/lib/utils/config/definitions.js index a87698c181359..8724f0e3bd3eb 100644 --- a/test/lib/utils/config/definitions.js +++ b/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/test/lib/utils/error-handler.js b/test/lib/utils/exit-handler.js similarity index 86% rename from test/lib/utils/error-handler.js rename to test/lib/utils/exit-handler.js index 9a681e52ce5db..72f94bfded7b0 100644 --- a/test/lib/utils/error-handler.js +++ b/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/test/lib/utils/read-package-name.js b/test/lib/utils/read-package-name.js index c8f88bacd4b84..a1a1b4a1504dc 100644 --- a/test/lib/utils/read-package-name.js +++ b/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/test/lib/version.js b/test/lib/version.js index 6a83375b0025c..df6d0dd797d0a 100644 --- a/test/lib/version.js +++ b/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/test/lib/view.js b/test/lib/view.js index 4544d7d5d1a94..5f2e5a8add5e9 100644 --- a/test/lib/view.js +++ b/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/test/lib/whoami.js b/test/lib/whoami.js index 5e350a32ebfb4..9190e3858b137 100644 --- a/test/lib/whoami.js +++ b/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)