New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add isRequired
flag option
#141
Changes from 4 commits
abd6b12
3d2f6c8
9b0c22a
45fb69a
aebcf85
6bde170
c095990
1d9fa7d
1f6423a
a5c24a2
bf0d816
8b03f9f
854e294
e25cfdc
c54437d
80cc740
019dd32
940fb96
3c1c710
22f0a77
e11a0ad
e5dac8b
1e04bdc
59de8b8
3dee237
4b7a7ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
const meow = require('.'); | ||
|
||
const cli = meow({ | ||
description: 'Custom description', | ||
help: ` | ||
Usage | ||
foo <input> | ||
`, | ||
flags: { | ||
trigger: { | ||
type: 'boolean', | ||
alias: 't' | ||
}, | ||
withTrigger: { | ||
type: 'string', | ||
isRequired: (flags, _) => { | ||
return flags.trigger; | ||
} | ||
} | ||
} | ||
}); | ||
console.log(`${cli.flags.trigger},${cli.flags.withTrigger}`); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
const meow = require('.'); | ||
|
||
const cli = meow({ | ||
description: 'Custom description', | ||
help: ` | ||
Usage | ||
foo <input> | ||
`, | ||
flags: { | ||
test: { | ||
type: 'string', | ||
alias: 't', | ||
isRequired: true | ||
}, | ||
number: { | ||
type: 'number', | ||
isRequired: true | ||
}, | ||
notRequired: { | ||
type: 'string' | ||
} | ||
} | ||
}); | ||
console.log(`${cli.flags.test},${cli.flags.number}`); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,11 +2,13 @@ import {PackageJson} from 'type-fest'; | |||||
|
||||||
declare namespace meow { | ||||||
type FlagType = 'string' | 'boolean' | 'number'; | ||||||
type IsRequiredPredicate = (flags: AnyFlags, input: string[]) => boolean; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
interface Flag<Type extends FlagType, Default> { | ||||||
readonly type?: Type; | ||||||
readonly alias?: string; | ||||||
readonly default?: Default; | ||||||
readonly isRequired?: boolean | IsRequiredPredicate | ||||||
} | ||||||
|
||||||
type StringFlag = Flag<'string', string>; | ||||||
|
@@ -24,6 +26,7 @@ declare namespace meow { | |||||
- `type`: Type of value. (Possible values: `string` `boolean` `number`) | ||||||
- `alias`: Usually used to define a short flag alias. | ||||||
- `default`: Default value when the flag is not specified. | ||||||
- `isRequired`: Boolean or Function that specifies if this flag is required. | ||||||
|
||||||
@example | ||||||
``` | ||||||
|
@@ -32,6 +35,9 @@ declare namespace meow { | |||||
type: 'string', | ||||||
alias: 'u', | ||||||
default: 'rainbow' | ||||||
isRequired: (flags, input) => { | ||||||
if (flags.otherFlag) return true; | ||||||
} | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,16 @@ const meow = (helpText, options) => { | |
options.flags | ||
) : options.flags; | ||
|
||
// Get a list of required flags | ||
const requiredFlags = []; | ||
if (typeof options.flags !== 'undefined') { | ||
for (const flagName of Object.keys(options.flags)) { | ||
if (options.flags[flagName].isRequired) { | ||
requiredFlags.push(flagName); | ||
} | ||
} | ||
} | ||
|
||
let minimistoptions = { | ||
arguments: options.input, | ||
...minimistFlags | ||
|
@@ -118,6 +128,30 @@ const meow = (helpText, options) => { | |
} | ||
} | ||
|
||
// Get a list of missing flags, that are required | ||
const missing = []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "missing" what? Needs a more descriptive name. |
||
for (const requiredFlag of requiredFlags) { | ||
let requiredByFunction = true; | ||
|
||
if (typeof options.flags[requiredFlag].isRequired === 'function') { | ||
requiredByFunction = options.flags[requiredFlag].isRequired(flags, input); | ||
} | ||
|
||
if (typeof flags[requiredFlag] === 'undefined' && requiredByFunction) { | ||
missing.push({key: requiredFlag, ...options.flags[requiredFlag]}); | ||
} | ||
} | ||
|
||
// Print error message for missing flags | ||
if (missing.length > 0) { | ||
console.log(`Missing required option${missing.length > 1 ? 's' : ''}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should be consistent with the word "flag" instead of "option". |
||
for (const flag of missing) { | ||
console.log(`\t--${flag.key}${flag.alias ? `, -${flag.alias}` : ''}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be |
||
} | ||
|
||
process.exit(2); | ||
} | ||
|
||
sindresorhus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return { | ||
input, | ||
flags, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,6 +97,7 @@ The key is the flag name and the value is an object with any of: | |
- `type`: Type of value. (Possible values: `string` `boolean` `number`) | ||
- `alias`: Usually used to define a short flag alias. | ||
- `default`: Default value when the flag is not specified. | ||
- `isRequired`: Boolean or Function that specified is this flag is required. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to describe the parameters the function receives. |
||
|
||
Example: | ||
|
||
|
@@ -106,6 +107,9 @@ flags: { | |
type: 'string', | ||
alias: 'u', | ||
default: 'rainbow' | ||
isRequired: (flags, input) => { | ||
if (flags.otherFlag) return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use braces |
||
} | ||
} | ||
} | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,6 +80,72 @@ test('spawn cli and test input flag', async t => { | |
t.is(stdout, 'bar'); | ||
}); | ||
|
||
test('spawn cli and test required flag with missing args', async t => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you move these new tests into a new test file? And also move all tests into a |
||
try { | ||
await execa('./fixture-required.js', []); | ||
} catch (error) { | ||
const {stdout, message} = error; | ||
t.regex(message, /Command failed with exit code 2/); | ||
t.regex(stdout, /Missing required option/); | ||
t.regex(stdout, /--test, -t/); | ||
t.regex(stdout, /--number/); | ||
t.notRegex(stdout, /--notRequired/); | ||
} | ||
}); | ||
|
||
test('spawn cli and test required flag with all args given', async t => { | ||
const {stdout} = await execa('./fixture-required.js', [ | ||
'-t', | ||
'test', | ||
'--number', | ||
'6' | ||
]); | ||
t.is(stdout, 'test,6'); | ||
}); | ||
|
||
test('spawn cli and test required flag with empty string', async t => { | ||
try { | ||
await execa('./fixture-required.js', ['--test', '']); | ||
} catch (error) { | ||
const {stdout, message} = error; | ||
t.regex(message, /Command failed with exit code 2/); | ||
t.regex(stdout, /Missing required option/); | ||
t.notRegex(stdout, /--test, -t/); | ||
} | ||
}); | ||
|
||
test('spawn cli and test required flag with empty number', async t => { | ||
try { | ||
await execa('./fixture-required.js', ['--number']); | ||
} catch (error) { | ||
const {stdout, message} = error; | ||
t.regex(message, /Command failed with exit code 2/); | ||
t.regex(stdout, /Missing required option/); | ||
t.regex(stdout, /--number/); | ||
} | ||
}); | ||
|
||
test('spawn cli and test required (specified as function) flag with without arguments', async t => { | ||
const {stdout} = await execa('./fixture-required-function.js', []); | ||
t.is(stdout, 'false,undefined'); | ||
}); | ||
|
||
test('spawn cli and test required (specified as function) flag with trigger only', async t => { | ||
try { | ||
await execa('./fixture-required-function.js', ['--trigger']); | ||
} catch (error) { | ||
const {stdout, message} = error; | ||
t.regex(message, /Command failed with exit code 2/); | ||
t.regex(stdout, /Missing required option/); | ||
t.regex(stdout, /--withTrigger/); | ||
} | ||
}); | ||
|
||
test('spawn cli and test required (specified as function) flag with trigger and dynamically required option', async t => { | ||
const {stdout} = await execa('./fixture-required-function.js', ['--trigger', '--withTrigger', 'specified']); | ||
t.is(stdout, 'true,specified'); | ||
}); | ||
|
||
test.serial('pkg.bin as a string should work', t => { | ||
meow({ | ||
pkg: { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of fixtures now. Can you move them into a
test/fixtures
directory?