depsweep
Version:
š± Automated intelligent dependency cleanup with environmental impact reporting
371 lines (370 loc) ⢠12.2 kB
JavaScript
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,
};