From ca486759b60b10343166929f0cd1b9c5efd17e6e Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Thu, 25 Sep 2025 15:20:19 +0330 Subject: [PATCH 1/3] treat options well --- src/cac.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/cac.ts b/src/cac.ts index a0d66f5..4bac138 100644 --- a/src/cac.ts +++ b/src/cac.ts @@ -78,6 +78,11 @@ export default async function tab( const targetCommand = isRootCommand ? t : command; if (targetCommand) { const handler = commandCompletionConfig?.options?.[argName]; + + // Check if option takes a value (has <> or [] in rawName, or is marked as required) + const takesValue = + option.required || /<[^>]+>|\[[^\]]+\]/.test(option.rawName); + if (handler) { // Has custom handler → value option if (shortFlag) { @@ -90,8 +95,24 @@ export default async function tab( } else { targetCommand.option(argName, option.description || '', handler); } + } else if (takesValue) { + // Takes value but no custom handler → value option with no completions + if (shortFlag) { + targetCommand.option( + argName, + option.description || '', + async () => [], // Empty completions + shortFlag + ); + } else { + targetCommand.option( + argName, + option.description || '', + async () => [] + ); + } } else { - // No custom handler → boolean flag + // No custom handler and doesn't take value → boolean flag if (shortFlag) { targetCommand.option(argName, option.description || '', shortFlag); } else { From 746c6f60bddec8a41686b89af0ab025e92d9fdfd Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Thu, 25 Sep 2025 15:24:47 +0330 Subject: [PATCH 2/3] update --- src/cac.ts | 10 ++++++---- test-regex-constant.js | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 test-regex-constant.js diff --git a/src/cac.ts b/src/cac.ts index 4bac138..1bf032a 100644 --- a/src/cac.ts +++ b/src/cac.ts @@ -15,6 +15,9 @@ const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; +// Regex to detect if an option takes a value (has or [optional] parameters) +const VALUE_OPTION_RE = /<[^>]+>|\[[^\]]+\]/; + function quoteIfNeeded(path: string): string { return path.includes(' ') ? `'${path}'` : path; } @@ -81,10 +84,9 @@ export default async function tab( // Check if option takes a value (has <> or [] in rawName, or is marked as required) const takesValue = - option.required || /<[^>]+>|\[[^\]]+\]/.test(option.rawName); + option.required || VALUE_OPTION_RE.test(option.rawName); if (handler) { - // Has custom handler → value option if (shortFlag) { targetCommand.option( argName, @@ -96,7 +98,7 @@ export default async function tab( targetCommand.option(argName, option.description || '', handler); } } else if (takesValue) { - // Takes value but no custom handler → value option with no completions + // Takes value but no custom handler = value option with no completions if (shortFlag) { targetCommand.option( argName, @@ -112,7 +114,7 @@ export default async function tab( ); } } else { - // No custom handler and doesn't take value → boolean flag + // No custom handler and doesn't take value = boolean flag if (shortFlag) { targetCommand.option(argName, option.description || '', shortFlag); } else { diff --git a/test-regex-constant.js b/test-regex-constant.js new file mode 100644 index 0000000..0f58d23 --- /dev/null +++ b/test-regex-constant.js @@ -0,0 +1,27 @@ +import { cac } from 'cac'; +import tab from './dist/src/cac.js'; + +const cli = cac('test-cli'); + +cli + .command('dev', 'Start development server') + .option('--port ', 'Port number') + .option('--verbose', 'Verbose output'); + +const completion = await tab(cli); + +const devCommand = completion.commands.get('dev'); +const portOption = devCommand?.options.get('port'); +const verboseOption = devCommand?.options.get('verbose'); + +if (portOption) { + portOption.handler = (complete) => { + complete('3000', 'Development port'); + complete('8080', 'Production port'); + }; +} + +console.log('Port option (should be value-taking):', portOption?.isBoolean); +console.log('Verbose option (should be boolean):', verboseOption?.isBoolean); + +cli.parse(); From 59b852c8628831001ce38a245fad617ba6cd2df4 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Thu, 25 Sep 2025 16:15:07 +0330 Subject: [PATCH 3/3] update --- test-regex-constant.js | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 test-regex-constant.js diff --git a/test-regex-constant.js b/test-regex-constant.js deleted file mode 100644 index 0f58d23..0000000 --- a/test-regex-constant.js +++ /dev/null @@ -1,27 +0,0 @@ -import { cac } from 'cac'; -import tab from './dist/src/cac.js'; - -const cli = cac('test-cli'); - -cli - .command('dev', 'Start development server') - .option('--port ', 'Port number') - .option('--verbose', 'Verbose output'); - -const completion = await tab(cli); - -const devCommand = completion.commands.get('dev'); -const portOption = devCommand?.options.get('port'); -const verboseOption = devCommand?.options.get('verbose'); - -if (portOption) { - portOption.handler = (complete) => { - complete('3000', 'Development port'); - complete('8080', 'Production port'); - }; -} - -console.log('Port option (should be value-taking):', portOption?.isBoolean); -console.log('Verbose option (should be boolean):', verboseOption?.isBoolean); - -cli.parse();