/** * Projects command for CLI. * * @module */ import { isOk, isErr } from "@mks2508/no-throw"; import chalk from "chalk"; import Table from "cli-table3"; import { getCoolifyService } from "../../coolify/index.js"; import { resolveProjectNameOrUuid } from "../name-resolver.js"; import { createSpinner, } from "../ui/spinners.js"; import { createSummaryCard, } from "../ui/index.js"; /** * Projects command handler. * Lists all projects, shows project details, or creates a new one. */ export async function projectsCommand( options: { full?: boolean; create?: string; description?: string; show?: string; // Show project details by UUID apps?: string; // Show apps for project by UUID } = {}, ) { console.log(""); console.log(chalk.bold.cyan("┌─────────────────────────────────────────────┐")); console.log(chalk.bold.cyan("│") + chalk.bold.white(" 📁 Coolify Projects") + chalk.bold.cyan(" │")); console.log(chalk.bold.cyan("└─────────────────────────────────────────────┘")); console.log(""); const spinner = createSpinner({ text: "Connecting to Coolify...", color: "cyan", }).start(); try { const coolify = getCoolifyService(); const initResult = await coolify.init(); if (isErr(initResult)) { spinner.fail("Connection failed"); console.error(chalk.red(` ✗ Failed to initialize: ${initResult.error.message}`)); return; } spinner.succeed("Connected to Coolify"); // Create mode if (options.create) { const creatingSpinner = createSpinner({ text: `Creating project "${options.create}"...`, color: "green", }).start(); const createResult = await coolify.createProject( options.create, options.description, ); if (isOk(createResult)) { creatingSpinner.succeed(`Project ${chalk.bold.white(options.create)} created`); console.log(""); console.log(createSummaryCard("Project Details", { "Name": { value: options.create, color: chalk.white }, "UUID": { value: createResult.value.uuid, color: chalk.cyan }, "Description": { value: options.description || "-", color: chalk.gray }, })); console.log(""); } else { creatingSpinner.fail("Failed to create project"); console.error(chalk.red(` ✗ ${createResult.error.message}`)); } return; } // Show project details mode if (options.show) { const resolvedShow = await resolveProjectNameOrUuid(options.show); if (!resolvedShow) { console.error(chalk.red(` ✗ Project not found: ${options.show}`)); return; } await showProjectDetails(coolify, resolvedShow); return; } // Show apps for project mode if (options.apps) { const resolvedApps = await resolveProjectNameOrUuid(options.apps); if (!resolvedApps) { console.error(chalk.red(` ✗ Project not found: ${options.apps}`)); return; } await showProjectApps(coolify, resolvedApps); return; } // List mode const listSpinner = createSpinner({ text: "Fetching projects...", color: "cyan", }).start(); const result = await coolify.listProjects(); if (isOk(result)) { listSpinner.succeed(`Found ${chalk.bold.green(String(result.value.length))} project(s)`); const projects = result.value; if (projects.length === 0) { console.log(""); console.log(chalk.yellow(" ⚠ No projects found")); console.log(""); return; } console.log(""); const table = new Table({ head: [ chalk.cyan("Name"), chalk.cyan("UUID"), chalk.cyan("Description"), ], wordWrap: true, colWidths: [30, 40, 50], }); for (const project of projects) { table.push([ chalk.white(project.name), chalk.gray(project.uuid.slice(0, 8) + "..."), project.description || chalk.gray("-"), ]); } console.log(table.toString()); console.log(""); console.log(chalk.gray(" Commands:")); console.log( " " + chalk.cyan("coolify-cli projects --show ") + chalk.gray(" - Show project details"), ); console.log( " " + chalk.cyan("coolify-cli projects --apps ") + chalk.gray(" - Show apps in project"), ); console.log(""); } else { listSpinner.fail("Failed to fetch projects"); console.error(chalk.red(` ✗ ${result.error.message}`)); } } catch (error) { spinner.fail("Error"); console.error( chalk.red( ` ✗ ${error instanceof Error ? error.message : String(error)}`, ), ); } } /** * Show detailed information about a project. */ async function showProjectDetails(coolify: any, projectUuid: string) { const spinner = createSpinner({ text: "Fetching project details...", color: "cyan", }).start(); // Get project info const projectsResult = await coolify.listProjects(); if (isErr(projectsResult)) { spinner.fail("Failed to fetch project"); console.error(chalk.red(` ✗ ${projectsResult.error.message}`)); return; } const project = projectsResult.value.find((p: any) => p.uuid === projectUuid); if (!project) { spinner.fail("Project not found"); console.error(chalk.red(` ✗ Project not found: ${projectUuid}`)); return; } // Get environments const envsResult = await coolify.getProjectEnvironments(projectUuid); const environments = isOk(envsResult) ? envsResult.value : []; // Get all apps and filter by project const appsResult = await coolify.listApplications(); const projectApps = isOk(appsResult) ? appsResult.value.filter((app: any) => app.environment_id && environments.some((env: any) => env.id === app.environment_id)) : []; // Get all databases and filter by project const dbsResult = await coolify.listDatabases(); const projectDbs = isOk(dbsResult) ? dbsResult.value.filter((db: any) => db.environment_id && environments.some((env: any) => env.id === db.environment_id)) : []; spinner.succeed("Project details retrieved"); console.log(""); console.log(createSummaryCard("Project Details", { "Name": { value: project.name, color: chalk.white }, "UUID": { value: project.uuid, color: chalk.gray }, "Description": { value: project.description || "-", color: chalk.gray }, "Environments": { value: String(environments.length), color: chalk.cyan }, "Applications": { value: String(projectApps.length), color: chalk.green }, "Databases": { value: String(projectDbs.length), color: chalk.yellow }, })); // Show environments if (environments.length > 0) { console.log(""); console.log(chalk.gray(" ") + chalk.bold("Environments:")); console.log(""); for (const env of environments) { const envApps = projectApps.filter((app: any) => app.environment_id === env.id); const envDbs = projectDbs.filter((db: any) => db.environment_id === env.id); console.log( " " + chalk.cyan("●") + " " + chalk.bold.white(env.name) + chalk.gray(` (${env.uuid.slice(0, 8)}...)`), ); console.log( " " + chalk.gray("Apps: ") + chalk.green(String(envApps.length)) + chalk.gray(" | Databases: ") + chalk.yellow(String(envDbs.length)), ); } console.log(""); } // Show apps if (projectApps.length > 0) { console.log(""); console.log(chalk.gray(" ") + chalk.bold("Applications:")); console.log(""); for (const app of projectApps) { const statusIcon = app.status.includes("running") ? chalk.green("●") : chalk.yellow("○"); console.log( " " + statusIcon + " " + chalk.bold.white(app.name) + chalk.gray(` (${app.uuid.slice(0, 8)}...)`), ); console.log( " " + chalk.gray("Status: ") + chalk.white(app.status) + chalk.gray(" | Branch: ") + chalk.cyan(app.git_branch || "-"), ); } console.log(""); } // Show databases if (projectDbs.length > 0) { console.log(""); console.log(chalk.gray(" ") + chalk.bold("Databases:")); console.log(""); for (const db of projectDbs) { const statusIcon = db.status === "running" ? chalk.green("●") : chalk.yellow("○"); console.log( " " + statusIcon + " " + chalk.bold.white(db.name) + chalk.gray(` (${db.type})`), ); console.log( " " + chalk.gray("Status: ") + chalk.white(db.status) + chalk.gray(" | UUID: ") + chalk.gray(db.uuid.slice(0, 8)) + chalk.gray("..."), ); } console.log(""); } } /** * Show all applications in a project. */ async function showProjectApps(coolify: any, projectUuid: string) { const spinner = createSpinner({ text: "Fetching project applications...", color: "cyan", }).start(); // Get environments const envsResult = await coolify.getProjectEnvironments(projectUuid); const environments = isOk(envsResult) ? envsResult.value : []; // Get all apps and filter by project const appsResult = await coolify.listApplications(); const projectApps = isOk(appsResult) ? appsResult.value.filter((app: any) => app.environment_id && environments.some((env: any) => env.id === app.environment_id)) : []; spinner.succeed(`Found ${chalk.bold.green(String(projectApps.length))} application(s)`); if (projectApps.length === 0) { console.log(""); console.log(chalk.yellow(" ⚠ No applications found in this project")); console.log(""); return; } console.log(""); const table = new Table({ head: [ chalk.cyan("Name"), chalk.cyan("Status"), chalk.cyan("Type"), chalk.cyan("Branch"), chalk.cyan("UUID"), ], wordWrap: true, colWidths: [30, 20, 15, 15, 40], }); for (const app of projectApps) { const statusColor = app.status.includes("running") ? chalk.green : chalk.yellow; const appType = getAppTypeFromSourceType(app.source_type); table.push([ chalk.white(app.name), statusColor(app.status), chalk.gray(appType), chalk.cyan(app.git_branch || "-"), chalk.gray(app.uuid.slice(0, 8) + "..."), ]); } console.log(table.toString()); console.log(""); } /** * Map source_type to readable app type. */ function getAppTypeFromSourceType(sourceType: string | undefined): string { if (!sourceType) return "unknown"; if (sourceType.includes("GithubApp")) return "github-app"; if (sourceType.includes("DeployKey")) return "deploy-key"; if (sourceType.includes("Dockerfile")) return "dockerfile"; if (sourceType.includes("DockerCompose")) return "docker-compose"; if (sourceType.includes("DockerImage")) return "docker-image"; if (sourceType.includes("Public")) return "public"; return "unknown"; }