testes/Modulos Angular/scripts/create-domain.js

1172 lines
36 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 \`<span class="status-badge \${config.class}">\${config.label}</span>\`;
}
},` : ''}
{
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<PaginatedResponse<${interfaceName}>> {
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<PaginatedResponse<${interfaceName}>>(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<void> {
return this.apiClient.delete<void>(\`${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 = `<div class="domain-container">
<div class="main-content">
<app-tab-system
#tabSystem
[config]="tabConfig"
[events]="tabEvents"
(tableEvent)="onTableEvent($event)">
</app-tab-system>
</div>
</div>`;
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 };