/** * Shared CLI action runners — all go through Coolify SDK. * * Eliminates boilerplate across CLI commands by providing reusable * patterns for init → call → format output. * * @module */ import ora from "ora"; import chalk from "chalk"; import Table from "cli-table3"; import { Coolify } from "../sdk.js"; import { formatStatus } from "../utils/format.js"; import { resolveUuid } from "./coolify-state.js"; import { resolveNameOrUuid } from "./name-resolver.js"; /** Singleton SDK instance for CLI (uses env vars / config file). */ let _sdk: Coolify | null = null; /** * Gets or creates the Coolify SDK singleton for CLI usage. * * @returns Coolify SDK instance */ function getSdk(): Coolify { if (!_sdk) _sdk = Coolify.fromEnv(); return _sdk; } /** * Runs a simple action (uuid → message) with spinner. * Used for: start, stop, restart, delete, cancel, etc. * * @param uuid - UUID (optional, reads .coolify.json if undefined) * @param actionLabel - Human label like "Starting application" * @param callFn - Function that calls the SDK method (throws on error) * @param successMsg - Success message template (receives uuid) */ export async function runAction( uuid: string | undefined, actionLabel: string, callFn: (sdk: Coolify, uuid: string) => Promise, successMsg: (uuid: string) => string, ): Promise { let resolved = resolveUuid(uuid); if (!resolved && uuid) { resolved = await resolveNameOrUuid(uuid); } if (!resolved) { console.error( chalk.red("Error: No UUID provided and no .coolify.json found"), ); return; } const spinner = ora("Connecting to Coolify...").start(); try { spinner.text = `${actionLabel}...`; await callFn(getSdk(), resolved); spinner.succeed(chalk.green(successMsg(resolved))); } catch (error) { spinner.fail( chalk.red( `Failed: ${error instanceof Error ? error.message : String(error)}`, ), ); } } /** * Column definition for table rendering. */ interface IColumnDef { /** Column header */ header: string; /** Value extractor */ value: (item: T) => string; } /** * Runs a list action and renders a table. * Used for: list apps, databases, services, servers, keys, etc. * * @param resourceLabel - Human label like "databases" * @param callFn - Function that calls the SDK list method (throws on error) * @param columns - Column definitions for table */ export async function runList( resourceLabel: string, callFn: (sdk: Coolify) => Promise, columns: IColumnDef[], ): Promise { try { const items = await callFn(getSdk()); if (items.length === 0) { console.log(chalk.yellow(`No ${resourceLabel} found`)); return; } const table = new Table({ head: columns.map((c) => chalk.cyan(c.header)), }); for (const item of items) { table.push(columns.map((c) => c.value(item))); } console.log(table.toString()); console.log(chalk.gray(`Total: ${items.length} ${resourceLabel}`)); } catch (error) { console.error( chalk.red( `Error: ${error instanceof Error ? error.message : String(error)}`, ), ); } } /** * Runs a get-by-uuid action and prints details. * Used for: show app, get database, get service, etc. * * @param uuid - UUID (optional, reads .coolify.json if undefined) * @param _resourceLabel - Human label * @param callFn - Function that calls the SDK get method (throws on error) * @param formatFn - Function that formats the result for display */ export async function runGet( uuid: string | undefined, _resourceLabel: string, callFn: (sdk: Coolify, uuid: string) => Promise, formatFn: (item: T) => void, ): Promise { let resolved = resolveUuid(uuid); if (!resolved && uuid) { resolved = await resolveNameOrUuid(uuid); } if (!resolved) { console.error( chalk.red("Error: No UUID provided and no .coolify.json found"), ); return; } try { const item = await callFn(getSdk(), resolved); formatFn(item); } catch (error) { console.error( chalk.red( `Error: ${error instanceof Error ? error.message : String(error)}`, ), ); } } /** * Gets the CLI SDK instance for commands that need direct access. * * @returns Coolify SDK instance */ export function getCliSdk(): Coolify { return getSdk(); } // Re-export for convenience export { formatStatus, chalk, Table };