embedia
Version:
Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys
371 lines (319 loc) โข 10.2 kB
JavaScript
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;