diff options
author | Gar <gar+gh@danger.computer> | 2021-03-11 03:11:34 +0300 |
---|---|---|
committer | Ruy Adorno <ruyadorno@hotmail.com> | 2021-03-18 22:54:36 +0300 |
commit | 41facf6435ced4e416d74111d9c3ff00ee19ab7d (patch) | |
tree | 1a6644f756670ee6fad67a9a59d135dc0f3e00ef | |
parent | a8d0751e4b7c7d8b808c8a49f288fc7272f729b0 (diff) |
feat(help): refactor npm help/help-search
Lots of dead code removed thanks to streamlining of logic.
`npm help` `npm <command>` and `npm help-search` are all now separated
concerns, handling their own use cases. `help` calls `help-search` as a
last resort, but `npm <command>` no longer tries to wind its way through
to `help-search` just to get the basic npm usage displayed.
The `did you mean` output has been expanded. It now always suggests top
level commands, scripts, and bins, and suggests them in the way they
should be called.
PR-URL: https://github.com/npm/cli/pull/2859
Credit: @wraithgar
Close: #2859
Reviewed-by: @ruyadorno
83 files changed, 802 insertions, 516 deletions
diff --git a/docs/content/commands/npm-dedupe.md b/docs/content/commands/npm-dedupe.md index 9b14e99dd..c6d26126d 100644 --- a/docs/content/commands/npm-dedupe.md +++ b/docs/content/commands/npm-dedupe.md @@ -1,7 +1,7 @@ --- title: npm-dedupe section: 1 -description: Reduce duplication +description: Reduce duplication in the package tree --- ### Synopsis @@ -10,7 +10,7 @@ description: Reduce duplication npm dedupe npm ddp -aliases: find-dupes, ddp +aliases: ddp ``` ### Description @@ -74,6 +74,7 @@ Using `npm find-dupes` will run the command in `--dry-run` mode. ### See Also +* [npm find-dupes](/cli-commands/find-dupes) * [npm ls](/cli-commands/ls) * [npm update](/cli-commands/update) * [npm install](/cli-commands/install) diff --git a/docs/content/commands/npm-find-dupes.md b/docs/content/commands/npm-find-dupes.md new file mode 100644 index 000000000..6f55d47bf --- /dev/null +++ b/docs/content/commands/npm-find-dupes.md @@ -0,0 +1,24 @@ +--- +title: npm-find-dupes +section: 1 +description: Find duplication in the package tree +--- + +### Synopsis + +```bash +npm find-dupes +``` + +### Description + +Runs `npm dedupe` in `--dry-run` mode, making npm only output the +duplications, without actually changing the package tree. + +### See Also + +* [npm dedupe](/cli-commands/dedupe) +* [npm ls](/cli-commands/ls) +* [npm update](/cli-commands/update) +* [npm install](/cli-commands/install) + diff --git a/docs/content/commands/npm-init.md b/docs/content/commands/npm-init.md index 8a40d90e8..4b0b8c4c4 100644 --- a/docs/content/commands/npm-init.md +++ b/docs/content/commands/npm-init.md @@ -1,7 +1,7 @@ --- title: npm-init section: 1 -description: create a package.json file +description: Create a package.json file --- ### Synopsis diff --git a/lib/access.js b/lib/access.js index 3020719a7..3df611e56 100644 --- a/lib/access.js +++ b/lib/access.js @@ -20,6 +20,10 @@ const subcommands = [ ] class Access extends BaseCommand { + static get description () { + return 'Set access level on published packages' + } + static get name () { return 'access' } diff --git a/lib/adduser.js b/lib/adduser.js index da318a1f3..f761ae386 100644 --- a/lib/adduser.js +++ b/lib/adduser.js @@ -9,6 +9,10 @@ const authTypes = { } class AddUser extends BaseCommand { + static get description () { + return 'Add a registry user account' + } + static get name () { return 'adduser' } diff --git a/lib/audit.js b/lib/audit.js index 27cebff9f..a752f202f 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -6,6 +6,11 @@ const BaseCommand = require('./base-command.js') class Audit extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Run a security audit' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'audit' } diff --git a/lib/base-command.js b/lib/base-command.js index 8e48caa2e..b8497fd44 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -6,6 +6,14 @@ class BaseCommand { this.npm = npm } + get name () { + return this.constructor.name + } + + get description () { + return this.constructor.description + } + get usage () { let usage = `npm ${this.constructor.name}\n\n` if (this.constructor.description) diff --git a/lib/bin.js b/lib/bin.js index b0acefef9..23098b670 100644 --- a/lib/bin.js +++ b/lib/bin.js @@ -2,6 +2,10 @@ const envPath = require('./utils/path.js') const BaseCommand = require('./base-command.js') class Bin extends BaseCommand { + static get description () { + return 'Display npm bin folder' + } + static get name () { return 'bin' } diff --git a/lib/bugs.js b/lib/bugs.js index 1814dd7bc..a0cef4c5e 100644 --- a/lib/bugs.js +++ b/lib/bugs.js @@ -5,6 +5,10 @@ const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') const BaseCommand = require('./base-command.js') class Bugs extends BaseCommand { + static get description () { + return 'Report bugs for a package in a web browser' + } + static get name () { return 'bugs' } diff --git a/lib/cache.js b/lib/cache.js index b2d42ab23..43902f43b 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -7,6 +7,10 @@ const rimraf = promisify(require('rimraf')) const BaseCommand = require('./base-command.js') class Cache extends BaseCommand { + static get description () { + return 'Manipulates packages cache' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'cache' @@ -21,6 +21,11 @@ const BaseCommand = require('./base-command.js') class CI extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Install a project with a clean slate' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'ci' } diff --git a/lib/cli.js b/lib/cli.js index 910b674ea..837d0876c 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -40,11 +40,14 @@ module.exports = (process) => { npm.load(async er => { if (er) return errorHandler(er) + + // npm --version=cli if (npm.config.get('version', 'cli')) { - console.log(npm.version) + npm.output(npm.version) return errorHandler.exit(0) } + // npm --versions=cli if (npm.config.get('versions', 'cli')) { npm.argv = ['version'] npm.config.set('usage', false, 'cli') @@ -57,9 +60,20 @@ module.exports = (process) => { if (impl) impl(npm.argv, errorHandler) else { - npm.config.set('usage', false) - npm.argv.unshift(cmd) - npm.commands.help(npm.argv, errorHandler) + try { + // I don't know why this is needed but we get a cb() not called if we + // omit it + npm.log.level = 'silent' + if (cmd) { + const didYouMean = require('./utils/did-you-mean.js') + const suggestions = await didYouMean(npm, cmd) + npm.output(suggestions) + } else + npm.output(npm.usage) + process.exitCode = 1 + } catch (err) { + errorHandler(err) + } } }) } diff --git a/lib/completion.js b/lib/completion.js index b11188770..fa3b5f2dd 100644 --- a/lib/completion.js +++ b/lib/completion.js @@ -46,13 +46,13 @@ const BaseCommand = require('./base-command.js') class Completion extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get name () { - return 'completion' + static get description () { + return 'Tab Completion for npm' } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get description () { - return 'npm command completion script. save to ~/.bashrc or ~/.zshrc' + static get name () { + return 'completion' } // completion for the completion command diff --git a/lib/config.js b/lib/config.js index 7d0147712..d5ef6ec50 100644 --- a/lib/config.js +++ b/lib/config.js @@ -30,6 +30,10 @@ const publicVar = k => !/^(\/\/[^:]+:)?_/.test(k) const BaseCommand = require('./base-command.js') class Config extends BaseCommand { + static get description () { + return 'Manage the npm configuration files' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'config' diff --git a/lib/dedupe.js b/lib/dedupe.js index 4dd759dac..b80a777fc 100644 --- a/lib/dedupe.js +++ b/lib/dedupe.js @@ -6,6 +6,11 @@ const BaseCommand = require('./base-command.js') class Dedupe extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Reduce duplication in the package tree' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'dedupe' } diff --git a/lib/deprecate.js b/lib/deprecate.js index a0c67f805..c640bcfe0 100644 --- a/lib/deprecate.js +++ b/lib/deprecate.js @@ -7,6 +7,10 @@ const libaccess = require('libnpmaccess') const BaseCommand = require('./base-command.js') class Deprecate extends BaseCommand { + static get description () { + return 'Deprecate a version of a package' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'deprecate' diff --git a/lib/diff.js b/lib/diff.js index 221213365..c5e4b403e 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -12,6 +12,10 @@ const readLocalPkg = require('./utils/read-local-package.js') const BaseCommand = require('./base-command.js') class Diff extends BaseCommand { + static get description () { + return 'The registry diff command' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'diff' diff --git a/lib/dist-tag.js b/lib/dist-tag.js index 81773a40a..13ec37fd8 100644 --- a/lib/dist-tag.js +++ b/lib/dist-tag.js @@ -8,6 +8,10 @@ const readLocalPkgName = require('./utils/read-local-package.js') const BaseCommand = require('./base-command.js') class DistTag extends BaseCommand { + static get description () { + return 'Modify package distribution tags' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'dist-tag' diff --git a/lib/docs.js b/lib/docs.js index 2dad7a26d..089d77eb0 100644 --- a/lib/docs.js +++ b/lib/docs.js @@ -1,17 +1,23 @@ const log = require('npmlog') const pacote = require('pacote') const openUrl = require('./utils/open-url.js') -const usageUtil = require('./utils/usage.js') const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') -class Docs { - constructor (npm) { - this.npm = npm +const BaseCommand = require('./base-command.js') +class Docs extends BaseCommand { + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Open documentation for a package in a web browser' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get name () { + return 'docs' } /* istanbul ignore next - see test/lib/load-all-commands.js */ - get usage () { - return usageUtil('docs', 'npm docs [<pkgname> [<pkgname> ...]]') + static get usage () { + return ['[<pkgname> [<pkgname> ...]]'] } exec (args, cb) { diff --git a/lib/doctor.js b/lib/doctor.js index ae69b6a77..99beb2527 100644 --- a/lib/doctor.js +++ b/lib/doctor.js @@ -33,6 +33,11 @@ const maskLabel = mask => { const BaseCommand = require('./base-command.js') class Doctor extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Check your npm environment' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'doctor' } diff --git a/lib/edit.js b/lib/edit.js index 1dbe8e4c1..79d41462e 100644 --- a/lib/edit.js +++ b/lib/edit.js @@ -9,6 +9,10 @@ const completion = require('./utils/completion/installed-shallow.js') const BaseCommand = require('./base-command.js') class Edit extends BaseCommand { + static get description () { + return 'Edit an installed package' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'edit' diff --git a/lib/exec.js b/lib/exec.js index 8c8606456..5b2e15831 100644 --- a/lib/exec.js +++ b/lib/exec.js @@ -40,13 +40,13 @@ const BaseCommand = require('./base-command.js') class Exec extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get name () { - return 'exec' + static get description () { + return 'Run a command from a local or remote npm package' } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get description () { - return 'Run a command from a local or remote npm package.' + static get name () { + return 'exec' } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/explain.js b/lib/explain.js index 6af761186..4c2908df6 100644 --- a/lib/explain.js +++ b/lib/explain.js @@ -8,6 +8,10 @@ const validName = require('validate-npm-package-name') const BaseCommand = require('./base-command.js') class Explain extends BaseCommand { + static get description () { + return 'Explain installed packages' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'explain' diff --git a/lib/explore.js b/lib/explore.js index 34f6d1079..5f9820c21 100644 --- a/lib/explore.js +++ b/lib/explore.js @@ -8,6 +8,10 @@ const completion = require('./utils/completion/installed-shallow.js') const BaseCommand = require('./base-command.js') class Explore extends BaseCommand { + static get description () { + return 'Browse an installed package' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'explore' diff --git a/lib/find-dupes.js b/lib/find-dupes.js index ecb945f47..8037876f5 100644 --- a/lib/find-dupes.js +++ b/lib/find-dupes.js @@ -3,6 +3,11 @@ const BaseCommand = require('./base-command.js') class FindDupes extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Find duplication in the package tree' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'find-dupes' } diff --git a/lib/fund.js b/lib/fund.js index a4986f3c4..302f10dcf 100644 --- a/lib/fund.js +++ b/lib/fund.js @@ -23,6 +23,11 @@ const BaseCommand = require('./base-command.js') class Fund extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Retrieve funding information' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'fund' } diff --git a/lib/get.js b/lib/get.js index a5d58accc..8cfb259a8 100644 --- a/lib/get.js +++ b/lib/get.js @@ -2,6 +2,11 @@ const BaseCommand = require('./base-command.js') class Get extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Get a value from the npm configuration' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'get' } diff --git a/lib/help-search.js b/lib/help-search.js index 62e1adb59..c1ceae441 100644 --- a/lib/help-search.js +++ b/lib/help-search.js @@ -1,15 +1,16 @@ const fs = require('fs') const path = require('path') const color = require('ansicolors') -const npmUsage = require('./utils/npm-usage.js') const { promisify } = require('util') const glob = promisify(require('glob')) const readFile = promisify(fs.readFile) -const didYouMean = require('./utils/did-you-mean.js') -const { cmdList } = require('./utils/cmd-list.js') const BaseCommand = require('./base-command.js') class HelpSearch extends BaseCommand { + static get description () { + return 'Search npm help documentation' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'help-search' @@ -26,28 +27,17 @@ class HelpSearch extends BaseCommand { async helpSearch (args) { if (!args.length) - throw this.usage + return this.npm.output(this.usage) const docPath = path.resolve(__dirname, '..', 'docs/content') - const files = await glob(`${docPath}/*/*.md`) const data = await this.readFiles(files) const results = await this.searchFiles(args, data, files) - // if only one result, then just show that help section. - if (results.length === 1) { - return this.npm.commands.help([path.basename(results[0].file, '.md')], er => { - if (er) - throw er - }) - } - const formatted = this.formatResults(args, results) if (!formatted.trim()) - npmUsage(this.npm, false) - else { + this.npm.output(`No matches in help for: ${args.join(' ')}\n`) + else this.npm.output(formatted) - this.npm.output(didYouMean(args[0], cmdList)) - } } async readFiles (files) { diff --git a/lib/help.js b/lib/help.js index 93abf878b..b9ff1c957 100644 --- a/lib/help.js +++ b/lib/help.js @@ -1,14 +1,23 @@ -const npmUsage = require('./utils/npm-usage.js') const { spawn } = require('child_process') const path = require('path') -const log = require('npmlog') const openUrl = require('./utils/open-url.js') -const glob = require('glob') +const { promisify } = require('util') +const glob = promisify(require('glob')) const BaseCommand = require('./base-command.js') +// Strips out the number from foo.7 or foo.7. or foo.7.tgz +// We don't currently compress our man pages but if we ever did this would +// seemlessly continue supporting it +const manNumberRegex = /\.(\d+)(\..*)?$/ + class Help extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Get help on npm' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'help' } @@ -22,13 +31,7 @@ class Help extends BaseCommand { if (opts.conf.argv.remain.length > 2) return [] const g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]') - const files = await new Promise((resolve, reject) => { - glob(g, function (er, files) { - if (er) - return reject(er) - resolve(files) - }) - }) + const files = await glob(g) return Object.keys(files.reduce(function (acc, file) { file = path.basename(file).replace(/\.[0-9]+$/, '') @@ -43,81 +46,45 @@ class Help extends BaseCommand { } async help (args) { - const argv = this.npm.config.parsedArgv.cooked + // By default we search all of our man subdirectories, but if the user has + // asked for a specific one we limit the search to just there + let manSearch = 'man*' + if (/^\d+$/.test(args[0])) + manSearch = `man${args.shift()}` - let argnum = 0 - if (args.length === 2 && ~~args[0]) - argnum = ~~args.shift() + if (!args.length) + return this.npm.output(this.npm.usage) // npm help foo bar baz: search topics - if (args.length > 1 && args[0]) + if (args.length > 1) return this.helpSearch(args) - const affordances = { - 'find-dupes': 'dedupe', - } - let section = affordances[args[0]] || this.npm.deref(args[0]) || args[0] - - // npm help <noargs>: show basic usage - if (!section) { - npmUsage(this.npm, argv[0] === 'help') - return - } - - // npm <command> -h: show command usage - if (this.npm.config.get('usage') && - this.npm.commands[section] && - this.npm.commands[section].usage) { - this.npm.config.set('loglevel', 'silent') - log.level = 'silent' - this.npm.output(this.npm.commands[section].usage) - return - } + let section = this.npm.deref(args[0]) || args[0] - let pref = [1, 5, 7] - if (argnum) - pref = [argnum].concat(pref.filter(n => n !== argnum)) + // support `npm help package.json` + section = section.replace('.json', '-json') - // npm help <section>: Try to find the path const manroot = path.resolve(__dirname, '..', 'man') + // find either section.n or npm-section.n + const f = `${manroot}/${manSearch}/?(npm-)${section}.[0-9]*` + let mans = await glob(f) + mans = mans.sort((a, b) => { + // Because of the glob we know the manNumberRegex will pass + const aManNumber = a.match(manNumberRegex)[1] + const bManNumber = b.match(manNumberRegex)[1] - // legacy - if (section === 'global') - section = 'folders' - else if (section.match(/.*json/)) - section = section.replace('.json', '-json') - - // find either /section.n or /npm-section.n - // The glob is used in the glob. The regexp is used much - // further down. Globs and regexps are different - const compextglob = '.+(gz|bz2|lzma|[FYzZ]|xz)' - const compextre = '\\.(gz|bz2|lzma|[FYzZ]|xz)$' - const f = '+(npm-' + section + '|' + section + ').[0-9]?(' + compextglob + ')' - return new Promise((resolve, reject) => { - glob(manroot + '/*/' + f, async (er, mans) => { - if (er) - return reject(er) - - if (!mans.length) { - this.helpSearch(args).then(resolve).catch(reject) - return - } - - mans = mans.map((man) => { - const ext = path.extname(man) - if (man.match(new RegExp(compextre))) - man = path.basename(man, ext) - - return man - }) - - this.viewMan(this.pickMan(mans, pref), (err) => { - if (err) - return reject(err) - return resolve() - }) - }) + // man number sort first so that 1 aka commands are preferred + if (aManNumber !== bManNumber) + return aManNumber - bManNumber + + return a.localeCompare(b) }) + const man = mans[0] + + if (man) + await this.viewMan(man) + else + return this.helpSearch(args) } helpSearch (args) { @@ -133,32 +100,11 @@ class Help extends BaseCommand { }) } - pickMan (mans, pref_) { - const nre = /([0-9]+)$/ - const pref = {} - pref_.forEach((sect, i) => pref[sect] = i) - mans = mans.sort((a, b) => { - const an = a.match(nre)[1] - const bn = b.match(nre)[1] - return an === bn ? (a > b ? -1 : 1) - : pref[an] < pref[bn] ? -1 - : 1 - }) - return mans[0] - } - - viewMan (man, cb) { - const nre = /([0-9]+)$/ - const num = man.match(nre)[1] - const section = path.basename(man, '.' + num) - - // at this point, we know that the specified man page exists - const manpath = path.join(__dirname, '..', 'man') + async viewMan (man) { const env = {} Object.keys(process.env).forEach(function (i) { env[i] = process.env[i] }) - env.MANPATH = manpath const viewer = this.npm.config.get('viewer') const opts = { @@ -175,48 +121,39 @@ class Help extends BaseCommand { break case 'browser': - bin = false - try { - const url = this.htmlMan(man) - openUrl(this.npm, url, 'help available at the following URL').then( - () => cb() - ).catch(cb) - } catch (err) { - cb(err) - } - break + await openUrl(this.npm, this.htmlMan(man), 'help available at the following URL') + return default: - args.push(num, section) + args.push(man) break } - if (bin) { - const proc = spawn(bin, args, opts) + const proc = spawn(bin, args, opts) + return new Promise((resolve, reject) => { proc.on('exit', (code) => { if (code) - return cb(new Error(`help process exited with code: ${code}`)) + return reject(new Error(`help process exited with code: ${code}`)) - return cb() + return resolve() }) - } + }) } + // Returns the path to the html version of the man page htmlMan (man) { - let sect = +man.match(/([0-9]+)$/)[1] - const f = path.basename(man).replace(/[.]([0-9]+)$/, '') + let sect = man.match(manNumberRegex)[1] + const f = path.basename(man).replace(manNumberRegex, '') switch (sect) { - case 1: + case '1': sect = 'commands' break - case 5: + case '5': sect = 'configuring-npm' break - case 7: + case '7': sect = 'using-npm' break - default: - throw new Error('invalid man section: ' + sect) } return 'file://' + path.resolve(__dirname, '..', 'docs', 'output', sect, f + '.html') } diff --git a/lib/hook.js b/lib/hook.js index 6cda3504f..64b1201cb 100644 --- a/lib/hook.js +++ b/lib/hook.js @@ -5,6 +5,10 @@ const Table = require('cli-table3') const BaseCommand = require('./base-command.js') class Hook extends BaseCommand { + static get description () { + return 'Manage registry hooks' + } + static get name () { return 'hook' } diff --git a/lib/init.js b/lib/init.js index 3d11050e4..81c673388 100644 --- a/lib/init.js +++ b/lib/init.js @@ -5,6 +5,11 @@ const BaseCommand = require('./base-command.js') class Init extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Create a package.json file' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'init' } diff --git a/lib/install-ci-test.js b/lib/install-ci-test.js index c52b5c9e8..871f24b2f 100644 --- a/lib/install-ci-test.js +++ b/lib/install-ci-test.js @@ -4,6 +4,10 @@ const CI = require('./ci.js') class InstallCITest extends CI { + static get description () { + return 'Install a project with a clean slate and run tests' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'install-ci-test' diff --git a/lib/install-test.js b/lib/install-test.js index 76c6f367d..d5664119d 100644 --- a/lib/install-test.js +++ b/lib/install-test.js @@ -4,6 +4,10 @@ const Install = require('./install.js') class InstallTest extends Install { + static get description () { + return 'Install package(s) and run tests' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'install-test' diff --git a/lib/install.js b/lib/install.js index 34d0b2353..363230d31 100644 --- a/lib/install.js +++ b/lib/install.js @@ -12,6 +12,11 @@ const runScript = require('@npmcli/run-script') const BaseCommand = require('./base-command.js') class Install extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Install a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'install' } diff --git a/lib/link.js b/lib/link.js index 9aad6dc1d..fe9cfd3a6 100644 --- a/lib/link.js +++ b/lib/link.js @@ -13,6 +13,11 @@ const reifyFinish = require('./utils/reify-finish.js') const BaseCommand = require('./base-command.js') class Link extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Symlink a package folder' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'link' } diff --git a/lib/logout.js b/lib/logout.js index 3784020ec..3589d287b 100644 --- a/lib/logout.js +++ b/lib/logout.js @@ -5,6 +5,11 @@ const BaseCommand = require('./base-command.js') class Logout extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Log out of the registry' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'logout' } @@ -24,6 +24,11 @@ const BaseCommand = require('./base-command.js') class LS extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'List installed packages' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'ls' } diff --git a/lib/npm.js b/lib/npm.js index d80067a11..78b6ba034 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -38,6 +38,7 @@ const proxyCmds = new Proxy({}, { const { definitions, flatten, shorthands } = require('./utils/config/index.js') const { shellouts } = require('./utils/cmd-list.js') +const usage = require('./utils/npm-usage.js') let warnedNonDashArg = false const _runCmd = Symbol('_runCmd') @@ -100,7 +101,7 @@ const npm = module.exports = new class extends EventEmitter { } if (this.config.get('usage')) { - console.log(impl.usage) + this.output(impl.usage) cb() } else { impl.exec(args, er => { @@ -274,6 +275,10 @@ const npm = module.exports = new class extends EventEmitter { this[k] = r } + get usage () { + return usage(this) + } + // XXX add logging to see if we actually use this get tmp () { if (!this[_tmpFolder]) { diff --git a/lib/org.js b/lib/org.js index b9f84b060..ddd2b03da 100644 --- a/lib/org.js +++ b/lib/org.js @@ -4,6 +4,10 @@ const Table = require('cli-table3') const BaseCommand = require('./base-command.js') class Org extends BaseCommand { + static get description () { + return 'Manage orgs' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'org' diff --git a/lib/outdated.js b/lib/outdated.js index 6a7b4e7b3..9b656d2ae 100644 --- a/lib/outdated.js +++ b/lib/outdated.js @@ -14,6 +14,11 @@ const BaseCommand = require('./base-command.js') class Outdated extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Check for outdated packages' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'outdated' } diff --git a/lib/owner.js b/lib/owner.js index b62f125ac..e537d82d0 100644 --- a/lib/owner.js +++ b/lib/owner.js @@ -8,6 +8,10 @@ const readLocalPkg = require('./utils/read-local-package.js') const BaseCommand = require('./base-command.js') class Owner extends BaseCommand { + static get description () { + return 'Manage package owners' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'owner' diff --git a/lib/pack.js b/lib/pack.js index cdd823430..4618e767e 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -12,6 +12,11 @@ const BaseCommand = require('./base-command.js') class Pack extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Create a tarball from a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'pack' } diff --git a/lib/ping.js b/lib/ping.js index 53467c93b..b68ea6b56 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -4,13 +4,13 @@ const BaseCommand = require('./base-command.js') class Ping extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get name () { - return 'ping' + static get description () { + return 'Ping npm registry' } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get description () { - return 'ping registry' + static get name () { + return 'ping' } exec (args, cb) { diff --git a/lib/prefix.js b/lib/prefix.js index 5ade87f64..1298a3c5b 100644 --- a/lib/prefix.js +++ b/lib/prefix.js @@ -2,6 +2,11 @@ const BaseCommand = require('./base-command.js') class Prefix extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Display prefix' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'prefix' } diff --git a/lib/profile.js b/lib/profile.js index 3d4857df0..9789146fd 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -38,6 +38,10 @@ const writableProfileKeys = [ const BaseCommand = require('./base-command.js') class Profile extends BaseCommand { + static get description () { + return 'Change settings on your registry profile' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'profile' diff --git a/lib/prune.js b/lib/prune.js index c2cddb1a2..e86d5c053 100644 --- a/lib/prune.js +++ b/lib/prune.js @@ -5,6 +5,11 @@ const reifyFinish = require('./utils/reify-finish.js') const BaseCommand = require('./base-command.js') class Prune extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Remove extraneous packages' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'prune' } diff --git a/lib/publish.js b/lib/publish.js index ed05d1902..410e911f9 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -19,6 +19,10 @@ const readJson = util.promisify(require('read-package-json')) const BaseCommand = require('./base-command.js') class Publish extends BaseCommand { + static get description () { + return 'Publish a package' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'publish' diff --git a/lib/rebuild.js b/lib/rebuild.js index c11747b97..5910ab3d1 100644 --- a/lib/rebuild.js +++ b/lib/rebuild.js @@ -7,6 +7,11 @@ const completion = require('./utils/completion/installed-deep.js') const BaseCommand = require('./base-command.js') class Rebuild extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Rebuild a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'rebuild' } diff --git a/lib/repo.js b/lib/repo.js index aa07e07a8..5ab136abd 100644 --- a/lib/repo.js +++ b/lib/repo.js @@ -8,6 +8,11 @@ const openUrl = require('./utils/open-url.js') const BaseCommand = require('./base-command.js') class Repo extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Open package repository page in the browser' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'repo' } diff --git a/lib/restart.js b/lib/restart.js index 1f3eb5af9..840eb2067 100644 --- a/lib/restart.js +++ b/lib/restart.js @@ -3,6 +3,11 @@ const LifecycleCmd = require('./utils/lifecycle-cmd.js') // This ends up calling run-script(['restart', ...args]) class Restart extends LifecycleCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Restart a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'restart' } diff --git a/lib/root.js b/lib/root.js index 1fe82c6fa..d24a2e20a 100644 --- a/lib/root.js +++ b/lib/root.js @@ -1,6 +1,11 @@ const BaseCommand = require('./base-command.js') class Root extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Display npm root' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'root' } diff --git a/lib/run-script.js b/lib/run-script.js index f6ca57b79..a7202548c 100644 --- a/lib/run-script.js +++ b/lib/run-script.js @@ -20,6 +20,11 @@ const cmdList = [ const BaseCommand = require('./base-command.js') class RunScript extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Run arbitrary package scripts' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'run-script' } @@ -70,9 +75,8 @@ class RunScript extends BaseCommand { if (this.npm.config.get('if-present')) return - const suggestions = didYouMean(event, Object.keys(scripts)) - throw new Error(`missing script: ${event}${ - suggestions ? `\n${suggestions}` : ''}`) + const suggestions = await didYouMean(this.npm, event) + throw new Error(suggestions) } // positional args only added to the main event, not pre/post diff --git a/lib/search.js b/lib/search.js index 4c206498f..aca9113ce 100644 --- a/lib/search.js +++ b/lib/search.js @@ -27,6 +27,11 @@ function prepareExcludes (searchexclude) { const BaseCommand = require('./base-command.js') class Search extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Search for pacakges' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'search' } diff --git a/lib/set-script.js b/lib/set-script.js index 624198132..df101a0ac 100644 --- a/lib/set-script.js +++ b/lib/set-script.js @@ -6,6 +6,11 @@ const rpj = require('read-package-json-fast') const BaseCommand = require('./base-command.js') class SetScript extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Set tasks in the scripts section of package.json' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'set-script' } diff --git a/lib/set.js b/lib/set.js index 787a8012c..74a002cd6 100644 --- a/lib/set.js +++ b/lib/set.js @@ -1,6 +1,10 @@ const BaseCommand = require('./base-command.js') class Set extends BaseCommand { + static get description () { + return 'Set a value in the npm configuration' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'set' diff --git a/lib/shrinkwrap.js b/lib/shrinkwrap.js index 76979b021..5d4a1ada9 100644 --- a/lib/shrinkwrap.js +++ b/lib/shrinkwrap.js @@ -8,6 +8,11 @@ const log = require('npmlog') const BaseCommand = require('./base-command.js') class Shrinkwrap extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Lock down dependency versions for publication' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'shrinkwrap' } diff --git a/lib/star.js b/lib/star.js index 9a8ee8c77..4c5cf4900 100644 --- a/lib/star.js +++ b/lib/star.js @@ -6,6 +6,10 @@ const getIdentity = require('./utils/get-identity') const BaseCommand = require('./base-command.js') class Star extends BaseCommand { + static get description () { + return 'Mark your favorite packages' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'star' diff --git a/lib/stars.js b/lib/stars.js index 758a3130d..db93d7b19 100644 --- a/lib/stars.js +++ b/lib/stars.js @@ -6,6 +6,11 @@ const getIdentity = require('./utils/get-identity.js') const BaseCommand = require('./base-command.js') class Stars extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'View packages marked as favorites' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'stars' } diff --git a/lib/start.js b/lib/start.js index 8987bc293..099b6e61b 100644 --- a/lib/start.js +++ b/lib/start.js @@ -3,6 +3,11 @@ const LifecycleCmd = require('./utils/lifecycle-cmd.js') // This ends up calling run-script(['start', ...args]) class Start extends LifecycleCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Start a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'start' } diff --git a/lib/stop.js b/lib/stop.js index a3857ab13..766d9c018 100644 --- a/lib/stop.js +++ b/lib/stop.js @@ -3,6 +3,11 @@ const LifecycleCmd = require('./utils/lifecycle-cmd.js') // This ends up calling run-script(['stop', ...args]) class Stop extends LifecycleCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Stop a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'stop' } diff --git a/lib/team.js b/lib/team.js index f84660af4..5298bb3b2 100644 --- a/lib/team.js +++ b/lib/team.js @@ -5,6 +5,10 @@ const otplease = require('./utils/otplease.js') const BaseCommand = require('./base-command.js') class Team extends BaseCommand { + static get description () { + return 'Manage organization teams and team memberships' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'team' diff --git a/lib/test.js b/lib/test.js index 991d1c873..2be2b54af 100644 --- a/lib/test.js +++ b/lib/test.js @@ -3,6 +3,11 @@ const LifecycleCmd = require('./utils/lifecycle-cmd.js') // This ends up calling run-script(['test', ...args]) class Test extends LifecycleCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Test a package' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'test' } diff --git a/lib/token.js b/lib/token.js index 3d7952ccf..a80988531 100644 --- a/lib/token.js +++ b/lib/token.js @@ -10,6 +10,10 @@ const readUserInfo = require('./utils/read-user-info.js') const BaseCommand = require('./base-command.js') class Token extends BaseCommand { + static get description () { + return 'Manage your authentication tokens' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'token' diff --git a/lib/uninstall.js b/lib/uninstall.js index c1c36ab6b..16a49b757 100644 --- a/lib/uninstall.js +++ b/lib/uninstall.js @@ -7,6 +7,10 @@ const completion = require('./utils/completion/installed-shallow.js') const BaseCommand = require('./base-command.js') class Uninstall extends BaseCommand { + static get description () { + return 'Remove a package' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'uninstall' diff --git a/lib/unpublish.js b/lib/unpublish.js index 06b2779b2..d49bb7ba4 100644 --- a/lib/unpublish.js +++ b/lib/unpublish.js @@ -12,6 +12,10 @@ const getIdentity = require('./utils/get-identity.js') const BaseCommand = require('./base-command.js') class Unpublish extends BaseCommand { + static get description () { + return 'Remove a package from the registry' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'unpublish' diff --git a/lib/unstar.js b/lib/unstar.js index 5786cfce6..bc32ba617 100644 --- a/lib/unstar.js +++ b/lib/unstar.js @@ -2,6 +2,11 @@ const Star = require('./star.js') class Unstar extends Star { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Remove an item from your favorite packages' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'unstar' } diff --git a/lib/update.js b/lib/update.js index 8ce52a442..66a668cd3 100644 --- a/lib/update.js +++ b/lib/update.js @@ -9,6 +9,11 @@ const completion = require('./utils/completion/installed-deep.js') const BaseCommand = require('./base-command.js') class Update extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'Update packages' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'update' } diff --git a/lib/utils/did-you-mean.js b/lib/utils/did-you-mean.js index c2bdf159d..3d8c6677c 100644 --- a/lib/utils/did-you-mean.js +++ b/lib/utils/did-you-mean.js @@ -1,12 +1,33 @@ const leven = require('leven') +const readJson = require('read-package-json-fast') +const { cmdList } = require('./cmd-list.js') -const didYouMean = (scmd, commands) => { - const best = commands +const didYouMean = async (npm, scmd) => { + const bestCmd = cmdList .filter(cmd => leven(scmd, cmd) < scmd.length * 0.4) - .map(str => ` ${str}`) - return best.length === 0 ? '' - : best.length === 1 ? `\nDid you mean this?\n${best[0]}` - : `\nDid you mean one of these?\n${best.slice(0, 3).join('\n')}` -} + .map(str => ` npm ${str} # ${npm.commands[str].description}`) + + const path = npm.localPrefix + const pkg = await readJson(`${path}/package.json`) + const { scripts } = pkg + // We would already be suggesting this in `npm x` so omit them here + const runScripts = ['stop', 'start', 'test', 'restart'] + const bestRun = Object.keys(scripts) + .filter(cmd => leven(scmd, cmd) < scmd.length * 0.4 && + !runScripts.includes(cmd)) + .map(str => ` npm run ${str} # run the "${str}" package script`) + + const { bin } = pkg + const bestBin = Object.keys(bin) + .filter(cmd => leven(scmd, cmd) < scmd.length * 0.4) + .map(str => ` npm exec ${str} # run the "${str}" command from either this or a remote npm package`) + + const best = [...bestCmd, ...bestRun, ...bestBin] + const suggestion = best.length === 0 ? '' + : best.length === 1 ? `\n\nDid you mean this?\n${best[0]}` + : `\n\nDid you mean one of these?\n${best.slice(0, 3).join('\n')}` + const result = `Unknown command: "${scmd}"${suggestion}` + return result +} module.exports = didYouMean diff --git a/lib/utils/npm-usage.js b/lib/utils/npm-usage.js index b77bca7be..bc397cb4d 100644 --- a/lib/utils/npm-usage.js +++ b/lib/utils/npm-usage.js @@ -1,14 +1,12 @@ -const didYouMean = require('./did-you-mean.js') const { dirname } = require('path') const { cmdList } = require('./cmd-list') -module.exports = (npm, valid = true) => { - npm.config.set('loglevel', 'silent') +module.exports = (npm) => { const usesBrowser = npm.config.get('viewer') === 'browser' ? ' (in a browser)' : '' - npm.log.level = 'silent' - npm.output(` -Usage: npm <command> + return `npm <command> + +Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project @@ -20,7 +18,7 @@ npm help <term> search for help on <term>${usesBrowser} npm help npm more involved overview${usesBrowser} All commands: -${npm.config.get('long') ? usages(npm) : ('\n ' + wrap(cmdList))} +${allCommands(npm)} Specify configs in the ini-formatted file: ${npm.config.get('userconfig')} @@ -29,14 +27,13 @@ or on the command line via: npm <command> --key=value More configuration info: npm help config Configuration fields: npm help 7 config -npm@${npm.version} ${dirname(dirname(__dirname))} -`) - - if (npm.argv.length >= 1) - npm.output(didYouMean(npm.argv[0], cmdList)) +npm@${npm.version} ${dirname(dirname(__dirname))}` +} - if (!valid) - process.exitCode = 1 +const allCommands = (npm) => { + if (npm.config.get('long')) + return usages(npm) + return ('\n ' + wrap(cmdList)) } const wrap = (arr) => { diff --git a/lib/version.js b/lib/version.js index 30097a3c3..18b7d7d6c 100644 --- a/lib/version.js +++ b/lib/version.js @@ -2,6 +2,10 @@ const libversion = require('libnpmversion') const BaseCommand = require('./base-command.js') class Version extends BaseCommand { + static get description () { + return 'Bump a package version' + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'version' diff --git a/lib/view.js b/lib/view.js index 7bbedcf65..e0df1e231 100644 --- a/lib/view.js +++ b/lib/view.js @@ -20,6 +20,11 @@ const readJson = async file => jsonParse(await readFile(file, 'utf8')) const BaseCommand = require('./base-command.js') class View extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get description () { + return 'View registry info' + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'view' } diff --git a/lib/whoami.js b/lib/whoami.js index 180eb3317..6e2841359 100644 --- a/lib/whoami.js +++ b/lib/whoami.js @@ -3,13 +3,13 @@ const getIdentity = require('./utils/get-identity.js') const BaseCommand = require('./base-command.js') class Whoami extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get name () { - return 'whoami' + static get description () { + return 'Display npm username' } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get description () { - return 'prints username according to given registry' + static get name () { + return 'whoami' } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/tap-snapshots/test-lib-dist-tag.js-TAP.test.js b/tap-snapshots/test-lib-dist-tag.js-TAP.test.js index 89a87ae64..06936795b 100644 --- a/tap-snapshots/test-lib-dist-tag.js-TAP.test.js +++ b/tap-snapshots/test-lib-dist-tag.js-TAP.test.js @@ -8,6 +8,8 @@ exports[`test/lib/dist-tag.js TAP add missing args > should exit usage error message 1`] = ` npm dist-tag +Modify package distribution tags + Usage: npm dist-tag add <pkg>@<version> [<tag>] npm dist-tag rm <pkg> <tag> @@ -21,6 +23,8 @@ Run "npm help dist-tag" for more info exports[`test/lib/dist-tag.js TAP add missing pkg name > should exit usage error message 1`] = ` npm dist-tag +Modify package distribution tags + Usage: npm dist-tag add <pkg>@<version> [<tag>] npm dist-tag rm <pkg> <tag> @@ -43,6 +47,8 @@ dist-tag add 1.0.0 to @scoped/another@7.7.7 exports[`test/lib/dist-tag.js TAP borked cmd usage > should show usage error 1`] = ` npm dist-tag +Modify package distribution tags + Usage: npm dist-tag add <pkg>@<version> [<tag>] npm dist-tag rm <pkg> <tag> @@ -62,6 +68,8 @@ latest: 1.0.0 exports[`test/lib/dist-tag.js TAP ls on missing name in current package > should throw usage error message 1`] = ` npm dist-tag +Modify package distribution tags + Usage: npm dist-tag add <pkg>@<version> [<tag>] npm dist-tag rm <pkg> <tag> @@ -111,6 +119,8 @@ exports[`test/lib/dist-tag.js TAP remove existing tag > should return success ms exports[`test/lib/dist-tag.js TAP remove missing pkg name > should exit usage error message 1`] = ` npm dist-tag +Modify package distribution tags + Usage: npm dist-tag add <pkg>@<version> [<tag>] npm dist-tag rm <pkg> <tag> diff --git a/tap-snapshots/test-lib-publish.js-TAP.test.js b/tap-snapshots/test-lib-publish.js-TAP.test.js index 97ce7a773..e777797d7 100644 --- a/tap-snapshots/test-lib-publish.js-TAP.test.js +++ b/tap-snapshots/test-lib-publish.js-TAP.test.js @@ -8,6 +8,8 @@ exports[`test/lib/publish.js TAP shows usage with wrong set of arguments > should print usage 1`] = ` npm publish +Publish a package + Usage: npm publish [<folder>] [--tag <tag>] [--access <public|restricted>] [--dry-run] diff --git a/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js b/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js index caabec66b..11f7ff024 100644 --- a/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js +++ b/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js @@ -6,8 +6,9 @@ */ 'use strict' exports[`test/lib/utils/npm-usage.js TAP usage basic usage > must match snapshot 1`] = ` +npm <command> -Usage: npm <command> +Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project @@ -38,55 +39,12 @@ More configuration info: npm help config Configuration fields: npm help 7 config npm@{VERSION} {BASEDIR} - -` - -exports[`test/lib/utils/npm-usage.js TAP usage did you mean? > must match snapshot 1`] = ` - -Usage: npm <command> - -npm install install all the dependencies in your project -npm install <foo> add the <foo> dependency to your project -npm test run this project's tests -npm run <foo> run the script named <foo> -npm <command> -h quick help on <command> -npm -l display usage info for all commands -npm help <term> search for help on <term> -npm help npm more involved overview - -All commands: - - access, adduser, audit, bin, bugs, cache, ci, completion, - config, dedupe, deprecate, diff, dist-tag, docs, doctor, - edit, exec, explain, explore, find-dupes, fund, get, help, - hook, init, install, install-ci-test, install-test, link, - ll, login, logout, ls, org, outdated, owner, pack, ping, - prefix, profile, prune, publish, rebuild, repo, restart, - root, run-script, search, set, set-script, shrinkwrap, star, - stars, start, stop, team, test, token, uninstall, unpublish, - unstar, update, version, view, whoami - -Specify configs in the ini-formatted file: - /some/config/file/.npmrc -or on the command line via: npm <command> --key=value - -More configuration info: npm help config -Configuration fields: npm help 7 config - -npm@{VERSION} {BASEDIR} - -` - -exports[`test/lib/utils/npm-usage.js TAP usage did you mean? > must match snapshot 2`] = ` - -Did you mean one of these? - install - uninstall ` exports[`test/lib/utils/npm-usage.js TAP usage set process.stdout.columns columns=0 > must match snapshot 1`] = ` +npm <command> -Usage: npm <command> +Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project @@ -117,12 +75,12 @@ More configuration info: npm help config Configuration fields: npm help 7 config npm@{VERSION} {BASEDIR} - ` exports[`test/lib/utils/npm-usage.js TAP usage set process.stdout.columns columns=90 > must match snapshot 1`] = ` +npm <command> -Usage: npm <command> +Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project @@ -153,12 +111,12 @@ More configuration info: npm help config Configuration fields: npm help 7 config npm@{VERSION} {BASEDIR} - ` exports[`test/lib/utils/npm-usage.js TAP usage with browser > must match snapshot 1`] = ` +npm <command> -Usage: npm <command> +Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project @@ -189,12 +147,12 @@ More configuration info: npm help config Configuration fields: npm help 7 config npm@{VERSION} {BASEDIR} - ` exports[`test/lib/utils/npm-usage.js TAP usage with long > must match snapshot 1`] = ` +npm <command> -Usage: npm <command> +Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project @@ -209,6 +167,8 @@ All commands: access npm access + Set access level on published packages + Usage: npm access public [<package>] npm access restricted [<package>] @@ -224,6 +184,8 @@ All commands: adduser npm adduser + Add a registry user account + Usage: npm adduser [--registry=url] [--scope=@orgname] [--always-auth] @@ -233,6 +195,8 @@ All commands: audit npm audit + Run a security audit + Usage: npm audit [--json] [--production] npm audit fix [--force|--package-lock-only|--dry-run|--production|--only=(dev|prod)] @@ -241,6 +205,8 @@ All commands: bin npm bin + Display npm bin folder + Usage: npm bin [-g] @@ -248,6 +214,8 @@ All commands: bugs npm bugs + Report bugs for a package in a web browser + Usage: npm bugs [<pkgname>] @@ -257,6 +225,8 @@ All commands: cache npm cache + Manipulates packages cache + Usage: npm cache add <tarball file> npm cache add <folder> @@ -270,6 +240,8 @@ All commands: ci npm ci + Install a project with a clean slate + Usage: npm ci @@ -279,7 +251,7 @@ All commands: completion npm completion - npm command completion script. save to ~/.bashrc or ~/.zshrc + Tab Completion for npm Usage: npm completion @@ -288,6 +260,8 @@ All commands: config npm config + Manage the npm configuration files + Usage: npm config set <key>=<value> [<key>=<value> ...] npm config get [<key> [<key> ...]] @@ -301,6 +275,8 @@ All commands: dedupe npm dedupe + Reduce duplication in the package tree + Usage: npm dedupe @@ -310,6 +286,8 @@ All commands: deprecate npm deprecate + Deprecate a version of a package + Usage: npm deprecate <pkg>[@<version>] <message> @@ -317,6 +295,8 @@ All commands: diff npm diff + The registry diff command + Usage: npm diff [...<paths>] npm diff --diff=<pkg-name> [...<paths>] @@ -328,6 +308,8 @@ All commands: dist-tag npm dist-tag + Modify package distribution tags + Usage: npm dist-tag add <pkg>@<version> [<tag>] npm dist-tag rm <pkg> <tag> @@ -337,12 +319,21 @@ All commands: Run "npm help dist-tag" for more info - docs npm docs [<pkgname> [<pkgname> ...]] + docs npm docs + + Open documentation for a package in a web browser + + Usage: + npm docs [<pkgname> [<pkgname> ...]] alias: home + + Run "npm help docs" for more info doctor npm doctor + Check your npm environment + Usage: npm doctor @@ -350,6 +341,8 @@ All commands: edit npm edit + Edit an installed package + Usage: npm edit <pkg>[/<subpkg>...] @@ -357,7 +350,7 @@ All commands: exec npm exec - Run a command from a local or remote npm package. + Run a command from a local or remote npm package Usage: npm exec -- <pkg>[@<version>] [args...] @@ -371,6 +364,8 @@ All commands: explain npm explain + Explain installed packages + Usage: npm explain <folder | specifier> @@ -380,6 +375,8 @@ All commands: explore npm explore + Browse an installed package + Usage: npm explore <pkg> [ -- <command>] @@ -387,6 +384,8 @@ All commands: find-dupes npm find-dupes + Find duplication in the package tree + Usage: npm find-dupes @@ -394,6 +393,8 @@ All commands: fund npm fund + Retrieve funding information + Usage: npm fund [--json] [--browser] [--unicode] [[<@scope>/]<pkg> [--which=<fundingSourceNumber>] @@ -401,6 +402,8 @@ All commands: get npm get + Get a value from the npm configuration + Usage: npm get [<key> ...] (See \`npm config\`) @@ -408,6 +411,8 @@ All commands: help npm help + Get help on npm + Usage: npm help <term> [<terms..>] @@ -417,6 +422,8 @@ All commands: hook npm hook + Manage registry hooks + Usage: npm hook add <pkg> <url> <secret> [--type=<type>] npm hook ls [pkg] @@ -427,6 +434,8 @@ All commands: init npm init + Create a package.json file + Usage: npm init [--force|-f|--yes|-y|--scope] npm init <@scope> (same as \`npx <@scope>/create\`) @@ -438,6 +447,8 @@ All commands: install npm install + Install a package + Usage: npm install [<@scope>/]<pkg> npm install [<@scope>/]<pkg>@<tag> @@ -456,6 +467,8 @@ All commands: install-ci-test npm install-ci-test + Install a project with a clean slate and run tests + Usage: npm install-ci-test @@ -465,6 +478,8 @@ All commands: install-test npm install-test + Install package(s) and run tests + Usage: npm install-test [<@scope>/]<pkg> npm install-test [<@scope>/]<pkg>@<tag> @@ -483,6 +498,8 @@ All commands: link npm link + Symlink a package folder + Usage: npm link (in package dir) npm link [<@scope>/]<pkg>[@<version>] @@ -493,6 +510,8 @@ All commands: ll npm ll + List installed packages + Usage: npm ll [[<@scope>/]<pkg> ...] @@ -502,6 +521,8 @@ All commands: login npm adduser + Add a registry user account + Usage: npm adduser [--registry=url] [--scope=@orgname] [--always-auth] @@ -511,6 +532,8 @@ All commands: logout npm logout + Log out of the registry + Usage: npm logout [--registry=<url>] [--scope=<@scope>] @@ -518,6 +541,8 @@ All commands: ls npm ls + List installed packages + Usage: npm ls npm ls [[<@scope>/]<pkg> ...] @@ -527,6 +552,8 @@ All commands: org npm org + Manage orgs + Usage: npm org set orgname username [developer | admin | owner] npm org rm orgname username @@ -538,6 +565,8 @@ All commands: outdated npm outdated + Check for outdated packages + Usage: npm outdated [[<@scope>/]<pkg> ...] @@ -545,6 +574,8 @@ All commands: owner npm owner + Manage package owners + Usage: npm owner add <user> [<@scope>/]<pkg> npm owner rm <user> [<@scope>/]<pkg> @@ -556,6 +587,8 @@ All commands: pack npm pack + Create a tarball from a package + Usage: npm pack [[<@scope>/]<pkg>...] [--dry-run] @@ -563,7 +596,7 @@ All commands: ping npm ping - ping registry + Ping npm registry Usage: npm ping @@ -572,6 +605,8 @@ All commands: prefix npm prefix + Display prefix + Usage: npm prefix [-g] @@ -579,6 +614,8 @@ All commands: profile npm profile + Change settings on your registry profile + Usage: npm profile enable-2fa [auth-only|auth-and-writes] npm profile disable-2fa @@ -589,6 +626,8 @@ All commands: prune npm prune + Remove extraneous packages + Usage: npm prune [[<@scope>/]<pkg>...] [--production] @@ -596,6 +635,8 @@ All commands: publish npm publish + Publish a package + Usage: npm publish [<folder>] [--tag <tag>] [--access <public|restricted>] [--dry-run] @@ -603,6 +644,8 @@ All commands: rebuild npm rebuild + Rebuild a package + Usage: npm rebuild [[<@scope>/]<name>[@<version>] ...] @@ -612,6 +655,8 @@ All commands: repo npm repo + Open package repository page in the browser + Usage: npm repo [<pkgname> [<pkgname> ...]] @@ -619,6 +664,8 @@ All commands: restart npm restart + Restart a package + Usage: npm restart [-- <args>] @@ -626,6 +673,8 @@ All commands: root npm root + Display npm root + Usage: npm root [-g] @@ -633,6 +682,8 @@ All commands: run-script npm run-script + Run arbitrary package scripts + Usage: npm run-script <command> [-- <args>] @@ -642,6 +693,8 @@ All commands: search npm search + Search for pacakges + Usage: npm search [-l|--long] [--json] [--parseable] [--no-description] [search terms ...] @@ -651,6 +704,8 @@ All commands: set npm set + Set a value in the npm configuration + Usage: npm set <key>=<value> [<key>=<value> ...] (See \`npm config\`) @@ -658,6 +713,8 @@ All commands: set-script npm set-script + Set tasks in the scripts section of package.json + Usage: npm set-script [<script>] [<command>] @@ -665,6 +722,8 @@ All commands: shrinkwrap npm shrinkwrap + Lock down dependency versions for publication + Usage: npm shrinkwrap @@ -672,6 +731,8 @@ All commands: star npm star + Mark your favorite packages + Usage: npm star [<pkg>...] @@ -679,6 +740,8 @@ All commands: stars npm stars + View packages marked as favorites + Usage: npm stars [<user>] @@ -686,6 +749,8 @@ All commands: start npm start + Start a package + Usage: npm start [-- <args>] @@ -693,6 +758,8 @@ All commands: stop npm stop + Stop a package + Usage: npm stop [-- <args>] @@ -700,6 +767,8 @@ All commands: team npm team + Manage organization teams and team memberships + Usage: npm team create <scope:team> [--otp <otpcode>] npm team destroy <scope:team> [--otp <otpcode>] @@ -711,6 +780,8 @@ All commands: test npm test + Test a package + Usage: npm test [-- <args>] @@ -720,6 +791,8 @@ All commands: token npm token + Manage your authentication tokens + Usage: npm token list npm token revoke <id|token> @@ -729,6 +802,8 @@ All commands: uninstall npm uninstall + Remove a package + Usage: npm uninstall [<@scope>/]<pkg>[@<version>]... [-S|--save|--no-save] @@ -738,6 +813,8 @@ All commands: unpublish npm unpublish + Remove a package from the registry + Usage: npm unpublish [<@scope>/]<pkg>[@<version>] @@ -745,6 +822,8 @@ All commands: unstar npm unstar + Remove an item from your favorite packages + Usage: npm unstar [<pkg>...] @@ -752,6 +831,8 @@ All commands: update npm update + Update packages + Usage: npm update [-g] [<pkg>...] @@ -761,6 +842,8 @@ All commands: version npm version + Bump a package version + Usage: npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git] @@ -770,6 +853,8 @@ All commands: view npm view + View registry info + Usage: npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...] @@ -779,7 +864,7 @@ All commands: whoami npm whoami - prints username according to given registry + Display npm username Usage: npm whoami [--registry <registry>] @@ -794,5 +879,4 @@ More configuration info: npm help config Configuration fields: npm help 7 config npm@{VERSION} {BASEDIR} - ` diff --git a/test/lib/cli.js b/test/lib/cli.js index b5441be1e..59769e13f 100644 --- a/test/lib/cli.js +++ b/test/lib/cli.js @@ -1,7 +1,11 @@ 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: [], @@ -21,8 +25,11 @@ const unsupportedMock = { } let errorHandlerCalled = null +let errorHandlerCb const errorHandlerMock = (...args) => { errorHandlerCalled = args + if (errorHandlerCb) + errorHandlerCb() } let errorHandlerExitCalled = null errorHandlerMock.exit = code => { @@ -39,15 +46,23 @@ const npmlogMock = { const requireInject = require('require-inject') const cli = requireInject.installGlobally('../../lib/cli.js', { '../../lib/npm.js': npmock, + '../../lib/utils/did-you-mean.js': () => 'test did you mean', '../../lib/utils/unsupported.js': unsupportedMock, '../../lib/utils/error-handler.js': errorHandlerMock, npmlog: npmlogMock, }) t.test('print the version, and treat npm_g to npm -g', t => { - const { log } = console - const consoleLogs = [] - console.log = (...msg) => consoleLogs.push(msg) + 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'], @@ -67,25 +82,13 @@ t.test('print the version, and treat npm_g to npm -g', t => { ['info', 'using', 'npm@%s', '99.99.99'], ['info', 'using', 'node@%s', '420.69.lol'], ]) - t.strictSame(consoleLogs, [['99.99.99']]) + t.strictSame(npmOutputs, [['99.99.99']]) t.strictSame(errorHandlerExitCalled, 0) - delete npmock.config.settings.version - process.argv = argv - console.log = log - npmock.argv.length = 0 - proc.argv.length = 0 - logs.length = 0 - consoleLogs.length = 0 - errorHandlerExitCalled = null - t.end() }) t.test('calling with --versions calls npm version with no args', t => { - const { log } = console - const consoleLogs = [] - console.log = (...msg) => consoleLogs.push(msg) const processArgv = process.argv const proc = { argv: ['node', 'npm', 'install', 'or', 'whatever', '--versions'], @@ -97,11 +100,10 @@ t.test('calling with --versions calls npm version with no args', t => { t.teardown(() => { delete npmock.config.settings.versions process.argv = processArgv - console.log = log npmock.argv.length = 0 proc.argv.length = 0 logs.length = 0 - consoleLogs.length = 0 + npmOutputs.length = 0 errorHandlerExitCalled = null delete npmock.commands.version }) @@ -117,7 +119,7 @@ t.test('calling with --versions calls npm version with no args', t => { ['info', 'using', 'node@%s', undefined], ]) - t.strictSame(consoleLogs, []) + t.strictSame(npmOutputs, []) t.strictSame(errorHandlerExitCalled, null) t.strictSame(args, []) @@ -127,55 +129,80 @@ t.test('calling with --versions calls npm version with no args', t => { cli(proc) }) -t.test('print usage if -h provided', t => { - const { log } = console - const consoleLogs = [] - console.log = (...msg) => consoleLogs.push(msg) +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.test('print usage if non-command param provided', t => { + const { output } = npmock + t.teardown(() => { + npmock.output = output + }) const proc = { argv: ['node', 'npm', 'asdf'], on: () => {}, } npmock.argv = ['asdf'] + npmock.output = (msg) => { + if (msg) { + t.match(msg, 'test did you mean', 'outputs did you mean') + t.end() + } + } + cli(proc) +}) +t.test('gracefully handles error printing usage', t => { + const { output } = npmock t.teardown(() => { - console.log = log - npmock.argv.length = 0 - proc.argv.length = 0 - logs.length = 0 - consoleLogs.length = 0 - errorHandlerExitCalled = null - delete npmock.commands.help + npmock.output = output + errorHandlerCb = null }) - - npmock.commands.help = (args, cb) => { - delete npmock.commands.help - t.equal(proc.title, 'npm') - t.strictSame(args, ['asdf']) - t.strictSame(npmock.argv, ['asdf']) - t.strictSame(proc.argv, ['node', 'npm', 'asdf']) - t.strictSame(logs, [ - 'pause', - ['verbose', 'cli', ['node', 'npm', 'asdf']], - ['info', 'using', 'npm@%s', '99.99.99'], - ['info', 'using', 'node@%s', undefined], - ]) - t.strictSame(consoleLogs, []) - t.strictSame(errorHandlerExitCalled, null) + const proc = { + argv: ['node', 'npm', 'asdf'], + on: () => {}, + } + npmock.argv = [] + npmock.output = (msg) => { + throw new Error('test exception') + } + errorHandlerCb = () => { + t.match(errorHandlerCalled, /test exception/) t.end() } - cli(proc) }) t.test('load error calls error handler', t => { - const er = new Error('poop') + t.teardown(() => { + errorHandlerCb = null + LOAD_ERROR = null + }) + + 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) - t.strictSame(errorHandlerCalled, [er]) - LOAD_ERROR = null - t.end() }) diff --git a/test/lib/help-search.js b/test/lib/help-search.js index cbb3947d3..f046ba3d2 100644 --- a/test/lib/help-search.js +++ b/test/lib/help-search.js @@ -2,33 +2,29 @@ const { test } = require('tap') const { join } = require('path') const requireInject = require('require-inject') const ansicolors = require('ansicolors') -const mockNpm = require('../fixtures/mock-npm') const OUTPUT = [] const output = (msg) => { OUTPUT.push(msg) } -let npmHelpArgs = null -let npmHelpErr = null -const config = { +const config = new Map(Object.entries({ long: false, -} -const npm = mockNpm({ +})) +const npmHelpErr = null +const npm = { color: false, config, + flatOptions: { + long: false, + }, + usage: 'npm test usage', commands: { help: (args, cb) => { - npmHelpArgs = args return cb(npmHelpErr) }, }, output, -}) - -let npmUsageArg = null -const npmUsage = (npm, arg) => { - npmUsageArg = arg } let globRoot = null @@ -47,7 +43,6 @@ const glob = (p, cb) => cb(null, Object.keys(globDir).map((file) => join(globRoot, file))) const HelpSearch = requireInject('../../lib/help-search.js', { - '../../lib/utils/npm-usage.js': npmUsage, glob, }) const helpSearch = new HelpSearch(npm) @@ -63,8 +58,7 @@ test('npm help-search', t => { if (err) throw err - t.match(OUTPUT, /Top hits for/, 'outputs results') - t.match(OUTPUT, /Did you mean this\?\n\s+exec/, 'matched command, so suggest it') + t.match(OUTPUT, /Top hits for "exec"/, 'outputs results') t.end() }) }) @@ -86,46 +80,12 @@ test('npm help-search multiple terms', t => { }) }) -test('npm help-search single result prints full section', t => { - globRoot = t.testdir(globDir) - t.teardown(() => { - OUTPUT.length = 0 - npmHelpArgs = null - globRoot = null - }) - - return helpSearch.exec(['does not exist in'], (err) => { - if (err) - throw err - - t.strictSame(npmHelpArgs, ['npm-install'], 'identified the correct man page and called help with it') - t.end() - }) -}) - -test('npm help-search single result propagates error', t => { - globRoot = t.testdir(globDir) - npmHelpErr = new Error('help broke') - t.teardown(() => { - OUTPUT.length = 0 - npmHelpArgs = null - npmHelpErr = null - globRoot = null - }) - - return helpSearch.exec(['does not exist in'], (err) => { - t.strictSame(npmHelpArgs, ['npm-install'], 'identified the correct man page and called help with it') - t.match(err, /help broke/, 'propagated the error from help') - t.end() - }) -}) - test('npm help-search long output', t => { globRoot = t.testdir(globDir) - config.long = true + config.set('long', true) t.teardown(() => { OUTPUT.length = 0 - config.long = false + config.set('long', false) globRoot = null }) @@ -140,11 +100,11 @@ test('npm help-search long output', t => { test('npm help-search long output with color', t => { globRoot = t.testdir(globDir) - config.long = true + config.set('long', true) npm.color = true t.teardown(() => { OUTPUT.length = 0 - config.long = false + config.set('long', false) npm.color = false globRoot = null }) @@ -161,7 +121,8 @@ test('npm help-search long output with color', t => { test('npm help-search no args', t => { return helpSearch.exec([], (err) => { - t.match(err, /npm help-search/, 'throws usage') + t.notOk(err) + t.match(OUTPUT, /npm help-search/, 'outputs usage') t.end() }) }) @@ -170,7 +131,6 @@ test('npm help-search no matches', t => { globRoot = t.testdir(globDir) t.teardown(() => { OUTPUT.length = 0 - npmUsageArg = null globRoot = null }) @@ -178,7 +138,7 @@ test('npm help-search no matches', t => { if (err) throw err - t.equal(npmUsageArg, false, 'called npmUsage for no matches') + t.match(OUTPUT, /No matches/) t.end() }) }) diff --git a/test/lib/help.js b/test/lib/help.js index ae2f7e99d..ccf13a7e4 100644 --- a/test/lib/help.js +++ b/test/lib/help.js @@ -2,11 +2,6 @@ const { test } = require('tap') const requireInject = require('require-inject') const { EventEmitter } = require('events') -let npmUsageArg = null -const npmUsage = (npm, arg) => { - npmUsageArg = arg -} - const npmConfig = { usage: false, viewer: undefined, @@ -16,6 +11,7 @@ const npmConfig = { let helpSearchArgs = null const OUTPUT = [] const npm = { + usage: 'test npm usage', config: { get: (key) => npmConfig[key], set: (key, value) => { @@ -48,7 +44,9 @@ const globDefaults = [ let globErr = null let globResult = globDefaults +let globParam const glob = (p, cb) => { + globParam = p return cb(globErr, globResult) } @@ -71,7 +69,6 @@ const openUrl = async (npm, url, msg) => { } const Help = requireInject('../../lib/help.js', { - '../../lib/utils/npm-usage.js': npmUsage, '../../lib/utils/open-url.js': openUrl, child_process: { spawn, @@ -81,15 +78,11 @@ const Help = requireInject('../../lib/help.js', { const help = new Help(npm) test('npm help', t => { - t.teardown(() => { - npmUsageArg = null - }) - return help.exec([], (err) => { if (err) throw err - t.equal(npmUsageArg, false, 'called npmUsage') + t.match(OUTPUT, ['test npm usage'], 'showed npm usage') t.end() }) }) @@ -107,22 +100,6 @@ test('npm help completion', async t => { t.rejects(help.completion({ conf: { argv: { remain: [] } } }), /glob failed/, 'glob errors propagate') }) -test('npm help -h', t => { - npmConfig.usage = true - t.teardown(() => { - npmConfig.usage = false - OUTPUT.length = 0 - }) - - return help.exec(['help'], (err) => { - if (err) - throw err - - t.strictSame(OUTPUT, ['npm help <term>'], 'outputs usage information for command') - t.end() - }) -}) - test('npm help multiple args calls search', t => { t.teardown(() => { helpSearchArgs = null @@ -180,7 +157,7 @@ test('npm help whoami', t => { throw err t.equal(spawnBin, 'man', 'calls man by default') - t.strictSame(spawnArgs, ['1', 'npm-whoami'], 'passes the correct arguments') + t.strictSame(spawnArgs, [globResult[0]], 'passes the correct arguments') t.end() }) }) @@ -212,12 +189,12 @@ test('npm help 5 install', t => { npmConfig.viewer = 'browser' globResult = [ '/root/man/man5/install.5', - '/root/man/man1/npm-install.1', ] t.teardown(() => { npmConfig.viewer = undefined globResult = globDefaults + globParam = null spawnBin = null spawnArgs = null }) @@ -226,6 +203,7 @@ test('npm help 5 install', t => { if (err) throw err + t.match(globParam, /man5/, 'searches only in man5 folder') t.match(openUrlArg, /configuring-npm(\/|\\)install.html$/, 'attempts to open the correct url') t.end() }) @@ -234,11 +212,11 @@ test('npm help 5 install', t => { test('npm help 7 config', t => { npmConfig.viewer = 'browser' globResult = [ - '/root/man/man1/npm-config.1', '/root/man/man7/config.7', ] t.teardown(() => { npmConfig.viewer = undefined + globParam = null globResult = globDefaults spawnBin = null spawnArgs = null @@ -248,49 +226,12 @@ test('npm help 7 config', t => { if (err) throw err + t.match(globParam, /man7/, 'searches only in man5 folder') t.match(openUrlArg, /using-npm(\/|\\)config.html$/, 'attempts to open the correct url') t.end() }) }) -test('npm help with browser viewer and invalid section throws', t => { - npmConfig.viewer = 'browser' - globResult = [ - '/root/man/man1/npm-config.1', - '/root/man/man7/config.7', - '/root/man/man9/config.9', - ] - t.teardown(() => { - npmConfig.viewer = undefined - globResult = globDefaults - spawnBin = null - spawnArgs = null - }) - - return help.exec(['9', 'config'], (err) => { - t.match(err, /invalid man section: 9/, 'throws appropriate error') - t.end() - }) -}) - -test('npm help global redirects to folders', t => { - globResult = ['/root/man/man5/folders.5'] - t.teardown(() => { - globResult = globDefaults - spawnBin = null - spawnArgs = null - }) - - return help.exec(['global'], (err) => { - if (err) - throw err - - t.equal(spawnBin, 'man', 'calls man by default') - t.strictSame(spawnArgs, ['5', 'folders'], 'passes the correct arguments') - t.end() - }) -}) - test('npm help package.json redirects to package-json', t => { globResult = ['/root/man/man5/package-json.5'] t.teardown(() => { @@ -304,7 +245,8 @@ test('npm help package.json redirects to package-json', t => { throw err t.equal(spawnBin, 'man', 'calls man by default') - t.strictSame(spawnArgs, ['5', 'package-json'], 'passes the correct arguments') + t.match(globParam, /package-json/, 'glob was asked to find package-json') + t.strictSame(spawnArgs, [globResult[0]], 'passes the correct arguments') t.end() }) }) @@ -327,7 +269,7 @@ test('npm help ?(un)star', t => { throw err t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly') - t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-unstar.1')`], 'passes the correct arguments') + t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-star.1')`], 'passes the correct arguments') t.end() }) }) @@ -350,7 +292,7 @@ test('npm help - woman viewer propagates errors', t => { return help.exec(['?(un)star'], (err) => { t.match(err, /help process exited with code: 1/, 'received the correct error') t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly') - t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-unstar.1')`], 'passes the correct arguments') + t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-star.1')`], 'passes the correct arguments') t.end() }) }) @@ -372,7 +314,7 @@ test('npm help un*', t => { throw err t.equal(spawnBin, 'man', 'calls man by default') - t.strictSame(spawnArgs, ['1', 'npm-unstar'], 'passes the correct arguments') + t.strictSame(spawnArgs, ['/root/man/man1/npm-uninstall.1'], 'passes the correct arguments') t.end() }) }) @@ -394,7 +336,7 @@ test('npm help - man viewer propagates errors', t => { return help.exec(['un*'], (err) => { t.match(err, /help process exited with code: 1/, 'received correct error') t.equal(spawnBin, 'man', 'calls man by default') - t.strictSame(spawnArgs, ['1', 'npm-unstar'], 'passes the correct arguments') + t.strictSame(spawnArgs, ['/root/man/man1/npm-uninstall.1'], 'passes the correct arguments') t.end() }) }) diff --git a/test/lib/load-all-commands.js b/test/lib/load-all-commands.js index e31a2b993..2a2d41818 100644 --- a/test/lib/load-all-commands.js +++ b/test/lib/load-all-commands.js @@ -1,27 +1,35 @@ -// Thanks to nyc not working properly with proxies this -// doesn't affect coverage. but it does ensure that every command has a usage -// that contains its name, and if it has completion it is a function -const npm = require('../../lib/npm.js') +// Thanks to nyc not working properly with proxies this doesn't affect +// coverage. but it does ensure that every command has a usage that contains +// its name, a description, and if it has completion it is a function +const requireInject = require('require-inject') +const npm = requireInject('../../lib/npm.js') const t = require('tap') const { cmdList } = require('../../lib/utils/cmd-list.js') -t.test('load npm', t => npm.load(er => { - if (er) - throw er -})) - +let npmOutput = [] +npm.output = (msg) => { + npmOutput = msg +} t.test('load each command', t => { - t.plan(cmdList.length) - for (const cmd of cmdList.sort((a, b) => a.localeCompare(b))) { - t.test(cmd, t => { - const impl = npm.commands[cmd] - if (impl.completion) { - t.plan(3) - t.isa(impl.completion, 'function', 'completion, if present, is a function') - } else - t.plan(2) - t.isa(impl, 'function', 'implementation is a function') - t.match(impl.usage, cmd, 'usage contains the command') - }) - } + t.plan(cmdList.length + 1) + npm.load((er) => { + t.notOk(er) + npm.config.set('usage', true) + for (const cmd of cmdList.sort((a, b) => a.localeCompare(b))) { + t.test(cmd, t => { + const impl = npm.commands[cmd] + if (impl.completion) + t.isa(impl.completion, 'function', 'completion, if present, is a function') + t.isa(impl, 'function', 'implementation is a function') + t.ok(impl.description, 'implementation has a description') + t.ok(impl.name, 'implementation has a name') + t.match(impl.usage, cmd, 'usage contains the command') + impl([], (err) => { + t.notOk(err) + t.match(npmOutput, impl.usage, 'usage is output') + t.end() + }) + }) + } + }) }) diff --git a/test/lib/npm.js b/test/lib/npm.js index dfb3cca64..eb0f8ab27 100644 --- a/test/lib/npm.js +++ b/test/lib/npm.js @@ -144,6 +144,7 @@ t.test('npm.load', t => { 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, diff --git a/test/lib/run-script.js b/test/lib/run-script.js index c598ea707..d2cac2f42 100644 --- a/test/lib/run-script.js +++ b/test/lib/run-script.js @@ -16,6 +16,11 @@ const npm = mockNpm({ localPrefix: __dirname, flatOptions, config, + commands: { + help: { + description: 'test help description', + }, + }, output: (...msg) => output.push(msg), }) @@ -259,20 +264,35 @@ t.test('try to run missing script', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ scripts: { hello: 'world' }, + bin: { goodnight: 'moon' }, }), }) t.test('no suggestions', t => { runScript.exec(['notevenclose'], er => { t.match(er, { - message: 'missing script: notevenclose', + message: 'Unknown command: "notevenclose"', }) t.end() }) }) - t.test('suggestions', t => { + t.test('script suggestions', t => { runScript.exec(['helo'], er => { t.match(er, { - message: 'missing script: helo\n\nDid you mean this?\n hello', + message: 'Unknown command: "helo"', + }) + t.match(er, { + message: 'npm run hello', + }) + t.end() + }) + }) + t.test('bin suggestions', t => { + runScript.exec(['goodneght'], er => { + t.match(er, { + message: 'Unknown command: "goodneght"', + }) + t.match(er, { + message: 'npm exec goodnight', }) t.end() }) diff --git a/test/lib/utils/did-you-mean.js b/test/lib/utils/did-you-mean.js index 0c9c95c7f..48b6d4027 100644 --- a/test/lib/utils/did-you-mean.js +++ b/test/lib/utils/did-you-mean.js @@ -1,7 +1,31 @@ const t = require('tap') +const requireInject = require('require-inject') +const npm = requireInject('../../../lib/npm.js') + const dym = require('../../../lib/utils/did-you-mean.js') -t.equal(dym('asdfa', ['asdf', 'asfd', 'adfs', 'safd', 'foobarbaz', 'foobar']), - '\nDid you mean this?\n asdf') -t.equal(dym('asdfa', ['asdf', 'sdfa', 'foo', 'bar', 'fdsa']), - '\nDid you mean one of these?\n asdf\n sdfa') -t.equal(dym('asdfa', ['install', 'list', 'test']), '') +t.test('did-you-mean', t => { + npm.load(err => { + t.notOk(err) + t.test('nistall', async t => { + const result = await dym(npm, 'nistall') + t.match(result, 'Unknown command') + t.match(result, 'npm install') + }) + t.test('sttest', async t => { + const result = await dym(npm, 'sttest') + t.match(result, 'Unknown command') + t.match(result, 'npm test') + t.match(result, 'npm run posttest') + }) + t.test('npz', async t => { + const result = await dym(npm, 'npxx') + t.match(result, 'Unknown command') + t.match(result, 'npm exec npx') + }) + t.test('qwuijbo', async t => { + const result = await dym(npm, 'qwuijbo') + t.match(result, 'Unknown command') + }) + t.end() + }) +}) diff --git a/test/lib/utils/npm-usage.js b/test/lib/utils/npm-usage.js index fbc453811..ebf637ae1 100644 --- a/test/lib/utils/npm-usage.js +++ b/test/lib/utils/npm-usage.js @@ -1,12 +1,5 @@ const t = require('tap') - -const OUTPUT = [] -const output = (...msg) => OUTPUT.push(msg) -const requireInject = require('require-inject') -const usage = require('../../../lib/utils/npm-usage.js') - -const npm = requireInject('../../../lib/npm.js') -npm.output = output +const npm = require('../../../lib/npm.js') t.test('usage', t => { t.afterEach((cb) => { @@ -29,61 +22,19 @@ t.test('usage', t => { npm.config.set('userconfig', '/some/config/file/.npmrc') t.test('basic usage', t => { - usage(npm) - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 + t.matchSnapshot(npm.usage) t.end() }) t.test('with browser', t => { npm.config.set('viewer', 'browser') - usage(npm) - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 - npm.config.set('viewer', null) + t.matchSnapshot(npm.usage) t.end() }) t.test('with long', t => { npm.config.set('long', true) - usage(npm) - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 - npm.config.set('long', false) - t.end() - }) - - t.test('did you mean?', t => { - npm.argv.push('unistnall') - usage(npm) - t.equal(OUTPUT.length, 2) - t.equal(OUTPUT[0].length, 1) - t.equal(OUTPUT[1].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - t.matchSnapshot(OUTPUT[1][0]) - OUTPUT.length = 0 - npm.argv.length = 0 - t.end() - }) - - t.test('did you mean?', t => { - npm.argv.push('unistnall') - const { exitCode } = process - t.teardown(() => { - if (t.passing()) - process.exitCode = exitCode - }) - // make sure it fails when invalid - usage(npm, false) - t.equal(process.exitCode, 1) - OUTPUT.length = 0 - npm.argv.length = 0 + t.matchSnapshot(npm.usage) t.end() }) @@ -106,11 +57,7 @@ t.test('usage', t => { configurable: true, writable: true, }) - usage(npm) - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 + t.matchSnapshot(npm.usage) t.end() }) } |