@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
JavaScript
#!/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 =