Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docker-compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
services:
author-dev-postgres:
container_name: 'author-dev-postgres'
image: 'postgres:15.1'
image: 'docker.io/library/postgres:15.1'
environment:
POSTGRES_DB: 'tailor_dev'
POSTGRES_USER: 'dev'
Expand All @@ -13,14 +13,14 @@ services:
restart: 'no'
author-dev-redis:
container_name: 'author-dev-redis'
image: redis:7.4.0
image: docker.io/library/redis:7.4.0
expose:
- '6379'
ports:
- '6379:6379'
author-dev-localstack:
container_name: author-dev-localstack
image: localstack/localstack
image: docker.io/localstack/localstack
expose:
- '4566'
ports:
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"scripts": {
"dev": "node ./scripts/boot.js",
"dc": "docker compose -f docker-compose.dev.yaml up & pnpm dev",
"ac:up": "node ./scripts/containers.js start",
"ac:down": "node ./scripts/containers.js stop",
"ac:status": "node ./scripts/containers.js status",
"build": "dotenv -- pnpm -r build",
"start": "cd ./apps/backend && pnpm start",
"dcs": "docker compose -f docker-compose.dev.yaml up & pnpm start",
Expand Down
169 changes: 169 additions & 0 deletions scripts/containers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import chalk from 'chalk';
import dotenv from 'dotenv';
import { execaCommand } from 'execa';
import fs from 'node:fs/promises';
import ora from 'ora';
import path from 'node:path';

const configPath = path.join(process.cwd(), '.env');
const config = dotenv.parse(await fs.readFile(configPath, 'utf-8'));

const SERVICES = [
{
name: 'author-dev-postgres',
image: 'docker.io/library/postgres:15.1',
port: '5432:5432',
env: {
POSTGRES_DB: config.DATABASE_NAME || 'tailor_dev',
POSTGRES_USER: config.DATABASE_USER || 'dev',
POSTGRES_PASSWORD: config.DATABASE_PASSWORD || 'dev',
},
},
{
name: 'author-dev-redis',
image: 'docker.io/library/redis:7.4.0',
port: '6379:6379',
},
{
name: 'author-dev-minio',
image: 'docker.io/minio/minio',
port: '4566:9000',
env: {
MINIO_ROOT_USER: config.STORAGE_KEY || 'test',
MINIO_ROOT_PASSWORD: config.STORAGE_SECRET || 'test',
},
args: 'server /data',
},
];

const run = (cmd) => execaCommand(cmd, { shell: true });

const ensureCLI = async () => {
try {
await run('which container');
} catch {
console.error(chalk.red('Apple Container CLI not found.'));
console.error(chalk.dim('Install via: brew install container'));
process.exit(1);
}
};

await ensureCLI();

const runQuiet = async (cmd) => {
try {
return await run(cmd);
} catch {
return null;
}
};

const status = async () => {
const output = await listRunning();
console.log(output || chalk.dim(' No containers running.'));
};

const listRunning = async () => {
const result = await runQuiet('container list');
return result?.stdout || '';
};

const isRuntimeUp = async () => !!(await listRunning());

const startRuntime = async () => {
const spinner = ora('Checking runtime...').start();
if (await isRuntimeUp()) {
spinner.succeed('Runtime is running');
return;
}
spinner.text = 'Starting runtime...';
try {
await run('container system start');
spinner.succeed('Runtime started');
} catch (err) {
spinner.fail('Failed to start runtime');
throw err;
}
};

const startService = async ({ name, image, port, env, args }, running) => {
const label = chalk.cyan(name);
const spinner = ora(`Starting ${label}...`).start();
if (running.includes(name)) {
spinner.info(`${label} already running`);
return;
}
await runQuiet(`container rm ${name}`);
const envFlags = Object.entries(env || {})
.map(([k, v]) => `-e ${k}=${v}`)
.join(' ');
const flags = [`-d`, `--name ${name}`, `-p ${port}`, envFlags, image, args]
.filter(Boolean)
.join(' ');
try {
await run(`container run ${flags}`);
spinner.succeed(`${label} started`);
} catch (err) {
spinner.fail(`${label} failed to start`);
throw err;
}
};

const start = async () => {
await startRuntime();
console.log();
const running = await listRunning();
await Promise.all(SERVICES.map((svc) => startService(svc, running)));
console.log();
await status();
};

const stopService = async (name, running) => {
const label = chalk.cyan(name);
const spinner = ora(`Stopping ${label}...`).start();
if (running.includes(name)) await runQuiet(`container stop ${name}`);
await runQuiet(`container rm ${name}`);
spinner.succeed(`${label} stopped`);
};

const stop = async () => {
const running = await listRunning();
await Promise.all(SERVICES.map(({ name }) => stopService(name, running)));
};

const restart = async () => {
await stop();
console.log();
await start();
};

const logs = async (name) => {
const target = name || SERVICES[0].name;
const result = await run(`container logs ${target}`);
console.log(result.stdout);
};

// ---------------------- CLI --------------------------

const [command, ...args] = process.argv.slice(2);

const commands = {
start,
up: start,
stop,
down: stop,
status,
ps: status,
restart,
logs,
};
const handler = commands[command || 'start'];

if (!handler) {
console.log(
`Usage: node containers.js {${Object.keys(commands).join('|')}} [name]`,
);
process.exit(1);
}

await handler(args[0]);
Loading