UNPKG

embedia

Version:

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

371 lines (319 loc) โ€ข 10.2 kB
const fs = require('fs-extra'); const path = require('path'); const { execSync } = require('child_process'); const logger = require('../utils/logger'); /** * DependencyManager - Automatically handles package installation and updates * Fixes the issue where users have to manually install required dependencies */ class DependencyManager { constructor() { this.requiredDependencies = { production: { '@google/generative-ai': '^0.21.0' }, development: { '@types/node': '^20.0.0' } }; this.installResults = { installed: [], updated: [], skipped: [], errors: [] }; } /** * Analyze and install required dependencies * @param {string} projectDirectory - Project directory * @param {Object} projectInfo - Project information from detector * @returns {Promise<Object>} Installation results */ async manageDependencies(projectDirectory, projectInfo) { try { logger.info('๐Ÿ“ฆ Analyzing project dependencies...'); // 1. Read existing package.json const packageJsonPath = path.join(projectDirectory, 'package.json'); const packageJson = await this.readPackageJson(packageJsonPath); // 2. Determine required dependencies based on project const required = this.determineRequiredDependencies(projectInfo); // 3. Check what's missing or needs updating const analysis = this.analyzeDependencies(packageJson, required); // 4. Install missing dependencies if (analysis.toInstall.length > 0) { await this.installDependencies(projectDirectory, analysis.toInstall); } // 5. Update package.json if needed if (analysis.toUpdate.length > 0) { await this.updatePackageJson(packageJsonPath, packageJson, analysis.toUpdate); } logger.success('โœ… Dependency management completed'); return this.installResults; } catch (error) { this.installResults.errors.push({ type: 'dependency_management', message: error.message }); throw error; } } /** * Read and parse package.json */ async readPackageJson(packageJsonPath) { try { if (await fs.pathExists(packageJsonPath)) { return await fs.readJson(packageJsonPath); } else { // For empty directories, create a minimal package.json logger.info('๐Ÿ“ฆ No package.json found, creating minimal package.json for dependencies'); const minimalPackageJson = { name: 'embedia-chatbot-project', version: '1.0.0', description: 'Project with Embedia chatbot integration', main: 'index.js', scripts: {}, dependencies: {}, devDependencies: {} }; await fs.writeJson(packageJsonPath, minimalPackageJson, { spaces: 2 }); return minimalPackageJson; } } catch (error) { throw new Error(`Failed to read/create package.json: ${error.message}`); } } /** * Determine required dependencies based on project type */ determineRequiredDependencies(projectInfo) { const required = { dependencies: { ...this.requiredDependencies.production }, devDependencies: {} }; // Add TypeScript types if project uses TypeScript if (projectInfo.config?.typescript) { required.devDependencies = { ...this.requiredDependencies.development }; } // Add Next.js specific dependencies if needed if (projectInfo.type?.startsWith('nextjs')) { // These should already be present, but we can verify versions required.peerDependencies = { 'next': '>=13.0.0', 'react': '>=18.0.0', 'react-dom': '>=18.0.0' }; } return required; } /** * Analyze current vs required dependencies */ analyzeDependencies(packageJson, required) { const analysis = { toInstall: [], toUpdate: [], satisfied: [] }; const currentDeps = { ...packageJson.dependencies || {}, ...packageJson.devDependencies || {} }; // Check production dependencies for (const [pkg, version] of Object.entries(required.dependencies)) { if (!currentDeps[pkg]) { analysis.toInstall.push({ package: pkg, version: version, type: 'dependency' }); } else { analysis.satisfied.push(pkg); } } // Check dev dependencies for (const [pkg, version] of Object.entries(required.devDependencies)) { if (!currentDeps[pkg]) { analysis.toInstall.push({ package: pkg, version: version, type: 'devDependency' }); } else { analysis.satisfied.push(pkg); } } // Check peer dependencies (warn if missing) if (required.peerDependencies) { for (const [pkg, version] of Object.entries(required.peerDependencies)) { if (!currentDeps[pkg]) { logger.warn(`โš ๏ธ Missing peer dependency: ${pkg} ${version}`); } } } return analysis; } /** * Install missing dependencies */ async installDependencies(projectDirectory, toInstall) { if (toInstall.length === 0) { return; } // Group by type const prodDeps = toInstall.filter(dep => dep.type === 'dependency'); const devDeps = toInstall.filter(dep => dep.type === 'devDependency'); // Install production dependencies if (prodDeps.length > 0) { await this.runNpmInstall(projectDirectory, prodDeps, false); } // Install dev dependencies if (devDeps.length > 0) { await this.runNpmInstall(projectDirectory, devDeps, true); } } /** * Run npm install command */ async runNpmInstall(projectDirectory, dependencies, isDev = false) { const packages = dependencies.map(dep => dep.version ? `${dep.package}@${dep.version}` : dep.package ); const devFlag = isDev ? ' --save-dev' : ''; const command = `npm install${devFlag} ${packages.join(' ')}`; logger.info(`๐Ÿ“ฆ Installing: ${packages.join(', ')}`); try { execSync(command, { cwd: projectDirectory, stdio: 'pipe', encoding: 'utf8' }); dependencies.forEach(dep => { this.installResults.installed.push(dep.package); logger.success(`โœ… Installed: ${dep.package}`); }); } catch (error) { dependencies.forEach(dep => { this.installResults.errors.push({ type: 'install_error', package: dep.package, message: error.message }); logger.error(`โŒ Failed to install: ${dep.package}`); }); throw new Error(`Dependency installation failed: ${error.message}`); } } /** * Update package.json with any additional metadata */ async updatePackageJson(packageJsonPath, packageJson, updates) { try { let modified = false; // Add any missing scripts if needed if (!packageJson.scripts) { packageJson.scripts = {}; } // Ensure dev script exists for Next.js projects if (!packageJson.scripts.dev && packageJson.dependencies?.next) { packageJson.scripts.dev = 'next dev'; modified = true; } if (modified) { await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); this.installResults.updated.push('package.json'); logger.success('๐Ÿ“„ Updated package.json'); } } catch (error) { this.installResults.errors.push({ type: 'package_json_update', message: error.message }); } } /** * Verify installation success */ async verifyInstallation(projectDirectory, requiredPackages) { const nodeModulesPath = path.join(projectDirectory, 'node_modules'); const verification = { verified: [], missing: [] }; for (const pkg of requiredPackages) { const packagePath = path.join(nodeModulesPath, pkg); if (await fs.pathExists(packagePath)) { verification.verified.push(pkg); } else { verification.missing.push(pkg); } } return verification; } /** * Get dependency status summary */ getSummary() { const { installed, updated, skipped, errors } = this.installResults; return { totalProcessed: installed.length + updated.length + skipped.length, successful: installed.length + updated.length, failed: errors.length, details: { installed, updated, skipped, errors } }; } /** * Check for outdated dependencies (optional) */ async checkForUpdates(projectDirectory) { try { const result = execSync('npm outdated --json', { cwd: projectDirectory, stdio: 'pipe', encoding: 'utf8' }); return JSON.parse(result); } catch (error) { // npm outdated returns non-zero exit code when outdated packages exist if (error.stdout) { try { return JSON.parse(error.stdout); } catch (parseError) { return {}; } } return {}; } } /** * Clean install if needed (removes node_modules and reinstalls) */ async cleanInstall(projectDirectory) { try { logger.info('๐Ÿงน Performing clean install...'); const nodeModulesPath = path.join(projectDirectory, 'node_modules'); const packageLockPath = path.join(projectDirectory, 'package-lock.json'); // Remove node_modules and package-lock.json if (await fs.pathExists(nodeModulesPath)) { await fs.remove(nodeModulesPath); } if (await fs.pathExists(packageLockPath)) { await fs.remove(packageLockPath); } // Reinstall execSync('npm install', { cwd: projectDirectory, stdio: 'pipe' }); logger.success('โœ… Clean install completed'); } catch (error) { throw new Error(`Clean install failed: ${error.message}`); } } } module.exports = DependencyManager;