#!/usr/bin/env node const readline = require('readline'); const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // 🎨 Cores para console const colors = { reset: '\x1b[0m', bright: '\x1b[1m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m' }; const log = { info: (msg) => console.log(`${colors.blue}ℹ️ ${msg}${colors.reset}`), success: (msg) => console.log(`${colors.green}✅ ${msg}${colors.reset}`), warning: (msg) => console.log(`${colors.yellow}⚠️ ${msg}${colors.reset}`), error: (msg) => console.log(`${colors.red}❌ ${msg}${colors.reset}`), title: (msg) => console.log(`${colors.cyan}${colors.bright}🚀 ${msg}${colors.reset}\n`) }; // 📝 Configuração do domínio let domainConfig = { name: '', displayName: '', menuPosition: '', hasPhotos: false, hasSideCard: false, hasKilometer: false, hasColor: false, hasStatus: false, remoteSelects: [] }; // 🎯 Função principal async function main() { try { log.title('CRIADOR DE DOMÍNIOS - SISTEMA PRAFROTA'); // Verificar pré-requisitos await checkPrerequisites(); // Coletar informações await gatherDomainInfo(); // Confirmar configuração await confirmConfiguration(); // Criar branch para o desenvolvimento await createBranch(); // Gerar domínio await generateDomain(); // Compilar e testar await compileAndTest(); // Commit automático await autoCommit(); log.success('🎉 DOMÍNIO CRIADO COM SUCESSO!'); log.success('🚀 Sistema totalmente integrado e funcional!'); log.info('📝 Próximos passos:'); log.info(` 1. Testar: http://localhost:4200/app/${domainConfig.name}`); log.info(` 2. Push: git push origin feature/domain-${domainConfig.name}`); log.info(` 3. Criar PR: Para integrar na branch main`); } catch (error) { log.error(`Erro: ${error.message}`); process.exit(1); } finally { rl.close(); } } // ✅ Verificar pré-requisitos async function checkPrerequisites() { log.info('Verificando pré-requisitos...'); // Verificar se está na branch main try { const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim(); if (currentBranch !== 'main') { throw new Error(`Você deve estar na branch main. Branch atual: ${currentBranch}`); } log.success('Branch main ativa'); } catch (error) { throw new Error('Erro ao verificar branch Git'); } // Verificar configuração Git try { const userName = execSync('git config user.name', { encoding: 'utf8' }).trim(); const userEmail = execSync('git config user.email', { encoding: 'utf8' }).trim(); if (!userName) { throw new Error('Nome do usuário Git não configurado'); } if (!userEmail || !userEmail.endsWith('@grupopralog.com.br')) { throw new Error('Email deve ter domínio @grupopralog.com.br'); } log.success(`Git configurado: ${userName} <${userEmail}>`); } catch (error) { throw new Error('Configuração Git inválida. Execute: git config --global user.email "seu.email@grupopralog.com.br"'); } } // 📝 Coletar informações do domínio async function gatherDomainInfo() { log.title('CONFIGURAÇÃO DO DOMÍNIO'); // Nome do domínio domainConfig.name = await question('📝 Nome do domínio (singular, minúsculo): '); if (!domainConfig.name.match(/^[a-z]+$/)) { throw new Error('Nome deve ser singular, minúsculo, sem espaços'); } domainConfig.displayName = await question('📋 Nome para exibição (plural): '); // Posição no menu console.log('\n🧭 Opções de posição no menu:'); console.log('1. vehicles (após Veículos)'); console.log('2. drivers (após Motoristas)'); console.log('3. routes (após Rotas)'); console.log('4. finances (após Finanças)'); console.log('5. reports (após Relatórios)'); console.log('6. settings (após Configurações)'); const menuChoice = await question('Escolha a posição (1-6): '); const menuOptions = ['', 'vehicles', 'drivers', 'routes', 'finances', 'reports', 'settings']; domainConfig.menuPosition = menuOptions[parseInt(menuChoice)] || 'vehicles'; // Recursos opcionais domainConfig.hasPhotos = await askYesNo('📸 Terá sub-aba de fotos?'); domainConfig.hasSideCard = await askYesNo('🃏 Terá Side Card (painel lateral)?'); // Componentes especializados log.info('\n🎨 Componentes especializados:'); domainConfig.hasKilometer = await askYesNo('🛣️ Terá campo de quilometragem?'); domainConfig.hasColor = await askYesNo('🎨 Terá campo de cor?'); domainConfig.hasStatus = await askYesNo('📊 Terá campo de status?'); // Remote selects const hasRemoteSelects = await askYesNo('🔍 Haverá campos para buscar dados de outras APIs?'); if (hasRemoteSelects) { await gatherRemoteSelects(); } } // 🔍 Coletar informações dos remote selects async function gatherRemoteSelects() { log.info('\n🔗 Configuração de campos Remote-Select:'); while (true) { const fieldName = await question('Nome do campo (ou "fim" para terminar): '); if (fieldName.toLowerCase() === 'fim') break; console.log('\nOpções de API:'); console.log('1. drivers (Motoristas)'); console.log('2. vehicles (Veículos)'); console.log('3. suppliers (Fornecedores)'); console.log('4. outro'); const apiChoice = await question('Escolha a API (1-4): '); const apiOptions = ['', 'drivers', 'vehicles', 'suppliers', 'custom']; const apiType = apiOptions[parseInt(apiChoice)] || 'custom'; let serviceName = apiType; if (apiType === 'custom') { serviceName = await question('Nome do service (ex: SuppliersService): '); } domainConfig.remoteSelects.push({ fieldName, apiType, serviceName }); log.success(`Campo ${fieldName} adicionado`); } } // ❓ Perguntar sim/não async function askYesNo(prompt) { const answer = await question(`${prompt} (s/n): `); return answer.toLowerCase().startsWith('s'); } // 🌿 Criar branch para desenvolvimento async function createBranch() { const branchName = `feature/domain-${domainConfig.name}`; log.title('CRIAÇÃO DE BRANCH'); try { // Verificar se a branch já existe try { execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { stdio: 'ignore' }); log.warning(`Branch '${branchName}' já existe`); const switchToBranch = await askYesNo('🔄 Deseja mudar para a branch existente?'); if (switchToBranch) { execSync(`git checkout ${branchName}`); log.success(`Mudado para branch existente: ${branchName}`); } else { log.error('Operação cancelada. Escolha um nome diferente ou use a branch existente.'); process.exit(1); } } catch (error) { // Branch não existe, criar nova log.info(`Criando nova branch: ${branchName}`); execSync(`git checkout -b ${branchName}`); log.success(`Branch criada e ativada: ${branchName}`); } log.info(`📝 Descrição da branch: Implementação do domínio ${domainConfig.displayName}`); log.info(`🎯 Funcionalidades: ${getFeaturesDescription()}`); } catch (error) { throw new Error(`Erro ao criar/mudar branch: ${error.message}`); } } // 📋 Gerar descrição das funcionalidades function getFeaturesDescription() { const features = []; features.push('CRUD básico'); if (domainConfig.hasPhotos) features.push('upload de fotos'); if (domainConfig.hasSideCard) features.push('painel lateral'); if (domainConfig.hasKilometer) features.push('campo quilometragem'); if (domainConfig.hasColor) features.push('seleção de cores'); if (domainConfig.hasStatus) features.push('controle de status'); if (domainConfig.remoteSelects.length > 0) features.push('integração com APIs'); return features.join(', '); } // 📋 Confirmar configuração async function confirmConfiguration() { log.title('CONFIRMAÇÃO DA CONFIGURAÇÃO'); console.log(`📝 Nome: ${domainConfig.name}`); console.log(`📋 Exibição: ${domainConfig.displayName}`); console.log(`🧭 Menu: após ${domainConfig.menuPosition}`); console.log(`📸 Fotos: ${domainConfig.hasPhotos ? 'Sim' : 'Não'}`); console.log(`🃏 Side Card: ${domainConfig.hasSideCard ? 'Sim' : 'Não'}`); console.log(`🛣️ Quilometragem: ${domainConfig.hasKilometer ? 'Sim' : 'Não'}`); console.log(`🎨 Cor: ${domainConfig.hasColor ? 'Sim' : 'Não'}`); console.log(`📊 Status: ${domainConfig.hasStatus ? 'Sim' : 'Não'}`); if (domainConfig.remoteSelects.length > 0) { console.log(`🔗 Remote Selects: ${domainConfig.remoteSelects.map(rs => rs.fieldName).join(', ')}`); } console.log(`\n🌿 Branch: feature/domain-${domainConfig.name}`); console.log(`🎯 Funcionalidades: ${getFeaturesDescription()}`); const confirm = await askYesNo('\n✅ Confirma a criação do domínio?'); if (!confirm) { log.error('Operação cancelada pelo usuário'); process.exit(0); } } // 🛠️ Gerar domínio async function generateDomain() { log.info('Gerando estrutura do domínio...'); // Criar diretório const domainPath = `projects/idt_app/src/app/domain/${domainConfig.name}`; if (!fs.existsSync(domainPath)) { fs.mkdirSync(domainPath, { recursive: true }); } // Gerar arquivos await generateComponent(); await generateService(); await generateInterface(); await generateTemplate(); await generateStyles(); await updateRouting(); await updateSidebar(); await updateMCP(); log.success('Estrutura gerada com sucesso!'); } // 📄 Gerar component async function generateComponent() { const componentName = capitalize(domainConfig.name); const template = generateComponentTemplate(componentName); const filePath = `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.component.ts`; fs.writeFileSync(filePath, template); log.success(`${componentName}Component criado`); } // 🔧 Template do componente function generateComponentTemplate(componentName) { const className = `${componentName}Component`; const interfaceName = componentName; const serviceName = `${componentName}Service`; return `import { Component, ChangeDetectorRef } from "@angular/core"; import { CommonModule, DatePipe } from "@angular/common"; import { TitleService } from "../../shared/services/theme/title.service"; import { HeaderActionsService } from "../../shared/services/header-actions.service"; import { ${serviceName} } from "./${domainConfig.name}.service"; import { ${interfaceName} } from "./${domainConfig.name}.interface"; import { TabSystemComponent } from "../../shared/components/tab-system/tab-system.component"; import { BaseDomainComponent, DomainConfig } from "../../shared/components/base-domain/base-domain.component"; import { TabFormConfig } from "../../shared/interfaces/generic-tab-form.interface"; import { TabFormConfigService } from "../../shared/components/tab-system/services/tab-form-config.service"; /** * 🎯 ${className} - Gestão de ${domainConfig.displayName} * * ✨ Implementa BaseDomainComponent + Registry Pattern * 🚀 Auto-registro de configurações para escalabilidade infinita! */ @Component({ selector: 'app-${domainConfig.name}', standalone: true, imports: [CommonModule, TabSystemComponent], providers: [DatePipe], templateUrl: './${domainConfig.name}.component.html', styleUrl: './${domainConfig.name}.component.scss' }) export class ${className} extends BaseDomainComponent<${interfaceName}> { constructor( private ${domainConfig.name}Service: ${serviceName}, titleService: TitleService, headerActionsService: HeaderActionsService, cdr: ChangeDetectorRef, private datePipe: DatePipe, private tabFormConfigService: TabFormConfigService ) { super(titleService, headerActionsService, cdr, ${domainConfig.name}Service); this.registerFormConfig(); } private registerFormConfig(): void { this.tabFormConfigService.registerFormConfig('${domainConfig.name}', () => this.getFormConfig()); } protected override getDomainConfig(): DomainConfig { return { domain: '${domainConfig.name}', title: '${domainConfig.displayName}', entityName: '${domainConfig.name}', pageSize: 50, subTabs: ['dados'${domainConfig.hasPhotos ? ", 'photos'" : ''}], columns: [ { field: "id", header: "Id", sortable: true, filterable: true, search: true, searchType: "number" }, { field: "name", header: "Nome", sortable: true, filterable: true, search: true, searchType: "text" }, ${domainConfig.hasStatus ? `{ field: "status", header: "Status", sortable: true, filterable: true, allowHtml: true, search: true, searchType: "select", searchOptions: [ { value: 'active', label: 'Ativo' }, { value: 'inactive', label: 'Inativo' } ], label: (value: any) => { const statusConfig: { [key: string]: { label: string, class: string } } = { 'active': { label: 'Ativo', class: 'status-active' }, 'inactive': { label: 'Inativo', class: 'status-inactive' } }; const config = statusConfig[value?.toLowerCase()] || { label: value, class: 'status-unknown' }; return \`\${config.label}\`; } },` : ''} { field: "created_at", header: "Criado em", sortable: true, filterable: true, search: true, searchType: "date", label: (date: any) => this.datePipe.transform(date, "dd/MM/yyyy HH:mm") || "-" } ]${domainConfig.hasSideCard ? `, sideCard: { enabled: true, title: "Resumo do ${componentName}", position: "right", width: "400px", component: "summary", data: { displayFields: [ { key: "name", label: "Nome", type: "text" }, ${domainConfig.hasStatus ? `{ key: "status", label: "Status", type: "status" },` : ''} { key: "created_at", label: "Criado em", type: "date" } ], statusField: "status" } }` : ''} }; } getFormConfig(): TabFormConfig { return { title: 'Dados do ${componentName}', entityType: '${domainConfig.name}', fields: [], submitLabel: 'Salvar ${componentName}', showCancelButton: true, subTabs: [ { id: 'dados', label: 'Dados Básicos', icon: 'fa-info-circle', enabled: true, order: 1, templateType: 'fields', requiredFields: ['name'], fields: [ { key: 'name', label: 'Nome', type: 'text', required: true, placeholder: 'Digite o nome' }${domainConfig.hasKilometer ? `, { key: 'odometer', label: 'Quilometragem', type: 'kilometer-input', placeholder: '0', formatOptions: { locale: 'pt-BR', useGrouping: true, suffix: ' km' } }` : ''}${domainConfig.hasColor ? `, { key: 'color', label: 'Cor', type: 'color-input', required: false, options: [ { value: { name: 'Branco', code: '#ffffff' }, label: 'Branco' }, { value: { name: 'Preto', code: '#000000' }, label: 'Preto' }, { value: { name: 'Azul', code: '#0000ff' }, label: 'Azul' }, { value: { name: 'Vermelho', code: '#ff0000' }, label: 'Vermelho' } ] }` : ''}${domainConfig.hasStatus ? `, { key: 'status', label: 'Status', type: 'select', required: true, options: [ { value: 'active', label: 'Ativo' }, { value: 'inactive', label: 'Inativo' } ] }` : ''}${generateRemoteSelectFields()} ] }${domainConfig.hasPhotos ? `, { id: 'photos', label: 'Fotos', icon: 'fa-camera', enabled: true, order: 2, templateType: 'fields', requiredFields: [], fields: [ { key: 'photoIds', label: 'Fotos', type: 'send-image', required: false, imageConfiguration: { maxImages: 10, maxSizeMb: 5, allowedTypes: ['image/jpeg', 'image/png', 'image/webp'], existingImages: [] } } ] }` : ''} ] }; } protected override getNewEntityData(): Partial<${interfaceName}> { return { name: '', ${domainConfig.hasStatus ? "status: 'active'," : ''} ${domainConfig.hasKilometer ? "odometer: 0," : ''} ${domainConfig.hasColor ? "color: { name: '', code: '#ffffff' }," : ''} }; } }`; } // 🔗 Gerar campos remote-select function generateRemoteSelectFields() { if (domainConfig.remoteSelects.length === 0) return ''; return domainConfig.remoteSelects.map(rs => ` { key: '${rs.fieldName}', label: '${capitalize(rs.fieldName)}', type: 'remote-select', remoteConfig: { service: this.${rs.serviceName.toLowerCase()}, searchField: 'name', displayField: 'name', valueField: 'id', modalTitle: 'Selecionar ${capitalize(rs.fieldName)}', placeholder: 'Digite para buscar...' } }`).join(','); } // 🛠️ Gerar service, interface, template, styles... async function generateService() { const serviceName = `${capitalize(domainConfig.name)}Service`; const interfaceName = capitalize(domainConfig.name); const template = `import { Injectable } from '@angular/core'; import { Observable, map } from 'rxjs'; import { Papa } from 'ngx-papaparse'; import { ApiClientService } from '../../shared/services/api/api-client.service'; import { ${interfaceName} } from './${domainConfig.name}.interface'; import { PaginatedResponse } from '../../shared/interfaces/paginate.interface'; import { DomainService } from '../../shared/components/base-domain/base-domain.component'; @Injectable({ providedIn: 'root' }) export class ${serviceName} implements DomainService<${interfaceName}> { constructor( private apiClient: ApiClientService, private papa: Papa ) {} get${interfaceName}s( page = 1, limit = 10, filters?: {[key: string]: string} ): Observable> { let url = \`${domainConfig.name}?page=\${page}&limit=\${limit}\`; if (filters) { const params = new URLSearchParams(); for (const [key, value] of Object.entries(filters)) { if (value) { params.append(key, value); } } url += \`&\${params.toString()}\`; } return this.apiClient.get>(url); } /** * Busca um ${domainConfig.name} específico por ID */ getById(id: string | number): Observable<${interfaceName}> { return this.apiClient.get<${interfaceName}>(\`${domainConfig.name}/\${id}\`); } /** * Remove um ${domainConfig.name} */ delete(id: string | number): Observable { return this.apiClient.delete(\`${domainConfig.name}/\${id}\`); } // ======================================== // 🎯 MÉTODOS ESPERADOS PELO BaseDomainComponent // ======================================== /** * ✅ Método genérico para listar - chamado automaticamente pelo BaseDomainComponent */ getEntities(page: number, pageSize: number, filters: any): Observable<{ data: ${interfaceName}[]; totalCount: number; pageCount: number; currentPage: number; }> { return this.get${interfaceName}s(page, pageSize, filters).pipe( map(response => ({ data: response.data, totalCount: response.totalCount, pageCount: response.pageCount, currentPage: response.currentPage })) ); } /** * ✅ Método genérico para criar - chamado automaticamente pelo BaseDomainComponent */ create(data: any): Observable<${interfaceName}> { return this.apiClient.post<${interfaceName}>('${domainConfig.name}', data); } /** * ✅ Método genérico para atualizar - chamado automaticamente pelo BaseDomainComponent */ update(id: any, data: any): Observable<${interfaceName}> { return this.apiClient.patch<${interfaceName}>(\`${domainConfig.name}/\${id}\`, data); } }`; const filePath = `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.service.ts`; fs.writeFileSync(filePath, template); log.success(`${serviceName} criado`); } async function generateInterface() { const interfaceName = capitalize(domainConfig.name); const template = `export interface ${interfaceName} { id: number; name: string; ${domainConfig.hasStatus ? "status: 'active' | 'inactive';" : ''} ${domainConfig.hasKilometer ? "odometer?: number;" : ''} ${domainConfig.hasColor ? "color?: { name: string; code: string };" : ''} ${domainConfig.remoteSelects.map(rs => `${rs.fieldName}_id?: number;`).join('\n ')} created_at?: string; updated_at?: string; }`; const filePath = `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.interface.ts`; fs.writeFileSync(filePath, template); log.success(`Interface ${interfaceName} criada`); } async function generateTemplate() { const template = `
`; const filePath = `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.component.html`; fs.writeFileSync(filePath, template); log.success('Template HTML criado'); } async function generateStyles() { const template = `// 🎨 Estilos específicos do componente ${capitalize(domainConfig.name)} // ERP SaaS - Sistema PraFrota .domain-container { display: flex; flex-direction: column; height: 100%; background-color: var(--background-color, #f8f9fa); .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; app-tab-system { flex: 1; display: flex; flex-direction: column; } } } // 📱 Responsividade para ERP @media (max-width: 768px) { .domain-container { padding: 0.5rem; .main-content { height: calc(100vh - 1rem); } } } // 🎯 Classes específicas do domínio .${domainConfig.name}-specific { // Estilos específicos do ${domainConfig.name} aqui } ${domainConfig.hasStatus ? ` // 📊 Status badges para ERP .status-badge { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 16px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; border: 1px solid transparent; transition: all 0.2s ease; &.status-active { background-color: #d4edda; color: #155724; border-color: #c3e6cb; &::before { content: '●'; margin-right: 4px; color: #28a745; } } &.status-inactive { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; &::before { content: '●'; margin-right: 4px; color: #dc3545; } } &.status-unknown { background-color: #e2e3e5; color: #383d41; border-color: #d6d8db; &::before { content: '●'; margin-right: 4px; color: #6c757d; } } }` : ''} ${domainConfig.hasColor ? ` // 🎨 Color display para ERP .color-display { display: inline-flex; align-items: center; gap: 8px; .color-circle { width: 20px; height: 20px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,0.1); } .color-name { font-size: 13px; color: var(--text-color, #495057); } }` : ''} // 🏢 ERP Theme compatibility :host { display: block; height: 100%; // CSS Variables para temas --primary-color: #007bff; --secondary-color: #6c757d; --success-color: #28a745; --danger-color: #dc3545; --warning-color: #ffc107; --info-color: #17a2b8; --light-color: #f8f9fa; --dark-color: #343a40; } // 🎯 Print styles para relatórios ERP @media print { .domain-container { background: white !important; .main-content { overflow: visible !important; } .status-badge { border: 1px solid #000 !important; background: white !important; color: black !important; } } }`; const filePath = `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.component.scss`; fs.writeFileSync(filePath, template); log.success('Estilos SCSS criados'); } // 🔄 Atualizar arquivos do sistema async function updateRouting() { log.info('Atualizando sistema de rotas...'); const routesPath = 'projects/idt_app/src/app/app.routes.ts'; try { let routesContent = fs.readFileSync(routesPath, 'utf8'); // Encontrar a posição de inserção baseada na menuPosition const newRoute = ` { path: '${domainConfig.name}', loadComponent: () => import('./domain/${domainConfig.name}/${domainConfig.name}.component') .then(m => m.${capitalize(domainConfig.name)}Component) },`; // Inserir a nova rota na posição apropriada const routePattern = /children: \[([\s\S]*?)\]/; const match = routesContent.match(routePattern); if (match) { const childrenContent = match[1]; const insertPosition = findInsertPosition(childrenContent, domainConfig.menuPosition); // Dividir o conteúdo das rotas filhas const lines = childrenContent.split('\n'); let insertIndex = -1; // Encontrar o índice de inserção baseado na posição do menu for (let i = 0; i < lines.length; i++) { if (lines[i].includes(`path: '${domainConfig.menuPosition}'`)) { // Inserir após a rota de referência insertIndex = i + 3; // path, loadComponent, then break; } } if (insertIndex === -1) { // Se não encontrou a posição de referência, inserir antes do último item (redirect) for (let i = lines.length - 1; i >= 0; i--) { if (lines[i].includes("redirectTo:")) { insertIndex = i - 1; break; } } } if (insertIndex > -1) { lines.splice(insertIndex, 0, newRoute); const newChildrenContent = lines.join('\n'); routesContent = routesContent.replace(routePattern, `children: [${newChildrenContent}]`); fs.writeFileSync(routesPath, routesContent); log.success('Rota adicionada ao sistema de roteamento'); } else { log.warning('Posição de inserção não encontrada - rota deve ser adicionada manualmente'); } } } catch (error) { log.warning(`Erro ao atualizar rotas: ${error.message} - será atualizado manualmente`); } } async function updateSidebar() { log.info('Atualizando menu da sidebar...'); const sidebarPath = 'projects/idt_app/src/app/shared/components/sidebar/sidebar.component.ts'; try { let sidebarContent = fs.readFileSync(sidebarPath, 'utf8'); // Criar a nova entrada do menu const newMenuItem = ` { id: '${domainConfig.name}', label: '${domainConfig.displayName}', icon: 'fas fa-${getIconForDomain(domainConfig.name)}', notifications: 0 },`; // Encontrar a posição de inserção no array menuItems const menuPattern = /menuItems: MenuItem\[\] = \[([\s\S]*?)\]/; const match = sidebarContent.match(menuPattern); if (match) { const menuContent = match[1]; const lines = menuContent.split('\n'); let insertIndex = -1; // Encontrar posição baseada na menuPosition for (let i = 0; i < lines.length; i++) { if (lines[i].includes(`id: '${domainConfig.menuPosition}'`)) { // Inserir após o item de referência insertIndex = findMenuItemEnd(lines, i) + 1; break; } } if (insertIndex === -1) { // Se não encontrou a posição de referência, inserir antes do último item for (let i = lines.length - 1; i >= 0; i--) { if (lines[i].trim().startsWith("{ id:") && !lines[i].includes("children")) { insertIndex = i + 1; break; } } } if (insertIndex > -1) { lines.splice(insertIndex, 0, newMenuItem); const newMenuContent = lines.join('\n'); sidebarContent = sidebarContent.replace(menuPattern, `menuItems: MenuItem[] = [${newMenuContent}]`); fs.writeFileSync(sidebarPath, sidebarContent); log.success('Menu adicionado à sidebar'); } else { log.warning('Posição de inserção na sidebar não encontrada - deve ser adicionado manualmente'); } } } catch (error) { log.warning(`Erro ao atualizar sidebar: ${error.message} - será atualizado manualmente`); } } async function updateMCP() { log.info('Atualizando configuração MCP...'); const mcpPath = '.mcp/config.json'; try { const mcpContent = JSON.parse(fs.readFileSync(mcpPath, 'utf8')); // Adicionar contexto do novo domínio const domainContext = { description: `Domain ${domainConfig.displayName} - Generated automatically`, files: [ `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.component.ts`, `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.service.ts`, `projects/idt_app/src/app/domain/${domainConfig.name}/${domainConfig.name}.interface.ts` ], features: [ "BaseDomainComponent integration", "Registry Pattern auto-registration", "CRUD operations", "Data table with filters and pagination", domainConfig.hasPhotos ? "Photo upload sub-tab" : null, domainConfig.hasSideCard ? "Side card panel" : null, domainConfig.hasKilometer ? "Kilometer input component" : null, domainConfig.hasColor ? "Color input component" : null, domainConfig.hasStatus ? "Status badges" : null ].filter(Boolean), apis: domainConfig.remoteSelects.map(rs => ({ field: rs.fieldName, service: rs.serviceName, type: rs.apiType })), generated: new Date().toISOString(), menuPosition: domainConfig.menuPosition }; // Adicionar ao contexto domains if (!mcpContent.contexts.domains) { mcpContent.contexts.domains = {}; } mcpContent.contexts.domains[domainConfig.name] = domainContext; // Atualizar array de domínios na seção recent-improvements if (mcpContent.contexts["recent-improvements"] && mcpContent.contexts["recent-improvements"]["automatic-domain-generation"]) { if (!mcpContent.contexts["recent-improvements"]["automatic-domain-generation"]["generated-domains"]) { mcpContent.contexts["recent-improvements"]["automatic-domain-generation"]["generated-domains"] = []; } mcpContent.contexts["recent-improvements"]["automatic-domain-generation"]["generated-domains"].push({ name: domainConfig.name, displayName: domainConfig.displayName, generatedAt: new Date().toISOString(), features: domainContext.features }); } fs.writeFileSync(mcpPath, JSON.stringify(mcpContent, null, 2)); log.success('Configuração MCP atualizada'); } catch (error) { log.warning(`Erro ao atualizar MCP: ${error.message} - será atualizado manualmente`); } } // Compilar e testar automaticamente async function compileAndTest() { log.title('COMPILAÇÃO E TESTES AUTOMÁTICOS'); try { log.info('Compilando aplicação...'); execSync('ng build idt_app --configuration development', { stdio: 'inherit', timeout: 120000 // 2 minutos timeout }); log.success('Compilação realizada com sucesso! ✨'); // Opcional: executar testes se existirem try { log.info('Verificando se há testes para executar...'); const testCommand = `ng test idt_app --watch=false --browsers=ChromeHeadless`; execSync(testCommand, { stdio: 'inherit', timeout: 60000 // 1 minuto timeout }); log.success('Testes executados com sucesso! 🧪'); } catch (testError) { log.warning('Testes não executados (podem não existir ou estar configurados)'); } } catch (error) { log.error(`Erro na compilação: ${error.message}`); throw new Error('Falha na compilação - verifique os erros acima'); } } // Commit automático async function autoCommit() { log.title('COMMIT AUTOMÁTICO'); try { const branchName = `feature/domain-${domainConfig.name}`; // Adicionar todos os arquivos execSync('git add .', { stdio: 'inherit' }); // Criar mensagem de commit const commitMessage = `feat: add ${domainConfig.displayName} domain ✨ Features implementadas: - Component: ${capitalize(domainConfig.name)}Component - Service: ${capitalize(domainConfig.name)}Service com ApiClientService - Interface: ${capitalize(domainConfig.name)} TypeScript - Templates: HTML e SCSS arquivos separados (ERP SaaS) - Registry Pattern: Auto-registro no TabFormConfigService ${domainConfig.hasPhotos ? '- Sub-aba de fotos com send-image component' : ''} ${domainConfig.hasSideCard ? '- Side card com resumo e status' : ''} ${domainConfig.hasKilometer ? '- Campo quilometragem com kilometer-input' : ''} ${domainConfig.hasColor ? '- Campo cor com color-input' : ''} ${domainConfig.hasStatus ? '- Campo status com badges coloridos' : ''} ${domainConfig.remoteSelects.length > 0 ? `- Remote-selects: ${domainConfig.remoteSelects.map(rs => rs.fieldName).join(', ')}` : ''} 🔧 Integração: - Roteamento: app.routes.ts - Menu: sidebar.component.ts (após ${domainConfig.menuPosition}) - MCP: .mcp/config.json 🎯 Gerado automaticamente via scripts/create-domain.js`; // Fazer commit execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' }); log.success(`Commit realizado na branch ${branchName}! 📝`); log.info(`Para fazer push: git push origin ${branchName}`); } catch (error) { log.warning(`Erro no commit automático: ${error.message}`); log.info('Você pode fazer o commit manualmente depois'); } } // Funções auxiliares function findInsertPosition(content, position) { // Encontrar posição de inserção baseada na menuPosition const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].includes(`'${position}'`)) { return i; } } return -1; } function findMenuItemEnd(lines, startIndex) { // Encontrar o final de um item do menu (considerando children) let braceCount = 0; let inMenuItem = false; for (let i = startIndex; i < lines.length; i++) { const line = lines[i]; if (line.includes('{ id:')) { inMenuItem = true; } if (inMenuItem) { const openBraces = (line.match(/{/g) || []).length; const closeBraces = (line.match(/}/g) || []).length; braceCount += openBraces - closeBraces; if (braceCount === 0 && line.includes('}')) { return i; } } } return startIndex; } function getIconForDomain(domainName) { // Mapear ícones baseados no nome do domínio const iconMap = { contracts: 'file-contract', suppliers: 'truck', employees: 'users', products: 'box', clients: 'handshake', orders: 'shopping-cart', inventory: 'warehouse', companies: 'building' }; return iconMap[domainName] || 'folder'; } // 🛠️ Funções utilitárias function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function question(prompt) { return new Promise((resolve) => { rl.question(prompt, resolve); }); } // 🚀 Executar if (require.main === module) { main(); } module.exports = { main };