[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124import type { CommandDef, ArgsDef, PositionalArgDef, CommandMeta } from 'citty';
interface FigSpec { name: string; description: string; options?: FigOption[]; subcommands?: FigSubcommand[]; args?: FigArg[];}
interface FigOption { name: string; description: string; args?: FigArg[]; isRequired?: boolean;}
interface FigSubcommand { name: string; description: string; options?: FigOption[]; subcommands?: FigSubcommand[]; args?: FigArg[];}
interface FigArg { name: string; description?: string; isOptional?: boolean; isVariadic?: boolean; suggestions?: FigSuggestion[];}
interface FigSuggestion { name: string; description?: string;}
async function processArgs<T extends ArgsDef>( args: T): Promise<{ options: FigOption[]; args: FigArg[] }> { const options: FigOption[] = []; const positionalArgs: FigArg[] = [];
for (const [name, arg] of Object.entries(args)) { if (arg.type === 'positional') { const positionalArg = arg as PositionalArgDef; positionalArgs.push({ name, description: positionalArg.description, isOptional: !positionalArg.required, // Assume variadic if the name suggests it (e.g. [...files]) isVariadic: name.startsWith('[...') || name.startsWith('<...'), }); } else { const option: FigOption = { name: `--${name}`, description: arg.description || '', isRequired: arg.required, };
if ('alias' in arg && arg.alias) { // Handle both string and array aliases const aliases = Array.isArray(arg.alias) ? arg.alias : [arg.alias]; aliases.forEach((alias) => { options.push({ ...option, name: `-${alias}`, }); }); }
options.push(option); } }
return { options, args: positionalArgs };}
async function processCommand<T extends ArgsDef>( command: CommandDef<T>, parentName = ''): Promise<FigSpec> { const resolvedMeta = await Promise.resolve(command.meta); const meta = resolvedMeta as CommandMeta; const subCommands = await Promise.resolve(command.subCommands);
if (!meta || !meta.name) { throw new Error('Command meta or name is missing'); }
const spec: FigSpec = { name: parentName ? `${parentName} ${meta.name}` : meta.name, description: meta.description || '', };
if (command.args) { const resolvedArgs = await Promise.resolve(command.args); // Cast to ArgsDef since we know the resolved value will be compatible const { options, args } = await processArgs(resolvedArgs as ArgsDef); if (options.length > 0) spec.options = options; if (args.length > 0) spec.args = args; }
if (subCommands) { spec.subcommands = await Promise.all( Object.entries(subCommands).map(async ([_, subCmd]) => { const resolved = await Promise.resolve(subCmd); return processCommand(resolved, spec.name); }) ); }
return spec;}
// TODO: this should be an extension of t.setup function and not something like this.export async function generateFigSpec<T extends ArgsDef>( command: CommandDef<T>): Promise<string> { const spec = await processCommand(command); return JSON.stringify(spec, null, 2);}