UNPKG

@gutenye/commander-completion-carapace

Version:

Effortlessly add intelligent autocomplete support to your Commander.js CLI app using Carapace. Supports Bash, Zsh, Fish, Nushell and more

118 lines 3.9 kB
import { merge } from 'lodash-es'; import invariant from 'tiny-invariant'; import * as yaml from 'yaml'; import { CARAPACE_SPECS_DIR } from './constants.js'; import { fs, logger, mergeWithoutNull, question } from './utils/index.js'; export async function installCompletion(program) { if (!program._enableCompletion) { return; } const { spec, text } = buildSpecText(program); if (!text) { return; } const path = `${CARAPACE_SPECS_DIR}/${spec.name}.yaml`; const content = await fs.inputFile(path, 'utf8'); if (content) { if (content === text) { return; } if (!program._enableCompletion.overwrite) { const answer = await question(`Overwrite completion file '${path}'? [y/n] `); const newAnswer = answer.trim().toLowerCase(); if (newAnswer !== 'y') { return; } } } await fs.outputFile(path, text); logger.log(`\nCompletion file is installed to '${path}'\n`); } function buildRootSpec(rootCommand) { const rootSpec = buildSpec(rootCommand); invariant(rootSpec, 'rootSpec is undefined'); const defaultCommand = rootCommand._findCommand(rootCommand._defaultCommandName); if (defaultCommand) { const defaultSpec = buildSpec(defaultCommand, { force: true }); if (defaultSpec?.flags) { rootSpec.flags = rootSpec.flags || {}; merge(rootSpec.flags, defaultSpec.flags); } if (defaultSpec?.completion) { rootSpec.completion = rootSpec.completion || {}; merge(rootSpec.completion, defaultSpec.completion); } } return rootSpec; } // I'm recursive function buildSpec(command, { force } = {}) { if (command._hidden && !force) { return; } const spec = { name: command._name, }; if (command._description) { spec.description = command._description; } if (command._aliases.length > 0) { spec.aliases = command._aliases; } const completion = {}; const positional = []; for (const argument of command.registeredArguments) { positional.push(argument.argChoices || []); } if (positional.some((v) => v.length > 0)) { completion.positional = positional; } for (const option of command.options) { spec.flags = spec.flags || {}; let flag = [option.short, option.long].filter(Boolean).join(', '); if (!option.isBoolean) { flag += '='; } if (option.required) { flag += '=!'; } if (option.optional) { flag += '=?'; } spec.flags[flag] = option.description; if (option.argChoices) { completion.flag = completion.flag || {}; completion.flag[option.name()] = option.argChoices; } } if (Object.keys(completion).length > 0) { spec.completion = completion; } if (command._completion) { spec.completion = spec.completion || {}; mergeWithoutNull(spec.completion, command._completion); } if (command._carapace) { mergeWithoutNull(spec, command._carapace); } for (const subcommand of command.commands) { const newSpec = buildSpec(subcommand); if (newSpec) { spec.commands = spec.commands || []; spec.commands.push(newSpec); } } return spec; } export function buildSpecText(command) { const spec = buildRootSpec(command); if (!(spec.commands || spec.persistentflags || spec.flags || spec.completion)) { return {}; } if (!command._name) { throw new Error('[completion.buildSpecText] command name is missing, use program.name() to define it'); } const text = yaml.stringify(spec); return { spec, text }; } //# sourceMappingURL=installCompletion.js.map