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
4 changes: 4 additions & 0 deletions examples/demo.cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ cli
.command('dev', 'Start dev server')
.option('-H, --host [host]', `Specify hostname`)
.option('-p, --port <port>', `Specify port`)
.option('-v, --verbose', `Enable verbose logging`)
.option('--quiet', `Suppress output`)
.action((options) => {});

cli
Expand All @@ -23,6 +25,8 @@ cli

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

cli.command('dev start', 'Start development server').action((options) => {});

cli
.command('copy <source> <destination>', 'Copy files')
.action((source, destination, options) => {});
Expand Down
23 changes: 22 additions & 1 deletion examples/demo.citty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ const devCommand = defineCommand({
description: 'Specify port',
alias: 'p',
},
verbose: {
type: 'boolean',
description: 'Enable verbose logging',
alias: 'v',
},
quiet: {
type: 'boolean',
description: 'Suppress output',
},
},
run: () => {},
});
Expand All @@ -65,6 +74,14 @@ const buildCommand = defineCommand({
run: () => {},
});

const startCommand = defineCommand({
meta: {
name: 'start',
description: 'Start development server',
},
run: () => {},
});

const copyCommand = defineCommand({
meta: {
name: 'copy',
Expand Down Expand Up @@ -100,9 +117,13 @@ const lintCommand = defineCommand({
run: () => {},
});

devCommand.subCommands = {
build: buildCommand,
start: startCommand,
};

main.subCommands = {
dev: devCommand,
build: buildCommand,
copy: copyCommand,
lint: lintCommand,
} as Record<string, CommandDef<ArgsDef>>;
Expand Down
8 changes: 8 additions & 0 deletions examples/demo.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ devCmd.option(
'p'
);

devCmd.option('verbose', 'Enable verbose logging', 'v');

// Add a simple quiet option to test basic option API (no handler, no alias)
devCmd.option('quiet', 'Suppress output');

// Serve command
const serveCmd = t.command('serve', 'Start the server');
serveCmd.option(
Expand All @@ -87,6 +92,9 @@ serveCmd.option(
// Build command
t.command('dev build', 'Build project');

// Start command
t.command('dev start', 'Start development server');

// Copy command with multiple arguments
const copyCmd = t
.command('copy', 'Copy files')
Expand Down
36 changes: 24 additions & 12 deletions src/cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
import * as powershell from './powershell';
import type { CAC } from 'cac';
import { assertDoubleDashes } from './shared';
import { OptionHandler } from './t';
import { CompletionConfig } from './shared';
import t from './t';

const noopOptionHandler: OptionHandler = function () {};

const execPath = process.execPath;
const processArgs = process.argv.slice(1);
const quotedExecPath = quoteIfNeeded(execPath);
Expand All @@ -25,7 +22,7 @@
export default async function tab(
instance: CAC,
completionConfig?: CompletionConfig
): Promise<any> {

Check failure on line 25 in src/cac.ts

View workflow job for this annotation

GitHub Actions / Lint and Type Check

Unexpected any. Specify a different type
// Add all commands and their options
for (const cmd of [instance.globalCommand, ...instance.commands]) {
if (cmd.name === 'complete') continue; // Skip completion command
Expand Down Expand Up @@ -73,19 +70,34 @@

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

// Add option using t.ts API
const targetCommand = isRootCommand ? t : command;
if (targetCommand) {
targetCommand.option(
argName, // Store just the option name without -- prefix
option.description || '',
commandCompletionConfig?.options?.[argName] ?? noopOptionHandler,
shortFlag
);
const handler = commandCompletionConfig?.options?.[argName];
if (handler) {
// Has custom handler → value option
if (shortFlag) {
targetCommand.option(
argName,
option.description || '',
handler,
shortFlag
);
} else {
targetCommand.option(argName, option.description || '', handler);
}
} else {
// No custom handler → boolean flag
if (shortFlag) {
targetCommand.option(argName, option.description || '', shortFlag);
} else {
targetCommand.option(argName, option.description || '');
}
}
}
}
}
Expand Down
61 changes: 43 additions & 18 deletions src/citty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
}

// Convert Handler from index.ts to OptionHandler from t.ts
function convertOptionHandler(handler: any): OptionHandler {

Check failure on line 36 in src/citty.ts

View workflow job for this annotation

GitHub Actions / Lint and Type Check

Unexpected any. Specify a different type
return function (
this: Option,
complete: (value: string, description: string) => void,
Expand Down Expand Up @@ -71,13 +71,13 @@
);

if (Array.isArray(result)) {
result.forEach((item: any) =>

Check failure on line 74 in src/citty.ts

View workflow job for this annotation

GitHub Actions / Lint and Type Check

Unexpected any. Specify a different type
complete(item.value, item.description || '')
);
} else if (result && typeof result.then === 'function') {
// Handle async handlers
result.then((items: any[]) => {

Check failure on line 79 in src/citty.ts

View workflow job for this annotation

GitHub Actions / Lint and Type Check

Unexpected any. Specify a different type
items.forEach((item: any) =>

Check failure on line 80 in src/citty.ts

View workflow job for this annotation

GitHub Actions / Lint and Type Check

Unexpected any. Specify a different type
complete(item.value, item.description || '')
);
});
Expand All @@ -85,8 +85,6 @@
};
}

const noopOptionHandler: OptionHandler = function () {};

async function handleSubCommands(
subCommands: SubCommandsDef,
parentCmd?: string,
Expand All @@ -105,7 +103,7 @@

// Add command using t.ts API
const commandName = parentCmd ? `${parentCmd} ${cmd}` : cmd;
const command = t.command(cmd, meta.description);
const command = t.command(commandName, meta.description);

// Set args for the command if it has positional arguments
if (isPositional && config.args) {
Expand Down Expand Up @@ -138,9 +136,6 @@
if (config.args) {
for (const [argName, argConfig] of Object.entries(config.args)) {
const conf = argConfig as ArgDef;
if (conf.type === 'positional') {
continue;
}
// Extract alias from the config if it exists
const shortFlag =
typeof conf === 'object' && 'alias' in conf
Expand All @@ -150,12 +145,22 @@
: undefined;

// Add option using t.ts API - store without -- prefix
command.option(
argName,
conf.description ?? '',
subCompletionConfig?.options?.[argName] ?? noopOptionHandler,
shortFlag
);
const handler = subCompletionConfig?.options?.[argName];
if (handler) {
// Has custom handler → value option
if (shortFlag) {
command.option(argName, conf.description ?? '', handler, shortFlag);
} else {
command.option(argName, conf.description ?? '', handler);
}
} else {
// No custom handler → boolean flag
if (shortFlag) {
command.option(argName, conf.description ?? '', shortFlag);
} else {
command.option(argName, conf.description ?? '');
}
}
}
}
}
Expand All @@ -164,7 +169,7 @@
export default async function tab<TArgs extends ArgsDef>(
instance: CommandDef<TArgs>,
completionConfig?: CompletionConfig
): Promise<any> {

Check failure on line 172 in src/citty.ts

View workflow job for this annotation

GitHub Actions / Lint and Type Check

Unexpected any. Specify a different type
const meta = await resolve(instance.meta);

if (!meta) {
Expand Down Expand Up @@ -206,13 +211,33 @@

if (instance.args) {
for (const [argName, argConfig] of Object.entries(instance.args)) {
const conf = argConfig as PositionalArgDef;
const conf = argConfig as ArgDef;

// Extract alias (same logic as subcommands)
const shortFlag =
typeof conf === 'object' && 'alias' in conf
? Array.isArray(conf.alias)
? conf.alias[0]
: conf.alias
: undefined;

// Add option using t.ts API - store without -- prefix
t.option(
argName,
conf.description ?? '',
completionConfig?.options?.[argName] ?? noopOptionHandler
);
const handler = completionConfig?.options?.[argName];
if (handler) {
// Has custom handler → value option
if (shortFlag) {
t.option(argName, conf.description ?? '', handler, shortFlag);
} else {
t.option(argName, conf.description ?? '', handler);
}
} else {
// No custom handler → boolean flag
if (shortFlag) {
t.option(argName, conf.description ?? '', shortFlag);
} else {
t.option(argName, conf.description ?? '');
}
}
}
}

Expand Down
Loading
Loading