UNPKG

@neurolint/cli

Version:

NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations

1,436 lines (1,216 loc) 52.6 kB
#!/usr/bin/env node const { program } = require('commander'); const chalk = require('chalk'); const ora = require('ora'); const fs = require('fs-extra'); const path = require('path'); const glob = require('fast-glob'); const { loadConfig, mergeConfig, validateConfig, generateExampleConfig } = require('./lib/config-loader'); const { formatConsoleOutput, formatJSONOutput, generateHTMLReport, createProgressTracker, createLayerProgressTracker, createStatsDisplay, formatFixResults } = require('./lib/output-formatter'); const { AuthManager } = require('./auth'); const { OrchestrationEngine } = require('./lib/orchestration-engine'); // Import the enhanced NeuroLint engine let NeuroLintProEnhanced; try { NeuroLintProEnhanced = require('./neurolint-pro-enhanced.js'); if (typeof NeuroLintProEnhanced === 'object' && NeuroLintProEnhanced.NeuroLintProEnhanced) { NeuroLintProEnhanced = NeuroLintProEnhanced.NeuroLintProEnhanced; } } catch (error) { console.error('Failed to load NeuroLint engine:', error.message); process.exit(1); } // Global auth manager instance const authManager = new AuthManager(); const version = '1.2.0'; program .name('neurolint') .description('NeuroLint CLI for React/Next.js modernization with advanced AST transformations') .version(version); // Global options program .option('--api-key <key>', 'NeuroLint Professional API key') .option('--config <path>', 'Config file path') .option('--verbose', 'Verbose output') .option('--dry-run', 'Preview changes without applying them') .option('--use-orchestration', 'Use advanced orchestration system (auto-detected)', true) .option('--use-cache', 'Enable performance caching', true) .option('--skip-unnecessary', 'Skip layers that won\'t make changes', true) .option('--parallel', 'Enable parallel processing for multiple files') .option('--batch', 'Enable batch processing for large file sets') .option('--workers <number>', 'Maximum number of worker threads (default: auto)', parseInt) .option('--batch-size <number>', 'Batch size for large file processing (default: 20)', parseInt); /** * Orchestrate command - Advanced processing with 6-layer architecture */ program .command('orchestrate') .description('Advanced processing with intelligent 6-layer orchestration system') .argument('[path]', 'File or directory path to process', process.cwd()) .option('--include <patterns>', 'File patterns to include (comma-separated)', '**/*.{ts,tsx,js,jsx}') .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)', '**/node_modules/**,**/dist/**,**/.next/**') .option('--layers <list>', 'Specific layers to run (1,2,3,4,5,6)', 'all') .option('--smart-selection', 'Use intelligent algorithm-based layer selection', true) .option('--performance-mode', 'Enable all performance optimizations', false) .option('--parallel', 'Enable parallel processing for multiple files') .option('--batch', 'Enable batch processing for large file sets (>50 files)') .option('--workers <number>', 'Maximum worker threads (default: auto-detect)', parseInt) .option('--batch-size <number>', 'Files per batch (default: 20)', parseInt) .option('--format <type>', 'Output format: console, json, html', 'console') .option('--output <file>', 'Save results to file') .action(async (targetPath, options) => { console.log(chalk.blue.bold('NeuroLint Advanced Orchestration')); console.log(chalk.gray('Intelligent 6-layer modernization with smart selection')); console.log(chalk.gray('Features: Parallel processing, memory-aware batching, intelligent caching\n')); // Initialize orchestration engine const engine = new OrchestrationEngine({ verbose: options.verbose || program.opts().verbose, dryRun: options.dryRun || program.opts().dryRun, useAdvancedOrchestration: options.useOrchestration !== false }); await engine.initialize(); // Display engine status if (options.verbose) { engine.displayStatus(); } const spinner = ora('Initializing orchestration...').start(); try { // Load and merge configuration const config = await loadConfig(options.config); const finalOptions = mergeConfig(config, { ...options, ...program.opts(), useCache: options.performanceMode || program.opts().useCache, skipUnnecessary: options.performanceMode || program.opts().skipUnnecessary, parallel: program.opts().parallel, batch: program.opts().batch, maxWorkers: program.opts().workers, batchSize: program.opts().batchSize }); // Parse layers const requestedLayers = parseLayersOption(finalOptions.layers); spinner.text = 'Running orchestration analysis...'; // Run orchestration const result = await engine.runFixes(targetPath, { ...finalOptions, layers: requestedLayers }); spinner.stop(); // Display results if (result.orchestrationUsed) { console.log(chalk.green('Advanced orchestration completed!')); if (result.performanceStats) { console.log(chalk.blue('\nPerformance Stats:')); console.log(` Cache hit rate: ${result.performanceStats.cacheStats.hitRate}`); console.log(` Average execution: ${result.performanceStats.averageExecutionTime}ms`); } } else { console.log(chalk.yellow('Legacy engine used (orchestration unavailable)')); } console.log(chalk.green(`\nProcessed ${result.summary.totalFiles} files`)); console.log(` Successful: ${result.summary.successfulFiles}`); if (result.fixesApplied && !finalOptions.dryRun) { console.log(chalk.cyan(' Changes applied to files')); } else { console.log(chalk.gray(' Dry run completed (no changes made)')); } // Format and save results if (finalOptions.format === 'json' || finalOptions.output) { const outputData = { orchestrationUsed: result.orchestrationUsed, summary: result.summary, results: result.results, performanceStats: result.performanceStats, timestamp: new Date().toISOString() }; if (finalOptions.output) { await fs.writeJson(finalOptions.output, outputData, { spaces: 2 }); console.log(chalk.blue(`Results saved to: ${finalOptions.output}`)); } else if (finalOptions.format === 'json') { console.log(JSON.stringify(outputData, null, 2)); } } // Record usage await recordUsage('orchestrate', { filesProcessed: result.summary.totalFiles, orchestrationUsed: result.orchestrationUsed, layers: requestedLayers }); } catch (error) { spinner.fail('Orchestration failed'); console.error(chalk.red(`Error: ${error.message}`)); if (options.verbose) { console.error(error.stack); } process.exit(1); } }); /** * Analyze command - Run analysis on files/directories */ program .command('analyze') .alias('scan') .description('Analyze React/Next.js files for modernization opportunities') .argument('[path]', 'File or directory path to analyze', process.cwd()) .option('--include <patterns>', 'File patterns to include (comma-separated)', '**/*.{ts,tsx,js,jsx}') .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)', '**/node_modules/**,**/dist/**,**/.next/**') .option('--format <type>', 'Output format: console, json, html', 'console') .option('--output <file>', 'Save analysis to file') .option('--layers <list>', 'Specific layers to analyze (1,2,3,4,5,6)', 'all') .action(async (targetPath, options) => { console.log(chalk.blue.bold('NeuroLint Analysis')); console.log(chalk.gray('Scanning for React/Next.js modernization opportunities\n')); // Load and merge configuration const config = await loadConfig(options.config); const finalOptions = mergeConfig(config, options); // Validate configuration const configValidation = validateConfig(finalOptions); if (configValidation.errors.length > 0) { console.error(chalk.red('Configuration errors:')); configValidation.errors.forEach(error => console.error(` - ${error}`)); process.exit(1); } if (configValidation.warnings.length > 0) { console.warn(chalk.yellow('Configuration warnings:')); configValidation.warnings.forEach(warning => console.warn(` - ${warning}`)); } // Analysis is free for all users (no authentication required) if (authManager.isAuthenticated()) { console.log(chalk.gray(`Authenticated as: ${authManager.getUserEmail()}`)); } else { console.log(chalk.gray('Running in free mode - analysis only')); } // Initialize orchestration engine for enhanced analysis const engine = new OrchestrationEngine({ verbose: finalOptions.verbose, dryRun: true, // Analysis is always dry-run useAdvancedOrchestration: finalOptions.useOrchestration !== false }); await engine.initialize(); const spinner = ora('Analyzing files...').start(); try { // Get files to analyze const files = await getFilesToProcess(targetPath, finalOptions); if (files.length === 0) { spinner.warn('No files found to analyze'); return; } // Use enhanced progress tracker spinner.stop(); console.log(`\n${chalk.blue('Analysis Starting')}`); console.log(chalk.gray(`Files: ${files.length} | Layers: ${finalOptions.layers} | Format: ${finalOptions.format}`)); const progress = createProgressTracker(files.length, { verbose: finalOptions.verbose, showETA: true, width: process.stdout.columns ? Math.min(50, Math.max(20, process.stdout.columns - 40)) : 40 }); // Real-time statistics (if verbose) const stats = createStatsDisplay({ realTime: finalOptions.verbose }); if (finalOptions.verbose) { stats.start(); } // Parse layers option const requestedLayers = parseLayersOption(finalOptions.layers); // Try enhanced orchestration analysis first let results = []; let orchestrationUsed = false; try { const orchestrationResult = await engine.runAnalysis(targetPath, { ...finalOptions, layers: requestedLayers }); if (orchestrationResult.orchestrationUsed) { results = orchestrationResult.results || []; orchestrationUsed = true; spinner.succeed('Enhanced orchestration analysis completed'); console.log(chalk.green(`Used advanced 6-layer orchestration system`)); if (orchestrationResult.performanceStats) { console.log(chalk.blue(`Cache hit rate: ${orchestrationResult.performanceStats.cacheStats.hitRate}`)); } } else { throw new Error('Orchestration not available, using legacy analysis'); } } catch (error) { // Fallback to legacy analysis if (finalOptions.verbose) { console.log(chalk.yellow('Using legacy analysis engine')); } const legacyResults = []; let processedFiles = 0; let totalIssues = 0; let totalErrors = 0; for (const filePath of files) { try { const content = await fs.readFile(filePath, 'utf-8'); const result = await NeuroLintProEnhanced( content, filePath, true, // Analysis mode (dry-run) requestedLayers, { userId: authManager.getUserId(), userTier: getUserTier(finalOptions), verbose: finalOptions.verbose } ); let fileResult = null; if (result.analysis) { fileResult = { filePath, ...result.analysis }; legacyResults.push(fileResult); const issues = result.analysis.detectedIssues?.length || 0; totalIssues += issues; } processedFiles++; progress.update(filePath, { warnings: fileResult?.detectedIssues?.filter(i => i.severity === 'warning').length || 0, errors: fileResult?.detectedIssues?.filter(i => i.severity === 'error').length || 0 }); // Update real-time stats if (finalOptions.verbose) { stats.update({ filesProcessed: processedFiles, issuesFound: totalIssues, errorsEncountered: totalErrors }); } } catch (error) { totalErrors++; progress.error(filePath, error.message); if (finalOptions.verbose) { console.warn(chalk.red(`Failed to analyze ${filePath}: ${error.message}`)); stats.update({ filesProcessed: processedFiles, errorsEncountered: totalErrors }); } } } results = legacyResults; } progress.complete(); if (finalOptions.verbose) { stats.stop(); } console.log(chalk.green(`Analysis complete: ${processedFiles} files processed`)); // Display results using enhanced formatter if (finalOptions.format === 'json') { const jsonOutput = formatJSONOutput(results, finalOptions); if (finalOptions.output) { await fs.writeFile(finalOptions.output, jsonOutput); console.log(chalk.blue(`Results saved to: ${finalOptions.output}`)); } else { console.log(jsonOutput); } } else if (finalOptions.format === 'html') { const outputPath = finalOptions.output || 'neurolint-report.html'; await generateHTMLReport(results, outputPath); console.log(chalk.blue(`HTML report saved to: ${outputPath}`)); } else { const consoleOutput = formatConsoleOutput(results, finalOptions); console.log(consoleOutput); if (finalOptions.output) { await fs.writeFile(finalOptions.output, consoleOutput.replace(/\u001b\[[0-9;]*m/g, '')); // Strip colors console.log(chalk.blue(`Results saved to: ${finalOptions.output}`)); } } // Record usage await recordUsage('analyze', { filesProcessed: processedFiles }); } catch (error) { spinner.fail('Analysis failed'); console.error(chalk.red(`Error: ${error.message}`)); if (options.verbose) { console.error(error.stack); } process.exit(1); } }); /** * Layer-specific commands */ const layers = [ { id: 1, name: 'config', description: 'Configuration modernization (tsconfig, next.config)' }, { id: 2, name: 'content', description: 'Content standardization (HTML entities, emojis)' }, { id: 3, name: 'components', description: 'Component intelligence (missing keys, PropTypes)' }, { id: 4, name: 'hydration', description: 'SSR/Hydration mastery (client/server safety)' }, { id: 5, name: 'approuter', description: 'Next.js App Router optimization' }, { id: 6, name: 'quality', description: 'Testing & validation framework' } ]; layers.forEach(layer => { const layerCommand = program .command(layer.name) .description(layer.description); // Scan subcommand layerCommand .command('scan') .description(`Scan for Layer ${layer.id} issues`) .argument('[path]', 'File or directory path', process.cwd()) .option('--include <patterns>', 'File patterns to include', '**/*.{ts,tsx,js,jsx}') .option('--exclude <patterns>', 'File patterns to exclude', '**/node_modules/**,**/dist/**,**/.next/**') .action(async (targetPath, options) => { await runLayerCommand('scan', layer.id, targetPath, options); }); // Fix subcommand layerCommand .command('fix') .description(`Apply Layer ${layer.id} fixes (Premium feature)`) .argument('[path]', 'File or directory path', process.cwd()) .option('--include <patterns>', 'File patterns to include', '**/*.{ts,tsx,js,jsx}') .option('--exclude <patterns>', 'File patterns to exclude', '**/node_modules/**,**/dist/**,**/.next/**') .option('--backup', 'Create backup before applying fixes', true) .action(async (targetPath, options) => { await runLayerCommand('fix', layer.id, targetPath, options); }); }); /** * Unified fix command */ program .command('fix') .description('Apply modernization fixes (Premium feature)') .argument('[path]', 'File or directory path to fix', process.cwd()) .option('--include <patterns>', 'File patterns to include', '**/*.{ts,tsx,js,jsx}') .option('--exclude <patterns>', 'File patterns to exclude', '**/node_modules/**,**/dist/**,**/.next/**') .option('--layers <list>', 'Specific layers to apply (1,2,3,4,5,6)', 'all') .option('--backup', 'Create backup before applying fixes', true) .action(async (targetPath, options) => { // Check authentication and usage limits const canProceed = await checkUsageLimits('fix', options); if (!canProceed) { if (!authManager.isAuthenticated()) { console.log(chalk.blue('\nFree tier benefits:')); console.log(chalk.gray(' Unlimited code analysis')); console.log(chalk.gray(' Full modernization reports')); console.log(chalk.gray(' Issue detection across all layers')); console.log(chalk.cyan('\nSign up for free at: https://neurolint.dev')); } return; } console.log(chalk.green.bold('NeuroLint Fixes')); console.log(chalk.gray('Applying React/Next.js modernization fixes\n')); // Initialize orchestration engine const engine = new OrchestrationEngine({ verbose: options.verbose || program.opts().verbose, dryRun: options.dryRun || program.opts().dryRun, useAdvancedOrchestration: program.opts().useOrchestration !== false }); await engine.initialize(); const spinner = ora('Preparing fixes...').start(); try { const files = await getFilesToProcess(targetPath, options); if (files.length === 0) { spinner.warn('No files found to fix'); return; } const requestedLayers = parseLayersOption(options.layers); const userTier = getUserTier(options); // Validate layer access const allowedLayers = getLayersForTier(userTier); const validLayers = requestedLayers.filter(layer => allowedLayers.includes(layer)); if (validLayers.length < requestedLayers.length) { const deniedLayers = requestedLayers.filter(layer => !allowedLayers.includes(layer)); console.log(chalk.yellow(`Tier restriction: Layers ${deniedLayers.join(', ')} require higher tier`)); } if (validLayers.length === 0) { spinner.fail('No accessible layers for your tier'); showUpgradePrompt(); return; } spinner.stop(); console.log(`\n${chalk.green.bold('Fix Operation Starting')}`); console.log(chalk.gray(`Files: ${files.length} | Layers: ${validLayers.join(', ')} | Tier: ${userTier}`)); // Create backups if requested let backupInfo = null; if (options.backup) { console.log(chalk.blue('\nCreating backup...')); backupInfo = await createBackup(files); console.log(chalk.green(`Backup created: ${backupInfo.backupId}`)); console.log(chalk.gray(` Location: ${backupInfo.backupDir}`)); console.log(chalk.gray(` Files: ${backupInfo.metadata.files.length} (${(backupInfo.metadata.totalSize / 1024).toFixed(1)}KB)`)); } // Enhanced progress tracking for fixes const progress = createProgressTracker(files.length, { verbose: options.verbose, showETA: true, width: process.stdout.columns ? Math.min(50, Math.max(20, process.stdout.columns - 40)) : 40 }); // Real-time statistics const stats = createStatsDisplay({ realTime: options.verbose }); if (options.verbose) { stats.start(); } const results = []; let processedFiles = 0; let totalFixes = 0; let totalErrors = 0; console.log('\nApplying fixes...'); for (const filePath of files) { try { const content = await fs.readFile(filePath, 'utf-8'); const result = await NeuroLintProEnhanced( content, filePath, false, // Apply fixes validLayers, { userId: authManager.getUserId(), userTier, verbose: options.verbose } ); let fileResult = null; if (result.transformedCode && result.transformedCode !== content) { await fs.writeFile(filePath, result.transformedCode); const fixCount = result.analysis?.appliedTransformations || result.layers?.reduce((sum, layer) => sum + (layer.changeCount || 0), 0) || 0; totalFixes += fixCount; fileResult = { filePath, fixesApplied: fixCount, layers: result.successfulLayers || [] }; results.push(fileResult); } processedFiles++; progress.update(filePath, { fixes: fileResult?.fixesApplied || 0, warnings: 0, errors: 0 }); // Update real-time stats if (options.verbose) { stats.update({ filesProcessed: processedFiles, fixesApplied: totalFixes, errorsEncountered: totalErrors }); } } catch (error) { totalErrors++; progress.error(filePath, error.message); if (options.verbose) { console.warn(chalk.red(`Failed to fix ${filePath}: ${error.message}`)); stats.update({ filesProcessed: processedFiles, errorsEncountered: totalErrors }); } } } progress.complete(); if (options.verbose) { stats.stop(); } // Display summary displayFixResults(results, validLayers); // Record usage await recordUsage('fix', { filesProcessed: processedFiles, fixesApplied: totalFixes, layers: validLayers }); } catch (error) { spinner.fail('Fix operation failed'); console.error(chalk.red(`Error: ${error.message}`)); if (options.verbose) { console.error(error.stack); } process.exit(1); } }); /** * Init-config command - Generate configuration file */ program .command('init-config') .alias('init') .description('Generate NeuroLint configuration file') .option('--init', 'Create .neurolintrc.json file') .option('--show', 'Show current configuration') .option('--validate', 'Validate existing configuration') .option('--format <type>', 'Config format: json, js', 'json') .option('--preset <name>', 'Use configuration preset: basic, recommended, strict') .action(async (options) => { if (options.init) { const configFileName = options.format === 'js' ? '.neurolintrc.js' : '.neurolintrc.json'; const configPath = path.join(process.cwd(), configFileName); if (await fs.pathExists(configPath)) { const choice = await inquireOverwrite(configPath); if (!choice) { console.log(chalk.yellow('Configuration file creation cancelled')); return; } } let exampleConfig = generateExampleConfig(); // Apply preset if specified if (options.preset) { exampleConfig = applyPreset(exampleConfig, options.preset); } if (options.format === 'js') { const jsContent = generateJSConfig(exampleConfig); await fs.writeFile(configPath, jsContent); } else { await fs.writeJson(configPath, exampleConfig, { spaces: 2 }); } console.log(chalk.green(`Created configuration file: ${configPath}`)); console.log(chalk.gray('Edit the file to customize your NeuroLint settings')); console.log(chalk.blue('\nNext steps:')); console.log(' 1. Review and adjust the configuration'); console.log(' 2. Run "neurolint analyze" to test your setup'); console.log(' 3. Use "neurolint init-config --validate" to check config validity'); } else if (options.show) { const config = await loadConfig(); console.log(chalk.blue('Current NeuroLint configuration:')); console.log(JSON.stringify(config, null, 2)); // Show configuration sources console.log(chalk.gray('\nConfiguration sources checked:')); console.log(' 1. Custom --config path'); console.log(' 2. .neurolintrc.json'); console.log(' 3. .neurolintrc.js'); console.log(' 4. package.json (neurolint field)'); console.log(' 5. Parent directories'); } else if (options.validate) { const config = await loadConfig(); const validation = validateConfig(config); if (validation.errors.length === 0 && validation.warnings.length === 0) { console.log(chalk.green('Configuration is valid')); } else { if (validation.errors.length > 0) { console.error(chalk.red('Configuration errors:')); validation.errors.forEach(error => console.error(` ERROR: ${error}`)); } if (validation.warnings.length > 0) { console.warn(chalk.yellow('Configuration warnings:')); validation.warnings.forEach(warning => console.warn(` WARNING: ${warning}`)); } } } else { console.log(chalk.blue.bold('NeuroLint Configuration Manager')); console.log('\nOptions:'); console.log(' --init Create configuration file'); console.log(' --show Show current configuration'); console.log(' --validate Validate existing configuration'); console.log(' --format Config format: json (default), js'); console.log(' --preset Use preset: basic, recommended, strict'); console.log('\nExamples:'); console.log(' neurolint init-config --init'); console.log(' neurolint init-config --init --format js --preset recommended'); console.log(' neurolint init-config --validate'); } }); /** * Backup management commands */ program .command('backup') .description('Backup management commands') .action(() => { console.log(chalk.blue.bold('NeuroLint Backup Management')); console.log('\nAvailable commands:'); console.log(' neurolint backup list List available backups'); console.log(' neurolint backup restore Restore from backup'); console.log(' neurolint backup clean Clean old backups'); console.log('\nBackups are created automatically when using --backup flag with fix commands.'); }); program .command('backup:list') .alias('backup-list') .description('List available backups') .action(async () => { try { const backupDirs = glob.sync('.neurolint-backup-*', { cwd: process.cwd() }); if (backupDirs.length === 0) { console.log(chalk.yellow('No backups found in current directory')); return; } console.log(chalk.blue.bold(`Found ${backupDirs.length} backup(s):\n`)); for (const dir of backupDirs.sort().reverse()) { const metadataPath = path.join(process.cwd(), dir, 'backup-metadata.json'); if (await fs.pathExists(metadataPath)) { const metadata = await fs.readJson(metadataPath); const age = Math.round((Date.now() - new Date(metadata.timestamp).getTime()) / (1000 * 60 * 60)); console.log(`${chalk.cyan(metadata.id)}`); console.log(` Directory: ${chalk.gray(dir)}`); console.log(` Created: ${chalk.gray(metadata.timestamp)} (${age}h ago)`); console.log(` Files: ${chalk.gray(metadata.files.length)} (${(metadata.totalSize / 1024).toFixed(1)}KB)`); } else { console.log(`${chalk.yellow(dir)} ${chalk.gray('(no metadata)')}`); } console.log(''); } } catch (error) { console.error(chalk.red('Failed to list backups:'), error.message); } }); program .command('backup:restore') .alias('backup-restore') .description('Restore files from backup') .argument('[backup-id]', 'Backup ID or directory name') .option('--dry-run', 'Preview restore without applying changes') .action(async (backupId, options) => { try { let backupDir; if (backupId) { // Find backup by ID or directory name const backupDirs = glob.sync('.neurolint-backup-*', { cwd: process.cwd() }); backupDir = backupDirs.find(dir => { return dir.includes(backupId) || dir === backupId; }); if (!backupDir) { console.error(chalk.red(`Backup not found: ${backupId}`)); console.log(chalk.gray('Use "neurolint backup list" to see available backups')); return; } } else { // Use most recent backup const backupDirs = glob.sync('.neurolint-backup-*', { cwd: process.cwd() }); if (backupDirs.length === 0) { console.error(chalk.red('No backups found')); return; } backupDir = backupDirs.sort().reverse()[0]; } const metadataPath = path.join(process.cwd(), backupDir, 'backup-metadata.json'); if (!await fs.pathExists(metadataPath)) { console.error(chalk.red('Backup metadata not found')); return; } const metadata = await fs.readJson(metadataPath); console.log(chalk.blue.bold('Restoring from backup:')); console.log(` ID: ${metadata.id}`); console.log(` Created: ${metadata.timestamp}`); console.log(` Files: ${metadata.files.length}`); if (options.dryRun) { console.log(chalk.yellow('\nDRY RUN - Files that would be restored:')); metadata.files.forEach(file => { console.log(` ${file.relativePath}`); }); return; } const spinner = ora('Restoring files...').start(); for (const file of metadata.files) { const backupFilePath = path.join(process.cwd(), backupDir, file.relativePath); const targetPath = file.originalPath; await fs.ensureDir(path.dirname(targetPath)); await fs.copy(backupFilePath, targetPath); } spinner.succeed(`Restored ${metadata.files.length} files from backup`); } catch (error) { console.error(chalk.red('Restore failed:'), error.message); } }); program .command('backup:clean') .alias('backup-clean') .description('Clean old backup directories') .option('--days <days>', 'Remove backups older than N days', '7') .option('--dry-run', 'Preview cleanup without deleting') .action(async (options) => { try { const days = parseInt(options.days); const cutoffTime = Date.now() - (days * 24 * 60 * 60 * 1000); const backupDirs = glob.sync('.neurolint-backup-*', { cwd: process.cwd() }); const toDelete = []; for (const dir of backupDirs) { const metadataPath = path.join(process.cwd(), dir, 'backup-metadata.json'); if (await fs.pathExists(metadataPath)) { const metadata = await fs.readJson(metadataPath); const backupTime = new Date(metadata.timestamp).getTime(); if (backupTime < cutoffTime) { toDelete.push({ dir, metadata }); } } else { // Delete backups without metadata if they're old enough const stats = await fs.stat(path.join(process.cwd(), dir)); if (stats.mtime.getTime() < cutoffTime) { toDelete.push({ dir, metadata: null }); } } } if (toDelete.length === 0) { console.log(chalk.green(`No backups older than ${days} days found`)); return; } console.log(chalk.yellow(`Found ${toDelete.length} backup(s) older than ${days} days:`)); toDelete.forEach(({ dir, metadata }) => { const age = metadata ? Math.round((Date.now() - new Date(metadata.timestamp).getTime()) / (1000 * 60 * 60 * 24)) : 'unknown'; console.log(` ${dir} (${age} days old)`); }); if (options.dryRun) { console.log(chalk.gray('\nDRY RUN - Use without --dry-run to actually delete')); return; } const spinner = ora('Cleaning old backups...').start(); for (const { dir } of toDelete) { await fs.remove(path.join(process.cwd(), dir)); } spinner.succeed(`Cleaned ${toDelete.length} old backup(s)`); } catch (error) { console.error(chalk.red('Backup cleanup failed:'), error.message); } }); /** * Layers command - Show layer information */ program .command('layers') .description('Show NeuroLint modernization layers and workflow') .action(() => { console.log(chalk.cyan.bold('NeuroLint Modernization Layers\n')); layers.forEach(layer => { console.log(`Layer ${layer.id}: ${chalk.white.bold(layer.name)}`); console.log(` ${chalk.gray(layer.description)}`); console.log(` Commands: ${chalk.blue(`neurolint ${layer.name} scan|fix`)}`); console.log(''); }); console.log(chalk.yellow('Recommended workflow:')); console.log(' 1. Run analysis: neurolint analyze'); console.log(' 2. Apply fixes: neurolint fix'); console.log(' 3. Or layer by layer: neurolint config fix, neurolint components fix, etc.\n'); console.log(chalk.cyan('Tier Access:')); console.log(' - Free: Unlimited scans + Layers 1-2 fixes'); console.log(' - Basic ($9/month): Unlimited Layer 1-4 fixes'); console.log(' - Professional ($29/month): All layers + AST transformations'); console.log(' - Business ($79/month): API access + CI/CD integration'); console.log(' - Enterprise ($149/month): Custom rules + priority support'); console.log(' - Premium ($299/month): White-glove support + SLA\n'); }); /** * Authentication commands */ program .command('login') .description('Login to NeuroLint service') .option('--email <email>', 'Email address') .action(async (options) => { let email = options.email; // Create readline interface for secure input const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); try { // Prompt for email if not provided if (!email) { email = await new Promise((resolve) => { rl.question('Email: ', (answer) => { resolve(answer); }); }); } // Prompt for password securely (no echo) const password = await new Promise((resolve) => { process.stdout.write('Password: '); process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding('utf8'); let password = ''; process.stdin.on('data', (key) => { if (key === '\u0003') { // Ctrl+C process.exit(); } else if (key === '\r' || key === '\n') { // Enter process.stdin.setRawMode(false); process.stdin.pause(); console.log(); // New line resolve(password); } else if (key === '\u007f') { // Backspace if (password.length > 0) { password = password.slice(0, -1); process.stdout.write('\b \b'); } } else { password += key; process.stdout.write('*'); } }); }); if (!email || !password) { console.error(chalk.red('Email and password are required')); return; } const result = await authManager.login(email, password); if (result.success) { spinner.succeed('Login successful!'); // Get user profile info const profile = await getUserProfile(); console.log(chalk.green(`\nWelcome back, ${result.user.email}!`)); if (profile) { console.log(chalk.cyan(`Plan: ${profile.tier.charAt(0).toUpperCase() + profile.tier.slice(1)}`)); if (profile.tier === 'free') { console.log(chalk.yellow(`Remaining fixes: ${profile.usage.remainingFixes || 0}`)); console.log(chalk.gray('Upgrade at https://neurolint.dev/pricing for more features')); } else { console.log(chalk.green('Unlimited fixes and premium features available')); } } console.log(chalk.blue('\nYou can now use:')); console.log(' neurolint analyze <path> # Analyze code'); console.log(' neurolint fix <path> # Apply fixes'); } else { spinner.fail('Login failed'); console.error(chalk.red(`Error: ${result.error}`)); if (result.error.includes('Invalid login credentials')) { console.log(chalk.yellow('\nDon\'t have an account? Sign up at https://neurolint.dev')); } process.exit(1); } } catch (error) { console.error(chalk.red(`Login error: ${error.message}`)); process.exit(1); } }); program .command('logout') .description('Logout from NeuroLint') .action(async () => { console.log(chalk.blue.bold('NeuroLint Logout')); try { const result = await authManager.logout(); if (result.success) { console.log(chalk.green('Logged out successfully')); } else { console.error(chalk.red(`Logout error: ${result.error}`)); } } catch (error) { console.error(chalk.red(`Logout error: ${error.message}`)); } }); program .command('status') .description('Show authentication, usage, and orchestration status') .action(async () => { console.log(chalk.blue.bold('NeuroLint Status')); console.log('='.repeat(40)); // Authentication status console.log(chalk.cyan('\nAuthentication:')); if (authManager.isAuthenticated()) { console.log(chalk.green(' Authenticated')); console.log(chalk.gray(` User: ${authManager.getUserEmail()}`)); const profile = await getUserProfile(); if (profile) { console.log(chalk.cyan(` Plan: ${profile.tier.charAt(0).toUpperCase() + profile.tier.slice(1)}`)); if (profile.tier === 'free') { console.log(chalk.yellow(` Remaining fixes: ${profile.usage.remainingFixes || 0}`)); } else { console.log(chalk.green(' Unlimited fixes available')); } } } else { console.log(chalk.red(' Not authenticated')); console.log(chalk.gray(' Run "neurolint login" to access premium features')); } // Orchestration system status console.log(chalk.cyan('\nOrchestration System:')); const engine = new OrchestrationEngine({ verbose: false }); await engine.initialize(); const capabilities = engine.getCapabilities(); if (capabilities.hasOrchestration) { console.log(chalk.green(' Advanced orchestration available')); console.log(chalk.blue(` Version: ${capabilities.version}`)); console.log(chalk.gray(' Enhanced Features:')); capabilities.features.forEach(feature => { console.log(chalk.gray(` - ${feature}`)); }); } else { console.log(chalk.yellow(' Legacy engine (orchestration unavailable)')); console.log(chalk.gray(' Enhanced features available with orchestration system')); } // CLI version info console.log(chalk.cyan('\nCLI Information:')); console.log(chalk.gray(` Version: ${version}`)); console.log(chalk.gray(` npm: @neurolint/cli`)); console.log(chalk.gray(` Homepage: https://neurolint.dev`)); console.log(chalk.cyan('\nAvailable Commands:')); console.log(chalk.gray(' neurolint analyze - Code analysis (free)')); console.log(chalk.gray(' neurolint orchestrate - Advanced processing')); console.log(chalk.gray(' neurolint fix - Apply fixes (premium)')); console.log(chalk.gray(' neurolint layers - Show layer information')); }); // Helper functions async function getFilesToProcess(targetPath, options) { const includePatterns = Array.isArray(options.include) ? options.include : options.include.split(',').map(p => p.trim()); const excludePatterns = Array.isArray(options.exclude) ? options.exclude : options.exclude.split(',').map(p => p.trim()); const isDirectory = (await fs.stat(targetPath)).isDirectory(); if (!isDirectory) { return [targetPath]; } const files = []; for (const pattern of includePatterns) { const globPattern = path.join(targetPath, pattern); const matches = glob.sync(globPattern, { ignore: excludePatterns.map(p => path.join(targetPath, p)) }); files.push(...matches); } // Remove duplicates and ensure files exist const uniqueFiles = [...new Set(files)]; const existingFiles = []; for (const file of uniqueFiles) { try { const stat = await fs.stat(file); if (stat.isFile()) { existingFiles.push(file); } } catch (error) { // File doesn't exist, skip } } return existingFiles; } // Helper functions for enhanced configuration async function inquireOverwrite(filePath) { const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(`Configuration file ${filePath} already exists. Overwrite? (y/N): `, (answer) => { rl.close(); resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'); }); }); } function applyPreset(baseConfig, presetName) { const presets = { basic: { layers: [1, 2], rules: { 'config/typescript-strict': 'warn', 'content/emoji-standardization': 'info' }, verbose: false }, recommended: { layers: [1, 2, 3, 4], rules: { 'config/typescript-strict': 'error', 'content/emoji-standardization': 'warn', 'components/missing-keys': 'error', 'hydration/client-server-safety': 'error' }, verbose: false }, strict: { layers: 'all', rules: { 'config/typescript-strict': 'error', 'content/emoji-standardization': 'error', 'components/missing-keys': 'error', 'components/prop-types-removal': 'error', 'hydration/client-server-safety': 'error', 'approuter/use-client-directive': 'error', 'quality/error-boundaries': 'error' }, verbose: true } }; const preset = presets[presetName]; if (!preset) { console.warn(chalk.yellow(`Unknown preset: ${presetName}. Using default configuration.`)); return baseConfig; } return { ...baseConfig, ...preset }; } function generateJSConfig(config) { return `// NeuroLint Configuration // See https://neurolint.dev/docs/configuration for details module.exports = ${JSON.stringify(config, null, 2)}; `; } function parseLayersOption(layersOption) { if (layersOption === 'all') { return [1, 2, 3, 4, 5, 6]; } return layersOption.split(',').map(l => parseInt(l.trim())).filter(l => l >= 1 && l <= 6); } function hasApiKey(options) { return !!(options.apiKey || process.env.NEUROLINT_API_KEY || process.env.NEUROLINT_PRO_KEY); } function getUserTier(options) { // Check if user has API key (legacy support) if (hasApiKey(options)) { return 'professional'; // Default paid tier } // If authenticated, we should get tier from the user profile if (authManager.isAuthenticated()) { // This will be determined by the enhanced engine when it checks usage return 'authenticated'; } return 'free'; } function getLayersForTier(tier) { const tierLayers = { 'Free': [], 'Basic': [1, 2], 'Professional': [1, 2, 3, 4], 'Business': [1, 2, 3, 4, 5], 'Enterprise': [1, 2, 3, 4, 5, 6], 'Premium': [1, 2, 3, 4, 5, 6] }; return tierLayers[tier] || []; } async function getUserProfile() { try { const { supabase } = require('./auth'); const { data, error } = await supabase .from('user_profiles') .select('*') .eq('id', authManager.getUserId()) .single(); if (error) throw error; return data; } catch (error) { return null; } } async function checkUsageLimits(action, options) { // Check if user has API key (legacy support) if (hasApiKey(options)) { return true; // Paid users have no limits } // Check authentication if (!authManager.isAuthenticated()) { console.error(chalk.red('ERROR: Authentication required for fixes')); console.log(chalk.gray('Run "neurolint login" to access premium features')); console.log(chalk.cyan('Free tier includes unlimited analysis with "neurolint analyze"')); return false; } const usageCheck = await authManager.checkUsageLimit(action); if (!usageCheck.canUse) { console.error(chalk.red(`ERROR: ${usageCheck.reason}`)); if (usageCheck.reason.includes('limit reached')) { console.log(chalk.cyan('Upgrade your plan at https://neurolint.dev/pricing')); console.log(chalk.gray('Plans start at $9/month for 2,000 fixes')); } return false; } if (usageCheck.remaining !== undefined && usageCheck.remaining > 0) { console.log(chalk.yellow(`ℹ ${usageCheck.remaining} fixes remaining this month`)); } return true; } async function recordUsage(action, data) { if (!authManager.isAuthenticated()) { return; // No usage recording for unauthenticated users } try { await authManager.recordUsage(action, data); if (process.env.NEUROLINT_VERBOSE) { console.log(chalk.gray(`Usage recorded: ${action}`), data); } } catch (error) { // Don't fail the operation if usage recording fails if (process.env.NEUROLINT_VERBOSE) { console.warn(chalk.yellow(`Failed to record usage: ${error.message}`)); } } } async function displayAnalysisResults(results, options) { if (results.length === 0) { console.log(chalk.green('SUCCESS: No issues detected in analyzed files\n')); return; } const totalIssues = results.reduce((sum, r) => sum + (r.detectedIssues?.length || 0), 0); console.log(chalk.yellow(`\nAnalysis Summary:`)); console.log(` Files analyzed: ${results.length}`); console.log(` Issues found: ${totalIssues}`); // Group issues by layer const issuesByLayer = {}; results.forEach(result => { if (result.detectedIssues) { result.detectedIssues.forEach(issue => { const layer = issue.layer || 'Unknown'; if (!issuesByLayer[layer]) issuesByLayer[layer] = []; issuesByLayer[layer].push(issue); }); } }); Object.entries(issuesByLayer).forEach(([layer, issues]) => { console.log(`\nLayer ${layer} Issues (${issues.length}):`); issues.slice(0, 3).forEach(issue => { console.log(` - ${issue.description || issue.type}`); }); if (issues.length > 3) { console.log(` ... and ${issues.length - 3} more`); } }); console.log(chalk.cyan('\nNext steps:')); console.log(' - Run "neurolint fix" to apply fixes (requires Premium)'); console.log(' - Run "neurolint layers" to see layer information'); console.log(' - Get premium access at: https://neurolint.dev/pricing\n'); // Save results if requested if (options.output) { await fs.writeJson(options.output, results, { spaces: 2 }); console.log(chalk.blue(`Results saved to: ${options.output}`)); } } function displayFixResults(results, layers) { if (results.length === 0) { console.log(chalk.yellow('No fixes were applied\n')); return; } console.log(chalk.green(`\nFix Summary:`)); console.log(` Files modified: ${results.length}`); console.log(` Layers applied: ${layers.join(', ')}`); results.slice(0, 5).forEach(result => { console.log(` - ${path.basename(result.filePath)}: ${result.fixesApplied} fixes`); }); if (results.length > 5) { console.log(` ... and ${results.length - 5} more files`); } console.log(chalk.cyan('\nRecommendations:')); console.log(' - Test your application thoroughly'); console.log(' - Review the changes before committing'); console.log(' - Run "neurolint analyze" to verify fixes\n'); } async function runLayerCommand(action, layerId, targetPath, options) { const layer = layers.find(l => l.id === layerId); if (!layer) { console.error(`Invalid layer: ${layerId}`); return; } console.log(chalk.blue.bold(`NeuroLint Layer ${layerId}: ${layer.name}`)); console.log(chalk.gray(layer.description + '\n')); if (action === 'fix' && !hasApiKey(options)) { console.log(chalk.yellow('PREMIUM FEATURE: Layer fixes require NeuroLint Professional')); console.log(chalk.cyan('Get access at: https://neurolint.dev/pricing\n')); showUpgradePrompt(); return; } const spinner = ora(`${action === 'scan' ? 'Scanning' : 'Applying fixes'} for Layer ${layerId}...`).start(); try { const files = await getFilesToProcess(targetPath, options); const results = []; for (const filePath of files) { const content = await fs.readFile(filePath, 'utf-8'); const result = await NeuroLintProEnhanced( content, filePath, action === 'scan', // dry-run for scan, false for fix [layerId], { userId: authManager.getUserId(), userTier: getUserTier(options), verbose: options.verbose } ); if (result.analysis || result.transformedCode) { results.push({ filePath, ...result }); if (action === 'fix' && result.transformedCode && result.transformedCode !== content) { await fs.writeFile(filePath, result.transformedCode); } } } spinner.succeed(`Layer ${layerId} ${action} complete: ${results.length} files processed`); if (action =