UNPKG

@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
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 };