UNPKG

gather-ts

Version:

A powerful code analysis and packaging tool designed for creating AI-friendly code representations for javascript and typescript projects.

372 lines 13.3 kB
"use strict"; // src/config/ConfigManager.ts Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigManager = void 0; const events_1 = require("events"); const errors_1 = require("@/errors"); const services_1 = require("@/types/services"); // Create a class that extends EventEmitter and implements needed methods class EventEmitterBase extends events_1.EventEmitter { on(event, listener) { return super.on(event, listener); } emit(event, ...args) { return super.emit(event, ...args); } removeAllListeners(event) { return super.removeAllListeners(event); } } const defaultConfig = { maxDepth: 5, topFilesCount: 5, showTokenCount: true, tokenizer: { model: "gpt-4", showWarning: true, }, outputFormat: { includeSummaryInFile: true, includeGenerationTime: true, includeUsageGuidelines: true, }, debug: false, cacheTokenCounts: true, }; class ConfigManager extends services_1.BaseService { constructor(projectRoot, deps, options = {}) { super(); this.deps = deps; this.metrics = { loads: 0, updates: 0, validationErrors: 0, lastUpdate: undefined, }; this.eventEmitter = new EventEmitterBase(); if (!projectRoot || !this.deps.fileSystem.exists(projectRoot)) { throw new errors_1.ConfigurationError("Project root directory does not exist", projectRoot); } this.projectRoot = projectRoot; this.debug = options.debug || false; this.watch = options.watch || false; this.configPath = options.configPath || this.deps.fileSystem.joinPath(projectRoot, "gather-ts.config.json"); this.config = { ...defaultConfig }; this.initializeMetrics(); this.logDebug(`ConfigManager created with root: ${projectRoot}`); this.logDebug(`Using config path: ${this.configPath}`); } // Delegate EventEmitter methods on(event, listener) { this.eventEmitter.on(event, listener); return this; } emit(event, ...args) { return this.eventEmitter.emit(event, ...args); } removeAllListeners(event) { this.eventEmitter.removeAllListeners(event); return this; } async initialize() { this.logDebug("Initializing ConfigManager"); try { await super.initialize(); await this.loadConfig(); if (this.watch) { this.startWatching(); } this.logDebug("ConfigManager initialization complete"); } catch (error) { throw new errors_1.ConfigurationError(`Failed to initialize ConfigManager: ${error instanceof Error ? error.message : String(error)}`, this.configPath); } } cleanup() { this.logDebug("Cleaning up ConfigManager"); if (this.watchHandler) { clearInterval(this.watchHandler); this.watchHandler = undefined; } this.removeAllListeners(); super.cleanup(); } initializeMetrics() { this.metrics = { loads: 0, updates: 0, validationErrors: 0, lastUpdate: undefined, }; } logDebug(message) { if (this.debug) { this.deps.logger.debug(message); } } getConfig() { this.checkInitialized(); return { ...this.config }; } startWatching() { this.logDebug("Starting config file watch"); if (this.watchHandler) { clearInterval(this.watchHandler); } this.watchHandler = setInterval(async () => { try { const stats = this.deps.fileSystem.statSync(this.configPath); if (stats.mtime.getTime() !== this.metrics.lastUpdate) { this.logDebug("Config file change detected"); await this.loadConfig({ isWatch: true }); } } catch (error) { this.deps.logger.warn(`Error watching config file: ${error instanceof Error ? error.message : String(error)}`); } }, 1000); } async loadConfig(options = {}) { this.logDebug(`Loading configuration${options.isWatch ? " (watch)" : ""}`); let fileConfig = {}; try { if (this.deps.fileSystem.exists(this.configPath)) { const configContent = await this.deps.fileSystem.readFile(this.configPath); fileConfig = JSON.parse(configContent); this.logDebug("Loaded configuration from file"); } else { this.logDebug("No config file found, using defaults"); } const newConfig = { ...defaultConfig, ...fileConfig, }; const validation = this.validateConfig(newConfig); if (!validation.isValid) { throw new errors_1.ConfigurationError(`Configuration validation failed: ${validation.errors.join(", ")}`, this.configPath); } const oldConfig = this.config; this.config = newConfig; this.metrics.loads++; this.metrics.lastUpdate = Date.now(); this.emit("configChange", { type: "update", oldConfig, newConfig, timestamp: Date.now(), isWatch: options.isWatch, }); } catch (error) { this.metrics.validationErrors++; throw new errors_1.ConfigurationError(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`, this.configPath); } } async updateConfig(updates) { this.logDebug("Updating configuration"); try { const newConfig = { ...this.config, ...updates, }; const validation = this.validateConfig(newConfig); if (!validation.isValid) { throw new errors_1.ValidationError(`Invalid configuration updates: ${validation.errors.join(", ")}`); } const oldConfig = this.config; this.config = newConfig; this.metrics.updates++; this.metrics.lastUpdate = Date.now(); await this.saveConfig(); this.emit("configChange", { type: "update", oldConfig, newConfig, timestamp: Date.now(), }); return this.getConfig(); } catch (error) { throw new errors_1.ConfigurationError(`Failed to update configuration: ${error instanceof Error ? error.message : String(error)}`, this.configPath); } } async saveConfig() { this.logDebug("Saving configuration"); try { const configString = JSON.stringify(this.config, null, 2); await this.deps.fileSystem.writeFile(this.configPath, configString); this.logDebug("Configuration saved successfully"); } catch (error) { throw new errors_1.ConfigurationError(`Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`, this.configPath); } } reset() { this.logDebug("Resetting configuration to defaults"); const oldConfig = this.config; this.config = { ...defaultConfig }; this.metrics.updates++; this.metrics.lastUpdate = Date.now(); this.emit("configChange", { type: "reset", oldConfig, newConfig: this.config, timestamp: Date.now(), }); } getMetrics() { return { ...this.metrics }; } onConfigChange(callback) { this.on("configChange", callback); } getProjectRoot() { return this.projectRoot; } getTokenizerModel() { return this.config.tokenizer.model; } getMaxDepth() { return this.config.maxDepth; } shouldShowTokenCount() { return this.config.showTokenCount; } getTopFilesCount() { return this.config.topFilesCount; } isDebugEnabled() { return this.debug; } getModelTokenLimit() { const limits = { "gpt-3.5-turbo": 16384, "gpt-4": 8192, "gpt-4o": 128000, "gpt-4o-mini": 128000, o1: 128000, "o1-mini": 128000, "o3-mini": 200000, }; const limit = limits[this.config.tokenizer.model]; if (!limit) { throw new errors_1.ConfigurationError(`Unsupported model: ${this.config.tokenizer.model}`, this.configPath); } return limit; } getTokenWarning(totalTokens) { if (!this.config.tokenizer.showWarning) return null; const limit = this.getModelTokenLimit(); if (totalTokens > limit) { return `Total tokens (${totalTokens.toLocaleString()}) exceed ${this.config.tokenizer.model}'s context limit of ${limit.toLocaleString()}`; } return null; } validateConfig(config) { this.logDebug("Validating configuration"); const result = { isValid: true, errors: [], warnings: [], }; try { // Validate with deps.validator this.deps.validator.validate(config, "config", { requiredFields: ["tokenizer", "outputFormat"], }); // Validate tokenizer configuration if (config.tokenizer) { this.validateTokenizerConfig(config, result); } // Validate output format if (config.outputFormat) { this.validateOutputFormat(config, result); } // Validate max depth if (config.maxDepth !== undefined) { if (typeof config.maxDepth !== "number" || config.maxDepth < 0) { result.errors.push("maxDepth must be a positive number or undefined"); result.isValid = false; } else if (config.maxDepth > 10) { result.warnings.push("High maxDepth value may impact performance"); } } // Validate top files count if (config.topFilesCount !== undefined) { if (typeof config.topFilesCount !== "number" || config.topFilesCount < 1) { result.errors.push("topFilesCount must be a positive number"); result.isValid = false; } else if (config.topFilesCount > 20) { result.warnings.push("Large topFilesCount value may impact readability"); } } } catch (error) { result.errors.push(error instanceof Error ? error.message : String(error)); result.isValid = false; } // Update metrics if validation failed if (!result.isValid) { this.metrics.validationErrors++; } return result; } validateTokenizerConfig(config, result) { const validModels = [ "gpt-3.5-turbo", "gpt-4", "gpt-4o", "gpt-4o-mini", "o1", "o1-mini", "o3-mini", ]; if (!config.tokenizer) { result.errors.push("Missing tokenizer configuration"); result.isValid = false; return; } if (!validModels.includes(config.tokenizer.model)) { result.errors.push(`Invalid tokenizer model. Valid models are: ${validModels.join(", ")}`); result.isValid = false; } if (typeof config.tokenizer.showWarning !== "boolean") { result.errors.push("tokenizer.showWarning must be a boolean"); result.isValid = false; } } validateOutputFormat(config, result) { if (!config.outputFormat) { result.errors.push("Missing output format configuration"); result.isValid = false; return; } const booleanFields = [ "includeSummaryInFile", "includeGenerationTime", "includeUsageGuidelines", ]; booleanFields.forEach((field) => { if (config.outputFormat[field] !== undefined && typeof config.outputFormat[field] !== "boolean") { result.errors.push(`outputFormat.${field} must be a boolean`); result.isValid = false; } }); if (config.outputFormat.format !== undefined && !["json", "text", "markdown"].includes(config.outputFormat.format)) { result.errors.push("outputFormat.format must be one of: json, text, markdown"); result.isValid = false; } } } exports.ConfigManager = ConfigManager; Object.assign(ConfigManager.prototype, events_1.EventEmitter.prototype); //# sourceMappingURL=ConfigManager.js.map