@neurolint/cli
Version:
NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations
427 lines (353 loc) • 15 kB
text/typescript
import chalk from "chalk";
import ora from "ora";
import { table } from "table";
import inquirer from "inquirer";
import { CacheManager } from "../../lib/cache-manager";
import fs from "fs-extra";
import path from "path";
interface CacheOptions {
verbose?: boolean;
force?: boolean;
// Stats options
detailed?: boolean;
// Clear options
type?: string;
// Warmup options
pattern?: string;
maxFiles?: number;
}
const cacheManager = new CacheManager();
export async function cacheCommand(action: string, options: CacheOptions = {}) {
try {
// Initialize cache manager
await cacheManager.initialize();
switch (action) {
case 'stats':
case 'status':
await showCacheStats(options);
break;
case 'clear':
await clearCache(options);
break;
case 'warmup':
await warmupCache(options);
break;
case 'cleanup':
await performCleanup(options);
break;
case 'config':
await showCacheConfig(options);
break;
default:
await showCacheHelp();
}
} catch (error) {
console.error(chalk.red(`Cache command failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
process.exit(1);
}
}
async function showCacheStats(options: CacheOptions) {
const spinner = ora('Loading cache statistics...').start();
try {
const stats = cacheManager.getStatistics();
spinner.succeed('Cache statistics loaded');
console.log(chalk.white.bold('\nNeuroLint Cache Statistics\n'));
// Overview Table
const overviewData = [
['Metric', 'Value'],
['Status', stats.initialized ? chalk.green('Initialized') : chalk.red('Not Initialized')],
['Cache Directory', stats.persistentCache.directory],
['Persistent Cache', stats.persistentCache.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')],
['Memory Cache', stats.memoryCache.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')],
['Total Cache Size', formatBytes(stats.persistentCache.size)],
['Total Cache Files', stats.persistentCache.files.toLocaleString()],
['Memory Cache Size', stats.memoryCache.size.toLocaleString()],
['Memory Hit Rate', `${(stats.memoryCache.hitRate * 100).toFixed(1)}%`]
];
console.log(table(overviewData, {
border: {
topBody: chalk.gray('─'),
topJoin: chalk.gray('┬'),
topLeft: chalk.gray('┌'),
topRight: chalk.gray('┐'),
bottomBody: chalk.gray('─'),
bottomJoin: chalk.gray('┴'),
bottomLeft: chalk.gray('└'),
bottomRight: chalk.gray('┘'),
bodyLeft: chalk.gray('│'),
bodyRight: chalk.gray('│'),
bodyJoin: chalk.gray('│'),
joinBody: chalk.gray('─'),
joinLeft: chalk.gray('├'),
joinRight: chalk.gray('┤'),
joinJoin: chalk.gray('┼')
}
}));
if (options.detailed) {
// Configuration Details
console.log(chalk.blue.bold('\nConfiguration Details\n'));
const configData = [
['Setting', 'Value'],
['Max Cache Size', formatBytes(stats.config.maxCacheSize)],
['Max Age', formatDuration(stats.config.maxAge)],
['Compression', stats.config.compressionEnabled ? 'Enabled' : 'Disabled'],
['Max Memory Items', stats.config.maxMemoryItems.toLocaleString()],
['Hash Algorithm', stats.config.hashAlgorithm],
['Version', stats.config.version]
];
console.log(table(configData, {
border: {
topBody: chalk.gray('─'),
topJoin: chalk.gray('┬'),
topLeft: chalk.gray('┌'),
topRight: chalk.gray('┐'),
bottomBody: chalk.gray('─'),
bottomJoin: chalk.gray('┴'),
bottomLeft: chalk.gray('└'),
bottomRight: chalk.gray('┘'),
bodyLeft: chalk.gray('│'),
bodyRight: chalk.gray('│'),
bodyJoin: chalk.gray('│'),
joinBody: chalk.gray('─'),
joinLeft: chalk.gray('├'),
joinRight: chalk.gray('┤'),
joinJoin: chalk.gray('┼')
}
}));
// Metadata
if (stats.metadata) {
console.log(chalk.blue.bold('\nCache Metadata\n'));
const metadataData = [
['Property', 'Value'],
['Created', new Date(stats.metadata.created).toLocaleString()],
['Last Cleanup', new Date(stats.metadata.lastCleanup).toLocaleString()],
['Version', stats.metadata.version]
];
console.log(table(metadataData, {
border: {
topBody: chalk.gray('─'),
topJoin: chalk.gray('┬'),
topLeft: chalk.gray('┌'),
topRight: chalk.gray('┐'),
bottomBody: chalk.gray('─'),
bottomJoin: chalk.gray('┴'),
bottomLeft: chalk.gray('└'),
bottomRight: chalk.gray('┘'),
bodyLeft: chalk.gray('│'),
bodyRight: chalk.gray('│'),
bodyJoin: chalk.gray('│'),
joinBody: chalk.gray('─'),
joinLeft: chalk.gray('├'),
joinRight: chalk.gray('┤'),
joinJoin: chalk.gray('┼')
}
}));
}
// Performance Recommendations
console.log(chalk.yellow.bold('\nPerformance Recommendations\n'));
const recommendations = generateRecommendations(stats);
if (recommendations.length > 0) {
recommendations.forEach((rec, index) => {
console.log(`${chalk.yellow((index + 1).toString())}. ${rec}`);
});
} else {
console.log(chalk.green('Cache is optimally configured'));
}
}
} catch (error) {
spinner.fail('Failed to load cache statistics');
throw error;
}
}
async function clearCache(options: CacheOptions) {
if (!options.force) {
const { confirmed } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmed',
message: 'Are you sure you want to clear the cache? This cannot be undone.',
default: false
}
]);
if (!confirmed) {
console.log(chalk.yellow('Cache clear cancelled'));
return;
}
}
const spinner = ora('Clearing cache...').start();
try {
const statsBefore = cacheManager.getStatistics();
await cacheManager.clearCache();
spinner.succeed('Cache cleared successfully');
console.log(chalk.green('\nCache Cleared\n'));
console.log(chalk.gray(`Freed ${formatBytes(statsBefore.persistentCache.size)} of disk space`));
console.log(chalk.gray(`Removed ${statsBefore.persistentCache.files.toLocaleString()} cache files`));
console.log(chalk.gray(`Cleared ${statsBefore.memoryCache.size.toLocaleString()} memory cache entries`));
} catch (error) {
spinner.fail('Failed to clear cache');
throw error;
}
}
async function warmupCache(options: CacheOptions) {
const spinner = ora('Scanning for files to warm up cache...').start();
try {
// Find files to warmup
const pattern = options.pattern || '**/*.{ts,tsx,js,jsx}';
const maxFiles = options.maxFiles || 100;
spinner.text = `Finding files matching: ${pattern}`;
// Get files from current directory
const { glob } = await import('glob');
const files = await glob(pattern, {
ignore: ['node_modules/**', 'dist/**', 'build/**', '.next/**'],
absolute: true,
cwd: process.cwd()
});
const filesToProcess = files.slice(0, maxFiles);
if (filesToProcess.length === 0) {
spinner.warn('No files found to warm up cache');
return;
}
spinner.text = `Warming up cache for ${filesToProcess.length} files...`;
await cacheManager.warmupCache(filesToProcess);
spinner.succeed(`Cache warmed up for ${filesToProcess.length} files`);
if (options.verbose) {
console.log(chalk.gray('\nWarmed up files:'));
filesToProcess.slice(0, 10).forEach(file => {
console.log(chalk.gray(` ${path.relative(process.cwd(), file)}`));
});
if (filesToProcess.length > 10) {
console.log(chalk.gray(` ... and ${filesToProcess.length - 10} more files`));
}
}
} catch (error) {
spinner.fail('Cache warmup failed');
throw error;
}
}
async function performCleanup(options: CacheOptions) {
const spinner = ora('Performing cache cleanup...').start();
try {
const statsBefore = cacheManager.getStatistics();
await cacheManager.performMaintenance();
const statsAfter = cacheManager.getStatistics();
const filesRemoved = statsBefore.persistentCache.files - statsAfter.persistentCache.files;
const spaceFreed = statsBefore.persistentCache.size - statsAfter.persistentCache.size;
spinner.succeed('Cache cleanup completed');
console.log(chalk.green('\nCache Cleanup Completed\n'));
if (filesRemoved > 0 || spaceFreed > 0) {
console.log(chalk.gray(`Removed ${filesRemoved.toLocaleString()} expired files`));
console.log(chalk.gray(`Freed ${formatBytes(spaceFreed)} of disk space`));
} else {
console.log(chalk.gray('No cleanup was necessary'));
}
if (options.verbose) {
console.log(chalk.gray(`\nCache files remaining: ${statsAfter.persistentCache.files.toLocaleString()}`));
console.log(chalk.gray(`Total cache size: ${formatBytes(statsAfter.persistentCache.size)}`));
}
} catch (error) {
spinner.fail('Cache cleanup failed');
throw error;
}
}
async function showCacheConfig(options: CacheOptions) {
const stats = cacheManager.getStatistics();
console.log(chalk.white.bold('\nCache Configuration\n'));
const config = stats.config;
console.log(chalk.blue('Cache Directory:'));
console.log(chalk.gray(` ${config.cacheDir}`));
console.log(chalk.blue('\nStorage Settings:'));
console.log(chalk.gray(` Max Cache Size: ${formatBytes(config.maxCacheSize)}`));
console.log(chalk.gray(` Max Age: ${formatDuration(config.maxAge)}`));
console.log(chalk.gray(` Compression: ${config.compressionEnabled ? 'Enabled' : 'Disabled'}`));
console.log(chalk.blue('\nMemory Cache:'));
console.log(chalk.gray(` Enabled: ${config.memoryCache ? 'Yes' : 'No'}`));
console.log(chalk.gray(` Max Items: ${config.maxMemoryItems.toLocaleString()}`));
console.log(chalk.blue('\nSecurity:'));
console.log(chalk.gray(` Hash Algorithm: ${config.hashAlgorithm}`));
console.log(chalk.gray(` Version: ${config.version}`));
if (options.verbose) {
console.log(chalk.blue('\nAdvanced Settings:'));
console.log(chalk.gray(` Persistent Cache: ${config.persistentCache ? 'Enabled' : 'Disabled'}`));
console.log(chalk.gray(` Auto-Cleanup: Enabled (every hour)`));
}
console.log(chalk.yellow('\nConfiguration Tips:'));
console.log(chalk.gray('• Increase maxCacheSize for larger projects'));
console.log(chalk.gray('• Reduce maxAge for frequently changing codebases'));
console.log(chalk.gray('• Enable compression to save disk space'));
console.log(chalk.gray('• Configure in .neurolintrc.json under "cache" section'));
}
async function showCacheHelp() {
console.log(chalk.white.bold('\nNeuroLint Cache Management\n'));
console.log(chalk.white('Available Commands:'));
console.log(chalk.gray(' stats Show cache statistics and status'));
console.log(chalk.gray(' clear Clear all cached data'));
console.log(chalk.gray(' warmup Pre-populate cache with project files'));
console.log(chalk.gray(' cleanup Remove expired cache entries'));
console.log(chalk.gray(' config Show cache configuration'));
console.log(chalk.white('\nOptions:'));
console.log(chalk.gray(' --verbose Show detailed information'));
console.log(chalk.gray(' --force Skip confirmation prompts'));
console.log(chalk.gray(' --detailed Show detailed statistics'));
console.log(chalk.gray(' --pattern <glob> File pattern for warmup (default: **/*.{ts,tsx,js,jsx})'));
console.log(chalk.gray(' --max-files <n> Maximum files to warmup (default: 100)'));
console.log(chalk.white('\nExamples:'));
console.log(chalk.gray(' neurolint cache stats --detailed'));
console.log(chalk.gray(' neurolint cache clear --force'));
console.log(chalk.gray(' neurolint cache warmup --pattern="src/**/*.tsx"'));
console.log(chalk.gray(' neurolint cache cleanup --verbose'));
console.log(chalk.white('\nCache Configuration:'));
console.log(chalk.gray('Add to .neurolintrc.json:'));
console.log(chalk.gray(`{
"cache": {
"maxCacheSize": "100MB",
"maxAge": "24h",
"compressionEnabled": true,
"memoryCache": true,
"maxMemoryItems": 1000
}
}`));
}
// Helper functions
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
const size = bytes / Math.pow(1024, i);
return `${size.toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`;
}
function formatDuration(ms: number): string {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}d ${hours % 24}h`;
if (hours > 0) return `${hours}h ${minutes % 60}m`;
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
}
function generateRecommendations(stats: any): string[] {
const recommendations: string[] = [];
// Check cache hit rate
if (stats.memoryCache.hitRate < 0.5) {
recommendations.push('Low cache hit rate detected. Consider increasing memory cache size or cache duration.');
}
// Check cache size
const usageRatio = stats.persistentCache.size / stats.config.maxCacheSize;
if (usageRatio > 0.9) {
recommendations.push('Cache is nearly full. Consider increasing maxCacheSize or running cleanup more frequently.');
}
// Check memory cache efficiency
if (stats.memoryCache.enabled && stats.memoryCache.size < 10) {
recommendations.push('Memory cache has few items. Consider running warmup to improve performance.');
}
// Check if compression would help
if (!stats.config.compressionEnabled && stats.persistentCache.size > 10 * 1024 * 1024) {
recommendations.push('Enable compression to reduce cache disk usage for large caches.');
}
// Check cache age
const daysSinceCreation = (Date.now() - stats.metadata.created) / (1000 * 60 * 60 * 24);
if (daysSinceCreation > 30 && stats.persistentCache.files > 1000) {
recommendations.push('Consider running cleanup to remove old cache entries and improve performance.');
}
return recommendations;
}