@gunshi/docs 0.27.6 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/api/context/interfaces/CommandContextParams.md +1 -0
- package/src/api/default/interfaces/Command.md +1 -0
- package/src/api/default/interfaces/CommandContext.md +1 -0
- package/src/api/default/interfaces/SubCommandable.md +1 -0
- package/src/api/definition/functions/define.md +2 -2
- package/src/api/generator/functions/generate.md +1 -1
- package/src/guide/advanced/nested-sub-commands.md +238 -0
- package/src/guide/essentials/composable.md +35 -0
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ Robust, modular, flexible, and localizable CLI library
|
|
|
33
33
|
- [Internationalization](src/guide/advanced/internationalization)
|
|
34
34
|
- [Documentation Generation](src/guide/advanced/docs-gen)
|
|
35
35
|
- [Advanced Lazy Loading and Sub-Commands](src/guide/advanced/advanced-lazy-loading)
|
|
36
|
+
- [Nested Sub-Commands](src/guide/advanced/nested-sub-commands)
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
### API References
|
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@ Parameters of [createCommandContext](../functions/createCommandContext.md)
|
|
|
22
22
|
| <a id="callmode"></a> `callMode?` | [`CommandCallMode`](../../default/type-aliases/CommandCallMode.md) | Command call mode. |
|
|
23
23
|
| <a id="clioptions"></a> `cliOptions?` | [`CliOptions`](../../default/interfaces/CliOptions.md)\<`G`\> | A command options, which is spicialized from `cli` function |
|
|
24
24
|
| <a id="command"></a> `command?` | `C` | A target command |
|
|
25
|
+
| <a id="commandpath"></a> `commandPath?` | `string`[] | The path of nested sub-commands resolved to reach the current command. |
|
|
25
26
|
| <a id="explicit"></a> `explicit?` | `ArgExplicitlyProvided`\<`ExtractArgs`\<`G`\>\> | Explicitly provided arguments |
|
|
26
27
|
| <a id="extensions"></a> `extensions?` | `E` | Plugin extensions to apply as the command context extension. |
|
|
27
28
|
| <a id="omitted"></a> `omitted?` | `boolean` | Whether the command is omitted |
|
|
@@ -22,4 +22,5 @@ Command interface.
|
|
|
22
22
|
| <a id="name"></a> `name?` | `string` | Command name. It's used to find command line arguments to execute from sub commands, and it's recommended to specify. |
|
|
23
23
|
| <a id="rendering"></a> `rendering?` | [`RenderingOptions`](RenderingOptions.md)\<`G`\> | Rendering control options **Since** v0.27.0 |
|
|
24
24
|
| <a id="run"></a> `run?` | [`CommandRunner`](../type-aliases/CommandRunner.md)\<`G`\> | Command runner. it's the command to be executed |
|
|
25
|
+
| <a id="subcommands"></a> `subCommands?` | \| `Record`\<`string`, [`SubCommandable`](SubCommandable.md)\> \| `Map`\<`string`, [`SubCommandable`](SubCommandable.md)\> | Nested sub-commands for this command. Allows building command trees like `git remote add`. Each key is the sub-command name, and the value is a command or lazy command. **Since** v0.28.0 |
|
|
25
26
|
| <a id="tokebab"></a> `toKebab?` | `boolean` | Whether to convert the camel-case style argument name to kebab-case. If you will set to `true`, All [`Command.args`](#args) names will be converted to kebab-case. |
|
|
@@ -19,6 +19,7 @@ Command context is the context of the command execution.
|
|
|
19
19
|
| <a id="_"></a> `_` | `string`[] | Original command line arguments. This argument is passed from `cli` function. |
|
|
20
20
|
| <a id="args"></a> `args` | `ExtractArgs`\<`G`\> | Command arguments, that is the arguments of the command that is executed. The command arguments is same [`Command.args`](Command.md#args). |
|
|
21
21
|
| <a id="callmode"></a> `callMode` | [`CommandCallMode`](../type-aliases/CommandCallMode.md) | Command call mode. The command call mode is `entry` when the command is executed as an entry command, and `subCommand` when the command is executed as a sub-command. |
|
|
22
|
+
| <a id="commandpath"></a> `commandPath` | `string`[] | The path of nested sub-commands that were resolved to reach the current command. For example, if the user runs `git remote add`, `commandPath` would be `['remote', 'add']`. For the entry command, this is an empty array. **Since** v0.28.0 |
|
|
22
23
|
| <a id="description"></a> `description` | `string` \| `undefined` | Command description, that is the description of the command that is executed. The command description is same [`CommandEnvironment.description`](CommandEnvironment.md#description). |
|
|
23
24
|
| <a id="env"></a> `env` | `Readonly`\<[`CommandEnvironment`](CommandEnvironment.md)\<`G`\>\> | Command environment, that is the environment of the command that is executed. The command environment is same [`CommandEnvironment`](CommandEnvironment.md). |
|
|
24
25
|
| <a id="explicit"></a> `explicit` | `ExtractArgExplicitlyProvided`\<`G`\> | Whether arguments were explicitly provided by the user. - `true`: The argument was explicitly provided via command line - `false`: The argument was not explicitly provided. This means either: - The value comes from a default value defined in the argument schema - The value is `undefined` (no explicit input and no default value) |
|
|
@@ -32,4 +32,5 @@ Index signature to allow additional properties
|
|
|
32
32
|
| <a id="name"></a> `name?` | `string` | see [Command.name](Command.md#name) |
|
|
33
33
|
| <a id="rendering"></a> `rendering?` | `any` | see [Command.rendering](Command.md#rendering) |
|
|
34
34
|
| <a id="run"></a> `run?` | (...`args`) => `any` | see [Command.run](Command.md#run) |
|
|
35
|
+
| <a id="subcommands"></a> `subCommands?` | `Record`\<`string`, `any`\> \| `Map`\<`string`, `any`\> | Nested sub-commands for this command. **See** [Command.subCommands](Command.md#subcommands) **Since** v0.28.0 |
|
|
35
36
|
| <a id="tokebab"></a> `toKebab?` | `boolean` | see [Command.toKebab](Command.md#tokebab) |
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Function: define()
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
function define<G, A, C>(definition): { [K in string | number | symbol]: (Pick<C, keyof C> & Partial<Pick<Command<G>, Exclude<"name", keyof C> | Exclude<"description", keyof C> | Exclude<"run", keyof C> | Exclude<"args", keyof C> | Exclude<"examples", keyof C> | Exclude<"toKebab", keyof C> | Exclude<"internal", keyof C> | Exclude<"entry", keyof C> | Exclude<"rendering", keyof C>>>)[K] };
|
|
6
|
+
function define<G, A, C>(definition): { [K in string | number | symbol]: (Pick<C, keyof C> & Partial<Pick<Command<G>, Exclude<"name", keyof C> | Exclude<"description", keyof C> | Exclude<"subCommands", keyof C> | Exclude<"run", keyof C> | Exclude<"args", keyof C> | Exclude<"examples", keyof C> | Exclude<"toKebab", keyof C> | Exclude<"internal", keyof C> | Exclude<"entry", keyof C> | Exclude<"rendering", keyof C>>>)[K] };
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Define a [command](../../default/interfaces/Command.md).
|
|
@@ -24,7 +24,7 @@ Define a [command](../../default/interfaces/Command.md).
|
|
|
24
24
|
|
|
25
25
|
## Returns
|
|
26
26
|
|
|
27
|
-
\{ \[K in string \| number \| symbol\]: (Pick\<C, keyof C\> & Partial\<Pick\<Command\<G\>, Exclude\<"name", keyof C\> \| Exclude\<"description", keyof C\> \| Exclude\<"run", keyof C\> \| Exclude\<"args", keyof C\> \| Exclude\<"examples", keyof C\> \| Exclude\<"toKebab", keyof C\> \| Exclude\<"internal", keyof C\> \| Exclude\<"entry", keyof C\> \| Exclude\<"rendering", keyof C\>\>\>)\[K\] \}
|
|
27
|
+
\{ \[K in string \| number \| symbol\]: (Pick\<C, keyof C\> & Partial\<Pick\<Command\<G\>, Exclude\<"name", keyof C\> \| Exclude\<"description", keyof C\> \| Exclude\<"subCommands", keyof C\> \| Exclude\<"run", keyof C\> \| Exclude\<"args", keyof C\> \| Exclude\<"examples", keyof C\> \| Exclude\<"toKebab", keyof C\> \| Exclude\<"internal", keyof C\> \| Exclude\<"entry", keyof C\> \| Exclude\<"rendering", keyof C\>\>\>)\[K\] \}
|
|
28
28
|
|
|
29
29
|
A defined [command](../../default/interfaces/Command.md)
|
|
30
30
|
|
|
@@ -21,7 +21,7 @@ Generate the command usage.
|
|
|
21
21
|
|
|
22
22
|
| Parameter | Type | Description |
|
|
23
23
|
| ------ | ------ | ------ |
|
|
24
|
-
| `command` | `string` \| `null` | usage generate command, if you want to generate the usage of the default command where there are target commands and sub-commands, specify `null`. |
|
|
24
|
+
| `command` | `string` \| `string`[] \| `null` | usage generate command, if you want to generate the usage of the default command where there are target commands and sub-commands, specify `null`. |
|
|
25
25
|
| `entry` | \| [`Command`](../../default/interfaces/Command.md)\<`G`\> \| [`LazyCommand`](../../default/type-aliases/LazyCommand.md)\<`G`\> | A [`entry command`](../../default/interfaces/Command.md) |
|
|
26
26
|
| `options` | [`GenerateOptions`](../type-aliases/GenerateOptions.md)\<`G`\> | A [`cli options`](../type-aliases/GenerateOptions.md) |
|
|
27
27
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Nested Sub-Commands
|
|
2
|
+
|
|
3
|
+
Gunshi supports nested sub-commands, allowing you to build hierarchical command trees similar to tools like Git (`git remote add`) or Docker (`docker container ls`).
|
|
4
|
+
|
|
5
|
+
## Why Use Nested Sub-Commands?
|
|
6
|
+
|
|
7
|
+
Nested sub-commands are useful when your CLI has command groups with related operations:
|
|
8
|
+
|
|
9
|
+
- **Organization**: Group related operations under a parent command (e.g., `remote add`, `remote remove`)
|
|
10
|
+
- **Discoverability**: Users can explore available operations at each level with `--help`
|
|
11
|
+
- **Scalability**: Add new nested commands without cluttering the top-level command list
|
|
12
|
+
|
|
13
|
+
## Basic Nested Sub-Commands
|
|
14
|
+
|
|
15
|
+
You can nest sub-commands by adding a `subCommands` property to any command definition:
|
|
16
|
+
|
|
17
|
+
```ts [cli.ts]
|
|
18
|
+
import { cli, define } from 'gunshi'
|
|
19
|
+
|
|
20
|
+
// Define leaf commands
|
|
21
|
+
const addCommand = define({
|
|
22
|
+
name: 'add',
|
|
23
|
+
description: 'Add a remote',
|
|
24
|
+
args: {
|
|
25
|
+
url: { type: 'string', required: true, description: 'Remote URL' }
|
|
26
|
+
},
|
|
27
|
+
run: ctx => {
|
|
28
|
+
console.log(`Adding remote: ${ctx.values.url}`)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const removeCommand = define({
|
|
33
|
+
name: 'remove',
|
|
34
|
+
description: 'Remove a remote',
|
|
35
|
+
args: {
|
|
36
|
+
name: { type: 'positional', description: 'Remote name' }
|
|
37
|
+
},
|
|
38
|
+
run: ctx => {
|
|
39
|
+
console.log(`Removing remote: ${ctx.values.name}`)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Define an intermediate command with nested sub-commands
|
|
44
|
+
const remoteCommand = define({
|
|
45
|
+
name: 'remote',
|
|
46
|
+
description: 'Manage remotes',
|
|
47
|
+
subCommands: {
|
|
48
|
+
add: addCommand,
|
|
49
|
+
remove: removeCommand
|
|
50
|
+
},
|
|
51
|
+
run: () => {
|
|
52
|
+
console.log('Use: git remote add|remove')
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Define the entry command
|
|
57
|
+
const entry = define({
|
|
58
|
+
name: 'main',
|
|
59
|
+
description: 'Git-like CLI',
|
|
60
|
+
run: () => {
|
|
61
|
+
console.log('Run --help for available commands')
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
await cli(process.argv.slice(2), entry, {
|
|
66
|
+
name: 'git',
|
|
67
|
+
version: '1.0.0',
|
|
68
|
+
subCommands: {
|
|
69
|
+
remote: remoteCommand
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
75
|
+
|
|
76
|
+
> [!TIP]
|
|
77
|
+
> The complete example code is [here](https://github.com/kazupon/gunshi/tree/main/playground/advanced/nested-sub-commands).
|
|
78
|
+
|
|
79
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
80
|
+
|
|
81
|
+
Now users can run:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
# Execute nested sub-command
|
|
85
|
+
$ npx tsx cli.ts remote add --url https://example.com
|
|
86
|
+
Adding remote: https://example.com
|
|
87
|
+
|
|
88
|
+
# Show help for intermediate command
|
|
89
|
+
$ npx tsx cli.ts remote --help
|
|
90
|
+
Manage remotes
|
|
91
|
+
|
|
92
|
+
USAGE:
|
|
93
|
+
git remote [COMMANDS] <OPTIONS>
|
|
94
|
+
|
|
95
|
+
COMMANDS:
|
|
96
|
+
[remote] <OPTIONS> Manage remotes
|
|
97
|
+
add <OPTIONS> Add a remote
|
|
98
|
+
remove <OPTIONS> Remove a remote
|
|
99
|
+
|
|
100
|
+
For more info, run any command with the `--help` flag:
|
|
101
|
+
git remote --help
|
|
102
|
+
git remote add --help
|
|
103
|
+
git remote remove --help
|
|
104
|
+
|
|
105
|
+
# Show help for leaf command
|
|
106
|
+
$ npx tsx cli.ts remote add --help
|
|
107
|
+
Add a remote
|
|
108
|
+
|
|
109
|
+
USAGE:
|
|
110
|
+
git remote add <OPTIONS>
|
|
111
|
+
|
|
112
|
+
OPTIONS:
|
|
113
|
+
--url <url> Remote URL
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Three or More Levels
|
|
117
|
+
|
|
118
|
+
You can nest commands to any depth:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const subSubCommand = define({
|
|
122
|
+
name: 'sub-sub',
|
|
123
|
+
description: 'A deeply nested command',
|
|
124
|
+
run: ctx => {
|
|
125
|
+
// ctx.commandPath will be ['level1', 'level2', 'sub-sub']
|
|
126
|
+
console.log(`Command path: ${ctx.commandPath.join(' > ')}`)
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const level2Command = define({
|
|
131
|
+
name: 'level2',
|
|
132
|
+
description: 'Second level',
|
|
133
|
+
subCommands: { 'sub-sub': subSubCommand },
|
|
134
|
+
run: () => {}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const level1Command = define({
|
|
138
|
+
name: 'level1',
|
|
139
|
+
description: 'First level',
|
|
140
|
+
subCommands: { level2: level2Command },
|
|
141
|
+
run: () => {}
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Using `commandPath`
|
|
146
|
+
|
|
147
|
+
The `CommandContext` includes a `commandPath` property that tells you the full path of commands that were resolved:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const addCommand = define({
|
|
151
|
+
name: 'add',
|
|
152
|
+
description: 'Add a remote',
|
|
153
|
+
run: ctx => {
|
|
154
|
+
console.log(ctx.commandPath) // ['remote', 'add']
|
|
155
|
+
console.log(ctx.callMode) // 'subCommand'
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Invocation | `commandPath` | `callMode` |
|
|
161
|
+
| ---------------- | ------------------- | -------------- |
|
|
162
|
+
| `cli` | `[]` | `'entry'` |
|
|
163
|
+
| `cli remote` | `['remote']` | `'subCommand'` |
|
|
164
|
+
| `cli remote add` | `['remote', 'add']` | `'subCommand'` |
|
|
165
|
+
|
|
166
|
+
## Intermediate Commands
|
|
167
|
+
|
|
168
|
+
When a user invokes an intermediate command (one that has nested sub-commands) without specifying a child, the intermediate command's `run` function is called with `omitted: true`. In this case, the built-in help system automatically shows a COMMANDS section listing the available nested sub-commands:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const remoteCommand = define({
|
|
172
|
+
name: 'remote',
|
|
173
|
+
description: 'Manage remotes',
|
|
174
|
+
subCommands: { add: addCommand },
|
|
175
|
+
run: ctx => {
|
|
176
|
+
if (ctx.omitted) {
|
|
177
|
+
// User ran `cli remote` without specifying a sub-command
|
|
178
|
+
// Help is shown automatically with COMMANDS section
|
|
179
|
+
console.log('Please specify a sub-command: add, remove')
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Nested Sub-Commands with Lazy Loading
|
|
186
|
+
|
|
187
|
+
For large CLIs, you can combine nested sub-commands with lazy loading:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { cli, define, lazy } from 'gunshi'
|
|
191
|
+
|
|
192
|
+
// Lazy-load the leaf command
|
|
193
|
+
const addCommand = lazy(() => import('./commands/remote-add.ts'), {
|
|
194
|
+
name: 'add',
|
|
195
|
+
description: 'Add a remote',
|
|
196
|
+
args: {
|
|
197
|
+
url: { type: 'string', required: true, description: 'Remote URL' }
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// The parent command can also be lazy-loaded
|
|
202
|
+
const remoteCommand = lazy(() => import('./commands/remote.ts'), {
|
|
203
|
+
name: 'remote',
|
|
204
|
+
description: 'Manage remotes',
|
|
205
|
+
subCommands: {
|
|
206
|
+
add: addCommand
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
await cli(process.argv.slice(2), entry, {
|
|
211
|
+
name: 'git',
|
|
212
|
+
subCommands: { remote: remoteCommand }
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
217
|
+
|
|
218
|
+
> [!IMPORTANT]
|
|
219
|
+
> When using `lazy()` with nested sub-commands, include `args` in the lazy definition (the second argument) if you want argument parsing to work before the command is loaded. The `subCommands` property is automatically carried over from the definition to the lazy command.
|
|
220
|
+
|
|
221
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
222
|
+
|
|
223
|
+
## Generating Documentation for Nested Commands
|
|
224
|
+
|
|
225
|
+
The `generate()` function supports nested command paths:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { generate } from 'gunshi/generator'
|
|
229
|
+
|
|
230
|
+
// Generate help for a nested command using array or space-separated string
|
|
231
|
+
const help = await generate(['remote', 'add'], entry, {
|
|
232
|
+
name: 'git',
|
|
233
|
+
subCommands: { remote: remoteCommand }
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Or using space-separated string
|
|
237
|
+
const help2 = await generate('remote add', entry, { ... })
|
|
238
|
+
```
|
|
@@ -319,6 +319,41 @@ This approach is particularly useful for CLIs that:
|
|
|
319
319
|
- Want to provide a default action when no sub-command matches
|
|
320
320
|
- Implement dynamic command resolution based on context
|
|
321
321
|
|
|
322
|
+
## Nested Sub-Commands
|
|
323
|
+
|
|
324
|
+
Gunshi also supports nested sub-commands for building hierarchical command trees like `git remote add`. You can add a `subCommands` property to any command definition:
|
|
325
|
+
|
|
326
|
+
```ts [cli.ts]
|
|
327
|
+
import { cli, define } from 'gunshi'
|
|
328
|
+
|
|
329
|
+
const addCommand = define({
|
|
330
|
+
name: 'add',
|
|
331
|
+
description: 'Add a remote',
|
|
332
|
+
args: { url: { type: 'string', required: true } },
|
|
333
|
+
run: ctx => console.log(`Adding: ${ctx.values.url}`)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const remoteCommand = define({
|
|
337
|
+
name: 'remote',
|
|
338
|
+
description: 'Manage remotes',
|
|
339
|
+
subCommands: { add: addCommand },
|
|
340
|
+
run: () => console.log('Use: remote add')
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
const entry = define({
|
|
344
|
+
name: 'main',
|
|
345
|
+
description: 'Git-like CLI',
|
|
346
|
+
run: () => console.log('Run --help for available commands')
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
await cli(process.argv.slice(2), entry, {
|
|
350
|
+
name: 'git',
|
|
351
|
+
subCommands: { remote: remoteCommand }
|
|
352
|
+
})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
For more details on nested sub-commands, including lazy loading, intermediate command handling, and `commandPath`, see the [Nested Sub-Commands](../advanced/nested-sub-commands.md) guide.
|
|
356
|
+
|
|
322
357
|
## Next Steps
|
|
323
358
|
|
|
324
359
|
Throughout this guide, you've learned how to build composable sub-commands that scale from simple to complex CLI applications.
|