@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
JavaScript
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