@neurolint/cli
Version:
NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations
338 lines (286 loc) • 9.52 kB
JavaScript
const chalk = require('chalk');
const fs = require('fs-extra');
const path = require('path');
/**
* Advanced progress monitoring for NeuroLint operations
* Provides real-time updates, statistics, and detailed reporting
*/
class ProgressMonitor {
constructor(options = {}) {
this.options = {
verbose: false,
showETA: true,
showStats: true,
updateInterval: 100, // ms
maxRecentItems: 5,
...options
};
this.state = {
startTime: Date.now(),
totalItems: 0,
processedItems: 0,
successItems: 0,
errorItems: 0,
warningItems: 0,
skippedItems: 0,
currentPhase: null,
phases: [],
recentItems: [],
stats: {},
isActive: false
};
this.intervalId = null;
this.lastUpdate = 0;
}
start(totalItems, phases = []) {
this.state.totalItems = totalItems;
this.state.phases = phases;
this.state.isActive = true;
this.state.startTime = Date.now();
if (this.options.showStats) {
this.startRealTimeDisplay();
}
console.log(chalk.blue.bold('Operation Starting'));
console.log(chalk.gray(`Total items: ${totalItems}${phases.length > 0 ? ` | Phases: ${phases.length}` : ''}`));
return this;
}
startPhase(phaseName, itemCount = null) {
this.state.currentPhase = {
name: phaseName,
startTime: Date.now(),
itemCount: itemCount || this.state.totalItems,
processedInPhase: 0
};
console.log(`\n${chalk.cyan('Phase:')} ${chalk.white.bold(phaseName)}`);
if (itemCount) {
console.log(chalk.gray(`Processing ${itemCount} items...`));
}
}
updateItem(itemName, result = {}) {
this.state.processedItems++;
if (this.state.currentPhase) {
this.state.currentPhase.processedInPhase++;
}
// Update counters based on result
if (result.success === false || result.errors > 0) {
this.state.errorItems++;
} else if (result.warnings > 0) {
this.state.warningItems++;
} else if (result.skipped) {
this.state.skippedItems++;
} else {
this.state.successItems++;
}
// Update recent items
const status = this.getItemStatus(result);
this.state.recentItems.push({ name: itemName, status, timestamp: Date.now() });
if (this.state.recentItems.length > this.options.maxRecentItems) {
this.state.recentItems.shift();
}
// Update custom stats
if (result.stats) {
Object.keys(result.stats).forEach(key => {
this.state.stats[key] = (this.state.stats[key] || 0) + result.stats[key];
});
}
// Throttled display update
const now = Date.now();
if (now - this.lastUpdate > this.options.updateInterval) {
this.updateDisplay();
this.lastUpdate = now;
}
}
completePhase() {
if (this.state.currentPhase) {
const duration = Date.now() - this.state.currentPhase.startTime;
console.log(`${chalk.green('[COMPLETE]')} ${this.state.currentPhase.name} completed in ${this.formatTime(duration)}`);
this.state.currentPhase = null;
}
}
complete() {
this.state.isActive = false;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
const totalTime = Date.now() - this.state.startTime;
const rate = this.state.processedItems / (totalTime / 1000);
// Clear progress line
process.stdout.write('\r\x1b[K');
// Final summary
console.log(chalk.green.bold('\nOperation Complete!'));
console.log(chalk.gray(`Total time: ${this.formatTime(totalTime)}`));
console.log(chalk.gray(`Processing rate: ${rate.toFixed(2)} items/second`));
// Results breakdown
console.log('\nResults:');
console.log(` ${chalk.green('Success:')} ${this.state.successItems}`);
if (this.state.warningItems > 0) {
console.log(` ${chalk.yellow('Warnings:')} ${this.state.warningItems}`);
}
if (this.state.errorItems > 0) {
console.log(` ${chalk.red('Errors:')} ${this.state.errorItems}`);
}
if (this.state.skippedItems > 0) {
console.log(` ${chalk.gray('⏭️ Skipped:')} ${this.state.skippedItems}`);
}
// Custom stats
if (Object.keys(this.state.stats).length > 0) {
console.log('\nStatistics:');
Object.entries(this.state.stats).forEach(([key, value]) => {
console.log(` ${key}: ${chalk.cyan(value)}`);
});
}
return this.getResultSummary();
}
error(message, details = {}) {
this.state.errorItems++;
if (this.options.verbose) {
console.log(`\n${chalk.red('ERROR:')} ${message}`);
if (details.file) {
console.log(chalk.gray(` File: ${details.file}`));
}
if (details.line) {
console.log(chalk.gray(` Line: ${details.line}`));
}
}
}
warning(message, details = {}) {
this.state.warningItems++;
if (this.options.verbose) {
console.log(`\n${chalk.yellow('WARNING:')} ${message}`);
if (details.file) {
console.log(chalk.gray(` File: ${details.file}`));
}
}
}
info(message) {
if (this.options.verbose) {
console.log(`\n${chalk.blue('ℹ️ Info:')} ${message}`);
}
}
// Private methods
startRealTimeDisplay() {
if (this.intervalId) return;
this.intervalId = setInterval(() => {
if (this.state.isActive) {
this.updateDisplay();
}
}, 1000); // Update every second
}
updateDisplay() {
if (!this.state.isActive) return;
const progress = this.getProgressInfo();
const progressBar = this.createProgressBar(progress.percentage);
// Clear line and show progress
process.stdout.write('\r\x1b[K');
process.stdout.write(
`${progressBar} ${chalk.cyan(progress.percentage + '%')} ` +
`(${this.state.processedItems}/${this.state.totalItems}) ` +
`${this.options.showETA ? 'ETA: ' + progress.eta : ''}`
);
// Show recent items in verbose mode
if (this.options.verbose && this.state.recentItems.length > 0) {
process.stdout.write('\n');
this.state.recentItems.slice(-3).forEach(item => {
console.log(` ${item.status} ${chalk.gray(path.basename(item.name))}`);
});
process.stdout.write(`\x1b[${Math.min(3, this.state.recentItems.length) + 1}A`); // Move cursor up
}
}
getProgressInfo() {
const percentage = Math.round((this.state.processedItems / this.state.totalItems) * 100);
const elapsed = Date.now() - this.state.startTime;
const rate = this.state.processedItems / (elapsed / 1000);
const remaining = this.state.totalItems - this.state.processedItems;
const eta = remaining > 0 && rate > 0 ? this.formatTime((remaining / rate) * 1000) : 'calculating...';
return { percentage, eta, rate, elapsed };
}
createProgressBar(percentage) {
const width = Math.min(40, process.stdout.columns ? Math.max(20, process.stdout.columns - 50) : 40);
const filled = Math.round((percentage / 100) * width);
const empty = width - filled;
return chalk.blue('���'.repeat(filled)) + chalk.gray('░'.repeat(empty));
}
getItemStatus(result) {
if (result.success === false || result.errors > 0) {
return chalk.red('[ERROR]');
} else if (result.warnings > 0) {
return chalk.yellow('[WARN]');
} else if (result.skipped) {
return chalk.gray('⏭️');
} else if (result.fixes > 0) {
return chalk.green('[FIXED]');
} else {
return chalk.green('[OK]');
}
}
formatTime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
getResultSummary() {
return {
total: this.state.totalItems,
processed: this.state.processedItems,
success: this.state.successItems,
warnings: this.state.warningItems,
errors: this.state.errorItems,
skipped: this.state.skippedItems,
duration: Date.now() - this.state.startTime,
stats: { ...this.state.stats }
};
}
}
/**
* Create a simple progress monitor for basic operations
*/
function createSimpleProgress(total, message = 'Processing') {
const monitor = new ProgressMonitor({ verbose: false, showStats: false });
monitor.start(total);
console.log(chalk.gray(message + '...'));
return monitor;
}
/**
* Create a detailed progress monitor for complex operations
*/
function createDetailedProgress(total, options = {}) {
return new ProgressMonitor({
verbose: true,
showStats: true,
showETA: true,
...options
});
}
/**
* Progress monitor for file operations
*/
class FileProgressMonitor extends ProgressMonitor {
constructor(options = {}) {
super({
...options,
customStats: ['filesRead', 'bytesProcessed', 'linesAnalyzed']
});
}
updateFile(filePath, result = {}) {
const stats = {
filesRead: 1,
bytesProcessed: result.fileSize || 0,
linesAnalyzed: result.lineCount || 0
};
this.updateItem(filePath, { ...result, stats });
}
}
module.exports = {
ProgressMonitor,
FileProgressMonitor,
createSimpleProgress,
createDetailedProgress
};