[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

fix: intercept `parseAsync()` - commander adapter (#121)

* fix: intercept parseAsync() commander

* add changeset

authored by

AmirHossein Sakhravi and committed by
GitHub
(Jun 2, 2026, 6:51 PM +0330) e784d62b 2c582772

+70 -10
+5
.changeset/twelve-planes-grin.md
··· 1 + --- 2 + '@bomb.sh/tab': patch 3 + --- 4 + 5 + fix: parseasync() commander's adapter
+27
examples/demo.commander-async.ts
··· 1 + import { Command } from 'commander'; 2 + import tab from '../src/commander'; 3 + 4 + const program = new Command('my-cli'); 5 + 6 + program 7 + .command('greet [name]') 8 + .description('Say hello') 9 + .action(async (name) => { 10 + // async handler requires parseAsync() 11 + console.log(`Hello, ${name ?? 'world'}!`); 12 + }); 13 + 14 + const completion = tab(program); 15 + 16 + for (const command of completion.commands.values()) { 17 + if (command.value === 'greet') { 18 + for (const [_argName, arg] of command.arguments.entries()) { 19 + arg.handler = (complete) => { 20 + complete('alice', 'Alice'); 21 + complete('bob', 'Bob'); 22 + }; 23 + } 24 + } 25 + } 26 + 27 + await program.parseAsync();
+29 -10
src/commander.ts
··· 73 73 } 74 74 }); 75 75 76 - // Override the parse method to handle completion requests before normal parsing 77 - const originalParse = instance.parse.bind(instance); 78 - instance.parse = function (argv?: readonly string[], options?: ParseOptions) { 76 + const getCompletionArgs = (argv?: readonly string[]): string[] | null => { 79 77 const args = argv || process.argv; 80 78 const completeIndex = args.findIndex((arg) => arg === 'complete'); 81 79 const dashDashIndex = args.findIndex((arg) => arg === '--'); ··· 85 83 dashDashIndex !== -1 && 86 84 dashDashIndex > completeIndex 87 85 ) { 88 - // This is a completion request, handle it directly 89 - const extra = args.slice(dashDashIndex + 1); 86 + return args.slice(dashDashIndex + 1); 87 + } 90 88 91 - // Handle the completion directly 92 - assertDoubleDashes(programName); 93 - t.parse(extra); 89 + return null; 90 + }; 91 + 92 + const handleCompletion = (extra: string[]): void => { 93 + assertDoubleDashes(programName); 94 + t.parse(extra); 95 + }; 96 + 97 + const originalParse = instance.parse.bind(instance); 98 + instance.parse = function (argv?: readonly string[], options?: ParseOptions) { 99 + const extra = getCompletionArgs(argv); 100 + if (extra) { 101 + handleCompletion(extra); 94 102 return instance; 95 103 } 104 + return originalParse(argv, options); 105 + }; 96 106 97 - // Normal parsing 98 - return originalParse(argv, options); 107 + const originalParseAsync = instance.parseAsync.bind(instance); 108 + instance.parseAsync = async function ( 109 + argv?: readonly string[], 110 + options?: ParseOptions 111 + ) { 112 + const extra = getCompletionArgs(argv); 113 + if (extra) { 114 + handleCompletion(extra); 115 + return instance; 116 + } 117 + return originalParseAsync(argv, options); 99 118 }; 100 119 101 120 return t;
+9
tests/cli.test.ts
··· 457 457 expect(output2).toContain('staging'); 458 458 expect(output2).toContain('production'); 459 459 }); 460 + 461 + it('should intercept completion when using parseAsync', async () => { 462 + const command = `pnpm tsx examples/demo.commander-async.ts complete -- `; 463 + const output = await runCommand(command); 464 + expect(output).toContain('greet'); 465 + expect(output).toContain('Say hello'); 466 + // directive line must be present, proving t.parse was invoked 467 + expect(output).toMatch(/:\d+\s*$/); 468 + }); 460 469 }); 461 470 462 471 describe('shell completion script generation', () => {