UNPKG

@catladder/cli

Version:

Panter cli tool for cloud CI/CD and DevOps

225 lines (224 loc) • 8.19 kB
"use strict"; var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function () { return m[k]; } }; } Object.defineProperty(o, k2, desc); } : function (o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; }); var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); } : function (o, v) { o["default"] = v; }); var __importStar = this && this.__importStar || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = this && this.__importDefault || function (mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const terminal_1 = require("./adapters/terminal"); const completion_1 = require("./completion"); const getProjectConfig_1 = require("./config/getProjectConfig"); const packageInfos_1 = __importDefault(require("./packageInfos")); const portForwards_1 = require("./utils/portForwards"); // Import all commands const commands = __importStar(require("./commands")); const program = new commander_1.Command(); program.name("catladder").description("Panter CLI tool for cloud CI/CD and DevOps").version(packageInfos_1.default.version).option("-y, --yes", "skip all confirmation prompts"); // Collect all command defs for completion const allCommandDefs = []; // Cache for intermediate subcommand groups (e.g., "project", "project cloudsql") const subcommandCache = new Map(); /** * Get or create a chain of nested subcommands. * E.g., for ["project", "cloudsql"], ensures program.command("project").command("cloudsql") exists. */ function getOrCreateParent(parts) { if (parts.length === 0) return program; const key = parts.join(" "); if (subcommandCache.has(key)) return subcommandCache.get(key); const parent = getOrCreateParent(parts.slice(0, -1)); const cmd = parent.command(parts[parts.length - 1]); subcommandCache.set(key, cmd); return cmd; } /** * Register a CommandDef with Commander.js. * - Command name can contain spaces for nesting (e.g., "project cloudsql proxy") * - Inputs with `positional: true` become Commander positional args * - Other inputs become optional --flags * - --inputs accepts JSON for programmatic use */ function registerCommand(def) { allCommandDefs.push(def); // Separate positional inputs from flag inputs const positionalInputs = []; const flagInputs = []; for (const [name, inputDef] of Object.entries(def.inputs)) { if (inputDef.positional) { positionalInputs.push([name, inputDef]); } else { flagInputs.push([name, inputDef]); } } // Split name into parent groups + leaf command const nameParts = def.name.split(" "); const parentParts = nameParts.slice(0, -1); const leafName = nameParts[nameParts.length - 1]; // Build leaf command string with positional args let cmdStr = leafName; for (const [name] of positionalInputs) { cmdStr += ` [${name}]`; } const parent = getOrCreateParent(parentParts); const cmd = parent.command(cmdStr).description(def.description); // Add --flag for each non-positional input for (const [name, inputDef] of flagInputs) { const flagName = name.replace(/([A-Z])/g, "-$1").toLowerCase(); const desc = inputDef.message.replace(/\s*🤔\s*$/, "").trim(); if (inputDef.type === "boolean") { cmd.option(`--${flagName}`, desc); cmd.option(`--no-${flagName}`, `negate: ${desc}`); } else { cmd.option(`--${flagName} <value>`, desc); } } // Add --inputs for JSON input cmd.option("--inputs <json>", "JSON object with input values"); cmd.action(async (...rawArgs) => { // Commander passes positional args first, then options object, then the Command const opts = rawArgs[rawArgs.length - 2]; // Parse positional args into cliOptions const cliOptions = {}; positionalInputs.forEach(([name], i) => { if (rawArgs[i] !== undefined) { cliOptions[name] = rawArgs[i]; } }); // Parse CLI flag overrides for (const [name, inputDef] of flagInputs) { const flagName = name.replace(/([A-Z])/g, "-$1").toLowerCase(); const camelName = flagName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); if (opts[camelName] !== undefined) { let value = opts[camelName]; if (inputDef.type === "number" && typeof value === "string") { value = Number(value); } cliOptions[name] = value; } } // Parse --inputs JSON let jsonInputs = {}; if (opts.inputs) { try { jsonInputs = JSON.parse(opts.inputs); } catch (_a) { console.error("Error: --inputs must be valid JSON"); process.exit(1); } } const ctx = (0, terminal_1.createTerminalContext)(def, { cliOptions, jsonInputs, yes: program.opts().yes }); try { await def.execute(ctx); } catch (err) { if (err.name === "MissingInputError") { console.error(`\n${err.message}\n`); process.exit(1); } throw err; } }); } // Register all commands (async to support isAvailable checks) async function registerAllCommands() { // Fetch contexts once for all isAvailable checks let contexts = []; try { contexts = await (0, getProjectConfig_1.getAllPipelineContexts)(); } catch (_a) { // No config available (e.g. outside a project) — contexts stays empty } for (const cmd of Object.values(commands)) { if (cmd && typeof cmd === "object" && "name" in cmd && "execute" in cmd) { const def = cmd; if (def.isAvailable) { if (!def.isAvailable(contexts)) continue; } registerCommand(def); } } } // Hidden __complete command for shell completion program.command("__complete", { hidden: true }).argument("[words]", "words typed so far").argument("[cursor]", "word at cursor").action(async (wordsStr, cursor) => { const words = wordsStr ? wordsStr.split(" ").filter(Boolean) : []; const cursorWord = cursor !== null && cursor !== void 0 ? cursor : ""; try { const completions = await (0, completion_1.getCompletions)(allCommandDefs, words, cursorWord); for (const c of completions) { console.log(c); } } catch (_a) { // Silently fail -- completion should never break the shell } process.exit(0); }); // completion commands const completionCmd = program.command("completion").description("shell completion setup"); completionCmd.command("zsh").description("output zsh completion script").action(() => { console.log((0, completion_1.generateZshCompletionScript)()); }); completionCmd.command("install").description("install shell completions into your .zshrc").action(async () => { await (0, completion_1.installCompletions)(); }); completionCmd.command("uninstall").description("remove shell completions from your .zshrc").action(async () => { await (0, completion_1.uninstallCompletions)(); }); // Cleanup on exit process.on("exit", () => { (0, portForwards_1.stopAllPortForwards)(); }); // Prevent Node.js from printing the (minified) source line on uncaught errors process.on("uncaughtException", err => { var _a; console.error((_a = err.stack) !== null && _a !== void 0 ? _a : `${err.name}: ${err.message}`); process.exit(1); }); process.on("unhandledRejection", reason => { var _a; if (reason instanceof Error) { console.error((_a = reason.stack) !== null && _a !== void 0 ? _a : `${reason.name}: ${reason.message}`); } else { console.error(reason); } process.exit(1); }); registerAllCommands().then(() => program.parse());