UNPKG

@bluecadet/launchpad-cli

Version:
122 lines 4.81 kB
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