Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 3 additions & 0 deletions examples/demo.cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ cli
.command('dev', 'Start dev server')
.option('-H, --host [host]', `Specify hostname`)
.option('-p, --port <port>', `Specify port`)
.option('-v, --verbose', `Enable verbose logging`)
.action((options) => {});

cli
Expand All @@ -23,6 +24,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
19 changes: 18 additions & 1 deletion examples/demo.citty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ const devCommand = defineCommand({
description: 'Specify port',
alias: 'p',
},
verbose: {
type: 'boolean',
description: 'Enable verbose logging',
alias: 'v',
},
},
run: () => {},
});
Expand All @@ -65,6 +70,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 +113,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
5 changes: 5 additions & 0 deletions examples/demo.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ devCmd.option(
'p'
);

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

// Serve command
const serveCmd = t.command('serve', 'Start the server');
serveCmd.option(
Expand All @@ -87,6 +89,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: 27 additions & 9 deletions src/cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
import * as powershell from './powershell';
import type { CAC } from 'cac';
import { assertDoubleDashes } from './shared';
import { OptionHandler } from './t';
import { OptionHandler, noopHandler } 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 +23,7 @@
export default async function tab(
instance: CAC,
completionConfig?: CompletionConfig
): Promise<any> {

Check failure on line 26 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 @@ -77,15 +75,35 @@
const shortFlag = option.name.match(/^-([a-zA-Z]), --/)?.[1];
const argName = option.name.replace(/^-[a-zA-Z], --/, '');

// Detect if this is a boolean option by checking if it has <value> or [value] in the raw definition
const isBoolean =
!option.rawName.includes('<') && !option.rawName.includes('[');

// 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
);
// Auto-detection handles boolean vs value options based on handler presence
const customHandler = commandCompletionConfig?.options?.[argName];
const handler = isBoolean ? noopHandler : customHandler;

if (shortFlag) {
if (handler) {
targetCommand.option(
argName,
option.description || '',
handler,
shortFlag
);
} else {
targetCommand.option(argName, option.description || '', shortFlag);
}
} else {
if (handler) {
targetCommand.option(argName, option.description || '', handler);
} else {
targetCommand.option(argName, option.description || '');
}
}
}
}
}
Expand Down
55 changes: 34 additions & 21 deletions src/citty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
} from 'citty';
import { generateFigSpec } from './fig';
import { CompletionConfig, assertDoubleDashes } from './shared';
import { OptionHandler, Command, Option, OptionsMap } from './t';
import { OptionHandler, Command, Option, OptionsMap, noopHandler } from './t';
import t from './t';

function quoteIfNeeded(path: string) {
Expand All @@ -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 @@ -149,13 +144,25 @@
: conf.alias
: undefined;

// Add option using t.ts API - store without -- prefix
command.option(
argName,
conf.description ?? '',
subCompletionConfig?.options?.[argName] ?? noopOptionHandler,
shortFlag
);
// Detect boolean options and use appropriate handler
const isBoolean = conf.type === 'boolean';
const customHandler = subCompletionConfig?.options?.[argName];
const handler = isBoolean ? noopHandler : customHandler;

// Add option using t.ts API - auto-detection handles boolean vs value options
if (shortFlag) {
if (handler) {
command.option(argName, conf.description ?? '', handler, shortFlag);
} else {
command.option(argName, conf.description ?? '', shortFlag);
}
} else {
if (handler) {
command.option(argName, conf.description ?? '', handler);
} else {
command.option(argName, conf.description ?? '');
}
}
}
}
}
Expand All @@ -164,7 +171,7 @@
export default async function tab<TArgs extends ArgsDef>(
instance: CommandDef<TArgs>,
completionConfig?: CompletionConfig
): Promise<any> {

Check failure on line 174 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 +213,19 @@

if (instance.args) {
for (const [argName, argConfig] of Object.entries(instance.args)) {
const conf = argConfig as PositionalArgDef;
// Add option using t.ts API - store without -- prefix
t.option(
argName,
conf.description ?? '',
completionConfig?.options?.[argName] ?? noopOptionHandler
);
const conf = argConfig as ArgDef;

// Detect boolean options and use appropriate handler
const isBoolean = conf.type === 'boolean';
const customHandler = completionConfig?.options?.[argName];
const handler = isBoolean ? noopHandler : customHandler;

// Add option using t.ts API - auto-detection handles boolean vs value options
if (handler) {
t.option(argName, conf.description ?? '', handler);
} else {
t.option(argName, conf.description ?? '');
}
}
}

Expand Down
Loading
Loading