/** * `wrnexus generate docker` — scaffold containerization for a WrNexus app: * a multi-stage Dockerfile (build with Bun → slim runtime), a .dockerignore, * and a docker-compose.yml (app + Postgres). Uses the app's `/healthz` endpoint * for the container health check. */ import { existsSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; const DOCKERFILE = `# syntax=docker/dockerfile:1 # --- build stage: install deps + produce dist/server.js --- FROM oven/bun:1 AS build WORKDIR /app COPY package.json bun.lock* bun.lockb* ./ RUN bun install COPY . . RUN bun run build # --- runtime stage: slim image with only the built server + migrations --- FROM oven/bun:1-slim AS runtime WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 COPY --from=build /app/dist ./dist COPY --from=build /app/app/db/migrations ./app/db/migrations EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --retries=3 \\ CMD bun -e "fetch('http://localhost:'+(process.env.PORT||3000)+'/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["bun", "dist/server.js"] `; const DOCKERIGNORE = `node_modules dist **/.wrnexus .git *.log *.db *.db-shm *.db-wal .DS_Store `; const COMPOSE = `services: app: build: . ports: - "3000:3000" environment: NODE_ENV: production PORT: "3000" DATABASE_URL: postgres://wrn:wrn@db:5432/app depends_on: db: condition: service_healthy restart: unless-stopped db: image: postgres:16-alpine environment: POSTGRES_USER: wrn POSTGRES_PASSWORD: wrn POSTGRES_DB: app healthcheck: test: ["CMD-SHELL", "pg_isready -U wrn -d app"] interval: 3s timeout: 3s retries: 20 volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata: `; function writeIfAbsent(path: string, content: string, name: string): void { if (existsSync(path)) { console.warn(` • ${name} already exists — skipped`); return; } writeFileSync(path, content, "utf8"); console.log(` ✓ ${name}`); } /** Scaffold Dockerfile, .dockerignore, and docker-compose.yml into `appRoot`. */ export function generateDocker(appRoot: string): void { const root = resolve(appRoot); console.log("Scaffolding containerization:"); writeIfAbsent(join(root, "Dockerfile"), DOCKERFILE, "Dockerfile"); writeIfAbsent(join(root, ".dockerignore"), DOCKERIGNORE, ".dockerignore"); writeIfAbsent(join(root, "docker-compose.yml"), COMPOSE, "docker-compose.yml"); console.log("\nBuild + run: docker compose up --build"); }