88 setupCommandArguments ,
99 safeExec ,
1010 safeExecSync ,
11- // createLogLevelHandler,
1211} from '../utils/shared.js' ;
1312
1413const COMMANDS_SECTION_RE = / ^ C o m m a n d s : \s * $ / i;
@@ -22,39 +21,51 @@ const CONTINUATION_COMMAND_RE = /^\s{12,}([a-z][a-z0-9-]*)\s+(.+)$/;
2221const EMPTY_LINE_FOLLOWED_BY_NON_COMMAND_RE = / ^ \s + [ a - z ] / ;
2322const DESCRIPTION_SPLIT_RE = / \s { 2 , } / ;
2423const CAPITAL_LETTER_START_RE = / ^ [ A - Z ] / ;
24+ const LINE_SPLIT_RE = / \r ? \n / ;
25+
26+ function toLines ( text : string ) : string [ ] {
27+ return stripAnsiEscapes ( text ) . split ( LINE_SPLIT_RE ) ;
28+ }
29+
30+ function findSectionStart ( lines : string [ ] , header : RegExp ) : number {
31+ for ( let i = 0 ; i < lines . length ; i ++ ) {
32+ if ( header . test ( lines [ i ] . trim ( ) ) ) return i + 1 ;
33+ }
34+ return - 1 ;
35+ }
2536
2637const bunOptionHandlers : OptionHandlers = {
2738 ...commonOptionHandlers ,
2839
29- silent : function ( complete ) {
40+ silent ( complete ) {
3041 complete ( 'true' , 'Enable silent mode' ) ;
3142 complete ( 'false' , 'Disable silent mode' ) ;
3243 } ,
3344
34- backend : function ( complete ) {
45+ backend ( complete ) {
3546 complete ( 'clonefile' , 'Clone files (default, fastest)' ) ;
3647 complete ( 'hardlink' , 'Use hard links' ) ;
3748 complete ( 'symlink' , 'Use symbolic links' ) ;
3849 complete ( 'copyfile' , 'Copy files' ) ;
3950 } ,
4051
41- linker : function ( complete ) {
52+ linker ( complete ) {
4253 complete ( 'isolated' , 'Isolated linker strategy' ) ;
4354 complete ( 'hoisted' , 'Hoisted linker strategy' ) ;
4455 } ,
4556
46- omit : function ( complete ) {
57+ omit ( complete ) {
4758 complete ( 'dev' , 'Omit devDependencies' ) ;
4859 complete ( 'optional' , 'Omit optionalDependencies' ) ;
4960 complete ( 'peer' , 'Omit peerDependencies' ) ;
5061 } ,
5162
52- shell : function ( complete ) {
63+ shell ( complete ) {
5364 complete ( 'bun' , 'Use Bun shell' ) ;
5465 complete ( 'system' , 'Use system shell' ) ;
5566 } ,
5667
57- 'unhandled-rejections' : function ( complete ) {
68+ 'unhandled-rejections' ( complete ) {
5869 complete ( 'strict' , 'Strict unhandled rejection handling' ) ;
5970 complete ( 'throw' , 'Throw on unhandled rejections' ) ;
6071 complete ( 'warn' , 'Warn on unhandled rejections' ) ;
@@ -63,61 +74,54 @@ const bunOptionHandlers: OptionHandlers = {
6374 } ,
6475} ;
6576
77+ /** ---------- Commands ---------- */
6678export function parseBunHelp ( helpText : string ) : Record < string , string > {
67- const helpLines = stripAnsiEscapes ( helpText ) . split ( / \r ? \n / ) ;
68-
69- let startIndex = - 1 ;
70- for ( let i = 0 ; i < helpLines . length ; i ++ ) {
71- if ( COMMANDS_SECTION_RE . test ( helpLines [ i ] . trim ( ) ) ) {
72- startIndex = i + 1 ;
73- break ;
74- }
75- }
79+ const lines = toLines ( helpText ) ;
7680
81+ const startIndex = findSectionStart ( lines , COMMANDS_SECTION_RE ) ;
7782 if ( startIndex === - 1 ) return { } ;
7883
7984 const commands : Record < string , string > = { } ;
8085
81- // parse bun's unique command format
82- for ( let i = startIndex ; i < helpLines . length ; i ++ ) {
83- const line = helpLines [ i ] ;
86+ for ( let i = startIndex ; i < lines . length ; i ++ ) {
87+ const line = lines [ i ] ;
8488
8589 // stop when we hit Flags section or empty line followed by non-command content
8690 if (
8791 FLAGS_SECTION_RE . test ( line . trim ( ) ) ||
8892 ( line . trim ( ) === '' &&
89- i + 1 < helpLines . length &&
90- ! helpLines [ i + 1 ] . match ( EMPTY_LINE_FOLLOWED_BY_NON_COMMAND_RE ) )
91- )
93+ i + 1 < lines . length &&
94+ ! lines [ i + 1 ] . match ( EMPTY_LINE_FOLLOWED_BY_NON_COMMAND_RE ) )
95+ ) {
9296 break ;
97+ }
9398
94- // Skip empty lines
95- if ( line . trim ( ) === '' ) continue ;
99+ if ( ! line . trim ( ) ) continue ;
96100
97- const mainCommandMatch = line . match ( MAIN_COMMAND_RE ) ;
98- if ( mainCommandMatch ) {
99- const [ , command , rest ] = mainCommandMatch ;
101+ // main command row
102+ const main = line . match ( MAIN_COMMAND_RE ) ;
103+ if ( main ) {
104+ const [ , command , rest ] = main ;
100105 if ( COMMAND_VALIDATION_RE . test ( command ) ) {
101106 const parts = rest . split ( DESCRIPTION_SPLIT_RE ) ;
102- let description = parts [ parts . length - 1 ] ;
107+ let desc = parts [ parts . length - 1 ] ;
103108
104- // If the last part starts with capital letter, it's likely the description
105- if ( description && CAPITAL_LETTER_START_RE . test ( description ) ) {
106- commands [ command ] = description . trim ( ) ;
109+ if ( desc && CAPITAL_LETTER_START_RE . test ( desc ) ) {
110+ commands [ command ] = desc . trim ( ) ;
107111 } else if ( parts . length > 1 ) {
108- for ( const part of parts ) {
109- if ( CAPITAL_LETTER_START_RE . test ( part ) ) {
110- commands [ command ] = part . trim ( ) ;
112+ for ( const p of parts ) {
113+ if ( CAPITAL_LETTER_START_RE . test ( p ) ) {
114+ commands [ command ] = p . trim ( ) ;
111115 break ;
112116 }
113117 }
114118 }
115119 }
116120 }
117121
118- const continuationMatch = line . match ( CONTINUATION_COMMAND_RE ) ;
119- if ( continuationMatch ) {
120- const [ , command , description ] = continuationMatch ;
122+ const cont = line . match ( CONTINUATION_COMMAND_RE ) ;
123+ if ( cont ) {
124+ const [ , command , description ] = cont ;
121125 if ( COMMAND_VALIDATION_RE . test ( command ) ) {
122126 commands [ command ] = description . trim ( ) ;
123127 }
@@ -134,90 +138,63 @@ export async function getBunCommandsFromMainHelp(): Promise<
134138 return output ? parseBunHelp ( output ) : { } ;
135139}
136140
137- // Parse bun options from help text
138141export function parseBunOptions (
139142 helpText : string ,
140143 { flagsOnly = true } : { flagsOnly ?: boolean } = { }
141144) : ParsedOption [ ] {
142- const helpLines = stripAnsiEscapes ( helpText ) . split ( / \r ? \n / ) ;
143- const optionsOut : ParsedOption [ ] = [ ] ;
144-
145- // Find the Flags: section
146- let optionsStartIndex = - 1 ;
147- for ( let i = 0 ; i < helpLines . length ; i ++ ) {
148- if ( FLAGS_SECTION_RE . test ( helpLines [ i ] . trim ( ) ) ) {
149- optionsStartIndex = i + 1 ;
150- break ;
151- }
152- }
153-
154- if ( optionsStartIndex === - 1 ) return [ ] ;
145+ const lines = toLines ( helpText ) ;
146+ const out : ParsedOption [ ] = [ ] ;
155147
156- // Parse bun's flag format
157- for ( let i = optionsStartIndex ; i < helpLines . length ; i ++ ) {
158- const line = helpLines [ i ] ;
148+ const start = findSectionStart ( lines , FLAGS_SECTION_RE ) ;
149+ if ( start === - 1 ) return out ;
159150
160- // Stop at examples or other sections
151+ for ( let i = start ; i < lines . length ; i ++ ) {
152+ const line = lines [ i ] ;
161153 if ( SECTION_END_RE . test ( line . trim ( ) ) ) break ;
162154
163- // Parse option lines like: " -c, --config=<val> Specify path to config file"
164- const optionMatch = line . match ( BUN_OPTION_RE ) ;
155+ const m = line . match ( BUN_OPTION_RE ) ;
156+ if ( ! m ) continue ;
165157
166- if ( optionMatch ) {
167- const [ , short , long , desc ] = optionMatch ;
158+ const [ , short , long , desc ] = m ;
159+ const takesValue = line . includes ( '=<' ) ; // bun shows value as --opt=<val>
160+ if ( flagsOnly && takesValue ) continue ;
168161
169- // Check if this option takes a value (has =<val>)
170- const takesValue = line . includes ( '=<' ) ;
171-
172- if ( flagsOnly && takesValue ) continue ;
173-
174- optionsOut . push ( {
175- short : short || undefined ,
176- long,
177- desc : desc . trim ( ) ,
178- } ) ;
179- }
162+ out . push ( {
163+ short : short || undefined ,
164+ long,
165+ desc : desc . trim ( ) ,
166+ } ) ;
180167 }
181168
182- return optionsOut ;
169+ return out ;
183170}
184171
185- // load dynamic options synchronously when requested
186172function loadBunOptionsSync ( cmd : LazyCommand , command : string ) : void {
187173 const output = safeExecSync ( `bun ${ command } --help` ) ;
188174 if ( ! output ) return ;
189175
190- const allOptions = parseBunOptions ( output , { flagsOnly : false } ) ;
176+ const options = parseBunOptions ( output , { flagsOnly : false } ) ;
191177
192- for ( const { long, short, desc } of allOptions ) {
193- const alreadyDefined = cmd . optionsRaw ?. get ?.( long ) ;
194- if ( ! alreadyDefined ) {
195- const handler = bunOptionHandlers [ long ] ;
196- if ( handler ) {
197- cmd . option ( long , desc , handler , short ) ;
198- } else {
199- cmd . option ( long , desc , short ) ;
200- }
201- }
178+ for ( const { long, short, desc } of options ) {
179+ const exists = cmd . optionsRaw ?. get ?.( long ) ;
180+ if ( exists ) continue ;
181+
182+ const handler = bunOptionHandlers [ long ] ;
183+ if ( handler ) cmd . option ( long , desc , handler , short ) ;
184+ else cmd . option ( long , desc , short ) ;
202185 }
203186}
204187
205188export async function setupBunCompletions (
206189 completion : PackageManagerCompletion
207190) : Promise < void > {
208191 try {
209- const commandsWithDescriptions = await getBunCommandsFromMainHelp ( ) ;
210-
211- for ( const [ command , description ] of Object . entries (
212- commandsWithDescriptions
213- ) ) {
214- const cmd = completion . command ( command , description ) ;
215-
216- // Setup common argument patterns
217- setupCommandArguments ( cmd , command , 'bun' ) ;
192+ const commands = await getBunCommandsFromMainHelp ( ) ;
218193
219- // Setup lazy option loading
220- setupLazyOptionLoading ( cmd , command , 'bun' , loadBunOptionsSync ) ;
194+ for ( const [ command , description ] of Object . entries ( commands ) ) {
195+ const c = completion . command ( command , description ) ;
196+ setupCommandArguments ( c , command , 'bun' ) ;
197+ setupLazyOptionLoading ( c , command , 'bun' , loadBunOptionsSync ) ;
221198 }
222- } catch ( _err ) { }
199+ } catch { }
223200}
0 commit comments