11import type { PackageManagerCompletion } from '../package-manager-completion.js' ;
2- import { stripAnsiEscapes , type ParsedOption } from '../utils/text-utils.js' ;
3- import {
4- LazyCommand ,
5- OptionHandlers ,
6- commonOptionHandlers ,
7- setupLazyOptionLoading ,
8- setupCommandArguments ,
9- safeExec ,
10- safeExecSync ,
11- } from '../utils/shared.js' ;
12-
13- const COMMANDS_SECTION_RE = / ^ C o m m a n d s : \s * $ / i;
14- const FLAGS_SECTION_RE = / ^ F l a g s : \s * $ / i;
15- const SECTION_END_RE = / ^ ( E x a m p l e s | F u l l d o c u m e n t a t i o n | L e a r n m o r e ) / i;
16- const COMMAND_VALIDATION_RE = / ^ [ a - z ] [ a - z 0 - 9 - ] * $ / ;
17- const BUN_OPTION_RE =
18- / ^ \s * (?: - ( [ a - z A - Z ] ) , ? \s * ) ? - - ( [ a - z ] [ a - z 0 - 9 - ] * ) (?: = < [ ^ > ] + > ) ? \s + ( .+ ) $ / ;
19- const MAIN_COMMAND_RE = / ^ ( [ a - z ] [ a - z 0 - 9 - ] * ) \s + ( .+ ) $ / ;
20- const CONTINUATION_COMMAND_RE = / ^ \s { 12 , } ( [ a - z ] [ a - z 0 - 9 - ] * ) \s + ( .+ ) $ / ;
21- const EMPTY_LINE_FOLLOWED_BY_NON_COMMAND_RE = / ^ \s + [ a - z ] / ;
22- const DESCRIPTION_SPLIT_RE = / \s { 2 , } / ;
23- const 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- }
36-
37- const bunOptionHandlers : OptionHandlers = {
38- ...commonOptionHandlers ,
39-
40- silent ( complete ) {
41- complete ( 'true' , 'Enable silent mode' ) ;
42- complete ( 'false' , 'Disable silent mode' ) ;
43- } ,
44-
45- backend ( complete ) {
46- complete ( 'clonefile' , 'Clone files (default, fastest)' ) ;
47- complete ( 'hardlink' , 'Use hard links' ) ;
48- complete ( 'symlink' , 'Use symbolic links' ) ;
49- complete ( 'copyfile' , 'Copy files' ) ;
50- } ,
51-
52- linker ( complete ) {
53- complete ( 'isolated' , 'Isolated linker strategy' ) ;
54- complete ( 'hoisted' , 'Hoisted linker strategy' ) ;
55- } ,
56-
57- omit ( complete ) {
58- complete ( 'dev' , 'Omit devDependencies' ) ;
59- complete ( 'optional' , 'Omit optionalDependencies' ) ;
60- complete ( 'peer' , 'Omit peerDependencies' ) ;
61- } ,
62-
63- shell ( complete ) {
64- complete ( 'bun' , 'Use Bun shell' ) ;
65- complete ( 'system' , 'Use system shell' ) ;
66- } ,
67-
68- 'unhandled-rejections' ( complete ) {
69- complete ( 'strict' , 'Strict unhandled rejection handling' ) ;
70- complete ( 'throw' , 'Throw on unhandled rejections' ) ;
71- complete ( 'warn' , 'Warn on unhandled rejections' ) ;
72- complete ( 'none' , 'Ignore unhandled rejections' ) ;
73- complete ( 'warn-with-error-code' , 'Warn with error code' ) ;
74- } ,
75- } ;
76-
77- /** ---------- Commands ---------- */
78- export function parseBunHelp ( helpText : string ) : Record < string , string > {
79- const lines = toLines ( helpText ) ;
80-
81- const startIndex = findSectionStart ( lines , COMMANDS_SECTION_RE ) ;
82- if ( startIndex === - 1 ) return { } ;
83-
84- const commands : Record < string , string > = { } ;
85-
86- for ( let i = startIndex ; i < lines . length ; i ++ ) {
87- const line = lines [ i ] ;
88-
89- // stop when we hit Flags section or empty line followed by non-command content
90- if (
91- FLAGS_SECTION_RE . test ( line . trim ( ) ) ||
92- ( line . trim ( ) === '' &&
93- i + 1 < lines . length &&
94- ! lines [ i + 1 ] . match ( EMPTY_LINE_FOLLOWED_BY_NON_COMMAND_RE ) )
95- ) {
96- break ;
97- }
98-
99- if ( ! line . trim ( ) ) continue ;
100-
101- // main command row
102- const main = line . match ( MAIN_COMMAND_RE ) ;
103- if ( main ) {
104- const [ , command , rest ] = main ;
105- if ( COMMAND_VALIDATION_RE . test ( command ) ) {
106- const parts = rest . split ( DESCRIPTION_SPLIT_RE ) ;
107- let desc = parts [ parts . length - 1 ] ;
108-
109- if ( desc && CAPITAL_LETTER_START_RE . test ( desc ) ) {
110- commands [ command ] = desc . trim ( ) ;
111- } else if ( parts . length > 1 ) {
112- for ( const p of parts ) {
113- if ( CAPITAL_LETTER_START_RE . test ( p ) ) {
114- commands [ command ] = p . trim ( ) ;
115- break ;
116- }
117- }
118- }
119- }
120- }
121-
122- const cont = line . match ( CONTINUATION_COMMAND_RE ) ;
123- if ( cont ) {
124- const [ , command , description ] = cont ;
125- if ( COMMAND_VALIDATION_RE . test ( command ) ) {
126- commands [ command ] = description . trim ( ) ;
127- }
128- }
129- }
130-
131- return commands ;
132- }
133-
134- export async function getBunCommandsFromMainHelp ( ) : Promise <
135- Record < string , string >
136- > {
137- const output = await safeExec ( 'bun --help' ) ;
138- return output ? parseBunHelp ( output ) : { } ;
139- }
140-
141- export function parseBunOptions (
142- helpText : string ,
143- { flagsOnly = true } : { flagsOnly ?: boolean } = { }
144- ) : ParsedOption [ ] {
145- const lines = toLines ( helpText ) ;
146- const out : ParsedOption [ ] = [ ] ;
147-
148- const start = findSectionStart ( lines , FLAGS_SECTION_RE ) ;
149- if ( start === - 1 ) return out ;
150-
151- for ( let i = start ; i < lines . length ; i ++ ) {
152- const line = lines [ i ] ;
153- if ( SECTION_END_RE . test ( line . trim ( ) ) ) break ;
154-
155- const m = line . match ( BUN_OPTION_RE ) ;
156- if ( ! m ) continue ;
157-
158- const [ , short , long , desc ] = m ;
159- const takesValue = line . includes ( '=<' ) ; // bun shows value as --opt=<val>
160- if ( flagsOnly && takesValue ) continue ;
161-
162- out . push ( {
163- short : short || undefined ,
164- long,
165- desc : desc . trim ( ) ,
166- } ) ;
167- }
168-
169- return out ;
170- }
171-
172- function loadBunOptionsSync ( cmd : LazyCommand , command : string ) : void {
173- const output = safeExecSync ( `bun ${ command } --help` ) ;
174- if ( ! output ) return ;
175-
176- const options = parseBunOptions ( output , { flagsOnly : false } ) ;
177-
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 ) ;
185- }
186- }
1872
1883export async function setupBunCompletions (
1894 completion : PackageManagerCompletion
190- ) : Promise < void > {
191- try {
192- const commands = await getBunCommandsFromMainHelp ( ) ;
193-
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 ) ;
198- }
199- } catch { }
200- }
5+ ) : Promise < void > { }
0 commit comments