Skip to content

Commit a97fae9

Browse files
committed
Add support for defaultCommand on subcommands
- Allow @command({ defaultCommand: "help" }) to show help when a subcommand is invoked without arguments - Document defaultCommand behavior in docs/defaultCommand.md - Update decorators, types, and parsing logic to support per-subcommand defaultCommand - Add tests for subcommand defaultCommand behavior
1 parent ceb66c6 commit a97fae9

6 files changed

Lines changed: 461 additions & 14 deletions

File tree

a.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Args, cli } from "@sigma/parse";
22
import { command, subCommand } from "./src/index.ts";
33

4-
@command
4+
@command({ defaultCommand: "help" })
55
class A {
66
n = 0;
77
}
@@ -13,7 +13,7 @@ class A {
1313
})
1414
class calculator extends Args {
1515
@subCommand(A)
16-
add: A;
16+
add?: A;
1717
}
1818

1919
// parse command line arguments

docs/defaultCommand.md

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
# `defaultCommand` Configuration
2+
3+
## Overview
4+
5+
The `defaultCommand` option allows you to specify what happens when a CLI
6+
command is invoked **without any arguments**. This works for both the main
7+
application command (via `@cli`) and subcommands (via `@command`).
8+
9+
## When Does `defaultCommand` Trigger?
10+
11+
### Main Command
12+
13+
For the main application command, `defaultCommand` triggers when:
14+
15+
```bash
16+
# Running the CLI with NO arguments at all
17+
myapp
18+
19+
# This is equivalent to not providing any flags, subcommands, or positional arguments
20+
```
21+
22+
**Does NOT trigger when:**
23+
24+
```bash
25+
myapp --help # Has an argument (--help flag)
26+
myapp subcommand # Has an argument (subcommand)
27+
myapp input.txt # Has an argument (positional argument)
28+
myapp --verbose # Has an argument (--verbose flag)
29+
```
30+
31+
### Subcommands
32+
33+
For subcommands, `defaultCommand` triggers when the subcommand is invoked
34+
**without additional arguments**:
35+
36+
```bash
37+
# Subcommand invoked with NO additional arguments
38+
myapp serve
39+
40+
# This means the subcommand name is provided, but nothing after it
41+
```
42+
43+
**Does NOT trigger when:**
44+
45+
```bash
46+
myapp serve --help # Subcommand has an argument (--help flag)
47+
myapp serve --port 8080 # Subcommand has arguments
48+
myapp serve input.txt # Subcommand has a positional argument
49+
```
50+
51+
## Usage Examples
52+
53+
### Main Command with `defaultCommand: "help"`
54+
55+
```typescript
56+
import { Args, cli } from "@sigma/parse";
57+
58+
@cli({
59+
name: "calculator",
60+
description: "A simple calculator",
61+
defaultCommand: "help",
62+
})
63+
class Calculator extends Args {
64+
@description("First number")
65+
a: number = 0;
66+
67+
@description("Second number")
68+
b: number = 0;
69+
}
70+
71+
// When run with no arguments:
72+
// $ calculator
73+
// Shows help text automatically
74+
```
75+
76+
### Subcommand with `defaultCommand: "help"`
77+
78+
```typescript
79+
import { Args, cli, command, description, subCommand } from "@sigma/parse";
80+
81+
@command({ defaultCommand: "help" })
82+
class ServeCommand {
83+
@description("Port to serve on")
84+
port: number = 3000;
85+
86+
@description("Host to bind to")
87+
host: string = "localhost";
88+
}
89+
90+
@cli({
91+
name: "devtool",
92+
description: "Development tool",
93+
})
94+
class DevTool extends Args {
95+
@description("Start the server")
96+
@subCommand(ServeCommand)
97+
serve?: ServeCommand;
98+
}
99+
100+
// When run without subcommand arguments:
101+
// $ devtool serve
102+
// Shows help for the serve subcommand (not the main help)
103+
//
104+
// Usage:
105+
// devtool serve [options]
106+
//
107+
// Options:
108+
// --port <number> (default: 3000)
109+
// Port to serve on
110+
// --host <string> (default: "localhost")
111+
// Host to bind to
112+
```
113+
114+
### Main Command with `defaultCommand` Pointing to a Subcommand
115+
116+
```typescript
117+
import { Args, cli, command, subCommand } from "@sigma/parse";
118+
119+
@command
120+
class InitCommand {
121+
@description("Project name")
122+
name: string = "my-project";
123+
}
124+
125+
@cli({
126+
name: "devtool",
127+
description: "Development tool",
128+
defaultCommand: "init", // Execute init by default
129+
})
130+
class DevTool extends Args {
131+
@description("Initialize a new project")
132+
@subCommand(InitCommand)
133+
init?: InitCommand;
134+
}
135+
136+
// When run with no arguments:
137+
// $ devtool
138+
// Automatically executes: devtool init
139+
// Result: init command runs with default values (name = "my-project")
140+
```
141+
142+
## Behavior Details
143+
144+
### `defaultCommand: "help"`
145+
146+
When set to `"help"`, the command will display its help text and exit (or throw
147+
if `exitOnHelp: false`).
148+
149+
### `defaultCommand: "<subcommand-name>"`
150+
151+
When set to a subcommand name, that subcommand will be executed with **no
152+
additional arguments** (using its default values).
153+
154+
### No `defaultCommand` Set
155+
156+
When `defaultCommand` is not specified:
157+
158+
- The command executes normally with default values
159+
- No special behavior occurs
160+
161+
## Inheritance Rules
162+
163+
### Subcommands DO NOT Inherit Parent's `defaultCommand`
164+
165+
This is by design to allow fine-grained control:
166+
167+
```typescript
168+
@command // No defaultCommand - will execute with defaults
169+
class BuildCommand {
170+
minify: boolean = false;
171+
}
172+
173+
@command({ defaultCommand: "help" }) // Has its own defaultCommand
174+
class ServeCommand {
175+
port: number = 3000;
176+
}
177+
178+
@cli({
179+
name: "devtool",
180+
defaultCommand: "help", // Only applies to main command
181+
})
182+
class DevTool extends Args {
183+
@subCommand(BuildCommand)
184+
build?: BuildCommand;
185+
186+
@subCommand(ServeCommand)
187+
serve?: ServeCommand;
188+
}
189+
190+
// $ devtool
191+
// Shows help (main command's defaultCommand)
192+
193+
// $ devtool build
194+
// Executes build with defaults (no defaultCommand set on BuildCommand)
195+
196+
// $ devtool serve
197+
// Shows help for serve (ServeCommand has defaultCommand: "help")
198+
```
199+
200+
### Error Handling Options ARE Inherited
201+
202+
While `defaultCommand` is not inherited, other options like `exitOnError` and
203+
`exitOnHelp` **are** inherited by subcommands:
204+
205+
```typescript
206+
@command({ defaultCommand: "help" })
207+
class ServeCommand {
208+
port: number = 3000;
209+
}
210+
211+
@cli({
212+
name: "devtool",
213+
exitOnHelp: false, // This IS inherited by subcommands
214+
})
215+
class DevTool extends Args {
216+
@subCommand(ServeCommand)
217+
serve?: ServeCommand;
218+
}
219+
220+
// When help is shown, it will throw ParseError instead of exiting
221+
// because exitOnHelp: false is inherited
222+
```
223+
224+
## Common Patterns
225+
226+
### 1. Help by Default Everywhere
227+
228+
```typescript
229+
@command({ defaultCommand: "help" })
230+
class SubCommand {
231+
// ...
232+
}
233+
234+
@cli({
235+
name: "myapp",
236+
defaultCommand: "help",
237+
})
238+
class MyApp extends Args {
239+
@subCommand(SubCommand)
240+
sub?: SubCommand;
241+
}
242+
243+
// Both main command and subcommand show help when called without args
244+
```
245+
246+
### 2. Auto-Execute Default Subcommand
247+
248+
```typescript
249+
@command
250+
class DefaultCommand {
251+
// ...
252+
}
253+
254+
@cli({
255+
name: "myapp",
256+
defaultCommand: "default", // Execute 'default' subcommand when no args
257+
})
258+
class MyApp extends Args {
259+
@subCommand(DefaultCommand)
260+
default?: DefaultCommand;
261+
}
262+
263+
// $ myapp
264+
// Automatically runs the default subcommand
265+
```
266+
267+
### 3. Mixed Behavior
268+
269+
```typescript
270+
@command({ defaultCommand: "help" })
271+
class ComplexCommand {
272+
// Show help if called without args
273+
}
274+
275+
@command
276+
class SimpleCommand {
277+
// Execute with defaults if called without args
278+
}
279+
280+
@cli({
281+
name: "myapp",
282+
// No defaultCommand - will fail if called without args
283+
})
284+
class MyApp extends Args {
285+
@subCommand(ComplexCommand)
286+
complex?: ComplexCommand;
287+
288+
@subCommand(SimpleCommand)
289+
simple?: SimpleCommand;
290+
}
291+
292+
// $ myapp
293+
// Error: missing subcommand
294+
295+
// $ myapp complex
296+
// Shows help for complex command
297+
298+
// $ myapp simple
299+
// Executes simple command with defaults
300+
```
301+
302+
## Summary
303+
304+
| Scenario | Triggers When | Example |
305+
| ---------------------------------- | ------------------------------------------ | ---------------------------- |
306+
| Main command with `defaultCommand` | No arguments at all | `myapp` |
307+
| Subcommand with `defaultCommand` | Subcommand invoked without additional args | `myapp serve` |
308+
| `defaultCommand: "help"` | Shows help text | Display usage information |
309+
| `defaultCommand: "<name>"` | Executes named subcommand | Run subcommand with defaults |
310+
| No `defaultCommand` | Normal execution | Use default values |

0 commit comments

Comments
 (0)