UNPKG

cartorio-plataforma-cli

Version:

CLI para gerenciar a Plataforma do Cartório de Criciúma com sistema de módulos

447 lines (391 loc) 18.1 kB
/** * Módulo para geração de comandos Docker para construção e execução de serviços */ const os = require('os'); const path = require('path'); const fs = require('fs'); const chalk = require('chalk'); /** * Classe para gerar comandos Docker para a plataforma */ class DockerCommandGenerator { /** * Cria um novo gerador de comandos Docker * @param {ModuleRegistry} moduleRegistry - Registro de módulos da plataforma */ constructor(moduleRegistry) { this.registry = moduleRegistry; this.isWindows = os.platform() === 'win32'; this.projectDir = null; // Armazenar o diretório do projeto encontrado } /** * Encontra o diretório principal do projeto * @returns {string} Diretório principal do projeto ou diretório atual se não encontrado */ findProjectDir() { // Retornar diretório já encontrado se disponível if (this.projectDir) { return this.projectDir; } const possibleLocations = [ process.cwd(), // Diretório atual path.resolve(process.cwd(), '..'), // Diretório pai path.resolve(process.cwd(), '../..'), // Dois níveis acima path.resolve(__dirname, '../..'), // Dois níveis acima do diretório do script path.resolve(__dirname, '../../..'), // Três níveis acima do diretório do script // Locais específicos para quando executado pela CLI instalada globalmente path.resolve(process.env.PWD || process.cwd()) ]; console.log(`Procurando diretório do projeto nas seguintes localizações:`); possibleLocations.forEach(loc => console.log(` - ${loc}`)); // Busca arquivos marcadores em cada local for (const location of possibleLocations) { // Verificar se o diretório existe if (!fs.existsSync(location)) { console.log(`❌ Diretório não existe: ${location}`); continue; } console.log(`Verificando diretório: ${location}`); // Primeiro, tentar encontrar o docker-compose.bake.hcl diretamente if (fs.existsSync(path.join(location, 'docker-compose.bake.hcl'))) { this.projectDir = location; console.log(`✅ Diretório do projeto encontrado via docker-compose.bake.hcl: ${this.projectDir}`); return this.projectDir; } // Depois, procurar por uma estrutura de diretório específica da plataforma // Verificar se contém um diretório 'plataforma-infra' const infraPath = path.join(location, 'plataforma-infra'); if (fs.existsSync(infraPath)) { // Verificar se o docker-compose.bake.hcl está dentro do plataforma-infra if (fs.existsSync(path.join(infraPath, 'docker-compose.bake.hcl'))) { this.projectDir = infraPath; // Usar o diretório plataforma-infra, não o diretório pai console.log(`✅ Diretório do projeto encontrado via plataforma-infra: ${this.projectDir}`); return this.projectDir; } } // Se encontrar um package.json, verificar se tem "name": "plataforma-cli" const packagePath = path.join(location, 'package.json'); if (fs.existsSync(packagePath)) { try { const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8')); if (packageData.name === 'plataforma-cli') { // Encontramos o package.json da CLI, o diretório do projeto está acima const projectPathFromCli = path.resolve(location, '..'); if (fs.existsSync(path.join(projectPathFromCli, 'docker-compose.bake.hcl'))) { this.projectDir = projectPathFromCli; console.log(`✅ Diretório do projeto encontrado via package.json: ${this.projectDir}`); return this.projectDir; } } } catch (error) { // Ignorar erros na leitura do package.json console.log(`⚠️ Erro ao ler package.json em ${location}: ${error.message}`); } } } // Se chegar aqui, tentar encontrar qualquer arquivo de Docker for (const location of possibleLocations) { const dockerFiles = [ 'docker-compose.yml', 'docker-compose.yaml', 'Dockerfile' ]; for (const file of dockerFiles) { if (fs.existsSync(path.join(location, file))) { this.projectDir = location; console.log(`✅ Diretório com arquivo Docker encontrado: ${this.projectDir}`); return this.projectDir; } } } // Adicional: procurar por pastas que contêm módulos for (const location of possibleLocations) { try { const items = fs.readdirSync(location, { withFileTypes: true }); for (const item of items) { if (item.isDirectory() && (item.name.includes('-frontend') || item.name.includes('-backend') || item.name === 'plataforma-infra' || item.name === 'root-config')) { this.projectDir = location; console.log(`✅ Diretório raiz encontrado pela detecção de módulos: ${this.projectDir}`); return this.projectDir; } } } catch (error) { // Ignorar erros na leitura do diretório } } // Fallback para o diretório atual this.projectDir = process.cwd(); console.warn(`⚠️ Não foi possível determinar diretório principal do projeto. Usando: ${this.projectDir}`); return this.projectDir; } /** * Cria um comando para execução em um diretório específico * @param {string} baseDir - Diretório base onde o comando deve ser executado * @param {string} command - Comando a ser executado * @returns {string} - Comando completo com mudança de diretório */ createCommand(baseDir, command) { if (!baseDir || typeof baseDir !== 'string') { console.warn(`⚠️ Aviso: baseDir inválido (${baseDir})`); baseDir = this.findProjectDir(); } // Verificar se o baseDir existe if (!fs.existsSync(baseDir)) { console.warn(`⚠️ Aviso: O diretório ${baseDir} não existe.`); // Tentar usar o diretório do projeto encontrado baseDir = this.findProjectDir(); console.log(`✅ Usando diretório do projeto: ${baseDir}`); } const absolutePath = path.resolve(baseDir); console.log(`📂 Executando comando no diretório: ${absolutePath}`); // Escape de caracteres especiais no caminho para evitar problemas no shell const escapedPath = this.isWindows ? `"${absolutePath}"` : absolutePath.replace(/(["\s'$`\\])/g, '\\$1'); if (this.isWindows) { return `cd /d ${escapedPath} && ${command}`; } else { return `cd ${escapedPath} && ${command}`; } } /** * Gera um comando de Docker Buildx Bake para construir imagens * @param {string} env - Ambiente (dev, local, prod) * @param {string} profileType - Tipo de profile (infra, core, modules, full, custom) * @param {Array} moduleList - Lista de módulos selecionados (para custom) * @returns {Object} - Comando e detalhes para executar */ generateBakeCommand(env, profileType, moduleList = []) { const envBuild = this.isWindows ? `set BUILDX_BAKE_ENTITLEMENTS_FS=0 &&` : `BUILDX_BAKE_ENTITLEMENTS_FS=0`; let bakeCommand; // Encontrar diretório do projeto const projectDir = this.findProjectDir(); // Buscar o arquivo bake.hcl const bakeFilePath = path.join(projectDir, 'docker-compose.bake.hcl'); if (!fs.existsSync(bakeFilePath)) { console.warn(`⚠️ Aviso: Arquivo docker-compose.bake.hcl não encontrado em ${projectDir}`); return { command: null, message: 'Arquivo docker-compose.bake.hcl não encontrado. Verifique a estrutura do projeto.' }; } console.log(`✅ Usando arquivo de bake: ${bakeFilePath}`); // Verificar quais targets existem no arquivo bake.hcl let availableTargets = []; try { const bakeContent = fs.readFileSync(bakeFilePath, 'utf8'); // Módulos conhecidos que devem funcionar e identificação de patterns de targets no bake.hcl const knownTargets = [ 'root-config-local', 'plataforma-frontend-local', 'plataforma-backend-local', 'modulo1-frontend-local', 'modulo1-backend-local', 'minutador-frontend-local', 'minutador-backend-local' ]; // Procurar por definições de targets no arquivo bake (padrão "target.*" ou "group.*") const targetMatches = bakeContent.match(/target\s*"([^"]+)"/g) || []; const groupMatches = bakeContent.match(/group\s*"([^"]+)"/g) || []; // Extrair os nomes dos targets dos matches const extractedTargets = [ ...targetMatches.map(m => m.match(/target\s*"([^"]+)"/)[1]), ...groupMatches.map(m => m.match(/group\s*"([^"]+)"/)[1]) ]; // Combinar com targets conhecidos availableTargets = [...new Set([...knownTargets, ...extractedTargets])]; console.log(chalk.dim('Debug: Targets disponíveis:'), availableTargets.join(', ')); } catch (error) { console.warn(`⚠️ Aviso: Erro ao ler targets do arquivo bake.hcl: ${error.message}`); // Se não puder ler os targets, supor que os conhecidos estão disponíveis availableTargets = [ 'root-config-local', 'plataforma-frontend-local', 'plataforma-backend-local', 'modulo1-frontend-local', 'modulo1-backend-local', 'minutador-frontend-local', 'minutador-backend-local' ]; } // Determinar o grupo de bake com base no tipo de perfil if (profileType === 'custom' && moduleList.length > 0) { // Filtrar módulos de infraestrutura, pois não precisam ser construídos const buildableModules = moduleList.filter(module => module.type !== 'infrastructure'); // Gerar targets específicos do bake para os módulos selecionados const targets = buildableModules.map(module => { if (module.profiles && module.profiles[env]) { return module.profiles[env]; } // Target padrão de fallback baseado no ID do módulo if (module.id) { return `${module.id}-${env}`; } return null; }).filter(target => target !== null); // Exibir aviso sobre infraestrutura const infraModules = moduleList.filter(m => m.type === 'infrastructure'); if (infraModules.length > 0) { console.log(chalk.blue('ℹ️ Os serviços de infraestrutura não precisam ser construídos (usam imagens oficiais)')); } // Filtrar apenas os targets que existem no arquivo bake const existingTargets = targets.filter(target => availableTargets.includes(target)); const missingTargets = targets.filter(target => !availableTargets.includes(target)); if (missingTargets.length > 0) { console.log(chalk.yellow(`⚠️ Alguns targets não foram encontrados no arquivo bake.hcl e serão ignorados: ${missingTargets.join(', ')}`)); } console.log(chalk.dim('Debug: Targets para bake:'), existingTargets.join(', ')); if (existingTargets.length > 0) { bakeCommand = `${envBuild} docker buildx bake -f "${bakeFilePath}" ${existingTargets.join(' ')}`; } else { return { command: null, message: 'Nenhum target válido encontrado para construção. Verifique se os módulos foram definidos no arquivo bake.hcl.' }; } } else { // Usar grupos predefinidos no arquivo bake let bakeGroup; if (profileType === 'all') { bakeGroup = env; // Usar grupo completo para 'all' } else if (profileType === 'infra') { // Aviso sobre infraestrutura console.log(chalk.blue('ℹ️ Os serviços de infraestrutura não precisam ser construídos (usam imagens oficiais)')); return { command: null, message: 'Os serviços de infraestrutura não precisam ser construídos, pois usam imagens oficiais.' }; } else { bakeGroup = `${env}-${profileType}`; // Verificar se o grupo existe no arquivo bake if (!availableTargets.includes(bakeGroup)) { console.log(chalk.yellow(`⚠️ O grupo de targets '${bakeGroup}' não foi encontrado no arquivo bake.hcl.`)); // Usar grupo padrão se disponível if (availableTargets.includes(env)) { bakeGroup = env; console.log(chalk.blue(`ℹ️ Usando grupo padrão '${env}' em vez disso.`)); } else { return { command: null, message: `O grupo de targets '${bakeGroup}' não existe. Verifique o arquivo bake.hcl.` }; } } } bakeCommand = `${envBuild} docker buildx bake -f "${bakeFilePath}" ${bakeGroup}`; } return { command: bakeCommand, env: env === 'dev' ? 'development' : (env === 'prod' ? 'production' : 'local') }; } /** * Gera um comando Docker Compose para iniciar serviços * @param {string} env - Ambiente (dev, local, prod) * @param {string} profileType - Tipo de profile (infra, core, modules, full, custom) * @param {Array} moduleList - Lista de módulos selecionados (para custom) * @returns {string} - Comando Docker Compose para iniciar os serviços */ generateComposeCommand(env, profileType, moduleList = []) { // Definir profiles padrão por ambiente e tipo const defaultProfiles = { // Ambiente de desenvolvimento 'dev': { 'infra': ['dev-infra'], 'core': ['dev-core', 'dev-infra'], 'all': ['dev'] }, // Ambiente local 'local': { 'infra': ['local-infra'], 'core': ['local-core', 'local-infra'], 'all': ['local'] }, // Ambiente de produção 'prod': { 'infra': ['prod-infra'], 'core': ['prod-core', 'prod-infra'], 'all': ['prod'] } }; // Inicializar arrays vazios const profiles = []; let envVars = []; let envCmd = this.isWindows ? `set ENVIRONMENT=${env} &&` : `ENVIRONMENT=${env}`; // Encontrar diretório do projeto const projectDir = this.findProjectDir(); // Verificar se existe um arquivo docker-compose.yml no diretório do projeto const composeFilePath = path.join(projectDir, 'docker-compose.yml'); if (!fs.existsSync(composeFilePath)) { console.warn(`⚠️ Aviso: Arquivo docker-compose.yml não encontrado em ${projectDir}`); } else { console.log(`✅ Usando arquivo de compose: ${composeFilePath}`); } // Selecionar profiles com base no ambiente e tipo if (profileType === 'custom' && moduleList.length > 0) { // Para perfil customizado, usar o perfil de infraestrutura do ambiente // e adicionar os serviços específicos via variáveis de ambiente profiles.push(...defaultProfiles[env]['infra']); // Adicionar variáveis de ambiente para os módulos selecionados moduleList.forEach(module => { if (module.id && module.type) { // Substituir hífens por sublinhados no ID do módulo const moduleId = module.id.replace(/-/g, '_').toUpperCase(); const moduleType = module.type.toUpperCase(); const moduleVar = `INCLUDE_${moduleId}_${moduleType}=true`; envVars.push(moduleVar); } }); } else { // Para perfis padrão, usar os profiles definidos if (defaultProfiles[env] && defaultProfiles[env][profileType]) { profiles.push(...defaultProfiles[env][profileType]); // Adicionar variáveis de ambiente para todos os módulos if (profileType === 'all' && this.registry) { const allModules = this.registry.getAllModules(); allModules.forEach(module => { if (module.id && module.type) { // Substituir hífens por sublinhados no ID do módulo const moduleId = module.id.replace(/-/g, '_').toUpperCase(); const moduleType = module.type.toUpperCase(); const moduleVar = `INCLUDE_${moduleId}_${moduleType}=true`; envVars.push(moduleVar); } }); } else if (profileType === 'core' && this.registry) { // Para core, adicionar apenas os módulos core const coreModules = this.registry.getModulesByCategory('core'); coreModules.forEach(module => { if (module.id && module.type) { // Substituir hífens por sublinhados no ID do módulo const moduleId = module.id.replace(/-/g, '_').toUpperCase(); const moduleType = module.type.toUpperCase(); const moduleVar = `INCLUDE_${moduleId}_${moduleType}=true`; envVars.push(moduleVar); } }); } } else { // Fallback para o ambiente padrão profiles.push(env); } } // Verificar se temos algum perfil selecionado if (profiles.length === 0) { profiles.push(env); // Usar o ambiente como perfil padrão } console.log(chalk.dim('Debug: Profiles para compose:'), profiles.join(', ')); // Construir o comando com os profiles const profileArgs = profiles.map(p => `--profile ${p}`).join(' '); // Adicionar variáveis de ambiente ao comando if (envVars.length > 0) { const envVarsStr = envVars.join(' '); envCmd = this.isWindows ? envVars.map(v => `set ${v} &&`).join(' ') + ` ${envCmd}` : `${envVarsStr} ${envCmd}`; } // Verificar se o arquivo .env existe const envFilePath = path.join(projectDir, `.env.${env}`); if (!fs.existsSync(envFilePath)) { console.warn(`⚠️ Aviso: Arquivo .env.${env} não encontrado em ${projectDir}`); } return `${envCmd} docker compose ${profileArgs} --env-file .env.${env} up -d`; } } module.exports = DockerCommandGenerator;