UNPKG

embedia

Version:

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

378 lines (315 loc) 10.7 kB
const fs = require('fs-extra'); const path = require('path'); const logger = require('../utils/logger'); /** * EnvironmentManager - Handles environment variables and API key setup * Fixes the issue where users must manually create .env.local and add API keys */ class EnvironmentManager { constructor() { this.environmentResults = { created: [], updated: [], warnings: [] }; } /** * Setup environment variables for the project * @param {string} projectDirectory - Project directory * @param {Object} options - Setup options * @returns {Promise<Object>} Environment setup results */ async setupEnvironment(projectDirectory, options = {}) { try { logger.info('🔑 Setting up environment variables...'); // 1. Create or update .env.local await this.createEnvLocal(projectDirectory, options.apiKey, options.aiProvider); // 2. Create .env.example for reference await this.createEnvExample(projectDirectory, options.aiProvider); // 3. Update .gitignore to exclude .env.local await this.updateGitignore(projectDirectory); // 4. Validate environment setup const validation = await this.validateEnvironment(projectDirectory); logger.success('✅ Environment setup completed'); return { ...this.environmentResults, validation }; } catch (error) { throw new Error(`Environment setup failed: ${error.message}`); } } /** * Create or update .env.local file */ async createEnvLocal(projectDirectory, apiKey = null, aiProvider = 'gemini') { const envLocalPath = path.join(projectDirectory, '.env.local'); const timestamp = new Date().toISOString(); // Read existing .env.local if it exists let existingEnv = ''; if (await fs.pathExists(envLocalPath)) { existingEnv = await fs.readFile(envLocalPath, 'utf8'); } // Parse existing environment variables const existingVars = this.parseEnvContent(existingEnv); // Define required environment variables based on AI provider const requiredVars = { 'NEXT_PUBLIC_CHATBOT_ENABLED': 'true' }; // Add provider-specific API key if (aiProvider === 'gemini') { requiredVars['GEMINI_API_KEY'] = apiKey || 'your_gemini_api_key_here'; } else if (aiProvider === 'openai') { requiredVars['OPENAI_API_KEY'] = apiKey || 'your_openai_api_key_here'; } // Merge with existing variables (don't overwrite existing values) const mergedVars = { ...requiredVars, ...existingVars }; // Generate new .env.local content const envContent = this.generateEnvContent(mergedVars, timestamp); // Write to file await fs.writeFile(envLocalPath, envContent, 'utf8'); if (existingEnv) { this.environmentResults.updated.push('.env.local'); logger.success('📝 Updated .env.local with new variables'); } else { this.environmentResults.created.push('.env.local'); logger.success('📝 Created .env.local file'); } // Show API key instructions if no key provided if (!apiKey || apiKey === 'your_gemini_api_key_here') { this.environmentResults.warnings.push({ type: 'api_key_needed', message: 'Please add your Gemini API key to .env.local', instructions: [ '1. Visit https://makersuite.google.com/app/apikey', '2. Create a new API key', '3. Replace "your_gemini_api_key_here" in .env.local with your actual key' ] }); } } /** * Create .env.example file for reference */ async createEnvExample(projectDirectory) { const envExamplePath = path.join(projectDirectory, '.env.example'); // Don't overwrite existing .env.example if (await fs.pathExists(envExamplePath)) { return; } const exampleContent = `# Embedia Chatbot Configuration # Copy this file to .env.local and add your actual API keys # Gemini API Key (required) # Get your key from: https://makersuite.google.com/app/apikey GEMINI_API_KEY=your_gemini_api_key_here # Chatbot Feature Toggle NEXT_PUBLIC_CHATBOT_ENABLED=true # Optional: Custom API endpoint # CHATBOT_API_ENDPOINT=/api/chat `; await fs.writeFile(envExamplePath, exampleContent, 'utf8'); this.environmentResults.created.push('.env.example'); logger.success('📄 Created .env.example file'); } /** * Update .gitignore to exclude .env.local */ async updateGitignore(projectDirectory) { const gitignorePath = path.join(projectDirectory, '.gitignore'); let gitignoreContent = ''; if (await fs.pathExists(gitignorePath)) { gitignoreContent = await fs.readFile(gitignorePath, 'utf8'); } // Check if .env.local is already ignored if (gitignoreContent.includes('.env.local')) { return; } // Add .env.local to .gitignore const envSection = ` # Environment variables .env.local .env.*.local `; const updatedContent = gitignoreContent + envSection; await fs.writeFile(gitignorePath, updatedContent, 'utf8'); if (gitignoreContent) { this.environmentResults.updated.push('.gitignore'); } else { this.environmentResults.created.push('.gitignore'); } logger.success('🔒 Updated .gitignore to exclude environment files'); } /** * Parse environment file content */ parseEnvContent(content) { const vars = {}; content.split('\n').forEach(line => { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) { const [key, ...valueParts] = trimmed.split('='); if (key && valueParts.length > 0) { vars[key.trim()] = valueParts.join('=').trim(); } } }); return vars; } /** * Generate environment file content */ generateEnvContent(vars, timestamp) { const lines = [ '# Environment variables for Embedia Chatbot', `# Last updated: ${timestamp}`, '', '# Gemini API Key (required for chatbot functionality)', '# Get your key from: https://makersuite.google.com/app/apikey' ]; // Add variables Object.entries(vars).forEach(([key, value]) => { if (key === 'GEMINI_API_KEY') { lines.push(`${key}=${value}`); lines.push(''); } else if (key.startsWith('NEXT_PUBLIC_')) { if (!lines.includes('# Public environment variables')) { lines.push('# Public environment variables'); } lines.push(`${key}=${value}`); } else { lines.push(`${key}=${value}`); } }); return lines.join('\n') + '\n'; } /** * Validate environment setup */ async validateEnvironment(projectDirectory) { const validation = { hasEnvLocal: false, hasApiKey: false, apiKeyValid: false, publicVarsSet: false, issues: [] }; const envLocalPath = path.join(projectDirectory, '.env.local'); // Check if .env.local exists if (await fs.pathExists(envLocalPath)) { validation.hasEnvLocal = true; const envContent = await fs.readFile(envLocalPath, 'utf8'); const vars = this.parseEnvContent(envContent); // Check for API key if (vars.GEMINI_API_KEY && vars.GEMINI_API_KEY !== 'your_gemini_api_key_here') { validation.hasApiKey = true; // Basic API key format validation if (vars.GEMINI_API_KEY.length > 10 && !vars.GEMINI_API_KEY.includes('your_')) { validation.apiKeyValid = true; } } // Check public variables if (vars.NEXT_PUBLIC_CHATBOT_ENABLED) { validation.publicVarsSet = true; } } else { validation.issues.push('.env.local file not found'); } // Generate issues list if (!validation.hasApiKey) { validation.issues.push('Gemini API key not set or using placeholder value'); } if (!validation.apiKeyValid && validation.hasApiKey) { validation.issues.push('API key appears to be invalid format'); } return validation; } /** * Prompt user for API key (interactive mode) */ async promptForApiKey() { // This would be implemented with a CLI prompt library // For now, return instructions return { instructions: [ '🔑 API Key Setup Required:', '', '1. Visit https://makersuite.google.com/app/apikey', '2. Click "Create API Key"', '3. Copy the generated key', '4. Add it to your .env.local file:', ' GEMINI_API_KEY=your_actual_api_key_here', '', '⚠️ Keep your API key secure and never commit it to version control!' ] }; } /** * Test API key validity */ async testApiKey(apiKey) { if (!apiKey || apiKey === 'your_gemini_api_key_here') { return { valid: false, error: 'No API key provided or using placeholder' }; } try { // Basic format validation if (apiKey.length < 10) { return { valid: false, error: 'API key appears too short' }; } // Note: We could make an actual API call here to test the key // but that would require the @google/genai package return { valid: true, message: 'API key format appears valid' }; } catch (error) { return { valid: false, error: `API key validation failed: ${error.message}` }; } } /** * Get environment setup summary */ getSummary() { const { created, updated, warnings } = this.environmentResults; return { filesCreated: created.length, filesUpdated: updated.length, warningsCount: warnings.length, details: { created, updated, warnings } }; } /** * Generate next steps for user */ getNextSteps(validation) { const steps = []; if (!validation.hasEnvLocal) { steps.push('❌ Create .env.local file with environment variables'); } if (!validation.hasApiKey) { steps.push('🔑 Add your Gemini API key to .env.local'); steps.push(' → Visit https://makersuite.google.com/app/apikey'); } if (validation.hasApiKey && !validation.apiKeyValid) { steps.push('⚠️ Verify your API key format is correct'); } if (validation.issues.length === 0) { steps.push('✅ Environment setup is complete!'); steps.push('🚀 You can now run: npm run dev'); } return steps; } } module.exports = EnvironmentManager;