file-mover
Version:
Script to move files and update imports automatically
442 lines • 16.8 kB
JavaScript
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