@versatil/sdlc-framework
Version:
🚀 AI-Native SDLC framework with 11-MCP ecosystem, RAG memory, OPERA orchestration, and 6 specialized agents achieving ZERO CONTEXT LOSS. Features complete CI/CD pipeline with 7 GitHub workflows (MCP testing, security scanning, performance benchmarking),
693 lines (602 loc) • 20.2 kB
text/typescript
/**
* VERSATIL Environment Manager - Enterprise Grade Multi-Environment System
* Handles environment detection, switching, and configuration management
*/
import * as fs from 'fs/promises';
import * as path from 'path';
import { VERSATILLogger } from '../utils/logger.js';
export type EnvironmentType = 'development' | 'testing' | 'staging' | 'production';
export interface EnvironmentConfig {
name: EnvironmentType;
description: string;
version: string;
settings: {
debug: boolean;
hotReload: boolean;
mockExternalServices: boolean;
strictValidation: boolean;
verboseLogging: boolean;
optimizations?: boolean;
};
services: {
mcp: {
host: string;
port: number;
ssl: boolean;
autoRestart: boolean;
watchFiles: boolean;
healthCheck?: boolean;
clustering?: boolean;
workers?: number;
};
database: {
type: string;
url: string;
pool: {
min: number;
max: number;
};
logging: boolean;
ssl?: boolean;
migrations?: boolean;
readReplicas?: boolean;
connectionTimeout?: number;
resetOnStart?: boolean;
};
supabase: {
url: string;
anonKey: string;
serviceKey: string;
local: boolean;
edgeFunctions?: boolean;
globalDistribution?: boolean;
};
opera: {
enabled: boolean;
autoUpdate: boolean;
updateChannel: string;
maxConcurrentGoals: number;
timeout: number;
clustering?: boolean;
distributedMode?: boolean;
};
};
agents: Record<string, any>;
testing: Record<string, any>;
monitoring: Record<string, any>;
security: Record<string, any>;
features: Record<string, any>;
deployment?: Record<string, any>;
performance?: Record<string, any>;
compliance?: Record<string, any>;
backup?: Record<string, any>;
disaster_recovery?: Record<string, any>;
cicd?: Record<string, any>;
}
export interface EnvironmentValidation {
isValid: boolean;
errors: string[];
warnings: string[];
recommendations: string[];
}
export interface EnvironmentHealth {
status: 'healthy' | 'degraded' | 'unhealthy';
services: Record<string, {
status: 'up' | 'down' | 'degraded';
responseTime?: number;
lastCheck: number;
error?: string;
}>;
agents: Record<string, {
status: 'active' | 'inactive' | 'error';
lastActivity: number;
performanceScore?: number;
}>;
overallHealth: number; // 0-100
}
export class EnvironmentManager {
private logger: VERSATILLogger;
private projectPath: string;
private configPath: string;
private currentEnvironment: EnvironmentType = 'development';
private environmentConfig: EnvironmentConfig | null = null;
private cachedConfigs: Map<EnvironmentType, EnvironmentConfig> = new Map();
constructor(projectPath: string = process.cwd()) {
this.logger = new VERSATILLogger();
this.projectPath = projectPath;
this.configPath = path.join(projectPath, 'config', 'environments');
}
/**
* Initialize environment manager
*/
async initialize(): Promise<void> {
this.logger.info('Initializing Environment Manager', {}, 'environment-manager');
// Detect current environment
await this.detectEnvironment();
// Load environment configuration
await this.loadEnvironmentConfig();
// Validate environment
const validation = await this.validateEnvironment();
if (!validation.isValid) {
this.logger.error('Environment validation failed', {
errors: validation.errors,
warnings: validation.warnings
}, 'environment-manager');
throw new Error(`Environment validation failed: ${validation.errors.join(', ')}`);
}
// Apply environment configuration
await this.applyEnvironmentConfig();
this.logger.info('Environment Manager initialized successfully', {
environment: this.currentEnvironment,
healthStatus: 'initializing'
}, 'environment-manager');
}
/**
* Detect current environment from various sources
*/
private async detectEnvironment(): Promise<EnvironmentType> {
// Priority order for environment detection:
// 1. Explicit environment variable
// 2. NODE_ENV
// 3. Runtime context detection
// 4. Default to development
// Check explicit VERSATIL_ENV
if (process.env.VERSATIL_ENV) {
const env = process.env.VERSATIL_ENV.toLowerCase() as EnvironmentType;
if (this.isValidEnvironment(env)) {
this.currentEnvironment = env;
this.logger.info('Environment detected from VERSATIL_ENV', { environment: env }, 'environment-manager');
return env;
}
}
// Check NODE_ENV
if (process.env.NODE_ENV) {
const nodeEnv = process.env.NODE_ENV.toLowerCase();
let mappedEnv: EnvironmentType;
switch (nodeEnv) {
case 'development':
case 'dev':
mappedEnv = 'development';
break;
case 'test':
case 'testing':
mappedEnv = 'testing';
break;
case 'staging':
case 'stage':
mappedEnv = 'staging';
break;
case 'production':
case 'prod':
mappedEnv = 'production';
break;
default:
mappedEnv = 'development';
}
this.currentEnvironment = mappedEnv;
this.logger.info('Environment detected from NODE_ENV', {
nodeEnv,
mappedEnv
}, 'environment-manager');
return mappedEnv;
}
// Runtime context detection
const contextEnv = await this.detectRuntimeContext();
this.currentEnvironment = contextEnv;
this.logger.info('Environment detected from runtime context', {
environment: contextEnv
}, 'environment-manager');
return contextEnv;
}
/**
* Detect environment from runtime context
*/
private async detectRuntimeContext(): Promise<EnvironmentType> {
// Check for CI/CD environment indicators
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.JENKINS_URL) {
return 'testing';
}
// Check for staging indicators
if (process.env.STAGING || process.env.HEROKU_APP_NAME?.includes('staging')) {
return 'staging';
}
// Check for production indicators
if (process.env.PRODUCTION ||
process.env.HEROKU_APP_NAME?.includes('prod') ||
process.env.VERCEL_ENV === 'production') {
return 'production';
}
// Default to development
return 'development';
}
/**
* Load environment configuration
*/
async loadEnvironmentConfig(environment?: EnvironmentType): Promise<EnvironmentConfig> {
const env = environment || this.currentEnvironment;
// Check cache first
if (this.cachedConfigs.has(env)) {
this.environmentConfig = this.cachedConfigs.get(env)!;
return this.environmentConfig;
}
const configFile = path.join(this.configPath, `${env}.json`);
try {
const configData = await fs.readFile(configFile, 'utf-8');
const config = JSON.parse(configData) as EnvironmentConfig;
// Process environment variable substitution
const processedConfig = this.processEnvironmentVariables(config);
// Cache the configuration
this.cachedConfigs.set(env, processedConfig);
this.environmentConfig = processedConfig;
this.logger.info('Environment configuration loaded', {
environment: env,
configFile,
servicesCount: Object.keys(config.services).length,
agentsCount: Object.keys(config.agents).length
}, 'environment-manager');
return processedConfig;
} catch (error) {
this.logger.error('Failed to load environment configuration', {
environment: env,
configFile,
error: error.message
}, 'environment-manager');
throw new Error(`Failed to load environment configuration for ${env}: ${error.message}`);
}
}
/**
* Process environment variable substitution in config
*/
private processEnvironmentVariables(config: EnvironmentConfig): EnvironmentConfig {
const configStr = JSON.stringify(config);
const processedStr = configStr.replace(/\$\{([^}]+)\}/g, (match, varName) => {
return process.env[varName] || match;
});
return JSON.parse(processedStr);
}
/**
* Switch to a different environment
*/
async switchEnvironment(newEnvironment: EnvironmentType): Promise<void> {
if (!this.isValidEnvironment(newEnvironment)) {
throw new Error(`Invalid environment: ${newEnvironment}`);
}
const previousEnvironment = this.currentEnvironment;
this.logger.info('Switching environment', {
from: previousEnvironment,
to: newEnvironment
}, 'environment-manager');
try {
// Load new environment configuration
await this.loadEnvironmentConfig(newEnvironment);
// Validate new environment
const validation = await this.validateEnvironment();
if (!validation.isValid) {
throw new Error(`Environment validation failed: ${validation.errors.join(', ')}`);
}
// Update current environment
this.currentEnvironment = newEnvironment;
// Apply new configuration
await this.applyEnvironmentConfig();
this.logger.info('Environment switched successfully', {
from: previousEnvironment,
to: newEnvironment
}, 'environment-manager');
} catch (error) {
this.logger.error('Failed to switch environment', {
from: previousEnvironment,
to: newEnvironment,
error: error.message
}, 'environment-manager');
throw error;
}
}
/**
* Validate current environment configuration
*/
async validateEnvironment(): Promise<EnvironmentValidation> {
const validation: EnvironmentValidation = {
isValid: true,
errors: [],
warnings: [],
recommendations: []
};
if (!this.environmentConfig) {
validation.isValid = false;
validation.errors.push('No environment configuration loaded');
return validation;
}
const config = this.environmentConfig;
// Validate required services
if (!config.services.mcp) {
validation.errors.push('MCP service configuration is required');
validation.isValid = false;
}
if (!config.services.database) {
validation.errors.push('Database service configuration is required');
validation.isValid = false;
}
// Validate environment-specific requirements
switch (this.currentEnvironment) {
case 'production':
this.validateProductionEnvironment(config, validation);
break;
case 'staging':
this.validateStagingEnvironment(config, validation);
break;
case 'testing':
this.validateTestingEnvironment(config, validation);
break;
case 'development':
this.validateDevelopmentEnvironment(config, validation);
break;
}
return validation;
}
/**
* Validate production environment specific requirements
*/
private validateProductionEnvironment(config: EnvironmentConfig, validation: EnvironmentValidation): void {
if (!config.services.mcp.ssl) {
validation.errors.push('SSL must be enabled in production');
validation.isValid = false;
}
if (!config.security.encryptionEnabled) {
validation.errors.push('Encryption must be enabled in production');
validation.isValid = false;
}
if (!config.monitoring.enabled) {
validation.errors.push('Monitoring must be enabled in production');
validation.isValid = false;
}
if (config.settings.debug) {
validation.warnings.push('Debug mode should be disabled in production');
}
if (!config.backup?.enabled) {
validation.warnings.push('Backup should be enabled in production');
}
}
/**
* Validate staging environment specific requirements
*/
private validateStagingEnvironment(config: EnvironmentConfig, validation: EnvironmentValidation): void {
if (!config.testing.performance?.enabled) {
validation.warnings.push('Performance testing should be enabled in staging');
}
if (!config.security.vulnerabilityScanning) {
validation.warnings.push('Vulnerability scanning should be enabled in staging');
}
}
/**
* Validate testing environment specific requirements
*/
private validateTestingEnvironment(config: EnvironmentConfig, validation: EnvironmentValidation): void {
if (!config.testing.coverage?.threshold || config.testing.coverage.threshold < 80) {
validation.warnings.push('Test coverage threshold should be at least 80% in testing environment');
}
if (!config.services.database.resetOnStart) {
validation.recommendations.push('Consider enabling database reset on start for testing environment');
}
}
/**
* Validate development environment specific requirements
*/
private validateDevelopmentEnvironment(config: EnvironmentConfig, validation: EnvironmentValidation): void {
if (!config.settings.debug) {
validation.recommendations.push('Debug mode is typically enabled in development');
}
if (!config.settings.hotReload) {
validation.recommendations.push('Hot reload is typically enabled in development');
}
}
/**
* Apply environment configuration to system
*/
private async applyEnvironmentConfig(): Promise<void> {
if (!this.environmentConfig) {
throw new Error('No environment configuration to apply');
}
// Set environment variables
process.env.NODE_ENV = this.currentEnvironment;
process.env.VERSATIL_ENV = this.currentEnvironment;
// Apply service configurations
const services = this.environmentConfig.services;
// MCP Service
process.env.OPERA_MCP_PORT = services.mcp.port.toString();
process.env.OPERA_MCP_HOST = services.mcp.host;
// Database
if (services.database.url && !services.database.url.startsWith('${')) {
process.env.DATABASE_URL = services.database.url;
}
// Supabase
if (services.supabase.url && !services.supabase.url.startsWith('${')) {
process.env.SUPABASE_URL = services.supabase.url;
}
if (services.supabase.anonKey && !services.supabase.anonKey.startsWith('${')) {
process.env.SUPABASE_ANON_KEY = services.supabase.anonKey;
}
// Apply feature flags
const features = this.environmentConfig.features;
Object.entries(features).forEach(([key, value]) => {
process.env[`FEATURE_${key.toUpperCase()}`] = value.toString();
});
this.logger.info('Environment configuration applied', {
environment: this.currentEnvironment,
appliedConfigs: Object.keys(services)
}, 'environment-manager');
}
/**
* Get current environment health status
*/
async getEnvironmentHealth(): Promise<EnvironmentHealth> {
const health: EnvironmentHealth = {
status: 'healthy',
services: {},
agents: {},
overallHealth: 100
};
if (!this.environmentConfig) {
health.status = 'unhealthy';
health.overallHealth = 0;
return health;
}
// Check service health
const services = this.environmentConfig.services;
let totalServices = 0;
let healthyServices = 0;
for (const [serviceName, serviceConfig] of Object.entries(services)) {
totalServices++;
const serviceHealth = await this.checkServiceHealth(serviceName, serviceConfig);
health.services[serviceName] = serviceHealth;
if (serviceHealth.status === 'up') {
healthyServices++;
}
}
// Check agent health
const agents = this.environmentConfig.agents;
let totalAgents = 0;
let activeAgents = 0;
for (const [agentName, agentConfig] of Object.entries(agents)) {
if (agentConfig.enabled) {
totalAgents++;
const agentHealth = await this.checkAgentHealth(agentName, agentConfig);
health.agents[agentName] = agentHealth;
if (agentHealth.status === 'active') {
activeAgents++;
}
}
}
// Calculate overall health
const serviceHealthPercent = totalServices > 0 ? (healthyServices / totalServices) * 100 : 100;
const agentHealthPercent = totalAgents > 0 ? (activeAgents / totalAgents) * 100 : 100;
health.overallHealth = Math.round((serviceHealthPercent + agentHealthPercent) / 2);
// Determine overall status
if (health.overallHealth >= 80) {
health.status = 'healthy';
} else if (health.overallHealth >= 50) {
health.status = 'degraded';
} else {
health.status = 'unhealthy';
}
return health;
}
/**
* Check individual service health
*/
private async checkServiceHealth(serviceName: string, serviceConfig: any): Promise<any> {
const startTime = Date.now();
try {
// Implement service-specific health checks
switch (serviceName) {
case 'mcp':
return await this.checkMCPHealth(serviceConfig);
case 'database':
return await this.checkDatabaseHealth(serviceConfig);
case 'supabase':
return await this.checkSupabaseHealth(serviceConfig);
default:
return {
status: 'up',
responseTime: Date.now() - startTime,
lastCheck: Date.now()
};
}
} catch (error) {
return {
status: 'down',
responseTime: Date.now() - startTime,
lastCheck: Date.now(),
error: error.message
};
}
}
/**
* Check MCP service health
*/
private async checkMCPHealth(config: any): Promise<any> {
// Simplified health check - in production, this would make actual HTTP requests
return {
status: 'up',
responseTime: Math.random() * 100,
lastCheck: Date.now()
};
}
/**
* Check database health
*/
private async checkDatabaseHealth(config: any): Promise<any> {
// Simplified health check
return {
status: 'up',
responseTime: Math.random() * 50,
lastCheck: Date.now()
};
}
/**
* Check Supabase health
*/
private async checkSupabaseHealth(config: any): Promise<any> {
// Simplified health check
return {
status: config.local ? 'up' : 'up',
responseTime: Math.random() * 200,
lastCheck: Date.now()
};
}
/**
* Check individual agent health
*/
private async checkAgentHealth(agentName: string, agentConfig: any): Promise<any> {
// Simplified agent health check
return {
status: agentConfig.enabled ? 'active' : 'inactive',
lastActivity: Date.now() - Math.random() * 300000, // Random within last 5 minutes
performanceScore: Math.round(80 + Math.random() * 20) // Random score 80-100
};
}
/**
* Get current environment configuration
*/
getCurrentEnvironment(): EnvironmentType {
return this.currentEnvironment;
}
/**
* Get current environment configuration
*/
getCurrentConfig(): EnvironmentConfig | null {
return this.environmentConfig;
}
/**
* Check if environment type is valid
*/
private isValidEnvironment(env: string): env is EnvironmentType {
return ['development', 'testing', 'staging', 'production'].includes(env);
}
/**
* Get available environments
*/
async getAvailableEnvironments(): Promise<EnvironmentType[]> {
try {
const files = await fs.readdir(this.configPath);
return files
.filter(file => file.endsWith('.json'))
.map(file => file.replace('.json', ''))
.filter(env => this.isValidEnvironment(env)) as EnvironmentType[];
} catch (error) {
this.logger.error('Failed to list available environments', { error: error.message }, 'environment-manager');
return [];
}
}
/**
* Export environment configuration for external use
*/
exportConfiguration(): any {
return {
currentEnvironment: this.currentEnvironment,
config: this.environmentConfig,
health: this.getEnvironmentHealth()
};
}
}
// Export singleton instance
export const environmentManager = new EnvironmentManager();