-
-
Notifications
You must be signed in to change notification settings - Fork 151
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 commands
option
#205
Add commands
option
#205
Changes from 1 commit
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 |
---|---|---|
|
@@ -26,6 +26,8 @@ type NumberFlag = Flag<'number', number> | Flag<'number', number[], true>; | |
type AnyFlag = StringFlag | BooleanFlag | NumberFlag; | ||
type AnyFlags = Record<string, AnyFlag>; | ||
|
||
type CommandType<Flags extends AnyFlags> = (helpMessage: string, options?: Options<Flags>) => typeof meow; | ||
|
||
export interface Options<Flags extends AnyFlags> { | ||
/** | ||
Pass in [`import.meta`](https://nodejs.org/dist/latest/docs/api/esm.html#esm_import_meta). This is used to find the correct package.json file. | ||
|
@@ -68,6 +70,30 @@ export interface Options<Flags extends AnyFlags> { | |
*/ | ||
readonly flags?: Flags; | ||
|
||
/** | ||
Define subcommands. | ||
|
||
The key is the name of the subcommand and the value is a function that returns an instance of `meow`. | ||
|
||
The following values get passed to the subcommand function: | ||
- `helpText`: The help text of parent `meow` instance. | ||
- `options`: The options from the parent `meow` instance. | ||
|
||
@example | ||
``` | ||
commands: { | ||
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. The example should be complete and runnable. I think it's better to define the subcommand function at the top-level, for readability. 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. The example should also illustrate how one might check for and call code for the subcommand. |
||
unicorn: (helpText, options) => meow({ | ||
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. The |
||
...options, | ||
description: 'Subcommand description', | ||
flags: { | ||
unicorn: {alias: 'u'}, | ||
} | ||
}) | ||
} | ||
``` | ||
*/ | ||
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 also needs to be documented in the readme. |
||
readonly commands?: Record<string, CommandType<Flags>>; | ||
|
||
/** | ||
Description to show above the help text. Default: The package.json `"description"` property. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -205,6 +205,16 @@ const meow = (helpText, options = {}) => { | |
} | ||
} | ||
|
||
// Subcommands | ||
const commands = {}; | ||
for (const [command, meowInstance] of Object.entries(options.commands ?? {})) { | ||
if (input[0] !== command) { | ||
continue; | ||
} | ||
|
||
commands[command] = meowInstance(helpText, {...options, argv: process.argv.slice(3), commands: {}}); | ||
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. Pass |
||
} | ||
|
||
const flags = camelCaseKeys(argv, {exclude: ['--', /^\w$/]}); | ||
const unnormalizedFlags = {...flags}; | ||
|
||
|
@@ -222,6 +232,7 @@ const meow = (helpText, options = {}) => { | |
|
||
return { | ||
input, | ||
commands, | ||
flags, | ||
unnormalizedFlags, | ||
pkg: package_, | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,37 @@ | ||||||
#!/usr/bin/env node | ||||||
import meow from '../../index.js'; | ||||||
|
||||||
const subcommand = (helpText, options = {}) => meow({ | ||||||
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
The |
||||||
...options, | ||||||
description: 'Subcommand description', | ||||||
help: ` | ||||||
Unicorn command | ||||||
Usage: | ||||||
foo unicorn <input> | ||||||
`, | ||||||
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. Tab-indentation. |
||||||
flags: { | ||||||
unicorn: {alias: 'u', isRequired: true}, | ||||||
}, | ||||||
}); | ||||||
|
||||||
const cli = meow({ | ||||||
importMeta: import.meta, | ||||||
description: 'Custom description', | ||||||
help: ` | ||||||
Usage | ||||||
foo unicorn <input> | ||||||
`, | ||||||
commands: { | ||||||
unicorn: subcommand, | ||||||
}, | ||||||
flags: { | ||||||
test: { | ||||||
type: 'number', | ||||||
alias: 't', | ||||||
isRequired: () => false, | ||||||
isMultiple: true, | ||||||
}, | ||||||
}, | ||||||
}); | ||||||
|
||||||
console.log(JSON.stringify(cli)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import path from 'node:path'; | ||
import {fileURLToPath} from 'node:url'; | ||
import test from 'ava'; | ||
import execa from 'execa'; | ||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
const fixtureSubcommands = path.join(__dirname, 'fixtures', 'fixture-commands.js'); | ||
|
||
test('spawn CLI and test subcommands', async t => { | ||
const {stdout} = await execa(fixtureSubcommands, [ | ||
'unicorn', | ||
'--unicorn', | ||
]); | ||
const {commands} = JSON.parse(stdout); | ||
t.assert('unicorn' in commands); | ||
t.deepEqual(commands.unicorn.input, []); | ||
t.deepEqual(commands.unicorn.commands, {}); | ||
t.deepEqual(commands.unicorn.flags, {unicorn: true}); | ||
}); | ||
|
||
test('spawn CLI and test subcommand flags', async t => { | ||
const error = await t.throwsAsync(execa(fixtureSubcommands, ['unicorn'])); | ||
const {stderr} = error; | ||
t.regex(stderr, /Missing required flag/); | ||
t.regex(stderr, /--unicorn/); | ||
}); | ||
|
||
test('spawn CLI and test subcommand help text', async t => { | ||
const {stdout} = await execa(fixtureSubcommands, [ | ||
'unicorn', | ||
'--help', | ||
]); | ||
t.regex(stdout, /Subcommand description/); | ||
t.regex(stdout, /Unicorn command/); | ||
}); | ||
|
||
test('spawn CLI and test CLI help text', async t => { | ||
const {stdout} = await execa(fixtureSubcommands, [ | ||
'--help', | ||
]); | ||
t.regex(stdout, /Custom description/); | ||
}); |
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.
Use tab-indentation.
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.
The description needs to be more extensive. It should describe how it works (that it doesn't actually call any commands for you, etc).