/** * compose.ts — Gerador de Docker Compose xCloud-ready * Scenario A: build-source (1 app + opcional db) * Scenario C: multi-service (vários apps + nginx-router) */ import type { DockerCompose } from './types'; const GHCR = (owner: string, repo: string) => `ghcr.io/${owner}/${repo}`; const DB = { postgres: 'postgres:16-alpine', mysql: 'mysql:8.0' }; /** Scenario A */ export function scenarioA(opts: { owner: string; repo: string; service: string; port: number; dbType: 'postgres' | 'mysql' | 'none'; env: Record; }): DockerCompose { const img = `${GHCR(opts.owner, opts.repo)}:latest`; const svcs: Record = { app: { image: img, ports: [`${opts.port}:3000`], environment: opts.env, networks: ['app-net'] }, }; if (opts.dbType !== 'none') { const isPg = opts.dbType === 'postgres'; svcs['db'] = { image: DB[opts.dbType], expose: [isPg ? '5432' : '3306'], environment: isPg ? { POSTGRES_DB: '${POSTGRES_DB}', POSTGRES_USER: '${POSTGRES_USER}', POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' } : { MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}', MYSQL_DATABASE: '${DB_DATABASE}', MYSQL_USER: '${DB_USERNAME}', MYSQL_PASSWORD: '${DB_PASSWORD}' }, volumes: ['db-data:/var/lib/postgresql/data'], healthcheck: { test: isPg ? ['CMD-SHELL','pg_isready -U ${POSTGRES_USER}'] : ['CMD','mysqladmin','ping','-h','localhost'], interval: '10s', timeout: '5s', retries: 5 }, networks: ['app-net'], }; svcs['app'].depends_on = [{ service: 'db', condition: 'service_healthy' }]; } return { services: svcs, ...(opts.dbType !== 'none' ? { volumes: { 'db-data': {} } } : {}), networks: { 'app-net': { driver: 'bridge' } } }; } /** Scenario C — multi-service com nginx-router */ export function scenarioC(opts: { owner: string; repo: string; services: Array<{ name: string; port: number; internalPort: number }>; dbType: 'postgres' | 'mysql' | 'none'; envs: Record>; }): DockerCompose { const svcs: Record = {}; const base = GHCR(opts.owner, opts.repo); for (const s of opts.services) { svcs[s.name] = { image: `${base}/${s.name}:latest`, expose: [String(s.internalPort)], environment: opts.envs[s.name] || {}, networks: ['app-net'] }; } if (opts.dbType !== 'none') { const isPg = opts.dbType === 'postgres'; svcs['db'] = { image: DB[opts.dbType], expose: [isPg ? '5432' : '3306'], environment: { POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' }, healthcheck: { test: isPg ? ['CMD-SHELL','pg_isready -U ${POSTGRES_USER}'] : ['CMD','mysqladmin','ping','-h','localhost'], interval: '10s', timeout: '5s', retries: 5 }, volumes: ['db-data:/var/lib/postgresql/data'], networks: ['app-net'], }; } // nginx-router única porta externa para xCloud const extPort = opts.services[0]?.port || 3080; svcs['nginx-router'] = { image: 'nginx:alpine', ports: [`${extPort}:80`], configs: [{ source: 'nginx_cfg', target: '/etc/nginx/conf.d/default.conf' }], depends_on: opts.services.map(s => ({ service: s.name })), networks: ['app-net'], }; const back = opts.services.find(s => s.name !== 'frontend'); const front = opts.services.find(s => s.name !== 'backend'); const bPort = back?.internalPort || 8000; const fPort = front?.port || opts.services[0]?.port || 3000; return { services: svcs, configs: { nginx_cfg: { content: `upstream backend { server backend:${bPort}; }\nupstream frontend { server frontend:${fPort}; }\n\nserver {\n listen 80;\n\n location /api/ { proxy_pass http://backend:${bPort}/; }\n location / { proxy_pass http://frontend:${fPort}/; }\n}\n`, }, }, volumes: opts.dbType !== 'none' ? { 'db-data': {} } : undefined, networks: { 'app-net': { driver: 'bridge' } }, }; } // Helper para nginx-router para adicionar a compose existente export function addNginxRouter(svcs: Record, externalPort: number = 3080): { configs: Record } { if (svcs['nginx-router']) return { configs: {} }; svcs['nginx-router'] = { image: 'nginx:alpine', ports: [`${externalPort}:80`], depends_on: Object.keys(svcs).filter(k => k !== 'nginx-router'), networks: ['app-net'], }; return { configs: { nginx_cfg: { content: `server {\n listen 80;\n location / { proxy_pass http://frontend:3000/; }\n}\n`, }, }, }; }