UNPKG

depsweep

Version:

🌱 Automated intelligent dependency cleanup with environmental impact reporting

371 lines (370 loc) • 12.2 kB
import * as fs from "node:fs/promises"; import * as path from "node:path"; import { LRUCache } from "lru-cache"; export class OptimizedCache { cache; hitCount = 0; missCount = 0; constructor(maxSize = 1000, ttl = 300000) { this.cache = new LRUCache({ max: maxSize, ttl: ttl, updateAgeOnGet: true, allowStale: false, }); } get(key) { const value = this.cache.get(key); if (value !== undefined) { this.hitCount++; return value; } this.missCount++; return undefined; } set(key, value) { this.cache.set(key, value); } has(key) { return this.cache.has(key); } clear() { this.cache.clear(); } getStats() { const total = this.hitCount + this.missCount; return { hitRate: total > 0 ? this.hitCount / total : 0, hitCount: this.hitCount, missCount: this.missCount, size: this.cache.size, }; } } export class OptimizedFileReader { static instance; fileCache = new OptimizedCache(500, 60000); readQueue = []; isProcessing = false; BATCH_SIZE = 50; MAX_CONCURRENT_READS = 10; static getInstance() { if (!OptimizedFileReader.instance) { OptimizedFileReader.instance = new OptimizedFileReader(); } return OptimizedFileReader.instance; } async readFile(filePath) { const cached = this.fileCache.get(filePath); if (cached !== undefined) { return cached; } return new Promise((resolve, reject) => { this.readQueue.push({ path: filePath, resolve, reject }); this.processQueue(); }); } async processQueue() { if (this.isProcessing || this.readQueue.length === 0) { return; } this.isProcessing = true; while (this.readQueue.length > 0) { const batch = this.readQueue.splice(0, this.BATCH_SIZE); const chunks = this.chunkArray(batch, this.MAX_CONCURRENT_READS); for (const chunk of chunks) { await Promise.allSettled(chunk.map(async ({ path: filePath, resolve, reject }) => { try { const content = await fs.readFile(filePath, "utf8"); this.fileCache.set(filePath, content); resolve(content); } catch (error) { reject(error); } })); } } this.isProcessing = false; } chunkArray(array, chunkSize) { const chunks = []; for (let i = 0; i < array.length; i += chunkSize) { chunks.push(array.slice(i, i + chunkSize)); } return chunks; } clearCache() { this.fileCache.clear(); } getCacheStats() { return this.fileCache.getStats(); } } export class OptimizedDependencyAnalyzer { static instance; analysisCache = new OptimizedCache(2000, 300000); dependencyGraphCache = new OptimizedCache(100, 600000); filePatternCache = new OptimizedCache(500, 300000); static getInstance() { if (!OptimizedDependencyAnalyzer.instance) { OptimizedDependencyAnalyzer.instance = new OptimizedDependencyAnalyzer(); } return OptimizedDependencyAnalyzer.instance; } getCompiledPatterns(dependency) { const cacheKey = `patterns:${dependency}`; const cached = this.filePatternCache.get(cacheKey); if (cached) { return cached; } const patterns = [ new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g"), new RegExp(`from\\s+['"]${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]`, "g"), new RegExp(`import\\s+.*\\s+from\\s+['"]${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]`, "g"), new RegExp(`require\\(['"]${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]\\)`, "g"), ]; this.filePatternCache.set(cacheKey, patterns); return patterns; } async isDependencyUsedInFile(dependency, filePath, context) { const cacheKey = `usage:${dependency}:${filePath}`; const cached = this.analysisCache.get(cacheKey); if (cached !== undefined) { return cached; } try { const fileReader = OptimizedFileReader.getInstance(); const content = await fileReader.readFile(filePath); if (content.length < 10) { this.analysisCache.set(cacheKey, false); return false; } if (!content.includes(dependency)) { this.analysisCache.set(cacheKey, false); return false; } const patterns = this.getCompiledPatterns(dependency); const isUsed = patterns.some((pattern) => pattern.test(content)); this.analysisCache.set(cacheKey, isUsed); return isUsed; } catch { this.analysisCache.set(cacheKey, false); return false; } } async processFilesInBatches(files, dependency, context, onProgress) { const results = []; const fileReader = OptimizedFileReader.getInstance(); const batchSize = Math.min(100, Math.max(10, Math.floor(files.length / 10))); for (let i = 0; i < files.length; i += batchSize) { const batch = files.slice(i, i + batchSize); const batchPromises = batch.map(async (file) => { const isUsed = await this.isDependencyUsedInFile(dependency, file, context); return isUsed ? file : null; }); const batchResults = await Promise.allSettled(batchPromises); for (const result of batchResults) { if (result.status === "fulfilled" && result.value) { results.push(result.value); } } onProgress?.(Math.min(i + batchSize, files.length), files.length); } return results; } clearCaches() { this.analysisCache.clear(); this.dependencyGraphCache.clear(); this.filePatternCache.clear(); } getCacheStats() { return { analysis: this.analysisCache.getStats(), dependencyGraph: this.dependencyGraphCache.getStats(), filePatterns: this.filePatternCache.getStats(), }; } } export class StringOptimizer { static STRING_POOL = new Map(); static MAX_POOL_SIZE = 1000; static intern(str) { if (str.length < 3) return str; if (StringOptimizer.STRING_POOL.has(str)) { return StringOptimizer.STRING_POOL.get(str); } if (StringOptimizer.STRING_POOL.size >= StringOptimizer.MAX_POOL_SIZE) { const entries = Array.from(StringOptimizer.STRING_POOL.entries()); const toRemove = entries.slice(0, Math.floor(StringOptimizer.MAX_POOL_SIZE / 4)); toRemove.forEach(([key]) => StringOptimizer.STRING_POOL.delete(key)); } StringOptimizer.STRING_POOL.set(str, str); return str; } static clearPool() { StringOptimizer.STRING_POOL.clear(); } static getPoolStats() { return { size: StringOptimizer.STRING_POOL.size, maxSize: StringOptimizer.MAX_POOL_SIZE, }; } } export class OptimizedFileSystem { static instance; dirCache = new OptimizedCache(100, 60000); statCache = new OptimizedCache(500, 30000); static getInstance() { if (!OptimizedFileSystem.instance) { OptimizedFileSystem.instance = new OptimizedFileSystem(); } return OptimizedFileSystem.instance; } async readDirectory(dirPath) { const cached = this.dirCache.get(dirPath); if (cached) { return cached; } try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); const files = entries .filter((entry) => entry.isFile()) .map((entry) => path.join(dirPath, entry.name)); this.dirCache.set(dirPath, files); return files; } catch { this.dirCache.set(dirPath, []); return []; } } async getFileStats(filePath) { const cached = this.statCache.get(filePath); if (cached) { return cached; } try { const stats = await fs.stat(filePath); this.statCache.set(filePath, stats); return stats; } catch { return null; } } clearCaches() { this.dirCache.clear(); this.statCache.clear(); } getCacheStats() { return { directories: this.dirCache.getStats(), stats: this.statCache.getStats(), }; } } export class PerformanceMonitor { static instance; metrics = new Map(); startTimes = new Map(); static getInstance() { if (!PerformanceMonitor.instance) { PerformanceMonitor.instance = new PerformanceMonitor(); } return PerformanceMonitor.instance; } startTimer(operation) { this.startTimes.set(operation, performance.now()); } endTimer(operation) { const startTime = this.startTimes.get(operation); if (!startTime) return 0; const duration = performance.now() - startTime; this.startTimes.delete(operation); const existing = this.metrics.get(operation); if (existing) { existing.count++; existing.totalTime += duration; existing.avgTime = existing.totalTime / existing.count; } else { this.metrics.set(operation, { count: 1, totalTime: duration, avgTime: duration, }); } return duration; } getMetrics() { return new Map(this.metrics); } reset() { this.metrics.clear(); this.startTimes.clear(); } logSummary() { console.log("\nšŸ“Š Performance Metrics:"); console.log("========================"); for (const [operation, stats] of this.metrics.entries()) { console.log(`${operation}:`); console.log(` Count: ${stats.count}`); console.log(` Total Time: ${stats.totalTime.toFixed(2)}ms`); console.log(` Average Time: ${stats.avgTime.toFixed(2)}ms`); console.log(""); } } } export class MemoryOptimizer { static instance; gcThreshold = 100 * 1024 * 1024; lastGcTime = 0; GC_INTERVAL = 30000; static getInstance() { if (!MemoryOptimizer.instance) { MemoryOptimizer.instance = new MemoryOptimizer(); } return MemoryOptimizer.instance; } checkMemoryUsage() { const usage = process.memoryUsage(); const used = usage.heapUsed; const total = usage.heapTotal; const now = Date.now(); const shouldGC = used > this.gcThreshold && now - this.lastGcTime > this.GC_INTERVAL; if (shouldGC) { this.lastGcTime = now; if (global.gc) { global.gc(); } } return { used, total, shouldGC }; } optimizeForLargeProjects() { this.gcThreshold = 200 * 1024 * 1024; } getMemoryStats() { const usage = process.memoryUsage(); return { heapUsed: usage.heapUsed, heapTotal: usage.heapTotal, external: usage.external, rss: usage.rss, arrayBuffers: usage.arrayBuffers, }; } } export const optimizations = { OptimizedCache, OptimizedFileReader, OptimizedDependencyAnalyzer, StringOptimizer, OptimizedFileSystem, PerformanceMonitor, MemoryOptimizer, };