diff --git a/docs/content/using-npm/config.md b/docs/content/using-npm/config.md index 435f9cee237da..1eedc26a5fbdf 100644 --- a/docs/content/using-npm/config.md +++ b/docs/content/using-npm/config.md @@ -96,6 +96,8 @@ The following shorthands are parsed on the command-line: * `-H`: `--usage` * `--help`: `--usage` * `-v`: `--version` +* `-w`: `--workspace` +* `--ws`: `--workspaces` * `-y`: `--yes` @@ -1311,6 +1313,34 @@ The program to use to view help content. Set to `"browser"` to view html help content in the default web browser. +#### `which` + +* Default: null +* Type: null or Number + +If there are multiple funding sources, which 1-indexed source URL to open. + +#### `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) + +#### `workspaces` + +* Default: false +* Type: Boolean + +Enable running a command in the context of **all** the configured +workspaces. + #### `yes` * Default: false diff --git a/lib/adduser.js b/lib/adduser.js index f761ae38664b2..f35b9829fe946 100644 --- a/lib/adduser.js +++ b/lib/adduser.js @@ -17,8 +17,12 @@ class AddUser extends BaseCommand { return 'adduser' } - static get usage () { - return ['[--registry=url] [--scope=@orgname] [--always-auth]'] + static get params () { + return [ + 'registry', + 'scope', + 'always-auth', + ] } exec (args, cb) { diff --git a/lib/audit.js b/lib/audit.js index a752f202f5dff..f990e1fa5efaa 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -16,13 +16,21 @@ class Audit extends BaseCommand { } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get usage () { + static get params () { return [ - '[--json] [--production]', - 'fix [--force|--package-lock-only|--dry-run|--production|--only=(dev|prod)]', + 'dry-run', + 'force', + 'json', + 'package-lock-only', + 'production', ] } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get usage () { + return ['[fix]'] + } + async completion (opts) { const argv = opts.conf.argv.remain diff --git a/lib/base-command.js b/lib/base-command.js index a142b3336d10b..7a9e4b8ee377e 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -1,5 +1,6 @@ // Base class for npm.commands[cmd] const usageUtil = require('./utils/usage.js') +const ConfigDefinitions = require('./utils/config/definitions.js') class BaseCommand { constructor (npm) { @@ -25,6 +26,9 @@ class BaseCommand { else usage = `${usage}${this.constructor.usage.map(u => `npm ${this.constructor.name} ${u}`).join('\n')}` + if (this.constructor.params) + usage = `${usage}\n\nOptions:\n[${this.constructor.params.map(p => ConfigDefinitions[p].usage).join('] [')}]` + // Mostly this just appends aliases, this could be more clear usage = usageUtil(this.constructor.name, usage) usage = `${usage}\n\nRun "npm help ${this.constructor.name}" for more info` diff --git a/lib/bin.js b/lib/bin.js index 23098b670d5ef..20e13f160f276 100644 --- a/lib/bin.js +++ b/lib/bin.js @@ -10,8 +10,8 @@ class Bin extends BaseCommand { return 'bin' } - static get usage () { - return ['[-g]'] + static get params () { + return ['global'] } exec (args, cb) { diff --git a/lib/fund.js b/lib/fund.js index 302f10dcf4b41..217a21d1fc3e3 100644 --- a/lib/fund.js +++ b/lib/fund.js @@ -32,9 +32,19 @@ class Fund extends BaseCommand { return 'fund' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return [ + 'json', + 'browser', + 'unicode', + 'which', + ] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[--json] [--browser] [--unicode] [[<@scope>/] [--which=]'] + return ['[[<@scope>/]]'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/install.js b/lib/install.js index 363230d31b199..54ea6d8251051 100644 --- a/lib/install.js +++ b/lib/install.js @@ -21,6 +21,14 @@ class Install extends BaseCommand { return 'install' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return [ + 'save', + 'save-exact', + ] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { return [ @@ -33,7 +41,7 @@ class Install extends BaseCommand { '', '', '', - '/ [--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save]', + '/', ] } diff --git a/lib/logout.js b/lib/logout.js index 3589d287bc5ed..adc19a923af9a 100644 --- a/lib/logout.js +++ b/lib/logout.js @@ -15,8 +15,11 @@ class Logout extends BaseCommand { } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get usage () { - return ['[--registry=] [--scope=<@scope>]'] + static get params () { + return [ + 'registry', + 'scope', + ] } exec (args, cb) { diff --git a/lib/pack.js b/lib/pack.js index 4618e767e64bb..92a2fbd7ac33c 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -21,9 +21,14 @@ class Pack extends BaseCommand { return 'pack' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return ['dry-run'] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[[<@scope>/]...] [--dry-run]'] + return ['[[<@scope>/]...]'] } exec (args, cb) { diff --git a/lib/ping.js b/lib/ping.js index b68ea6b567446..30379377482ec 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -8,6 +8,11 @@ class Ping extends BaseCommand { return 'Ping npm registry' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return ['registry'] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get name () { return 'ping' diff --git a/lib/prune.js b/lib/prune.js index e86d5c05314bb..1da86a3e82187 100644 --- a/lib/prune.js +++ b/lib/prune.js @@ -14,9 +14,14 @@ class Prune extends BaseCommand { return 'prune' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return ['production'] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[[<@scope>/]...] [--production]'] + return ['[[<@scope>/]...]'] } exec (args, cb) { diff --git a/lib/publish.js b/lib/publish.js index 410e911f9a29f..b121cb3d36773 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -28,10 +28,15 @@ class Publish extends BaseCommand { return 'publish' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return ['tag', 'access', 'dry-run'] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { return [ - '[] [--tag ] [--access ] [--dry-run]', + '[]', ] } diff --git a/lib/root.js b/lib/root.js index d24a2e20a99de..635a68e256318 100644 --- a/lib/root.js +++ b/lib/root.js @@ -11,8 +11,8 @@ class Root extends BaseCommand { } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get usage () { - return ['[-g]'] + static get params () { + return ['global'] } exec (args, cb) { diff --git a/lib/search.js b/lib/search.js index aca9113cec354..bdd374ab6edf7 100644 --- a/lib/search.js +++ b/lib/search.js @@ -36,9 +36,19 @@ class Search extends BaseCommand { return 'search' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return [ + 'long', + 'json', + 'parseable', + 'description', + ] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[-l|--long] [--json] [--parseable] [--no-description] [search terms ...]'] + return ['[search terms ...]'] } exec (args, cb) { diff --git a/lib/uninstall.js b/lib/uninstall.js index 16a49b757cd95..11e65533a8e98 100644 --- a/lib/uninstall.js +++ b/lib/uninstall.js @@ -16,9 +16,14 @@ class Uninstall extends BaseCommand { return 'uninstall' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return ['save'] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[<@scope>/][@]... [-S|--save|--no-save]'] + return ['[<@scope>/]...'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/update.js b/lib/update.js index 66a668cd32c94..6a87dd9ecddcf 100644 --- a/lib/update.js +++ b/lib/update.js @@ -18,9 +18,14 @@ class Update extends BaseCommand { return 'update' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return ['global'] + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[-g] [...]'] + return ['[...]'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/utils/config/definition.js b/lib/utils/config/definition.js index b6323a51259dc..cb4eb78210c6e 100644 --- a/lib/utils/config/definition.js +++ b/lib/utils/config/definition.js @@ -15,14 +15,16 @@ const required = [ const allowed = [ 'default', - 'type', + 'defaultDescription', + 'deprecated', 'description', 'flatten', + 'hint', + 'key', 'short', + 'type', 'typeDescription', - 'defaultDescription', - 'deprecated', - 'key', + 'usage', ] const { @@ -43,6 +45,10 @@ class Definition { this.defaultDescription = describeValue(this.default) if (!this.typeDescription) this.typeDescription = describeType(this.type) + if (!this.hint) + this.hint = `<${this.key}>` + if (!this.usage) + this.usage = describeUsage(this) } validate () { @@ -73,6 +79,28 @@ ${description} } } +// Usage for a single param, abstracted because we have arrays of types in +// config definition +const paramUsage = (type, def) => { + let key = `--${def.key}` + if (def.short && typeof def.short === 'string') + key = `-${def.short}|${key}` + if (type === Boolean) + return `${key}` + else + return `${key} ${def.hint}` +} + +const describeUsage = (def) => { + if (Array.isArray(def.type)) { + if (!def.type.some(d => d !== null && typeof d !== 'string')) + return `--${def.key} <${def.type.filter(d => d).join('|')}>` + else + return def.type.filter(d => d).map((t) => paramUsage(t, def)).join('|') + } + return paramUsage(def.type, def) +} + const describeType = type => { if (Array.isArray(type)) { const descriptions = type diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js index 4dd1abe09ed37..9a7c0092f04d9 100644 --- a/lib/utils/config/definitions.js +++ b/lib/utils/config/definitions.js @@ -467,6 +467,7 @@ define('depth', { define('description', { default: true, type: Boolean, + usage: '--no-description', description: ` Show the description in \`npm search\` `, @@ -1287,6 +1288,7 @@ define('otp', { define('package', { default: [], + hint: '[@]', type: [String, Array], description: ` The package to install for [\`npm exec\`](/commands/npm-exec) @@ -1448,6 +1450,7 @@ define('registry', { define('save', { default: true, + usage: '-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer', type: Boolean, short: 'S', description: ` @@ -1611,6 +1614,7 @@ define('scope', { the scope of the current project, if any, or "" `, type: String, + hint: '<@scope>', description: ` Associate an operation with a scope for a scoped registry. @@ -2003,6 +2007,15 @@ define('viewer', { `, }) +define('which', { + default: null, + hint: '', + type: [null, Number], + description: ` + If there are multiple funding sources, which 1-indexed source URL to open. + `, +}) + define('workspace', { default: [], type: [String, Array], diff --git a/lib/whoami.js b/lib/whoami.js index 6e2841359f7e1..82c4520d9e883 100644 --- a/lib/whoami.js +++ b/lib/whoami.js @@ -13,8 +13,8 @@ class Whoami extends BaseCommand { } /* istanbul ignore next - see test/lib/load-all-commands.js */ - static get usage () { - return ['[--registry ]'] + static get params () { + return ['registry'] } exec (args, cb) { diff --git a/tap-snapshots/test-lib-publish.js-TAP.test.js b/tap-snapshots/test-lib-publish.js-TAP.test.js index e777797d70b27..172ed5b29f478 100644 --- a/tap-snapshots/test-lib-publish.js-TAP.test.js +++ b/tap-snapshots/test-lib-publish.js-TAP.test.js @@ -11,7 +11,10 @@ npm publish Publish a package Usage: -npm publish [] [--tag ] [--access ] [--dry-run] +npm publish [] + +Options: +[--tag ] [--access ] [--dry-run] Run "npm help publish" for more info ` diff --git a/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js index d6a72b99b24fc..2ed810da8a284 100644 --- a/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js +++ b/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js @@ -144,6 +144,7 @@ Array [ "version", "versions", "viewer", + "which", "workspace", "workspaces", "yes", diff --git a/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js index 909b31f83cc28..10cc2b2388138 100644 --- a/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js +++ b/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js @@ -1192,6 +1192,13 @@ The program to use to view help content. Set to \`"browser"\` to view html help content in the default web browser. +#### \`which\` + +* Default: null +* Type: null or Number + +If there are multiple funding sources, which 1-indexed source URL to open. + #### \`workspace\` * Default: 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 11f7ff02408ae..260e2ab8f8d50 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 @@ -187,7 +187,10 @@ All commands: Add a registry user account Usage: - npm adduser [--registry=url] [--scope=@orgname] [--always-auth] + npm adduser + + Options: + [--registry ] [--scope <@scope>] [--always-auth] aliases: login, add-user @@ -198,8 +201,10 @@ All commands: Run a security audit Usage: - npm audit [--json] [--production] - npm audit fix [--force|--package-lock-only|--dry-run|--production|--only=(dev|prod)] + npm audit [fix] + + Options: + [--dry-run] [-f|--force] [--json] [--package-lock-only] [--production] Run "npm help audit" for more info @@ -208,7 +213,10 @@ All commands: Display npm bin folder Usage: - npm bin [-g] + npm bin + + Options: + [-g|--global] Run "npm help bin" for more info @@ -396,7 +404,10 @@ All commands: Retrieve funding information Usage: - npm fund [--json] [--browser] [--unicode] [[<@scope>/] [--which=] + npm fund [[<@scope>/]] + + Options: + [--json] [--browser|--browser ] [--unicode] [--which ] Run "npm help fund" for more info @@ -459,7 +470,10 @@ All commands: npm install npm install npm install - npm install / [--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save] + npm install / + + Options: + [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] [-E|--save-exact] aliases: i, in, ins, inst, insta, instal, isnt, isnta, isntal, add @@ -490,7 +504,10 @@ All commands: npm install-test npm install-test npm install-test - npm install-test / [--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save] + npm install-test / + + Options: + [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] [-E|--save-exact] alias: it @@ -524,7 +541,10 @@ All commands: Add a registry user account Usage: - npm adduser [--registry=url] [--scope=@orgname] [--always-auth] + npm adduser + + Options: + [--registry ] [--scope <@scope>] [--always-auth] aliases: login, add-user @@ -535,7 +555,10 @@ All commands: Log out of the registry Usage: - npm logout [--registry=] [--scope=<@scope>] + npm logout + + Options: + [--registry ] [--scope <@scope>] Run "npm help logout" for more info @@ -590,7 +613,10 @@ All commands: Create a tarball from a package Usage: - npm pack [[<@scope>/]...] [--dry-run] + npm pack [[<@scope>/]...] + + Options: + [--dry-run] Run "npm help pack" for more info @@ -601,6 +627,9 @@ All commands: Usage: npm ping + Options: + [--registry ] + Run "npm help ping" for more info prefix npm prefix @@ -629,7 +658,10 @@ All commands: Remove extraneous packages Usage: - npm prune [[<@scope>/]...] [--production] + npm prune [[<@scope>/]...] + + Options: + [--production] Run "npm help prune" for more info @@ -638,7 +670,10 @@ All commands: Publish a package Usage: - npm publish [] [--tag ] [--access ] [--dry-run] + npm publish [] + + Options: + [--tag ] [--access ] [--dry-run] Run "npm help publish" for more info @@ -676,7 +711,10 @@ All commands: Display npm root Usage: - npm root [-g] + npm root + + Options: + [-g|--global] Run "npm help root" for more info @@ -696,7 +734,10 @@ All commands: Search for pacakges Usage: - npm search [-l|--long] [--json] [--parseable] [--no-description] [search terms ...] + npm search [search terms ...] + + Options: + [-l|--long] [--json] [-p|--parseable] [--no-description] aliases: s, se, find @@ -805,7 +846,10 @@ All commands: Remove a package Usage: - npm uninstall [<@scope>/][@]... [-S|--save|--no-save] + npm uninstall [<@scope>/]... + + Options: + [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] aliases: un, unlink, remove, rm, r @@ -834,7 +878,10 @@ All commands: Update packages Usage: - npm update [-g] [...] + npm update [...] + + Options: + [-g|--global] aliases: up, upgrade, udpate @@ -867,7 +914,10 @@ All commands: Display npm username Usage: - npm whoami [--registry ] + npm whoami + + Options: + [--registry ] Run "npm help whoami" for more info diff --git a/test/lib/load-all-commands.js b/test/lib/load-all-commands.js index 2a2d41818fa80..d7eb2eae0a8ad 100644 --- a/test/lib/load-all-commands.js +++ b/test/lib/load-all-commands.js @@ -1,6 +1,8 @@ // 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 +// coverage. but it does ensure that every command has a usage that renders, +// contains its 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 requireInject = require('require-inject') const npm = requireInject('../../lib/npm.js') const t = require('tap') diff --git a/test/lib/set-script.js b/test/lib/set-script.js index 7a057c503652f..b6b6e2519f5ba 100644 --- a/test/lib/set-script.js +++ b/test/lib/set-script.js @@ -31,6 +31,7 @@ test.test('fails when package.json not found', (t) => { }) test.test('fails on invalid JSON', (t) => { const SetScript = requireInject('../../lib/set-script.js', { + '../../lib/utils/config/definitions.js': {}, fs: { readFile: () => {}, // read-package-json-fast explodes w/o this readFileSync: (name, charcode) => { @@ -45,6 +46,7 @@ test.test('fails on invalid JSON', (t) => { test.test('creates scripts object', (t) => { var mockFile = '' const SetScript = requireInject('../../lib/set-script.js', { + '../../lib/utils/config/definitions.js': {}, fs: { readFileSync: (name, charcode) => { return '{}' @@ -70,6 +72,7 @@ test.test('creates scripts object', (t) => { test.test('warns before overwriting', (t) => { var warningListened = '' const SetScript = requireInject('../../lib/set-script.js', { + '../../lib/utils/config/definitions.js': {}, fs: { readFileSync: (name, charcode) => { return JSON.stringify({ @@ -102,6 +105,7 @@ test.test('warns before overwriting', (t) => { test.test('provided indentation and eol is used', (t) => { var mockFile = '' const SetScript = requireInject('../../lib/set-script.js', { + '../../lib/utils/config/definitions.js': {}, fs: { readFileSync: (name, charcode) => { return '{}' @@ -128,6 +132,7 @@ test.test('provided indentation and eol is used', (t) => { test.test('goes to default when undefined indent and eol provided', (t) => { var mockFile = '' const SetScript = requireInject('../../lib/set-script.js', { + '../../lib/utils/config/definitions.js': {}, fs: { readFileSync: (name, charcode) => { return '{}' diff --git a/test/lib/shrinkwrap.js b/test/lib/shrinkwrap.js index b3de4f5fae28e..faf452ea702f2 100644 --- a/test/lib/shrinkwrap.js +++ b/test/lib/shrinkwrap.js @@ -37,6 +37,7 @@ const mocks = { } }, '../../lib/utils/usage.js': () => 'usage instructions', + '../../lib/utils/config/definitions.js': {}, } t.afterEach(cb => { diff --git a/test/lib/utils/config/definition.js b/test/lib/utils/config/definition.js index 25530f7237138..56e10da0cbd7d 100644 --- a/test/lib/utils/config/definition.js +++ b/test/lib/utils/config/definition.js @@ -21,6 +21,8 @@ t.test('basic definition', async t => { default: 'some default value', defaultDescription: '"some default value"', type: [Number, String], + hint: '', + usage: '--key |--key ', typeDescription: 'Number or String', description: 'just a test thingie', }) @@ -78,6 +80,39 @@ t.test('basic definition', async t => { description: 'asdf', }) t.equal(multi123Semver.typeDescription, '1, 2, 3, or SemVer string (can be set multiple times)') + const hasUsage = new Definition('key', { + default: 'test default', + type: String, + description: 'test description', + usage: 'test usage', + }) + t.equal(hasUsage.usage, 'test usage') + const hasShort = new Definition('key', { + default: 'test default', + short: 't', + type: String, + description: 'test description', + }) + t.equal(hasShort.usage, '-t|--key ') + const hardCodedTypes = new Definition('key', { + default: 'test default', + type: ['string1', 'string2'], + description: 'test description', + }) + t.equal(hardCodedTypes.usage, '--key ') + const hardCodedOptionalTypes = new Definition('key', { + default: 'test default', + type: [null, 'string1', 'string2'], + description: 'test description', + }) + t.equal(hardCodedOptionalTypes.usage, '--key ') + const hasHint = new Definition('key', { + default: 'test default', + type: String, + description: 'test description', + hint: '', + }) + t.equal(hasHint.usage, '--key ') }) t.test('missing fields', async t => {