UNPKG

embedia

Version:

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

276 lines (238 loc) 7.94 kB
const fs = require('fs-extra'); const path = require('path'); const logger = require('../utils/logger'); /** * Write project files to disk * @param {Array} projectFiles - Array of {path, content} objects * @param {string} targetDirectory - Directory to write files to * @param {Object} options - Writing options * @returns {Promise<Object>} Result of file writing operation */ async function writeProjectFiles(projectFiles, targetDirectory = process.cwd(), options = {}) { const { overwrite = false, backup = true, createDirectories = true } = options; const results = { success: [], skipped: [], errors: [], backups: [] }; logger.step(`Writing ${projectFiles.length} files to ${targetDirectory}`); for (const file of projectFiles) { try { const fullPath = path.join(targetDirectory, file.path); const directory = path.dirname(fullPath); // Create directory if it doesn't exist if (createDirectories) { await fs.ensureDir(directory); } // Check if file exists const fileExists = await fs.pathExists(fullPath); if (fileExists && !overwrite) { if (backup) { await createBackup(fullPath); results.backups.push(file.path); } else { logger.warn(`File exists, skipping: ${file.path}`); results.skipped.push(file.path); continue; } } // Write the file await fs.writeFile(fullPath, file.content, 'utf8'); logger.debug(`Written: ${file.path}`); results.success.push(file.path); } catch (error) { logger.error(`Failed to write ${file.path}: ${error.message}`); results.errors.push({ path: file.path, error: error.message }); } } // Log summary if (results.success.length > 0) { logger.success(`Successfully wrote ${results.success.length} files`); } if (results.skipped.length > 0) { logger.warn(`Skipped ${results.skipped.length} existing files`); } if (results.backups.length > 0) { logger.info(`Created backups for ${results.backups.length} files`); } if (results.errors.length > 0) { logger.error(`Failed to write ${results.errors.length} files`); } return results; } /** * Create a backup of an existing file * @param {string} filePath - Path to file to backup * @returns {Promise<string>} Backup file path */ async function createBackup(filePath) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `${filePath}.backup-${timestamp}`; try { await fs.copy(filePath, backupPath); logger.debug(`Created backup: ${backupPath}`); return backupPath; } catch (error) { logger.warn(`Failed to create backup for ${filePath}: ${error.message}`); throw error; } } /** * Merge content with existing package.json * @param {string} newPackageContent - New package.json content * @param {string} targetPath - Path to existing package.json * @returns {Promise<string>} Merged package.json content */ async function mergePackageJson(newPackageContent, targetPath) { try { const newPackage = JSON.parse(newPackageContent); let existingPackage = {}; // Try to read existing package.json try { const existingContent = await fs.readFile(targetPath, 'utf8'); existingPackage = JSON.parse(existingContent); } catch (error) { // If file doesn't exist or can't be parsed, use empty object logger.debug('No existing package.json found or could not parse'); } // Merge dependencies const merged = { ...existingPackage, ...newPackage, dependencies: { ...existingPackage.dependencies, ...newPackage.dependencies }, devDependencies: { ...existingPackage.devDependencies, ...newPackage.devDependencies }, scripts: { ...existingPackage.scripts, ...newPackage.scripts } }; return JSON.stringify(merged, null, 2); } catch (error) { logger.warn(`Failed to merge package.json: ${error.message}`); return newPackageContent; // Return new content as fallback } } /** * Update existing .env.local with new environment variables * @param {string} newEnvContent - New environment variables * @param {string} targetPath - Path to existing .env.local * @returns {Promise<string>} Updated .env.local content */ async function mergeEnvFile(newEnvContent, targetPath) { try { let existingContent = ''; // Try to read existing .env.local try { existingContent = await fs.readFile(targetPath, 'utf8'); } catch (error) { // File doesn't exist, that's okay logger.debug('No existing .env.local found'); } // Parse environment variables const existingVars = parseEnvContent(existingContent); const newVars = parseEnvContent(newEnvContent); // Merge variables (new ones take precedence if they don't exist) const mergedVars = { ...existingVars }; Object.entries(newVars).forEach(([key, value]) => { if (!mergedVars[key]) { mergedVars[key] = value; logger.debug(`Adding new environment variable: ${key}`); } else { logger.debug(`Environment variable ${key} already exists, keeping existing value`); } }); // Convert back to .env format return Object.entries(mergedVars) .map(([key, value]) => `${key}=${value}`) .join('\n'); } catch (error) { logger.warn(`Failed to merge .env.local: ${error.message}`); return newEnvContent; // Return new content as fallback } } /** * Parse .env file content into key-value pairs * @param {string} content - .env file content * @returns {Object} Key-value pairs */ function parseEnvContent(content) { const vars = {}; content.split('\n').forEach(line => { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { const [key, ...valueParts] = trimmed.split('='); if (key && valueParts.length > 0) { vars[key.trim()] = valueParts.join('=').trim(); } } }); return vars; } /** * Smart file writing that handles special files differently * @param {Array} projectFiles - Project files to write * @param {string} targetDirectory - Target directory * @param {Object} projectInfo - Project information * @returns {Promise<Object>} Write results */ async function smartWriteFiles(projectFiles, targetDirectory, projectInfo) { const results = { success: [], merged: [], skipped: [], errors: [] }; for (const file of projectFiles) { try { const fullPath = path.join(targetDirectory, file.path); // Handle special files if (file.path === 'package.json' && projectInfo.hasPackageJson) { const mergedContent = await mergePackageJson(file.content, fullPath); await fs.writeFile(fullPath, mergedContent, 'utf8'); results.merged.push(file.path); logger.info(`Merged package.json with existing dependencies`); continue; } if (file.path === '.env.local') { const mergedContent = await mergeEnvFile(file.content, fullPath); await fs.writeFile(fullPath, mergedContent, 'utf8'); results.merged.push(file.path); logger.info(`Merged .env.local with existing variables`); continue; } // Regular file writing const directory = path.dirname(fullPath); await fs.ensureDir(directory); await fs.writeFile(fullPath, file.content, 'utf8'); results.success.push(file.path); } catch (error) { logger.error(`Failed to write ${file.path}: ${error.message}`); results.errors.push({ path: file.path, error: error.message }); } } return results; } module.exports = { writeProjectFiles, createBackup, mergePackageJson, mergeEnvFile, smartWriteFiles };