UNPKG

file-mover

Version:

Script to move files and update imports automatically

442 lines 16.8 kB
import { promises as fs } from "fs"; import path from "path"; import { updateImportsInFile } from "../fileOps.js"; import { analyzeImports } from "../importUtils.js"; // No-op timer for when performance tracking is disabled class NoOpTimer { end() { return 0; } } class Performance { metrics; verbose; timerStack; runId; logDir; constructor(verbose = false) { this.verbose = verbose; this.metrics = this.getInitialMetrics(); this.timerStack = new Map(); this.runId = this.generateRunId(); this.logDir = path.join(process.cwd(), "performance"); this.ensureLogDirectory(); } generateRunId() { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}-${String(now.getMinutes()).padStart(2, "0")}-${String(now.getSeconds()).padStart(2, "0")}`; } async ensureLogDirectory() { try { await fs.mkdir(this.logDir, { recursive: true }); } catch { // Directory might already exist, ignore error } } getInitialMetrics() { return { totalTime: 0, fileDiscovery: { time: 0, fileCount: 0 }, validation: { time: 0, moveCount: 0 }, importAnalysis: { time: 0, totalFiles: 0, filesWithImports: 0, fileReadTime: 0, astParseTime: 0, importMatchingTime: 0, individualFileTimes: [], }, fileOperations: { time: 0, moves: 0, updates: 0, writeTime: 0, moveTime: 0, }, cachePerformance: { astCacheHits: 0, fileCacheHits: 0, totalCacheLookups: 0 }, individualMoves: [], }; } calculateSummary() { const totalFiles = this.metrics.importAnalysis.totalFiles; const totalMoves = this.metrics.individualMoves.length; const totalUpdates = this.metrics.fileOperations.updates; const avgAnalysisTime = totalMoves > 0 ? this.metrics.individualMoves.reduce((sum, move) => sum + move.analysisTime, 0) / totalMoves : 0; const avgMoveTime = totalMoves > 0 ? this.metrics.individualMoves.reduce((sum, move) => sum + move.moveTime, 0) / totalMoves : 0; const avgUpdateTime = totalMoves > 0 ? this.metrics.individualMoves.reduce((sum, move) => sum + move.updateTime, 0) / totalMoves : 0; const avgFileAnalysisTime = this.metrics.importAnalysis.individualFileTimes.length > 0 ? this.metrics.importAnalysis.individualFileTimes.reduce((sum, file) => sum + file.totalTime, 0) / this.metrics.importAnalysis.individualFileTimes.length : 0; const cacheHitRate = this.metrics.cachePerformance.totalCacheLookups > 0 ? (this.metrics.cachePerformance.astCacheHits + this.metrics.cachePerformance.fileCacheHits) / this.metrics.cachePerformance.totalCacheLookups : 0; return { totalFiles, totalMoves, totalUpdates, avgAnalysisTime, avgMoveTime, avgUpdateTime, avgFileAnalysisTime, cacheHitRate, }; } async savePerformanceLog() { if (!this.verbose) return; const logEntry = { timestamp: new Date().toISOString(), runId: this.runId, metrics: this.metrics, summary: this.calculateSummary(), }; const logFile = path.join(this.logDir, `performance_${this.runId}.json`); await fs.writeFile(logFile, JSON.stringify(logEntry, null, 2)); } async updatePerformanceHistory() { if (!this.verbose) return; const historyFile = path.join(this.logDir, "performance_history.json"); let history = []; try { const existingData = await fs.readFile(historyFile, "utf8"); history = JSON.parse(existingData); } catch { // File doesn't exist or is invalid, start with empty array } const logEntry = { timestamp: new Date().toISOString(), runId: this.runId, metrics: this.metrics, summary: this.calculateSummary(), }; // Add new entry and keep only the last 50 runs history.push(logEntry); if (history.length > 50) { history = history.slice(-50); } await fs.writeFile(historyFile, JSON.stringify(history, null, 2)); } generatePerformanceTable() { if (!this.verbose) return ""; const summary = this.calculateSummary(); const table = ` ╔══════════════════════════════════════════════════════════════════════════════╗ ║ PERFORMANCE SUMMARY ║ ╠══════════════════════════════════════════════════════════════════════════════╣ ║ Total Execution Time: ${this.metrics.totalTime.toFixed(2)}ms ║ ║ Files Discovered: ${this.metrics.fileDiscovery.fileCount} (${this.metrics.fileDiscovery.time.toFixed(2)}ms) ║ ║ Moves Validated: ${this.metrics.validation.moveCount} (${this.metrics.validation.time.toFixed(2)}ms) ║ ║ Files Analyzed: ${this.metrics.importAnalysis.totalFiles} (${this.metrics.importAnalysis.time.toFixed(2)}ms) ║ ║ Files with Imports: ${this.metrics.importAnalysis.filesWithImports} ║ ║ Total Moves: ${this.metrics.individualMoves.length} ║ ║ Total Updates: ${this.metrics.fileOperations.updates} ║ ╠══════════════════════════════════════════════════════════════════════════════╣ ║ AVERAGE TIMES (per move) ║ ║ Analysis: ${summary.avgAnalysisTime.toFixed(2)}ms | Move: ${summary.avgMoveTime.toFixed(2)}ms | Update: ${summary.avgUpdateTime.toFixed(2)}ms ║ ║ File Analysis: ${summary.avgFileAnalysisTime.toFixed(2)}ms | Cache Hit Rate: ${(summary.cacheHitRate * 100).toFixed(1)}% ║ ╚══════════════════════════════════════════════════════════════════════════════╝ `; return table; } startTimer(label) { if (!this.verbose) { return new NoOpTimer(); } const startTime = globalThis.performance.now(); this.timerStack.set(label, startTime); return { end: () => { const endTime = globalThis.performance.now(); const duration = endTime - startTime; this.timerStack.delete(label); return duration; }, }; } trackFileAnalysis(file, readTime, parseTime, matchTime, importCount) { if (!this.verbose) return; this.metrics.importAnalysis.individualFileTimes.push({ file, readTime, parseTime, matchTime, totalTime: readTime + parseTime + matchTime, importCount, }); } addFileOpTime(type, time) { if (!this.verbose) return; if (type === "move") { this.metrics.fileOperations.moveTime += time; } else { this.metrics.fileOperations.writeTime += time; } } addFileOpUpdates(count) { if (!this.verbose) return; this.metrics.fileOperations.updates += count; } addFileOpMoves(count) { if (!this.verbose) return; this.metrics.fileOperations.moves += count; } setFileOpTotalTime(time) { if (!this.verbose) return; this.metrics.fileOperations.time = time; } addValidationTime(time, moveCount) { if (!this.verbose) return; this.metrics.validation.time += time; this.metrics.validation.moveCount += moveCount; } addDiscoveryTime(time, fileCount) { if (!this.verbose) return; this.metrics.fileDiscovery.time += time; this.metrics.fileDiscovery.fileCount = fileCount; } setImportAnalysisTime(time, totalFiles, filesWithImports) { if (!this.verbose) return; this.metrics.importAnalysis.time = time; this.metrics.importAnalysis.totalFiles = totalFiles; this.metrics.importAnalysis.filesWithImports = filesWithImports; } setImportAnalysisBreakdown(read, parse, match) { if (!this.verbose) return; this.metrics.importAnalysis.fileReadTime = read; this.metrics.importAnalysis.astParseTime = parse; this.metrics.importAnalysis.importMatchingTime = match; } clearFileAnalysisTimes() { if (!this.verbose) return; this.metrics.importAnalysis.individualFileTimes = []; } addMoveMetrics(move) { if (!this.verbose) return; this.metrics.individualMoves.push(move); } setTotalTime(time) { if (!this.verbose) return; this.metrics.totalTime = time; } async printSummary() { if (!this.verbose) return; console.log(this.generatePerformanceTable()); // Save performance data await this.savePerformanceLog(); await this.updatePerformanceHistory(); } clearCaches() { if (!this.verbose) return; this.metrics.cachePerformance = { astCacheHits: 0, fileCacheHits: 0, totalCacheLookups: 0 }; } reset() { if (!this.verbose) return; this.metrics = this.getInitialMetrics(); } getMetrics() { return this.metrics; } trackCacheHit(type) { if (!this.verbose) return; if (type === "ast") { this.metrics.cachePerformance.astCacheHits++; } else { this.metrics.cachePerformance.fileCacheHits++; } } trackCacheLookup() { if (!this.verbose) return; this.metrics.cachePerformance.totalCacheLookups++; } } export function getPerformance(verbose = false) { return new Performance(verbose); } export class MoveTracker { perf; totalTimer = null; validationTimer = null; fileDiscoveryTimer = null; precomputeTimer = null; fileOpsTimer = null; analysisTimer = null; moveTimer = null; updateMovedFileTimer = null; updateTimer = null; constructor(verbose) { this.perf = getPerformance(verbose); } startTotalTimer() { this.totalTimer = this.perf.startTimer("Total execution"); } startValidationTimer() { this.validationTimer = this.perf.startTimer("Validation"); } endValidationTimer(moveCount) { if (this.validationTimer) { this.perf.addValidationTime(this.validationTimer.end(), moveCount); this.validationTimer = null; } } startFileDiscoveryTimer() { this.fileDiscoveryTimer = this.perf.startTimer("File discovery"); } endFileDiscoveryTimer(fileCount) { if (this.fileDiscoveryTimer) { this.perf.addDiscoveryTime(this.fileDiscoveryTimer.end(), fileCount); this.fileDiscoveryTimer = null; } } startPrecomputeTimer() { this.precomputeTimer = this.perf.startTimer("Pre-computing import paths"); } endPrecomputeTimer() { if (this.precomputeTimer) { this.precomputeTimer.end(); this.precomputeTimer = null; } } startFileOpsTimer() { this.fileOpsTimer = this.perf.startTimer("File operations"); } endFileOpsTimer() { if (this.fileOpsTimer) { this.perf.addFileOpTime("move", this.fileOpsTimer.end()); this.fileOpsTimer = null; } } startAnalysisTimer(moveIndex) { this.analysisTimer = this.perf.startTimer(`Import analysis for move ${moveIndex + 1}`); } endAnalysisTimer() { if (this.analysisTimer) { const time = this.analysisTimer.end(); this.analysisTimer = null; return time; } return 0; } clearFileAnalysisTimes() { this.perf.clearFileAnalysisTimes(); } getFileAnalysisTimes() { return this.perf.getMetrics().importAnalysis.individualFileTimes; } startMoveTimer(moveIndex) { this.moveTimer = this.perf.startTimer(`Physical file move ${moveIndex + 1}`); } endMoveTimer() { if (this.moveTimer) { const time = this.moveTimer.end(); this.moveTimer = null; return time; } return 0; } startUpdateMovedFileTimer(moveIndex) { this.updateMovedFileTimer = this.perf.startTimer(`Moved file import updates ${moveIndex + 1}`); } endUpdateMovedFileTimer() { if (this.updateMovedFileTimer) { const time = this.updateMovedFileTimer.end(); this.updateMovedFileTimer = null; return time; } return 0; } startUpdateTimer(moveIndex) { this.updateTimer = this.perf.startTimer(`Import updates for move ${moveIndex + 1}`); } endUpdateTimer() { if (this.updateTimer) { const time = this.updateTimer.end(); this.updateTimer = null; return time; } return 0; } addMoveMetrics(metrics) { this.perf.addMoveMetrics(metrics); } setTotalTime() { if (this.totalTimer) { this.perf.setTotalTime(this.totalTimer.end()); this.totalTimer = null; } } async printSummary() { await this.perf.printSummary(); } addFileOpUpdates(totalUpdates) { this.perf.addFileOpUpdates(totalUpdates); } setImportAnalysisTime(time, totalFiles, filesWithImports) { this.perf.setImportAnalysisTime(time, totalFiles, filesWithImports); } setImportAnalysisBreakdown(read, parse, match) { this.perf.setImportAnalysisBreakdown(read, parse, match); } startOverallAnalysisTimer() { return this.perf.startTimer("Overall import analysis"); } } /** * Batch update imports in multiple files to reduce I/O overhead */ export async function batchUpdateImports({ importAnalysis }) { const updatePromises = importAnalysis.map(async ({ file, imports }) => { const updated = await updateImportsInFile({ currentFilePath: file, imports, }); return updated ? 1 : 0; }); const results = await Promise.all(updatePromises); const totalUpdates = results.reduce((sum, count) => sum + count, 0); return totalUpdates; } /** * Analyze which files import the target file with performance tracking */ export async function analyzeImportsWithTracking(sourceFiles, targetImportPaths, prefTracker) { // Use the pure utility function for the core logic const results = await analyzeImports(sourceFiles, targetImportPaths); // Calculate files with imports const filesWithImports = results.length; // Aggregate individual file times from the performance tracker const individualFileTimes = prefTracker.getFileAnalysisTimes(); const totalFileReadTime = individualFileTimes.reduce((sum, file) => sum + file.readTime, 0); const totalAstParseTime = individualFileTimes.reduce((sum, file) => sum + file.parseTime, 0); const totalImportMatchingTime = individualFileTimes.reduce((sum, file) => sum + file.matchTime, 0); // Update performance metrics prefTracker.setImportAnalysisTime(0, sourceFiles.length, filesWithImports); // Time will be set by caller prefTracker.setImportAnalysisBreakdown(totalFileReadTime, totalAstParseTime, totalImportMatchingTime); return results; } //# sourceMappingURL=moveTracker.js.map