#!/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 };