UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and

228 lines (227 loc) 7.92 kB
/** * Environment File Management Utilities for NeuroLink CLI * * Handles .env file operations including backup, update, and validation. */ import fs from "fs"; import chalk from "chalk"; import { logger } from "../../lib/utils/logger.js"; /** * Create a timestamped backup of the existing .env file */ export function backupEnvFile(envPath = ".env") { const result = { existed: false, }; if (fs.existsSync(envPath)) { result.existed = true; const timestamp = new Date() .toISOString() .replace(/[:.]/g, "-") .replace("T", "_") .split(".")[0]; // Remove milliseconds const backupPath = `${envPath}.backup.${timestamp}`; try { fs.copyFileSync(envPath, backupPath); result.backupPath = backupPath; } catch (error) { throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`); } } return result; } /** * Parse .env file content into key-value pairs */ export function parseEnvFile(content) { const result = {}; const lines = content.split("\n"); for (const line of lines) { const trimmedLine = line.trim(); // Skip empty lines and comments if (!trimmedLine || trimmedLine.startsWith("#")) { continue; } // Find the first = character const equalIndex = trimmedLine.indexOf("="); if (equalIndex === -1) { continue; // Invalid line format } const key = trimmedLine.substring(0, equalIndex).trim(); const value = trimmedLine.substring(equalIndex + 1).trim(); // Remove surrounding quotes if present const cleanValue = value.replace(/^["']|["']$/g, ""); result[key] = cleanValue; } return result; } /** * Generate .env file content from key-value pairs */ export function generateEnvContent(envVars, existingContent) { const lines = []; const existingVars = existingContent ? parseEnvFile(existingContent) : {}; const processedKeys = new Set(); // If we have existing content, preserve its structure and comments if (existingContent) { const existingLines = existingContent.split("\n"); for (const line of existingLines) { const trimmedLine = line.trim(); // Preserve comments and empty lines if (!trimmedLine || trimmedLine.startsWith("#")) { lines.push(line); continue; } const equalIndex = trimmedLine.indexOf("="); if (equalIndex === -1) { lines.push(line); // Preserve invalid lines as-is continue; } const key = trimmedLine.substring(0, equalIndex).trim(); if (Object.prototype.hasOwnProperty.call(envVars, key)) { // Update existing variable lines.push(`${key}=${envVars[key]}`); processedKeys.add(key); } else { // Preserve existing variable lines.push(line); } } } // Add new variables that weren't in the existing file const newVars = Object.keys(envVars).filter((key) => !processedKeys.has(key)); if (newVars.length > 0) { if (lines.length > 0 && lines[lines.length - 1].trim() !== "") { lines.push(""); // Add blank line before new variables } if (!existingContent) { lines.push("# NeuroLink AI Provider Configuration"); } for (const key of newVars) { lines.push(`${key}=${envVars[key]}`); } } return lines.join("\n") + (lines.length > 0 ? "\n" : ""); } /** * Update .env file with new environment variables */ export function updateEnvFile(newVars, envPath = ".env", createBackup = true) { const result = { backup: { existed: false }, updated: [], added: [], unchanged: [], }; // Create backup if requested and file exists if (createBackup) { result.backup = backupEnvFile(envPath); } // Read existing content let existingContent = ""; let existingVars = {}; if (fs.existsSync(envPath)) { existingContent = fs.readFileSync(envPath, "utf8"); existingVars = parseEnvFile(existingContent); } // Categorize changes for (const [key, value] of Object.entries(newVars)) { if (Object.prototype.hasOwnProperty.call(existingVars, key)) { if (existingVars[key] !== value) { result.updated.push(key); } else { result.unchanged.push(key); } } else { result.added.push(key); } } // Generate new content const newContent = generateEnvContent(newVars, existingContent); // Write updated file try { fs.writeFileSync(envPath, newContent, "utf8"); } catch (error) { throw new Error(`Failed to write .env file: ${error instanceof Error ? error.message : String(error)}`); } return result; } /** * Display environment file update summary */ export function displayEnvUpdateSummary(result, quiet = false) { if (quiet) { return; } if (result.backup.existed && result.backup.backupPath) { logger.always(chalk.gray(`💾 Created backup: ${result.backup.backupPath}`)); } if (result.added.length > 0) { logger.always(chalk.green(`➕ Added ${result.added.length} new variables: ${result.added.join(", ")}`)); } if (result.updated.length > 0) { logger.always(chalk.yellow(`🔄 Updated ${result.updated.length} existing variables: ${result.updated.join(", ")}`)); } if (result.unchanged.length > 0) { logger.always(chalk.gray(`✓ ${result.unchanged.length} variables unchanged: ${result.unchanged.join(", ")}`)); } const totalChanges = result.added.length + result.updated.length; if (totalChanges > 0) { logger.always(chalk.blue(`📝 Environment file updated with ${totalChanges} changes`)); } else { logger.always(chalk.gray("📝 No changes needed to environment file")); } } /** * Validate .env file format and required variables */ export function validateEnvFile(envPath = ".env", requiredVars = []) { const result = { valid: true, errors: [], warnings: [], variables: {}, }; if (!fs.existsSync(envPath)) { result.valid = false; result.errors.push(`Environment file not found: ${envPath}`); return result; } try { const content = fs.readFileSync(envPath, "utf8"); result.variables = parseEnvFile(content); // Check for required variables for (const requiredVar of requiredVars) { if (!Object.prototype.hasOwnProperty.call(result.variables, requiredVar) || !result.variables[requiredVar]) { result.valid = false; result.errors.push(`Missing required variable: ${requiredVar}`); } } // Check for common formatting issues const lines = content.split("\n"); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line || line.startsWith("#")) { continue; } if (!line.includes("=")) { result.warnings.push(`Line ${i + 1}: Invalid format (missing =)`); } else if (line.startsWith("=")) { result.warnings.push(`Line ${i + 1}: Empty variable name`); } } } catch (error) { result.valid = false; result.errors.push(`Failed to read environment file: ${error instanceof Error ? error.message : String(error)}`); } return result; }