Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/src/guide/essentials/declarative.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Each option can have the following properties:
<!-- eslint-enable markdown/no-missing-label-refs -->
- `description`: A description of what the option does
- `default`: Default value if the option is not provided
- `required`: Set to `true` if the option is required (Note: Positional arguments defined with `type: 'positional'` are implicitly required by the parser).
- `required`: Set to `true` if the option is required (Note: Positional arguments defined with `type: 'positional'` without `multiple: true` are implicitly required by the parser).

@ota-meshi ota-meshi Dec 11, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this part probably wasn't correctly explaining the behavior before (v0.27 onwards), so I changed it.

- `multiple`: Set to `true` if multiple option values are allowed
- `toKebab`: Set to `true` to convert camelCase argument names to kebab-case in help text and command-line usage
- `parse`: A function to parse and validate the argument value. Required when `type` is 'custom'
Expand Down
24 changes: 24 additions & 0 deletions packages/plugin-renderer/src/__snapshots__/usage.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,30 @@ OPTIONS:
"
`;

exports[`multiple positional arguments 1`] = `
"A test command

USAGE:
cmd1 test <foo> [<bar> ...]

ARGUMENTS:
foo The foo argument
bar The bar argument
"
`;

exports[`multiple positional arguments with required 1`] = `
"A test command

USAGE:
cmd1 test <foo> <bar> [<bar> ...]

ARGUMENTS:
foo The foo argument
bar The bar argument
"
`;

exports[`no arguments 1`] = `
"A test command

Expand Down
87 changes: 87 additions & 0 deletions packages/plugin-renderer/src/usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,93 @@ test('mixed positionals and optionals', async () => {
expect(await renderUsage<WithI18nAndRenderer>(ctx)).toMatchSnapshot()
})

test('multiple positional arguments', async () => {
const command = {
args: {
foo: {
type: 'positional',
description: 'The foo argument'
},
bar: {
type: 'positional',
description: 'The bar argument',
multiple: true
}
},
name: 'test',
description: 'A test command',
run: NOOP
} as Command<GunshiParams<{ args: Args }>>

const ctx = await createCommandContext({
args: command.args!,
explicit: {},
values: {},
positionals: [],
rest: [],
argv: [],
tokens: [], // dummy, due to test
omitted: false,
callMode: 'subCommand',
command,
extensions: {
[i18nPlugin.id]: i18nPlugin.extension,
[rendererPlugin.id]: rendererPlugin.extension
},
cliOptions: {
cwd: '/path/to/cmd1',
version: '0.0.0',
name: 'cmd1'
}
})

expect(await renderUsage<WithI18nAndRenderer>(ctx)).toMatchSnapshot()
})

test('multiple positional arguments with required', async () => {
const command = {
args: {
foo: {
type: 'positional',
description: 'The foo argument'
},
bar: {
type: 'positional',
description: 'The bar argument',
multiple: true,
required: true
}
},
name: 'test',
description: 'A test command',
run: NOOP
} as Command<GunshiParams<{ args: Args }>>

const ctx = await createCommandContext({
args: command.args!,
explicit: {},
values: {},
positionals: [],
rest: [],
argv: [],
tokens: [], // dummy, due to test
omitted: false,
callMode: 'subCommand',
command,
extensions: {
[i18nPlugin.id]: i18nPlugin.extension,
[rendererPlugin.id]: rendererPlugin.extension
},
cliOptions: {
cwd: '/path/to/cmd1',
version: '0.0.0',
name: 'cmd1'
}
})

expect(await renderUsage<WithI18nAndRenderer>(ctx)).toMatchSnapshot()
})

test('no examples', async () => {
const command = {
args: {
Expand Down
11 changes: 10 additions & 1 deletion packages/plugin-renderer/src/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,16 @@ async function generatePositionalArgsUsage<
function generatePositionalSymbols(args: Args): string {
return hasPositionalArgs(args)
? getPositionalArgs(args)
.map(([name]) => `<${name}>`)
.map(([name, arg]) => {
const elements: string[] = []
if (!arg.multiple || arg.required) {
elements.push(`<${name}>`)
}
if (arg.multiple) {
elements.push(`[<${name}> ...]`)
}
return elements.join(' ')
})
.join(' ')
: ''
}
Loading