UNPKG

embedia

Version:

Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys

358 lines (306 loc) 10.6 kB
const fs = require('fs-extra'); const path = require('path'); const { glob } = require('glob'); const chalk = require('chalk'); /** * ImprovedConflictScanner - More consistent and clear conflict detection * Categorizes conflicts by severity and provides actionable information */ class ImprovedConflictScanner { constructor() { // Define conflict severity levels this.SEVERITY = { CRITICAL: 'critical', // Blocks integration HIGH: 'high', // May cause issues MEDIUM: 'medium', // Should be addressed LOW: 'low', // Informational INFO: 'info' // Just awareness }; // Define conflict categories this.CATEGORY = { API_ROUTE: 'API Route Conflict', EXISTING_CHAT: 'Existing Chat Implementation', PORT_CONFLICT: 'Port Conflict', DEPENDENCY: 'Dependency Conflict', STYLE_CONFLICT: 'Style Conflict', ENV_VAR: 'Environment Variable', BUILD_CONFIG: 'Build Configuration' }; } async scanProject(projectPath) { const conflicts = []; // Run all scanners const scanResults = await Promise.all([ this.scanAPIRoutes(projectPath), this.scanExistingChat(projectPath), this.scanDependencies(projectPath), this.scanEnvironment(projectPath), this.scanBuildConfig(projectPath) ]); // Flatten and sort by severity scanResults.forEach(result => conflicts.push(...result)); return this.categorizeAndSort(conflicts); } /** * Scan for API route conflicts */ async scanAPIRoutes(projectPath) { const conflicts = []; const apiPaths = [ 'app/api/chat/route.js', 'app/api/chat/route.ts', 'pages/api/chat.js', 'pages/api/chat.ts', 'app/api/embedia/chat/route.js', 'app/api/embedia/chat/route.ts' ]; for (const apiPath of apiPaths) { const fullPath = path.join(projectPath, apiPath); if (await fs.pathExists(fullPath)) { const isEmbediaRoute = apiPath.includes('embedia'); conflicts.push({ category: this.CATEGORY.API_ROUTE, severity: isEmbediaRoute ? this.SEVERITY.INFO : this.SEVERITY.HIGH, file: apiPath, message: isEmbediaRoute ? 'Existing Embedia route found (likely from previous installation)' : 'Existing chat API route found - will use alternative endpoint', resolution: isEmbediaRoute ? 'No action needed - existing Embedia route will be updated' : 'Embedia will use /api/embedia/chat endpoint', autoResolvable: true }); } } return conflicts; } /** * Scan for existing chat implementations */ async scanExistingChat(projectPath) { const conflicts = []; const chatPatterns = [ 'components/**/*[Cc]hat*.{js,jsx,ts,tsx}', 'src/**/*[Cc]hat*.{js,jsx,ts,tsx}', 'app/**/*[Cc]hat*.{js,jsx,ts,tsx}' ]; for (const pattern of chatPatterns) { const files = await glob(pattern, { cwd: projectPath, ignore: ['**/node_modules/**', '**/generated/**', '**/.next/**'] }); for (const file of files) { // Skip Embedia files if (file.includes('embedia') || file.includes('EmbediaChatLoader')) { continue; } const content = await fs.readFile(path.join(projectPath, file), 'utf8'); // Check if it's actually a chat component const isChatComponent = content.includes('message') && (content.includes('send') || content.includes('chat')); if (isChatComponent) { conflicts.push({ category: this.CATEGORY.EXISTING_CHAT, severity: this.SEVERITY.MEDIUM, file: file, message: 'Existing chat component found', resolution: 'Embedia will be installed alongside - no conflicts', autoResolvable: true }); } } } // Limit to top 5 to avoid noise return conflicts.slice(0, 5); } /** * Scan for dependency conflicts */ async scanDependencies(projectPath) { const conflicts = []; const packageJsonPath = path.join(projectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); const deps = { ...packageJson.dependencies || {}, ...packageJson.devDependencies || {} }; // Check for AI/Chat related dependencies const aiDeps = ['openai', '@google/generative-ai', 'langchain', 'ai']; for (const dep of aiDeps) { if (deps[dep]) { conflicts.push({ category: this.CATEGORY.DEPENDENCY, severity: this.SEVERITY.INFO, file: 'package.json', message: `Found ${dep} dependency (v${deps[dep]})`, resolution: 'No conflict - Embedia can work alongside other AI libraries', autoResolvable: true }); } } } return conflicts; } /** * Scan for environment variables */ async scanEnvironment(projectPath) { const conflicts = []; const envFiles = ['.env', '.env.local', '.env.development']; for (const envFile of envFiles) { const envPath = path.join(projectPath, envFile); if (await fs.pathExists(envPath)) { const content = await fs.readFile(envPath, 'utf8'); // Check for API keys const hasGeminiKey = content.includes('GEMINI_API_KEY'); const hasOpenAIKey = content.includes('OPENAI_API_KEY'); if (hasGeminiKey || hasOpenAIKey) { conflicts.push({ category: this.CATEGORY.ENV_VAR, severity: this.SEVERITY.INFO, file: envFile, message: `Found existing AI API keys in ${envFile}`, resolution: 'Good! You already have API keys configured', autoResolvable: true }); } } } return conflicts; } /** * Scan for build configuration issues */ async scanBuildConfig(projectPath) { const conflicts = []; // Check Next.js config const nextConfigs = ['next.config.js', 'next.config.mjs']; for (const configFile of nextConfigs) { const configPath = path.join(projectPath, configFile); if (await fs.pathExists(configPath)) { const content = await fs.readFile(configPath, 'utf8'); // Check for potential issues if (content.includes('rewrites') || content.includes('redirects')) { conflicts.push({ category: this.CATEGORY.BUILD_CONFIG, severity: this.SEVERITY.LOW, file: configFile, message: 'Custom rewrites/redirects detected in Next.js config', resolution: 'Embedia routes will work alongside your custom configuration', autoResolvable: true }); } } } return conflicts; } /** * Categorize and sort conflicts */ categorizeAndSort(conflicts) { // Group by category const categorized = {}; conflicts.forEach(conflict => { if (!categorized[conflict.category]) { categorized[conflict.category] = []; } categorized[conflict.category].push(conflict); }); // Sort by severity within each category const severityOrder = { [this.SEVERITY.CRITICAL]: 0, [this.SEVERITY.HIGH]: 1, [this.SEVERITY.MEDIUM]: 2, [this.SEVERITY.LOW]: 3, [this.SEVERITY.INFO]: 4 }; Object.keys(categorized).forEach(category => { categorized[category].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity] ); }); // Return flat array sorted by severity return conflicts.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity] ); } /** * Get conflict summary for display */ getConflictSummary(conflicts) { const summary = { total: conflicts.length, bySeverity: {}, byCategory: {}, critical: [], autoResolvable: 0 }; conflicts.forEach(conflict => { // Count by severity summary.bySeverity[conflict.severity] = (summary.bySeverity[conflict.severity] || 0) + 1; // Count by category summary.byCategory[conflict.category] = (summary.byCategory[conflict.category] || 0) + 1; // Track critical issues if (conflict.severity === this.SEVERITY.CRITICAL) { summary.critical.push(conflict); } // Count auto-resolvable if (conflict.autoResolvable) { summary.autoResolvable++; } }); return summary; } /** * Display conflicts in a user-friendly way */ displayConflicts(conflicts) { if (conflicts.length === 0) { console.log(chalk.green('✅ No conflicts detected')); return; } const summary = this.getConflictSummary(conflicts); // Display summary console.log(chalk.yellow(`\n⚠️ Found ${summary.total} potential conflicts\n`)); // Group by category for display const byCategory = {}; conflicts.forEach(conflict => { if (!byCategory[conflict.category]) { byCategory[conflict.category] = []; } byCategory[conflict.category].push(conflict); }); // Display each category Object.entries(byCategory).forEach(([category, categoryConflicts]) => { console.log(chalk.bold(`${category}:`)); categoryConflicts.forEach(conflict => { const severityColor = { [this.SEVERITY.CRITICAL]: chalk.red, [this.SEVERITY.HIGH]: chalk.yellow, [this.SEVERITY.MEDIUM]: chalk.blue, [this.SEVERITY.LOW]: chalk.gray, [this.SEVERITY.INFO]: chalk.gray }[conflict.severity]; console.log(severityColor(` • ${conflict.file}`)); console.log(chalk.gray(` ${conflict.message}`)); if (conflict.resolution) { console.log(chalk.green(` → ${conflict.resolution}`)); } }); console.log(''); }); // Summary if (summary.critical.length > 0) { console.log(chalk.red.bold(`❌ ${summary.critical.length} critical issues require attention`)); } else if (summary.autoResolvable === summary.total) { console.log(chalk.green('✅ All conflicts can be resolved automatically')); } else { console.log(chalk.blue(`ℹ️ ${summary.autoResolvable}/${summary.total} conflicts will be handled automatically`)); } } } module.exports = ImprovedConflictScanner;