feat(core-docker): modulo docker com detector de stacks, gerador de compose e dockerfile para 10 stacks
- 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
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user