|
| 1 | +import type { Command } from "commander"; |
| 2 | +import chalk from "chalk"; |
| 3 | +import ora from "ora"; |
| 4 | +import { |
| 5 | + isDockerAvailable, |
| 6 | + listDatabaseContainers, |
| 7 | + startContainer, |
| 8 | + stopContainer, |
| 9 | +} from "../infra/docker/docker.service.js"; |
| 10 | + |
| 11 | +async function assertDockerAvailable(): Promise<void> { |
| 12 | + const available = await isDockerAvailable(); |
| 13 | + if (!available) { |
| 14 | + console.error( |
| 15 | + chalk.red( |
| 16 | + "\n✖ Docker is not available. Make sure Docker is installed and the daemon is running.\n", |
| 17 | + ), |
| 18 | + ); |
| 19 | + process.exit(1); |
| 20 | + } |
| 21 | +} |
| 22 | + |
| 23 | +export function registerDockerCommand(program: Command): void { |
| 24 | + const dockerCmd = program |
| 25 | + .command("docker") |
| 26 | + .helpCommand(false) |
| 27 | + .description("Manage database containers (postgres, mysql, mariadb)") |
| 28 | + .addHelpText( |
| 29 | + "after", |
| 30 | + ` |
| 31 | +Examples: |
| 32 | + hdx docker list |
| 33 | + hdx docker list --all |
| 34 | + hdx docker start pg-dev |
| 35 | + hdx docker stop pg-dev |
| 36 | + hdx docker stop pg-dev --remove`, |
| 37 | + ); |
| 38 | + |
| 39 | + dockerCmd |
| 40 | + .command("list") |
| 41 | + .alias("ls") |
| 42 | + .description("List running database containers") |
| 43 | + .option("-a, --all", "Include stopped containers") |
| 44 | + .action(async (opts: { all?: boolean }) => { |
| 45 | + try { |
| 46 | + await assertDockerAvailable(); |
| 47 | + |
| 48 | + const spinner = ora("Fetching database containers...").start(); |
| 49 | + const containers = await listDatabaseContainers(opts.all); |
| 50 | + |
| 51 | + if (containers.length === 0) { |
| 52 | + spinner.warn( |
| 53 | + opts.all |
| 54 | + ? "No database containers found" |
| 55 | + : "No running database containers found", |
| 56 | + ); |
| 57 | + console.log( |
| 58 | + chalk.gray( |
| 59 | + " Only postgres, mysql, and mariadb images are listed.\n", |
| 60 | + ), |
| 61 | + ); |
| 62 | + return; |
| 63 | + } |
| 64 | + |
| 65 | + const label = opts.all ? "container(s) found" : "running container(s)"; |
| 66 | + spinner.succeed(`Found ${containers.length} ${label}\n`); |
| 67 | + |
| 68 | + const nameWidth = |
| 69 | + Math.max(16, ...containers.map((c) => c.name.length)) + 2; |
| 70 | + const engineWidth = 12; |
| 71 | + const portWidth = 8; |
| 72 | + |
| 73 | + const header = ` ${"NAME".padEnd(nameWidth)}${"ENGINE".padEnd(engineWidth)}${"PORT".padEnd(portWidth)}STATUS`; |
| 74 | + console.log(chalk.bold(header)); |
| 75 | + console.log( |
| 76 | + chalk.gray( |
| 77 | + ` ${"─".repeat(nameWidth + engineWidth + portWidth + 12)}`, |
| 78 | + ), |
| 79 | + ); |
| 80 | + |
| 81 | + for (const c of containers) { |
| 82 | + const portDisplay = c.hostPort |
| 83 | + ? chalk.cyan(c.hostPort.padEnd(portWidth)) |
| 84 | + : chalk.gray("─".padEnd(portWidth)); |
| 85 | + const isRunning = c.status.toLowerCase().startsWith("up"); |
| 86 | + const statusDisplay = isRunning |
| 87 | + ? chalk.green(c.status) |
| 88 | + : chalk.gray(c.status); |
| 89 | + console.log( |
| 90 | + ` ${chalk.cyan(c.name.padEnd(nameWidth))}${c.engineType.padEnd(engineWidth)}${portDisplay}${statusDisplay}`, |
| 91 | + ); |
| 92 | + } |
| 93 | + |
| 94 | + console.log(); |
| 95 | + } catch (err) { |
| 96 | + const message = err instanceof Error ? err.message : String(err); |
| 97 | + console.error(chalk.red(`\n✖ ${message}\n`)); |
| 98 | + process.exit(1); |
| 99 | + } |
| 100 | + }); |
| 101 | + |
| 102 | + dockerCmd |
| 103 | + .command("start <name>") |
| 104 | + .description("Start a stopped database container") |
| 105 | + .action(async (name: string) => { |
| 106 | + try { |
| 107 | + await assertDockerAvailable(); |
| 108 | + const spinner = ora(`Starting container "${name}"...`).start(); |
| 109 | + await startContainer(name); |
| 110 | + spinner.succeed(`Container "${name}" started\n`); |
| 111 | + } catch (err) { |
| 112 | + const message = err instanceof Error ? err.message : String(err); |
| 113 | + console.error(chalk.red(`\n✖ ${message}\n`)); |
| 114 | + process.exit(1); |
| 115 | + } |
| 116 | + }); |
| 117 | + |
| 118 | + dockerCmd |
| 119 | + .command("stop <name>") |
| 120 | + .description("Stop a running database container") |
| 121 | + .option("-r, --remove", "Remove the container after stopping") |
| 122 | + .action(async (name: string, opts: { remove?: boolean }) => { |
| 123 | + try { |
| 124 | + await assertDockerAvailable(); |
| 125 | + const action = opts.remove |
| 126 | + ? `Stopping and removing container "${name}"...` |
| 127 | + : `Stopping container "${name}"...`; |
| 128 | + const spinner = ora(action).start(); |
| 129 | + await stopContainer(name, opts.remove); |
| 130 | + const done = opts.remove |
| 131 | + ? `Container "${name}" stopped and removed\n` |
| 132 | + : `Container "${name}" stopped\n`; |
| 133 | + spinner.succeed(done); |
| 134 | + } catch (err) { |
| 135 | + const message = err instanceof Error ? err.message : String(err); |
| 136 | + console.error(chalk.red(`\n✖ ${message}\n`)); |
| 137 | + process.exit(1); |
| 138 | + } |
| 139 | + }); |
| 140 | +} |
0 commit comments