@bluecadet/launchpad-cli
Version:
CLI for @bluecadet/launchpad utilities
122 lines • 4.81 kB
JavaScript
import { okAsync } from "neverthrow";
import { cliLogger } from "./cli-logger.js";
import { withDaemonOrController } from "./controller-execution.js";
const RESERVED_COMMAND_NAMES = new Set(["start", "stop", "status", "help", "version"]);
function isLeafCommand(decl) {
return "commands" in decl;
}
function buildPositionalToken(pos) {
const variadicSuffix = pos.variadic ? ".." : "";
const inner = `${pos.name}${variadicSuffix}`;
return pos.required ? `<${inner}>` : `[${inner}]`;
}
function applyLeafOptions(yargsInstance, leaf) {
let y = yargsInstance;
if (leaf.flags) {
for (const [flagName, flag] of Object.entries(leaf.flags)) {
y = y.option(flagName, {
type: flag.type,
alias: flag.alias,
describe: flag.description,
default: flag.default,
demandOption: flag.required,
array: flag.array,
});
}
}
if (leaf.positionals) {
for (const pos of leaf.positionals) {
y = y.positional(pos.name, {
type: pos.type,
describe: pos.description,
demandOption: pos.required,
array: pos.variadic,
});
}
}
return y;
}
function buildLeafCommandString(leaf) {
if (!leaf.positionals || leaf.positionals.length === 0) {
return leaf.name;
}
const tokens = leaf.positionals.map(buildPositionalToken);
return `${leaf.name} ${tokens.join(" ")}`;
}
function collectArgValues(args, leaf) {
const merged = {};
if (leaf.flags) {
for (const flagName of Object.keys(leaf.flags)) {
if (flagName in args) {
merged[flagName] = args[flagName];
}
}
}
if (leaf.positionals) {
for (const pos of leaf.positionals) {
if (pos.name in args) {
merged[pos.name] = args[pos.name];
}
}
}
return merged;
}
function registerLeaf(yargsInstance, pluginConfig, leaf, dir, controllerConfig) {
const commandStr = buildLeafCommandString(leaf);
return yargsInstance.command(commandStr, leaf.description ?? "", (y) => applyLeafOptions(y, leaf), async (args) => {
const argValues = collectArgValues(args, leaf);
const buildCommands = (baseCommands) => baseCommands.map((cmd) => ({ ...argValues, ...cmd }));
const result = await withDaemonOrController(dir, controllerConfig, {
mode: leaf.mode ?? "task",
ifDaemon: (client) => {
return buildCommands(leaf.commands).reduce((acc, cmd) => acc.andThen(() => client.executeCommand(cmd)), okAsync(undefined));
},
otherwise: (controller) => {
return controller.registerPlugin(pluginConfig).andThen(() => {
return buildCommands(leaf.commands).reduce((acc, cmd) => acc.andThen(() => controller.executeCommand(cmd)), okAsync(undefined));
});
},
});
if (result.isErr()) {
cliLogger.error(result.error);
process.exit(1);
}
});
}
function registerGroup(yargsInstance, pluginConfig, group, dir, controllerConfig) {
return yargsInstance.command(`${group.name} <command>`, group.description ?? "", (y) => {
let sub = y;
for (const subDecl of group.subcommands) {
sub = registerDeclaration(sub, pluginConfig, subDecl, dir, controllerConfig);
}
return sub;
}, () => { });
}
function registerDeclaration(yargsInstance, pluginConfig, decl, dir, controllerConfig) {
if (isLeafCommand(decl)) {
return registerLeaf(yargsInstance, pluginConfig, decl, dir, controllerConfig);
}
return registerGroup(yargsInstance, pluginConfig, decl, dir, controllerConfig);
}
function detectConflicts(entries) {
const seen = new Map();
for (const { pluginConfig, declaration } of entries) {
const name = declaration.name;
if (RESERVED_COMMAND_NAMES.has(name)) {
throw new Error(`CLI command conflict: "${name}" is a reserved command name and cannot be declared by plugin "${pluginConfig.name}"`);
}
if (seen.has(name)) {
throw new Error(`CLI command conflict: "${name}" is declared by both "${seen.get(name)}" and "${pluginConfig.name}"`);
}
seen.set(name, pluginConfig.name);
}
}
export function registerPluginCliCommands(yargsInstance, entries, dir, controllerConfig) {
detectConflicts(entries);
let y = yargsInstance;
for (const { pluginConfig, declaration } of entries) {
y = registerDeclaration(y, pluginConfig, declaration, dir, controllerConfig);
}
return y;
}
//# sourceMappingURL=plugin-cli-registration.js.map