2855032e76
- detector de stacks por presenca de arquivos - gerador de docker-compose.yml Scenario A + Scenario C - gerador de Dockerfiles para: nodejs, nextjs, nestjs, nuxt, laravel, php-generic, python, go, rust, wordpress - validador xCloud: build:, proxy conflicts, multi-porta, healthcheck - barrel exports em src/docker/index.ts - organizado por domain-driven design
104 lines
4.4 KiB
TypeScript
104 lines
4.4 KiB
TypeScript
/**
|
|
* 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<string, string>;
|
|
}): DockerCompose {
|
|
const img = `${GHCR(opts.owner, opts.repo)}:latest`;
|
|
const svcs: Record<string, any> = {
|
|
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<string, Record<string, string>>;
|
|
}): DockerCompose {
|
|
const svcs: Record<string, any> = {};
|
|
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<string, any>, externalPort: number = 3080): { configs: Record<string,{content:string}> } {
|
|
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`,
|
|
},
|
|
},
|
|
};
|
|
}
|