cli / mcp for bitbucket
at main 103 lines 2.9 kB view raw
1import { 2 configureClient, 3 type Result, 4 resolveAuth, 5 resolveWorkspaceAndRepo, 6} from '@bitbucket-tool/core'; 7import type { Command } from 'commander'; 8 9interface ArgumentConfig { 10 syntax: string; 11 description: string; 12 // biome-ignore lint/suspicious/noExplicitAny: parser can return any type based on command definition 13 parser?: (value: string) => any; 14} 15 16interface OptionConfig { 17 flag: string; 18 description: string; 19 // biome-ignore lint/suspicious/noExplicitAny: option defaults can be any type 20 default?: any; 21} 22 23export interface CommandParams { 24 workspace: string; 25 repoSlug: string; 26 // biome-ignore lint/suspicious/noExplicitAny: args are dynamically typed based on parser functions 27 args: any[]; 28 // biome-ignore lint/suspicious/noExplicitAny: options are dynamically typed based on command definition 29 options: Record<string, any>; 30} 31 32interface CommandConfig { 33 name: string; 34 description: string; 35 alias?: string; 36 arguments?: ArgumentConfig[]; 37 options?: OptionConfig[]; 38 action: (params: CommandParams) => Promise<void>; 39} 40 41export const unwrapOrExit = <T>(result: Result<T>): T => { 42 if (!result.ok) { 43 console.error(`Error: ${result.error.message}`); 44 if (result.error.detail) { 45 console.error(JSON.stringify(result.error.detail, null, 2)); 46 } 47 process.exit(1); 48 } 49 return result.data; 50}; 51 52const wrapAction = (handler: (params: CommandParams) => Promise<void>) => { 53 // biome-ignore lint/suspicious/noExplicitAny: commander.js passes arguments dynamically 54 return async (...rawArgs: any[]) => { 55 try { 56 const command = rawArgs[rawArgs.length - 1]; 57 const options = command.opts(); 58 const args = rawArgs.slice(0, -2); 59 60 const { workspace, repoSlug } = resolveWorkspaceAndRepo({ 61 workspace: options.workspace, 62 repoSlug: options.repo, 63 }); 64 65 const auth = resolveAuth(); 66 await configureClient(auth); 67 68 await handler({ workspace, repoSlug, args, options }); 69 } catch (error) { 70 console.error('Error:', error instanceof Error ? error.message : String(error)); 71 process.exit(1); 72 } 73 }; 74}; 75 76export const defineCommand = (config: CommandConfig) => { 77 return (program: Command): Command => { 78 const cmd = program.command(config.name).description(config.description); 79 80 if (config.alias) { 81 cmd.alias(config.alias); 82 } 83 84 config.arguments?.forEach((arg) => { 85 cmd.argument(arg.syntax, arg.description, arg.parser); 86 }); 87 88 config.options?.forEach((opt) => { 89 if (opt.default !== undefined) { 90 cmd.option(opt.flag, opt.description, opt.default); 91 } else { 92 cmd.option(opt.flag, opt.description); 93 } 94 }); 95 96 cmd.option('-w, --workspace <workspace>', 'Bitbucket workspace'); 97 cmd.option('-r, --repo <repo>', 'Repository slug'); 98 99 cmd.action(wrapAction(config.action)); 100 101 return cmd; 102 }; 103};