Skip to content

Commit 32c2f66

Browse files
authored
fix: boolean opts (#27)
* boolean opts * noop func * signitures * boolean flags * prettier * update
1 parent 571f12e commit 32c2f66

File tree

8 files changed

+366
-60
lines changed

8 files changed

+366
-60
lines changed

examples/demo.cac.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ cli
1313
.command('dev', 'Start dev server')
1414
.option('-H, --host [host]', `Specify hostname`)
1515
.option('-p, --port <port>', `Specify port`)
16+
.option('-v, --verbose', `Enable verbose logging`)
17+
.option('--quiet', `Suppress output`)
1618
.action((options) => {});
1719

1820
cli
@@ -23,6 +25,8 @@ cli
2325

2426
cli.command('dev build', 'Build project').action((options) => {});
2527

28+
cli.command('dev start', 'Start development server').action((options) => {});
29+
2630
cli
2731
.command('copy <source> <destination>', 'Copy files')
2832
.action((source, destination, options) => {});

examples/demo.citty.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ const devCommand = defineCommand({
5353
description: 'Specify port',
5454
alias: 'p',
5555
},
56+
verbose: {
57+
type: 'boolean',
58+
description: 'Enable verbose logging',
59+
alias: 'v',
60+
},
61+
quiet: {
62+
type: 'boolean',
63+
description: 'Suppress output',
64+
},
5665
},
5766
run: () => {},
5867
});
@@ -65,6 +74,14 @@ const buildCommand = defineCommand({
6574
run: () => {},
6675
});
6776

77+
const startCommand = defineCommand({
78+
meta: {
79+
name: 'start',
80+
description: 'Start development server',
81+
},
82+
run: () => {},
83+
});
84+
6885
const copyCommand = defineCommand({
6986
meta: {
7087
name: 'copy',
@@ -100,9 +117,13 @@ const lintCommand = defineCommand({
100117
run: () => {},
101118
});
102119

120+
devCommand.subCommands = {
121+
build: buildCommand,
122+
start: startCommand,
123+
};
124+
103125
main.subCommands = {
104126
dev: devCommand,
105-
build: buildCommand,
106127
copy: copyCommand,
107128
lint: lintCommand,
108129
} as Record<string, CommandDef<ArgsDef>>;

examples/demo.t.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ devCmd.option(
6262
'p'
6363
);
6464

65+
devCmd.option('verbose', 'Enable verbose logging', 'v');
66+
67+
// Add a simple quiet option to test basic option API (no handler, no alias)
68+
devCmd.option('quiet', 'Suppress output');
69+
6570
// Serve command
6671
const serveCmd = t.command('serve', 'Start the server');
6772
serveCmd.option(
@@ -87,6 +92,9 @@ serveCmd.option(
8792
// Build command
8893
t.command('dev build', 'Build project');
8994

95+
// Start command
96+
t.command('dev start', 'Start development server');
97+
9098
// Copy command with multiple arguments
9199
const copyCmd = t
92100
.command('copy', 'Copy files')

src/cac.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@ import * as fish from './fish';
44
import * as powershell from './powershell';
55
import type { CAC } from 'cac';
66
import { assertDoubleDashes } from './shared';
7-
import { OptionHandler } from './t';
87
import { CompletionConfig } from './shared';
98
import t from './t';
109

11-
const noopOptionHandler: OptionHandler = function () {};
12-
1310
const execPath = process.execPath;
1411
const processArgs = process.argv.slice(1);
1512
const quotedExecPath = quoteIfNeeded(execPath);
@@ -73,19 +70,34 @@ export default async function tab(
7370

7471
// Add command options
7572
for (const option of [...instance.globalCommand.options, ...cmd.options]) {
76-
// Extract short flag from the name if it exists (e.g., "-c, --config" -> "c")
77-
const shortFlag = option.name.match(/^-([a-zA-Z]), --/)?.[1];
78-
const argName = option.name.replace(/^-[a-zA-Z], --/, '');
73+
// Extract short flag from the rawName if it exists (e.g., "-c, --config" -> "c")
74+
const shortFlag = option.rawName.match(/^-([a-zA-Z]), --/)?.[1];
75+
const argName = option.name; // option.name is already clean (e.g., "config")
7976

8077
// Add option using t.ts API
8178
const targetCommand = isRootCommand ? t : command;
8279
if (targetCommand) {
83-
targetCommand.option(
84-
argName, // Store just the option name without -- prefix
85-
option.description || '',
86-
commandCompletionConfig?.options?.[argName] ?? noopOptionHandler,
87-
shortFlag
88-
);
80+
const handler = commandCompletionConfig?.options?.[argName];
81+
if (handler) {
82+
// Has custom handler → value option
83+
if (shortFlag) {
84+
targetCommand.option(
85+
argName,
86+
option.description || '',
87+
handler,
88+
shortFlag
89+
);
90+
} else {
91+
targetCommand.option(argName, option.description || '', handler);
92+
}
93+
} else {
94+
// No custom handler → boolean flag
95+
if (shortFlag) {
96+
targetCommand.option(argName, option.description || '', shortFlag);
97+
} else {
98+
targetCommand.option(argName, option.description || '');
99+
}
100+
}
89101
}
90102
}
91103
}

src/citty.ts

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ function convertOptionHandler(handler: any): OptionHandler {
8585
};
8686
}
8787

88-
const noopOptionHandler: OptionHandler = function () {};
89-
9088
async function handleSubCommands(
9189
subCommands: SubCommandsDef,
9290
parentCmd?: string,
@@ -105,7 +103,7 @@ async function handleSubCommands(
105103

106104
// Add command using t.ts API
107105
const commandName = parentCmd ? `${parentCmd} ${cmd}` : cmd;
108-
const command = t.command(cmd, meta.description);
106+
const command = t.command(commandName, meta.description);
109107

110108
// Set args for the command if it has positional arguments
111109
if (isPositional && config.args) {
@@ -138,9 +136,6 @@ async function handleSubCommands(
138136
if (config.args) {
139137
for (const [argName, argConfig] of Object.entries(config.args)) {
140138
const conf = argConfig as ArgDef;
141-
if (conf.type === 'positional') {
142-
continue;
143-
}
144139
// Extract alias from the config if it exists
145140
const shortFlag =
146141
typeof conf === 'object' && 'alias' in conf
@@ -150,12 +145,22 @@ async function handleSubCommands(
150145
: undefined;
151146

152147
// Add option using t.ts API - store without -- prefix
153-
command.option(
154-
argName,
155-
conf.description ?? '',
156-
subCompletionConfig?.options?.[argName] ?? noopOptionHandler,
157-
shortFlag
158-
);
148+
const handler = subCompletionConfig?.options?.[argName];
149+
if (handler) {
150+
// Has custom handler → value option
151+
if (shortFlag) {
152+
command.option(argName, conf.description ?? '', handler, shortFlag);
153+
} else {
154+
command.option(argName, conf.description ?? '', handler);
155+
}
156+
} else {
157+
// No custom handler → boolean flag
158+
if (shortFlag) {
159+
command.option(argName, conf.description ?? '', shortFlag);
160+
} else {
161+
command.option(argName, conf.description ?? '');
162+
}
163+
}
159164
}
160165
}
161166
}
@@ -206,13 +211,33 @@ export default async function tab<TArgs extends ArgsDef>(
206211

207212
if (instance.args) {
208213
for (const [argName, argConfig] of Object.entries(instance.args)) {
209-
const conf = argConfig as PositionalArgDef;
214+
const conf = argConfig as ArgDef;
215+
216+
// Extract alias (same logic as subcommands)
217+
const shortFlag =
218+
typeof conf === 'object' && 'alias' in conf
219+
? Array.isArray(conf.alias)
220+
? conf.alias[0]
221+
: conf.alias
222+
: undefined;
223+
210224
// Add option using t.ts API - store without -- prefix
211-
t.option(
212-
argName,
213-
conf.description ?? '',
214-
completionConfig?.options?.[argName] ?? noopOptionHandler
215-
);
225+
const handler = completionConfig?.options?.[argName];
226+
if (handler) {
227+
// Has custom handler → value option
228+
if (shortFlag) {
229+
t.option(argName, conf.description ?? '', handler, shortFlag);
230+
} else {
231+
t.option(argName, conf.description ?? '', handler);
232+
}
233+
} else {
234+
// No custom handler → boolean flag
235+
if (shortFlag) {
236+
t.option(argName, conf.description ?? '', shortFlag);
237+
} else {
238+
t.option(argName, conf.description ?? '');
239+
}
240+
}
216241
}
217242
}
218243

0 commit comments

Comments
 (0)