[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.

feat: add bun handler (#51)

* add bun handler

* update

authored by

AmirHossein Sakhravi and committed by
GitHub
(Sep 14, 2025, 9:56 PM +0330) a942235f 578f633b

+190 -1
+190 -1
bin/handlers/bun-handler.ts
··· 1 1 import 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 = /^Commands:\s*$/i; 14 + const FLAGS_SECTION_RE = /^Flags:\s*$/i; 15 + const SECTION_END_RE = /^(Examples|Full documentation|Learn more)/i; 16 + const COMMAND_VALIDATION_RE = /^[a-z][a-z0-9-]*$/; 17 + const BUN_OPTION_RE = 18 + /^\s*(?:-([a-zA-Z]),?\s*)?--([a-z][a-z0-9-]*)(?:=<[^>]+>)?\s+(.+)$/; 19 + const MAIN_COMMAND_RE = /^ ([a-z][a-z0-9-]*)\s+(.+)$/; 20 + const CONTINUATION_COMMAND_RE = /^\s{12,}([a-z][a-z0-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 + backend(complete) { 41 + complete('clonefile', ' '); 42 + complete('hardlink', ' '); 43 + complete('symlink', ' '); 44 + complete('copyfile', ' '); 45 + }, 46 + 47 + linker(complete) { 48 + complete('isolated', ' '); 49 + complete('hoisted', ' '); 50 + }, 51 + 52 + omit(complete) { 53 + complete('dev', ' '); 54 + complete('optional', ' '); 55 + complete('peer', ' '); 56 + }, 57 + 58 + shell(complete) { 59 + complete('bun', ' '); 60 + complete('system', ' '); 61 + }, 62 + 63 + 'unhandled-rejections'(complete) { 64 + complete('strict', ' '); 65 + complete('throw', ' '); 66 + complete('warn', ' '); 67 + complete('none', ' '); 68 + complete('warn-with-error-code', ' '); 69 + }, 70 + }; 71 + 72 + export function parseBunHelp(helpText: string): Record<string, string> { 73 + const lines = toLines(helpText); 74 + 75 + const startIndex = findSectionStart(lines, COMMANDS_SECTION_RE); 76 + if (startIndex === -1) return {}; 77 + 78 + const commands: Record<string, string> = {}; 79 + 80 + for (let i = startIndex; i < lines.length; i++) { 81 + const line = lines[i]; 82 + 83 + // stop when we hit Flags section or empty line followed by non-command content 84 + if ( 85 + FLAGS_SECTION_RE.test(line.trim()) || 86 + (line.trim() === '' && 87 + i + 1 < lines.length && 88 + !lines[i + 1].match(EMPTY_LINE_FOLLOWED_BY_NON_COMMAND_RE)) 89 + ) { 90 + break; 91 + } 92 + 93 + if (!line.trim()) continue; 94 + 95 + // main command row 96 + const main = line.match(MAIN_COMMAND_RE); 97 + if (main) { 98 + const [, command, rest] = main; 99 + if (COMMAND_VALIDATION_RE.test(command)) { 100 + const parts = rest.split(DESCRIPTION_SPLIT_RE); 101 + let desc = parts[parts.length - 1]; 102 + 103 + if (desc && CAPITAL_LETTER_START_RE.test(desc)) { 104 + commands[command] = desc.trim(); 105 + } else if (parts.length > 1) { 106 + for (const p of parts) { 107 + if (CAPITAL_LETTER_START_RE.test(p)) { 108 + commands[command] = p.trim(); 109 + break; 110 + } 111 + } 112 + } 113 + } 114 + } 115 + 116 + const cont = line.match(CONTINUATION_COMMAND_RE); 117 + if (cont) { 118 + const [, command, description] = cont; 119 + if (COMMAND_VALIDATION_RE.test(command)) { 120 + commands[command] = description.trim(); 121 + } 122 + } 123 + } 124 + 125 + return commands; 126 + } 127 + 128 + export async function getBunCommandsFromMainHelp(): Promise< 129 + Record<string, string> 130 + > { 131 + const output = await safeExec('bun --help'); 132 + return output ? parseBunHelp(output) : {}; 133 + } 134 + 135 + export function parseBunOptions( 136 + helpText: string, 137 + { flagsOnly = true }: { flagsOnly?: boolean } = {} 138 + ): ParsedOption[] { 139 + const lines = toLines(helpText); 140 + const out: ParsedOption[] = []; 141 + 142 + const start = findSectionStart(lines, FLAGS_SECTION_RE); 143 + if (start === -1) return out; 144 + 145 + for (let i = start; i < lines.length; i++) { 146 + const line = lines[i]; 147 + if (SECTION_END_RE.test(line.trim())) break; 148 + 149 + const m = line.match(BUN_OPTION_RE); 150 + if (!m) continue; 151 + 152 + const [, short, long, desc] = m; 153 + const takesValue = line.includes('=<'); // bun shows value as --opt=<val> 154 + if (flagsOnly && takesValue) continue; 155 + 156 + out.push({ 157 + short: short || undefined, 158 + long, 159 + desc: desc.trim(), 160 + }); 161 + } 162 + 163 + return out; 164 + } 165 + 166 + function loadBunOptionsSync(cmd: LazyCommand, command: string): void { 167 + const output = safeExecSync(`bun ${command} --help`); 168 + if (!output) return; 169 + 170 + const options = parseBunOptions(output, { flagsOnly: false }); 171 + 172 + for (const { long, short, desc } of options) { 173 + const exists = cmd.optionsRaw?.get?.(long); 174 + if (exists) continue; 175 + 176 + const handler = bunOptionHandlers[long]; 177 + if (handler) cmd.option(long, desc, handler, short); 178 + else cmd.option(long, desc, short); 179 + } 180 + } 2 181 3 182 export async function setupBunCompletions( 4 183 completion: PackageManagerCompletion 5 - ): Promise<void> {} 184 + ): Promise<void> { 185 + try { 186 + const commands = await getBunCommandsFromMainHelp(); 187 + 188 + for (const [command, description] of Object.entries(commands)) { 189 + const c = completion.command(command, description); 190 + setupCommandArguments(c, command, 'bun'); 191 + setupLazyOptionLoading(c, command, 'bun', loadBunOptionsSync); 192 + } 193 + } catch {} 194 + }