UNPKG

@invisiblecities/sidequest-cqo

Version:

Configuration-agnostic TypeScript and ESLint orchestrator with real-time watch mode, SQLite persistence, and intelligent terminal detection

405 lines 14.8 kB
/** * Configuration Manager for Code Quality Orchestrator * Handles database initialization, service configuration, and environment setup */ import path from "node:path"; import * as fs from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { initializeDatabase, closeDatabase } from "../database/connection.js"; // ES module equivalent of __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); import { getStorageService, resetStorageService } from "./storage-service.js"; // ============================================================================ // Default Configurations // ============================================================================ export const DEFAULT_CONFIG = { database: { path: process.env["CQO_DB_PATH"] || "./data/code-quality.db", enableWAL: true, enableMetrics: true, maxHistoryDays: 30, }, performance: { batchSize: 100, maxMemoryMB: 256, enableCaching: true, }, scheduling: { defaultFrequencyMs: 30_000, // 30 seconds adaptivePolling: true, maxConcurrentChecks: 3, }, watch: { enabled: true, intervalMs: 3000, // 3 seconds debounceMs: 500, autoCleanup: true, }, monitoring: { enablePerformanceMetrics: true, logLevel: "info", metricsRetentionDays: 7, }, }; // ============================================================================ // Configuration Manager Class // ============================================================================ export class ConfigManager { config; databaseInitialized = false; servicesInitialized = false; constructor(config = {}) { this.config = this.mergeConfigs(DEFAULT_CONFIG, config); } /** * Deep merge configuration objects */ mergeConfigs(defaultConfig, userConfig) { const merged = { ...defaultConfig }; for (const [key, value] of Object.entries(userConfig)) { if (value && typeof value === "object" && !Array.isArray(value)) { merged[key] = { ...defaultConfig[key], ...value, }; } else if (value !== undefined) { merged[key] = value; } } return merged; } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Update configuration (will require reinitialization) */ updateConfig(updates) { this.config = this.mergeConfigs(this.config, updates); // Mark as needing reinitialization this.databaseInitialized = false; this.servicesInitialized = false; } /** * Initialize database with current configuration */ async initializeDatabase() { if (this.databaseInitialized) { const connectionModule = await import("../database/connection.js"); return connectionModule.getDatabase(); } // Ensure data directory exists const databasePath = this.config.database.path; const databaseDirectory = path.dirname(databasePath); await fs.mkdir(databaseDirectory, { recursive: true }); // Create database configuration const databaseConfig = { path: databasePath, enableWAL: this.config.database.enableWAL ?? true, pragmas: { journal_mode: (this.config.database.enableWAL ?? true) ? "WAL" : "DELETE", synchronous: "NORMAL", cache_size: Math.floor((this.config.performance.maxMemoryMB * 1024 * 1024) / 1024), // Convert MB to KB foreign_keys: "ON", temp_store: "memory", mmap_size: this.config.performance.maxMemoryMB * 1024 * 1024, // Convert MB to bytes }, migrations: { enabled: false, // Use direct schema initialization path: path.join(__dirname, "../database/migrations"), }, }; console.log(`[ConfigManager] Initializing database at: ${databasePath}`); const database = await initializeDatabase(databaseConfig); this.databaseInitialized = true; console.log("[ConfigManager] Database initialized successfully"); return database; } /** * Initialize all services */ async initializeServices() { if (!this.databaseInitialized) { await this.initializeDatabase(); } if (this.servicesInitialized) { return { storageService: getStorageService(), }; } console.log("[ConfigManager] Initializing services..."); // Reset any existing service instances resetStorageService(); // Create storage service configuration const storageConfig = { database: { path: this.config.database.path, enableWAL: this.config.database.enableWAL ?? true, pragmas: {}, migrations: { enabled: false, path: "" }, }, batchSize: this.config.performance.batchSize ?? 1000, maxHistoryAge: this.config.database.maxHistoryDays ?? 30, enablePerformanceMetrics: this.config.monitoring.enablePerformanceMetrics ?? true, }; // Initialize storage service const storageService = getStorageService(storageConfig); this.servicesInitialized = true; console.log("[ConfigManager] Services initialized successfully"); return { storageService }; } /** * Perform health checks on all services */ async healthCheck() { const results = { database: false, storageService: false, overall: false, }; try { // Check database health const { DatabaseConnection } = await import("../database/connection.js"); results.database = await DatabaseConnection.healthCheck(); // Check storage service if (this.servicesInitialized) { const storageService = getStorageService(); const stats = await storageService.getStorageStats(); results.storageService = typeof stats.totalViolations === "number"; } results.overall = results.database && results.storageService; } catch (error) { console.error("[ConfigManager] Health check failed:", error); } return results; } /** * Get comprehensive system statistics */ async getSystemStats() { const stats = { config: this.config, database: undefined, storage: undefined, performance: { uptime: process.uptime(), memoryUsage: process.memoryUsage(), }, }; try { // Get database stats if (this.databaseInitialized) { const { DatabaseConnection } = await import("../database/connection.js"); stats.database = await DatabaseConnection.getStats(); } // Get storage stats if (this.servicesInitialized) { const storageService = getStorageService(); stats.storage = await storageService.getStorageStats(); } } catch (error) { console.error("[ConfigManager] Failed to get system stats:", error); } return stats; } /** * Perform maintenance tasks */ async performMaintenance() { const results = { dataCleanup: undefined, databaseOptimization: false, errors: [], }; try { // Clean up old data if (this.servicesInitialized) { const storageService = getStorageService(); results.dataCleanup = await storageService.cleanupOldData(); } // Optimize database if (this.databaseInitialized) { const { DatabaseConnection } = await import("../database/connection.js"); await DatabaseConnection.analyze(); results.databaseOptimization = true; } console.log("[ConfigManager] Maintenance completed successfully"); } catch (error) { const errorMessage = `Maintenance failed: ${error}`; results.errors.push(errorMessage); console.error("[ConfigManager]", errorMessage); } return results; } /** * Gracefully shutdown all services */ async shutdown() { console.log("[ConfigManager] Shutting down services..."); try { // Close database connection if (this.databaseInitialized) { await closeDatabase(); this.databaseInitialized = false; } // Reset service instances resetStorageService(); this.servicesInitialized = false; console.log("[ConfigManager] Shutdown completed successfully"); } catch (error) { console.error("[ConfigManager] Shutdown error:", error); throw error; } } /** * Load configuration from file */ static async loadFromFile(configPath) { try { const configData = await fs.readFile(configPath, "utf8"); const config = JSON.parse(configData); return new ConfigManager(config); } catch (error) { console.warn(`[ConfigManager] Could not load config from ${configPath}, using defaults:`, error); return new ConfigManager(); } } /** * Save configuration to file */ async saveToFile(configPath) { try { const configDirectory = path.dirname(configPath); await fs.mkdir(configDirectory, { recursive: true }); const configData = JSON.stringify(this.config, undefined, 2); await fs.writeFile(configPath, configData, "utf8"); console.log(`[ConfigManager] Configuration saved to: ${configPath}`); } catch (error) { console.error(`[ConfigManager] Failed to save config to ${configPath}:`, error); throw error; } } /** * Create configuration for different environments */ static createEnvironmentConfig(environment) { const baseConfig = { ...DEFAULT_CONFIG }; switch (environment) { case "development": { return new ConfigManager({ ...baseConfig, database: { ...baseConfig.database, path: "./data/dev-code-quality.db", }, monitoring: { ...baseConfig.monitoring, logLevel: "debug", }, watch: { ...baseConfig.watch, intervalMs: 1000, // Faster updates for development }, }); } case "test": { return new ConfigManager({ ...baseConfig, database: { ...baseConfig.database, path: ":memory:", // In-memory database for tests maxHistoryDays: 1, }, performance: { ...baseConfig.performance, batchSize: 10, // Smaller batches for testing }, monitoring: { ...baseConfig.monitoring, enablePerformanceMetrics: false, logLevel: "warn", }, }); } case "production": { return new ConfigManager({ ...baseConfig, database: { ...baseConfig.database, path: "./data/prod-code-quality.db", maxHistoryDays: 90, // Longer retention in production }, performance: { ...baseConfig.performance, batchSize: 200, // Larger batches for efficiency maxMemoryMB: 512, }, monitoring: { ...baseConfig.monitoring, logLevel: "info", metricsRetentionDays: 30, }, }); } default: { return new ConfigManager(baseConfig); } } } } // ============================================================================ // Configuration Utilities // ============================================================================ /** * Validate configuration object * @archaeology-exclude temporary "Will be replaced by Zod schema validation in next version" * @archaeology-recheck-after 0.3.0 * @since 0.2.0 */ export function validateConfig(config) { const errors = []; // Validate database path if (config.database?.path && config.database.path !== ":memory:") { const databasePath = config.database.path; if (!path.isAbsolute(databasePath) && !databasePath.startsWith("./")) { errors.push("Database path must be absolute or relative (starting with ./)"); } } // Validate performance settings if (config.performance?.batchSize && config.performance.batchSize < 1) { errors.push("Batch size must be at least 1"); } if (config.performance?.maxMemoryMB && config.performance.maxMemoryMB < 64) { errors.push("Max memory must be at least 64MB"); } // Validate scheduling settings if (config.scheduling?.defaultFrequencyMs && config.scheduling.defaultFrequencyMs < 1000) { errors.push("Default frequency must be at least 1000ms"); } // Validate watch settings if (config.watch?.intervalMs && config.watch.intervalMs < 100) { errors.push("Watch interval must be at least 100ms"); } return errors; } /** * Get configuration based on environment */ export function getEnvironmentConfig() { const environment = process.env["NODE_ENV"] || "development"; return ConfigManager.createEnvironmentConfig(environment); } //# sourceMappingURL=config-manager.js.map