#!/usr/bin/env node // 🎯 CREATE-DOMAIN V2.0 - API ANALYZER // Análise automática de APIs para geração inteligente de interfaces TypeScript const https = require('https'); const http = require('http'); /** * 🚀 ANALISADOR HÍBRIDO DE API * Implementa 4 estratégias para detectar automaticamente a estrutura dos dados: * 1. OpenAPI/Swagger * 2. API Response Analysis * 3. Smart Detection * 4. Intelligent Fallback */ class APIAnalyzer { constructor(baseUrl = 'https://prafrota-be-bff-tenant-api.grupopra.tech', headers = {}) { this.baseUrl = baseUrl; this.timeout = 10000; // 10 segundos this.customHeaders = headers; // Headers customizados para autenticação } /** * 🎯 MÉTODO PRINCIPAL - Análise Híbrida (MODO RIGOROSO) */ async analyzeAPI(domainName, strictMode = true) { console.log(`🔍 Iniciando análise híbrida para domínio: ${domainName}`); console.log(`🔒 Modo rigoroso: ${strictMode ? 'ATIVADO' : 'DESATIVADO'}`); const results = { strategy: null, interface: null, fields: [], metadata: {}, success: false }; // ===== ESTRATÉGIA 1: OpenAPI/Swagger ===== console.log('📋 Tentativa 1: OpenAPI/Swagger...'); try { const swaggerResult = await this.analyzeOpenAPI(domainName); if (swaggerResult.success) { console.log('✅ OpenAPI/Swagger: Sucesso!'); return { ...swaggerResult, strategy: 'openapi' }; } } catch (error) { console.log(`⚠️ OpenAPI/Swagger falhou: ${error.message}`); } // ===== ESTRATÉGIA 2: Análise de Resposta (CRÍTICA) ===== console.log('🔍 Tentativa 2: Análise de resposta da API...'); try { const responseResult = await this.analyzeAPIResponse(domainName); if (responseResult.success) { console.log('✅ Análise de resposta: Sucesso!'); return { ...responseResult, strategy: 'response_analysis' }; } else if (strictMode && responseResult.endpointExists) { // 🚨 MODO RIGOROSO: Se endpoint existe mas falhou, PARAR execução const error = new Error(`🚨 ERRO CRÍTICO: Endpoint /${domainName}/ existe mas não conseguiu acessar dados. Verifique autenticação, CORS ou estrutura da resposta.`); error.code = 'ENDPOINT_ACCESS_FAILED'; error.endpoint = responseResult.failedEndpoint; throw error; } } catch (error) { if (error.code === 'ENDPOINT_ACCESS_FAILED') { throw error; // Re-throw critical errors } console.log(`⚠️ Análise de resposta falhou: ${error.message}`); } // ===== ESTRATÉGIA 3: Detecção Inteligente ===== console.log('🤖 Tentativa 3: Detecção inteligente...'); try { const smartResult = await this.smartDetection(domainName); if (smartResult.success) { if (strictMode) { console.log('⚠️ AVISO: Usando Smart Detection em modo rigoroso - dados podem não ser precisos'); } console.log('✅ Detecção inteligente: Sucesso!'); return { ...smartResult, strategy: 'smart_detection' }; } } catch (error) { console.log(`⚠️ Detecção inteligente falhou: ${error.message}`); } // ===== ESTRATÉGIA 4: Fallback Inteligente ===== if (strictMode) { const error = new Error(`🚨 MODO RIGOROSO: Não foi possível obter dados reais da API para o domínio '${domainName}'. Todas as estratégias falharam.`); error.code = 'STRICT_MODE_FAILURE'; throw error; } console.log('🔄 Estratégia 4: Fallback inteligente...'); const fallbackResult = this.intelligentFallback(domainName); console.log('✅ Fallback aplicado com sucesso!'); return { ...fallbackResult, strategy: 'intelligent_fallback' }; } /** * 📋 ESTRATÉGIA 1: OpenAPI/Swagger Analysis */ async analyzeOpenAPI(domainName) { const swaggerEndpoints = [ `${this.baseUrl}/api-docs`, `${this.baseUrl}/swagger.json`, `${this.baseUrl}/openapi.json`, `${this.baseUrl}/docs/json` ]; for (const endpoint of swaggerEndpoints) { try { console.log(`🔍 Testando endpoint: ${endpoint}`); const swaggerDoc = await this.fetchJSON(endpoint); if (swaggerDoc && swaggerDoc.components && swaggerDoc.components.schemas) { // Procurar schema do domínio const possibleSchemas = [ `${this.capitalize(domainName)}`, `${this.capitalize(domainName)}Dto`, `${this.capitalize(domainName)}Entity`, `${this.capitalize(domainName)}Response` ]; for (const schemaName of possibleSchemas) { const schema = swaggerDoc.components.schemas[schemaName]; if (schema) { console.log(`✅ Schema encontrado: ${schemaName}`); return { success: true, interface: this.generateFromOpenAPISchema(domainName, schema), fields: this.extractFieldsFromSchema(schema), metadata: { source: 'openapi', schemaName: schemaName, endpoint: endpoint } }; } } } } catch (error) { console.log(`⚠️ Endpoint ${endpoint} falhou: ${error.message}`); continue; } } return { success: false }; } /** * 🔍 ESTRATÉGIA 2: API Response Analysis (MODO RIGOROSO) */ async analyzeAPIResponse(domainName) { // Testar tanto singular quanto plural const singularDomain = domainName.endsWith('s') ? domainName.slice(0, -1) : domainName; const pluralDomain = domainName.endsWith('s') ? domainName : `${domainName}s`; const apiEndpoints = [ // Testar singular primeiro (mais comum em APIs REST) `${this.baseUrl}/${singularDomain}?page=1&limit=1`, `${this.baseUrl}/api/${singularDomain}?page=1&limit=1`, `${this.baseUrl}/${singularDomain}`, `${this.baseUrl}/api/${singularDomain}`, // Depois testar plural `${this.baseUrl}/${pluralDomain}?page=1&limit=1`, `${this.baseUrl}/api/${pluralDomain}?page=1&limit=1`, `${this.baseUrl}/${pluralDomain}`, `${this.baseUrl}/api/${pluralDomain}` ]; let endpointExists = false; let failedEndpoint = null; for (const endpoint of apiEndpoints) { try { console.log(`🔍 Analisando endpoint: ${endpoint}`); const response = await this.fetchJSON(endpoint); if (response) { endpointExists = true; // Endpoint respondeu, existe // Verificar se tem estrutura de dados válida if (response.data && Array.isArray(response.data) && response.data.length > 0) { const sampleObject = response.data[0]; console.log(`✅ Exemplo encontrado com ${Object.keys(sampleObject).length} campos`); return { success: true, interface: this.generateFromSample(domainName, sampleObject), fields: this.extractFieldsFromSample(sampleObject), metadata: { source: 'api_response', endpoint: endpoint, sampleSize: response.data.length, totalCount: response.totalCount || 'unknown' } }; } else { // Endpoint existe mas não tem dados ou estrutura incorreta console.log(`⚠️ Endpoint ${endpoint} respondeu mas sem dados válidos`); failedEndpoint = endpoint; console.log(`📋 Estrutura da resposta:`, Object.keys(response)); if (response.data) { console.log(`📦 response.data tipo: ${Array.isArray(response.data) ? 'array' : typeof response.data}`); console.log(`📈 response.data.length: ${response.data.length}`); } } } } catch (error) { if (error.message.includes('404') || error.message.includes('Not Found')) { console.log(`⚠️ Endpoint ${endpoint} não encontrado (404)`); } else { console.log(`⚠️ Endpoint ${endpoint} falhou: ${error.message}`); // Outros erros podem indicar que endpoint existe mas há problema de acesso if (!error.message.includes('Timeout') && !error.message.includes('ECONNREFUSED')) { endpointExists = true; failedEndpoint = endpoint; } } continue; } } return { success: false, endpointExists, failedEndpoint }; } /** * 🤖 ESTRATÉGIA 3: Smart Detection */ async smartDetection(domainName) { // Tentar detectar padrões baseados no nome do domínio const domainPatterns = this.getDomainPatterns(domainName); if (domainPatterns.length > 0) { return { success: true, interface: this.generateFromPatterns(domainName, domainPatterns), fields: domainPatterns, metadata: { source: 'smart_detection', patterns: domainPatterns.map(p => p.name) } }; } return { success: false }; } /** * 🔄 ESTRATÉGIA 4: Intelligent Fallback */ intelligentFallback(domainName) { // Template base inteligente baseado em padrões comuns const baseFields = [ { name: 'id', type: 'number', required: true, description: 'Identificador único' }, { name: 'name', type: 'string', required: true, description: 'Nome do registro' }, { name: 'description', type: 'string', required: false, description: 'Descrição opcional' }, { name: 'status', type: 'string', required: false, description: 'Status do registro' }, { name: 'created_at', type: 'string', required: false, description: 'Data de criação' }, { name: 'updated_at', type: 'string', required: false, description: 'Data de atualização' } ]; // Adicionar campos específicos baseados no nome do domínio const specificFields = this.getSpecificFieldsByDomain(domainName); const allFields = [...baseFields, ...specificFields]; return { success: true, interface: this.generateFromFields(domainName, allFields), fields: allFields, metadata: { source: 'intelligent_fallback', baseFields: baseFields.length, specificFields: specificFields.length } }; } /** * 🏗️ GERADORES DE INTERFACE */ generateFromOpenAPISchema(domainName, schema) { const className = this.capitalize(domainName); let interfaceCode = `/**\n * 🎯 ${className} Interface - Generated from OpenAPI\n * \n * Auto-generated from API schema\n */\nexport interface ${className} {\n`; if (schema.properties) { Object.entries(schema.properties).forEach(([fieldName, fieldSchema]) => { const isRequired = schema.required && schema.required.includes(fieldName); const optional = isRequired ? '' : '?'; const type = this.openAPITypeToTypeScript(fieldSchema); const description = fieldSchema.description ? ` // ${fieldSchema.description}` : ''; interfaceCode += ` ${fieldName}${optional}: ${type};${description}\n`; }); } interfaceCode += '}'; return interfaceCode; } generateFromSample(domainName, sampleObject) { const className = this.capitalize(domainName); let interfaceCode = `/**\n * 🎯 ${className} Interface - Generated from API Response\n * \n * Auto-generated from real API data\n */\nexport interface ${className} {\n`; Object.entries(sampleObject).forEach(([key, value]) => { const type = this.detectTypeScript(value); const isOptional = value === null || value === undefined; const optional = isOptional ? '?' : ''; const description = this.inferDescription(key, type); interfaceCode += ` ${key}${optional}: ${type}; // ${description}\n`; }); interfaceCode += '}'; return interfaceCode; } generateFromPatterns(domainName, patterns) { const className = this.capitalize(domainName); let interfaceCode = `/**\n * 🎯 ${className} Interface - Generated from Smart Patterns\n * \n * Auto-generated using intelligent pattern detection\n */\nexport interface ${className} {\n`; // 🔧 Sempre incluir campos base obrigatórios const baseFields = [ { name: 'id', type: 'number', required: true, description: 'Identificador único' }, { name: 'name', type: 'string', required: false, description: 'Nome do registro' }, { name: 'status', type: 'string', required: false, description: 'Status do registro' }, { name: 'created_at', type: 'string', required: false, description: 'Data de criação' }, { name: 'updated_at', type: 'string', required: false, description: 'Data de atualização' } ]; // Adicionar campos base primeiro baseFields.forEach(field => { const optional = field.required ? '' : '?'; interfaceCode += ` ${field.name}${optional}: ${field.type}; // ${field.description}\n`; }); // Depois adicionar campos específicos do domínio patterns.forEach(field => { const optional = field.required ? '' : '?'; interfaceCode += ` ${field.name}${optional}: ${field.type}; // ${field.description}\n`; }); interfaceCode += '}'; return interfaceCode; } generateFromFields(domainName, fields) { const className = this.capitalize(domainName); let interfaceCode = `/**\n * 🎯 ${className} Interface - Intelligent Fallback\n * \n * Generated using intelligent fallback with domain-specific patterns\n */\nexport interface ${className} {\n`; fields.forEach(field => { const optional = field.required ? '' : '?'; interfaceCode += ` ${field.name}${optional}: ${field.type}; // ${field.description}\n`; }); interfaceCode += '}'; return interfaceCode; } /** * 🔧 UTILITÁRIOS */ async fetchJSON(url, timeout = this.timeout) { return new Promise((resolve, reject) => { const protocol = url.startsWith('https:') ? https : http; const timeoutId = setTimeout(() => reject(new Error('Timeout')), timeout); // Parse URL para adicionar headers const urlParts = new URL(url); const options = { hostname: urlParts.hostname, port: urlParts.port || (protocol === https ? 443 : 80), path: urlParts.pathname + urlParts.search, method: 'GET', headers: { 'Accept': 'application/json', 'User-Agent': 'API-Analyzer-V2.0', ...this.customHeaders // Adicionar headers customizados } }; const req = protocol.request(options, (res) => { clearTimeout(timeoutId); let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (error) { reject(new Error('Invalid JSON')); } }); }); req.on('error', (error) => { clearTimeout(timeoutId); reject(error); }); req.end(); }); } detectTypeScript(value) { if (value === null || value === undefined) return 'any'; const type = typeof value; if (type === 'string') { // Detectar datas if (/^\d{4}-\d{2}-\d{2}/.test(value)) return 'string'; // Detectar emails if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'string'; return 'string'; } if (type === 'number') { return Number.isInteger(value) ? 'number' : 'number'; } if (type === 'boolean') return 'boolean'; if (Array.isArray(value)) { if (value.length === 0) return 'any[]'; return `${this.detectTypeScript(value[0])}[]`; } if (type === 'object') { return 'any'; // Para objetos complexos } return 'any'; } openAPITypeToTypeScript(schema) { if (schema.type === 'integer' || schema.type === 'number') return 'number'; if (schema.type === 'string') return 'string'; if (schema.type === 'boolean') return 'boolean'; if (schema.type === 'array') { const itemType = schema.items ? this.openAPITypeToTypeScript(schema.items) : 'any'; return `${itemType}[]`; } if (schema.type === 'object') return 'any'; return 'any'; } inferDescription(fieldName, type) { const descriptions = { id: 'Identificador único', name: 'Nome do registro', title: 'Título', description: 'Descrição', status: 'Status do registro', active: 'Se está ativo', created_at: 'Data de criação', updated_at: 'Data de atualização', deleted_at: 'Data de exclusão', email: 'Endereço de email', phone: 'Número de telefone', address: 'Endereço', price: 'Preço', amount: 'Quantidade/Valor', quantity: 'Quantidade', user_id: 'ID do usuário', company_id: 'ID da empresa' }; return descriptions[fieldName] || `Campo ${fieldName} (${type})`; } getDomainPatterns(domainName) { const patterns = { // Padrões para veículos vehicle: [ { name: 'brand', type: 'string', required: false, description: 'Marca do veículo' }, { name: 'model', type: 'string', required: false, description: 'Modelo do veículo' }, { name: 'year', type: 'number', required: false, description: 'Ano do veículo' }, { name: 'plate', type: 'string', required: false, description: 'Placa do veículo' }, { name: 'color', type: 'string', required: false, description: 'Cor do veículo' } ], // Padrões para usuários user: [ { name: 'email', type: 'string', required: true, description: 'Email do usuário' }, { name: 'password', type: 'string', required: false, description: 'Senha (hash)' }, { name: 'role', type: 'string', required: false, description: 'Papel do usuário' }, { name: 'avatar', type: 'string', required: false, description: 'URL do avatar' } ], // Padrões para produtos product: [ { name: 'price', type: 'number', required: false, description: 'Preço do produto' }, { name: 'category', type: 'string', required: false, description: 'Categoria do produto' }, { name: 'stock', type: 'number', required: false, description: 'Estoque disponível' }, { name: 'sku', type: 'string', required: false, description: 'Código SKU' } ], // Padrões para empresas company: [ { name: 'cnpj', type: 'string', required: false, description: 'CNPJ da empresa' }, { name: 'address', type: 'string', required: false, description: 'Endereço da empresa' }, { name: 'phone', type: 'string', required: false, description: 'Telefone da empresa' } ] }; // Buscar padrões exatos e similares const lowerDomain = domainName.toLowerCase(); if (patterns[lowerDomain]) { return patterns[lowerDomain]; } // Buscar padrões parciais for (const [pattern, fields] of Object.entries(patterns)) { if (lowerDomain.includes(pattern) || pattern.includes(lowerDomain)) { return fields; } } return []; } getSpecificFieldsByDomain(domainName) { // Retorna campos específicos baseados no nome do domínio const specificFields = this.getDomainPatterns(domainName); return specificFields; } extractFieldsFromSchema(schema) { if (!schema.properties) return []; return Object.entries(schema.properties).map(([name, fieldSchema]) => ({ name, type: this.openAPITypeToTypeScript(fieldSchema), required: schema.required && schema.required.includes(name), description: fieldSchema.description || this.inferDescription(name, this.openAPITypeToTypeScript(fieldSchema)) })); } extractFieldsFromSample(sampleObject) { return Object.entries(sampleObject).map(([name, value]) => ({ name, type: this.detectTypeScript(value), required: value !== null && value !== undefined, description: this.inferDescription(name, this.detectTypeScript(value)) })); } capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } } // Função principal para uso no create-domain-v2.js async function analyzeAPIForDomain(domainName, baseUrl, strictMode = true) { const analyzer = new APIAnalyzer(baseUrl); return await analyzer.analyzeAPI(domainName, strictMode); } module.exports = { APIAnalyzer, analyzeAPIForDomain };