UNPKG

gather-ts

Version:

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

396 lines 16.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CompileContext = void 0; const path_1 = __importDefault(require("path")); const events_1 = require("events"); const errors_1 = require("@/errors"); class CompileContext extends events_1.EventEmitter { constructor(deps, options = {}) { super(); this.deps = deps; this.isInitialized = false; this.metrics = { filesProcessed: 0, totalTokens: 0, processingTime: 0, memoryUsage: 0, dependencyAnalysisTime: 0, tokenizationTime: 0, outputGenerationTime: 0, errors: 0, warnings: 0, }; this.debug = options.debug || false; this.batchSize = options.batchSize || 100; this.includeMetrics = options.includeMetrics || false; this.startTime = Date.now(); this.options = { entryFiles: [], outputFile: "", rootDir: "", }; this.stats = { startTime: new Date().toISOString(), endTime: new Date().toISOString(), duration: 0, totalFiles: 0, processedFiles: 0, skippedFiles: 0, totalTokens: 0, }; this.dependencies = {}; } async initialize() { if (this.isInitialized) { this.logDebug("CompileContext already initialized"); return; } this.logDebug("Initializing CompileContext"); try { // Initialize all required services await Promise.all([ this.deps.ignoreHandler.initialize?.(), this.deps.tokenCounter.initialize?.(), this.deps.dependencyAnalyzer.initialize?.(), ]); this.startTime = Date.now(); this.isInitialized = true; this.logDebug("CompileContext initialization complete"); } catch (error) { this.handleError("initialization", error); } } cleanup() { this.logDebug("Cleaning up CompileContext"); // Cleanup all services this.deps.ignoreHandler.cleanup?.(); this.deps.tokenCounter.cleanup?.(); this.deps.dependencyAnalyzer.cleanup?.(); this.removeAllListeners(); this.isInitialized = false; } logDebug(message) { if (this.debug) { this.deps.logger.debug(`[CompileContext] ${message}`); } } handleError(phase, error) { const errorMessage = error instanceof Error ? error.message : String(error); this.metrics.errors++; this.emit("error", { error: error, phase }); throw new errors_1.ValidationError(`Error in ${phase} phase: ${errorMessage}`); } updateMetrics(update) { Object.assign(this.metrics, update); this.metrics.processingTime = Date.now() - this.startTime; this.metrics.memoryUsage = process.memoryUsage().heapUsed; } resetMetrics() { this.metrics = { filesProcessed: 0, totalTokens: 0, processingTime: 0, memoryUsage: 0, dependencyAnalysisTime: 0, tokenizationTime: 0, outputGenerationTime: 0, errors: 0, warnings: 0, }; this.startTime = Date.now(); } emitProgress(completed, total, message) { if (!this.currentPhase) return; this.emit("progress", { phase: this.currentPhase, completed, total, message, }); } async executePhase(phase, executor) { const startTime = Date.now(); this.currentPhase = phase; try { this.emit("phase:start", { phase, timestamp: startTime }); const result = await executor(); const endTime = Date.now(); const phaseResult = { phase, success: true, data: result, metrics: { startTime, endTime, duration: endTime - startTime, memoryUsage: process.memoryUsage().heapUsed, }, }; this.emit("phase:end", { phase, timestamp: endTime, result: phaseResult, }); return result; } catch (error) { this.handleError(phase, error); } finally { this.currentPhase = undefined; } } async compile(options) { if (!this.isInitialized) { throw new errors_1.ValidationError("CompileContext not initialized"); } // Update the options property with the provided options Object.assign(this.options, options); this.resetMetrics(); this.logDebug("Starting compilation"); try { const rootDir = options.rootDir || this.deps.configManager.getProjectRoot(); // Validate entry files const entryFiles = await this.executePhase("initialization", async () => { return await this.deps.dependencyAnalyzer.validateEntryFiles(options.entryFiles, rootDir); }); // Analyze dependencies const dependencyAnalysis = await this.executePhase("dependency-analysis", async () => { const startTime = Date.now(); const result = await this.deps.dependencyAnalyzer.analyzeDependencies(entryFiles, rootDir); this.updateMetrics({ dependencyAnalysisTime: Date.now() - startTime, }); return result; }); // Gather relevant files const relevantFiles = await this.executePhase("file-processing", async () => { const files = await this.deps.dependencyAnalyzer.gatherDependencies(dependencyAnalysis.dependencies, entryFiles, options.maxDepth); return files.map((file) => ({ absolute: file, relative: this.deps.fileSystem.getRelativePath(rootDir, file), path: this.deps.fileSystem.getRelativePath(rootDir, file), })); }); // Process required files const processedFiles = await this.processRequiredFiles(relevantFiles, rootDir); // Read file contents const filesWithContent = await this.loadFileContents(processedFiles); // Generate token summary const stats = await this.executePhase("tokenization", async () => { const startTime = Date.now(); const result = await this.deps.tokenCounter.generateSummary(filesWithContent); this.updateMetrics({ tokenizationTime: Date.now() - startTime, totalTokens: result.totalTokens, filesProcessed: filesWithContent.length, }); return result; }); // Generate output const output = await this.executePhase("output-generation", async () => { const startTime = Date.now(); const result = await this.generateOutput(filesWithContent, this.deps.configManager); this.updateMetrics({ outputGenerationTime: Date.now() - startTime, }); return result; }); // Ensure output directory exists await this.deps.fileSystem.createDirectory(path_1.default.dirname(options.outputFile), true); // Write output file await this.deps.fileSystem.writeFile(options.outputFile, output); // Make sure to update stats and dependencies during compilation this.stats.startTime = new Date().toISOString(); // ... update other stats as compilation progresses ... this.stats.endTime = new Date().toISOString(); this.stats.duration = Date.now() - this.startTime; const result = { filesProcessed: this.stats.processedFiles, outputPath: options.outputFile, totalTokens: this.stats.totalTokens, generationTime: this.stats.endTime, metadata: { createdAt: this.stats.startTime, updatedAt: this.stats.endTime, version: "1.0.0", }, }; this.logDebug("Compilation completed successfully"); if (this.debug) { this.logDebug(`Metrics: ${JSON.stringify(this.getMetrics(), null, 2)}`); } return result; } catch (error) { this.handleError("compile", error); } } async processRequiredFiles(relativeFiles, rootDir) { this.logDebug("Processing required files"); const config = this.deps.configManager.getConfig(); const processedFiles = [...relativeFiles]; if (config.requiredFiles?.length) { this.logDebug(`Found ${config.requiredFiles.length} required files in config`); for (const requiredFile of config.requiredFiles) { const absolutePath = this.deps.fileSystem.resolvePath(rootDir, requiredFile); if (!processedFiles.some((f) => f.absolute === absolutePath)) { if (this.deps.fileSystem.exists(absolutePath)) { processedFiles.push({ absolute: absolutePath, relative: this.deps.fileSystem.getRelativePath(rootDir, absolutePath), path: this.deps.fileSystem.getRelativePath(rootDir, absolutePath), }); this.logDebug(`Added required file: ${requiredFile}`); } else { this.metrics.warnings++; this.deps.logger.warn(`Required file not found: ${requiredFile}`); } } } } return processedFiles; } async loadFileContents(files, options = {}) { this.logDebug(`Loading contents for ${files.length} files`); const result = await Promise.all(files.map(async (file) => { try { const content = await this.deps.fileSystem.readFile(file.absolute, { encoding: options.encoding || "utf8", // Use encoding option }); // Validate content if requested if (options.validateContent && (!content || content.trim() === "")) { this.deps.logger.warn(`Empty file content for: ${file.path}`); return { ...file, content: " ", }; } // Check file size limit if (options.maxSize && Buffer.byteLength(content) > options.maxSize) { throw new errors_1.ValidationError(`File exceeds size limit: ${file.path}`); } return { ...file, content, }; } catch (error) { this.metrics.errors++; throw new errors_1.ValidationError(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`, { filePath: file.absolute }); } })); // Use stats for caching and metrics this.stats.processedFiles += result.length; this.logDebug(`Loaded contents for ${result.length} files`); return result; } getMetrics() { return { ...this.metrics, processingTime: Date.now() - this.startTime, memoryUsage: process.memoryUsage().heapUsed, }; } async generateOutput(filesWithContent, configManager) { const generationTime = new Date().toISOString(); const config = configManager.getConfig(); const output = []; try { // Add main header output.push(this.deps.templateManager.render("mainHeader", { tool: "gather-ts", date: generationTime, })); // Add file summary let customNotes = ""; if (config.customText?.beforeSummary) { customNotes = "\n## Important Notes\n" + config.customText.beforeSummary; } output.push(this.deps.templateManager.render("fileSummary", { tool: "gather-ts", customNotes, })); // Add repository structure const fileList = filesWithContent.map((file) => file.path).join("\n"); output.push(this.deps.templateManager.render("repositoryStructure", { fileList, })); // Add repository files header output.push(this.deps.templateManager.render("repositoryFiles", {})); // Add each file let processed = 0; const total = filesWithContent.length; for (const file of filesWithContent) { output.push(this.deps.templateManager.render("fileEntry", { filePath: file.path, fileContent: file.content, })); processed++; this.emit("progress", { phase: "output-generation", completed: processed, total, message: `Processing ${file.path}`, }); } // Add footer if configured if (config.customText?.footer) { output.push("\n" + config.customText.footer); } const result = output.join("\n"); // Validate output contains all expected sections this.validateOutput(result); return result; } catch (error) { this.deps.logger.error(`Failed to generate output: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Output generation failed: ${error instanceof Error ? error.message : String(error)}`); } } validateOutput(output) { const requiredSections = [ "File Summary", "Repository Structure", "Repository Files", ]; for (const section of requiredSections) { if (!output.includes(section)) { throw new Error(`Generated output missing required section: ${section}`); } } // Validate basic structure if (!output.includes("================")) { throw new Error("Generated output missing file separators"); } this.logDebug("Output validation complete"); } generateMetricsReport() { const metrics = this.getMetrics(); return [ "================================================================", "Performance Metrics", "================================================================", "", `Processing Time: ${metrics.processingTime}ms`, `Dependency Analysis: ${metrics.dependencyAnalysisTime}ms`, `Tokenization: ${metrics.tokenizationTime}ms`, `Output Generation: ${metrics.outputGenerationTime}ms`, `Memory Usage: ${(metrics.memoryUsage / 1024 / 1024).toFixed(2)}MB`, `Files Processed: ${metrics.filesProcessed}`, `Total Tokens: ${metrics.totalTokens}`, `Errors: ${metrics.errors}`, `Warnings: ${metrics.warnings}`, "", ].join("\n"); } } exports.CompileContext = CompileContext; //# sourceMappingURL=CompileContext.js.map