nx
Version:
121 lines (120 loc) • 4.93 kB
JavaScript
;
// Slow-path completion: command/subcommand/flag names. Runs after
// tryValueCompletion has nothing to offer.
Object.defineProperty(exports, "__esModule", { value: true });
exports.DESC_SEPARATOR = void 0;
exports.tryCommandSurfaceCompletion = tryCommandSurfaceCompletion;
exports.getTopLevelCommands = getTopLevelCommands;
exports.getCommandCompletions = getCommandCompletions;
exports.formatDescription = formatDescription;
exports.shellRendersDescriptions = shellRendersDescriptions;
const trigger_1 = require("./trigger");
const argv_layout_1 = require("./argv-layout");
const metadata_1 = require("./metadata");
const command_handlers_1 = require("./command-handlers");
/** Slow-path entry point. Returns true if anything was emitted. */
function tryCommandSurfaceCompletion(argv = process.argv) {
const parsed = (0, argv_layout_1.parseCompletionArgs)(argv);
if (parsed === null)
return false;
const matched = getCommandCompletions(parsed.current, parsed.tokens);
const completions = matched !== null
? matched
: getTopLevelCommands(parsed.current, shellRendersDescriptions());
if (completions === null || completions.length === 0)
return false;
for (const line of completions) {
console.log(line);
}
return true;
}
/** Top-level command names. Unions yargs handlers with the completion
* registry's single-token paths (infix targets). */
function getTopLevelCommands(current, withDesc) {
const handlers = (0, command_handlers_1.getNxCommandHandlers)();
const seen = new Set();
const completions = [];
for (const name of Object.keys(handlers)) {
if (name === '$0' || name.startsWith('_'))
continue;
if (current && !name.startsWith(current))
continue;
const handler = handlers[name];
if (handler?.description === false)
continue; // hidden
seen.add(name);
const desc = withDesc ? formatDescription(handler?.description) : '';
completions.push(desc ? `${name}${exports.DESC_SEPARATOR}${desc}` : name);
}
// Infix targets + any other top-level completion-only paths.
for (const name of (0, metadata_1.getRegisteredTopLevelPaths)()) {
if (seen.has(name))
continue;
if (current && !name.startsWith(current))
continue;
seen.add(name);
completions.push(name);
}
return completions;
}
/**
* Enumerates subcommands + options of a matched top-level command. Returns
* null when no top-level command name is matched in `args`.
*/
function getCommandCompletions(current, args) {
const handlers = (0, command_handlers_1.getNxCommandHandlers)();
const cmdName = args.find((a) => handlers[a]);
if (!cmdName) {
return null;
}
const handler = handlers[cmdName];
// Once we recognize a top-level command in `args`, we own the slot —
// return [] (not null) for the "found a command but can't enumerate its
// surface" case so the caller doesn't fall back to top-level commands
// (which would mis-offer e.g. `exec` for `nx g c ex`).
if (typeof handler.builder !== 'function') {
return [];
}
const intro = (0, command_handlers_1.introspectBuilder)(handler.builder);
if (!intro)
return [];
const completions = [];
const isFlagPrefix = current.startsWith('-');
const withDesc = shellRendersDescriptions();
if (!isFlagPrefix) {
for (const [name, desc] of intro.subcommands) {
const formatted = withDesc ? formatDescription(desc) : '';
completions.push(formatted ? `${name}${exports.DESC_SEPARATOR}${formatted}` : name);
}
}
for (const [name, desc] of intro.options) {
const formatted = withDesc ? formatDescription(desc) : '';
completions.push(formatted ? `--${name}${exports.DESC_SEPARATOR}${formatted}` : `--${name}`);
}
if (!current) {
return completions;
}
const flagName = current.replace(/^-+/, '');
return completions.filter((c) => {
if (isFlagPrefix) {
return c.startsWith(`--${flagName}`);
}
return c.startsWith(current);
});
}
/** value\tdescription separator. TAB because completion values can contain
* colons (`my-app:build`); descriptions get TABs collapsed. */
exports.DESC_SEPARATOR = '\t';
/** Strip the y18n marker yargs prepends to its built-in --help / --version
* descriptions, and collapse stray TABs so they can't forge the
* value/description separator. */
function formatDescription(raw) {
if (!raw)
return '';
return raw.replace(/^__yargsString__:/, '').replace(/\t/g, ' ');
}
/** zsh (compadd -d) and fish (complete -a) parse `value\tdescription`. */
function shellRendersDescriptions() {
const shell = (0, trigger_1.getCompletionShell)();
return shell === 'zsh' || shell === 'fish';
}