From b876442241b9d366a0541714bbee1ae50d6746fd Mon Sep 17 00:00:00 2001 From: Gar Date: Fri, 19 Mar 2021 11:56:18 -0700 Subject: [PATCH] fix(usage): tie usage to config This starts us down the path of tying the params our commands accept to their config items. For now it is optional, and not every current config item would cleanly render if we added them today. The ones that are added here DO render nicely, and we can iterate from here. We can also at a later date do the same kind of appraoch with our positional args. PR-URL: https://github.com/npm/cli/pull/2908 Credit: @wraithgar Close: #2908 Reviewed-by: @nlf, @isaacs --- docs/content/using-npm/config.md | 30 +++++++ lib/adduser.js | 8 +- lib/audit.js | 14 +++- lib/base-command.js | 4 + lib/bin.js | 4 +- lib/fund.js | 12 ++- lib/install.js | 10 ++- lib/logout.js | 7 +- lib/pack.js | 7 +- lib/ping.js | 5 ++ lib/prune.js | 7 +- lib/publish.js | 7 +- lib/root.js | 4 +- lib/search.js | 12 ++- lib/uninstall.js | 7 +- lib/update.js | 7 +- lib/utils/config/definition.js | 36 +++++++- lib/utils/config/definitions.js | 13 +++ lib/whoami.js | 4 +- tap-snapshots/test-lib-publish.js-TAP.test.js | 5 +- ...ib-utils-config-definitions.js-TAP.test.js | 1 + ...b-utils-config-describe-all.js-TAP.test.js | 7 ++ .../test-lib-utils-npm-usage.js-TAP.test.js | 84 +++++++++++++++---- test/lib/load-all-commands.js | 6 +- test/lib/set-script.js | 5 ++ test/lib/shrinkwrap.js | 1 + test/lib/utils/config/definition.js | 35 ++++++++ 27 files changed, 297 insertions(+), 45 deletions(-) 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 => {