UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

672 lines (671 loc) • 26.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.manageChangeDetector = manageChangeDetector; const chalk_1 = __importDefault(require("chalk")); const prompts_1 = __importDefault(require("prompts")); const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const change_detector_1 = require("../utils/change-detector"); const error_handler_1 = require("../utils/error-handler"); async function manageChangeDetector(options = {}) { const { spinner, verbose, json } = options; try { if (options.scan) { await scanForChanges(options, spinner); return; } if (options.status) { await showDetectorStatus(options, spinner); return; } if (options.stats) { await showDetectorStats(options, spinner); return; } if (options.check) { await checkFileChanges(options, spinner); return; } if (options.clear) { await clearCache(options, spinner); return; } if (options.watch) { await watchForChanges(options, spinner); return; } if (options.compare) { await compareChanges(options, spinner); return; } if (options.interactive) { await interactiveChangeDetection(options, spinner); return; } // Default: perform a scan await scanForChanges(options, spinner); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Change detection operation failed')); throw error; } } async function scanForChanges(options, spinner) { const targetPath = options.path || process.cwd(); if (!(await fs.pathExists(targetPath))) { throw new error_handler_1.ValidationError(`Path does not exist: ${targetPath}`); } if (spinner) spinner.setText('Scanning for changes...'); try { const detectionOptions = { useContentHashing: options.useHashing ?? true, useMetadataOnly: options.metadataOnly ?? false, trackMoves: options.trackMoves ?? true, recursiveDepth: options.maxDepth ?? 10, enableCache: options.enableCache ?? true, cacheLocation: options.cacheLocation, hashingOptions: { algorithm: options.algorithm ?? 'sha256', skipBinary: options.skipBinary ?? false, chunkSize: options.chunkSize ?? 64 * 1024, maxFileSize: options.maxFileSize ?? 50 * 1024 * 1024 } }; const result = await (0, change_detector_1.detectChanges)(targetPath, detectionOptions); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(result, null, 2)); return; } console.log(chalk_1.default.cyan('\\nšŸ” Change Detection Results')); console.log(chalk_1.default.gray('═'.repeat(50))); if (result.totalChanges === 0) { console.log(chalk_1.default.green('āœ… No changes detected')); console.log(chalk_1.default.gray('All files are up to date.')); } else { console.log(`Total changes: ${chalk_1.default.blue(result.totalChanges)}`); if (result.added.length > 0) { console.log(`\\n${chalk_1.default.green('āž• Added files:')} (${result.added.length})`); for (const file of result.added.slice(0, 10)) { console.log(` + ${file}`); } if (result.added.length > 10) { console.log(` ... and ${result.added.length - 10} more`); } } if (result.modified.length > 0) { console.log(`\\n${chalk_1.default.yellow('šŸ“ Modified files:')} (${result.modified.length})`); for (const file of result.modified.slice(0, 10)) { console.log(` ~ ${file}`); } if (result.modified.length > 10) { console.log(` ... and ${result.modified.length - 10} more`); } } if (result.deleted.length > 0) { console.log(`\\n${chalk_1.default.red('āž– Deleted files:')} (${result.deleted.length})`); for (const file of result.deleted.slice(0, 10)) { console.log(` - ${file}`); } if (result.deleted.length > 10) { console.log(` ... and ${result.deleted.length - 10} more`); } } if (result.moved.length > 0) { console.log(`\\n${chalk_1.default.blue('šŸ”„ Moved files:')} (${result.moved.length})`); for (const move of result.moved.slice(0, 5)) { console.log(` ${move.from} → ${move.to}`); } if (result.moved.length > 5) { console.log(` ... and ${result.moved.length - 5} more`); } } } // Performance metrics console.log(chalk_1.default.cyan('\\n⚔ Performance:')); console.log(` Scan time: ${chalk_1.default.blue(formatDuration(result.scanTime / 1000))}`); console.log(` Hashing time: ${chalk_1.default.blue(formatDuration(result.hashingTime / 1000))}`); if (options.verbose) { const efficiency = result.scanTime > 0 ? ((result.hashingTime / result.scanTime) * 100).toFixed(1) : '0'; console.log(` Hashing efficiency: ${chalk_1.default.blue(efficiency)}%`); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to scan for changes')); throw error; } } async function showDetectorStatus(options, spinner) { const targetPath = options.path || process.cwd(); if (spinner) spinner.setText('Checking detector status...'); try { const detector = await (0, change_detector_1.createChangeDetector)(targetPath, { enableCache: options.enableCache ?? true, cacheLocation: options.cacheLocation }); const stats = detector.getCacheStats(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ path: targetPath, cacheEnabled: options.enableCache ?? true, stats }, null, 2)); return; } console.log(chalk_1.default.cyan('\\nšŸ“Š Change Detector Status')); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(`Target path: ${chalk_1.default.blue(targetPath)}`); console.log(`Cache enabled: ${chalk_1.default.blue(options.enableCache ?? true ? 'Yes' : 'No')}`); console.log(`Cached files: ${chalk_1.default.blue(stats.cacheSize)}`); console.log(`Total tracked: ${chalk_1.default.blue(stats.totalFiles)}`); console.log(`Memory usage: ${chalk_1.default.blue(stats.memoryUsage)}`); console.log(`Cache hit rate: ${chalk_1.default.blue(stats.hitRate.toFixed(1))}%`); if (options.verbose) { const cacheFile = options.cacheLocation || path.join(targetPath, '.re-shell', 'change-cache.json'); console.log(`\\nCache location: ${chalk_1.default.gray(cacheFile)}`); if (await fs.pathExists(cacheFile)) { const cacheStats = await fs.stat(cacheFile); console.log(`Cache file size: ${chalk_1.default.gray(formatBytes(cacheStats.size))}`); console.log(`Last updated: ${chalk_1.default.gray(cacheStats.mtime.toLocaleString())}`); } else { console.log(`Cache file: ${chalk_1.default.gray('Not created yet')}`); } } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to get detector status')); throw error; } } async function showDetectorStats(options, spinner) { const targetPath = options.path || process.cwd(); if (spinner) spinner.setText('Gathering detection statistics...'); try { const detector = await (0, change_detector_1.createChangeDetector)(targetPath, { enableCache: options.enableCache ?? true, cacheLocation: options.cacheLocation }); const stats = detector.getCacheStats(); // Perform a quick scan to get more detailed stats const result = await detector.detectChanges(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ cacheStats: stats, lastScan: result }, null, 2)); return; } console.log(chalk_1.default.cyan('\\nšŸ“ˆ Change Detection Statistics')); console.log(chalk_1.default.gray('═'.repeat(50))); // Cache statistics console.log(chalk_1.default.cyan('\\nCache Performance:')); console.log(` Cached entries: ${chalk_1.default.blue(stats.cacheSize)}`); console.log(` Total files tracked: ${chalk_1.default.blue(stats.totalFiles)}`); console.log(` Memory usage: ${chalk_1.default.blue(stats.memoryUsage)}`); console.log(` Hit rate: ${chalk_1.default.blue(stats.hitRate.toFixed(1))}%`); // Last scan results console.log(chalk_1.default.cyan('\\nLast Scan Results:')); console.log(` Total changes: ${chalk_1.default.blue(result.totalChanges)}`); console.log(` Added files: ${chalk_1.default.green(result.added.length)}`); console.log(` Modified files: ${chalk_1.default.yellow(result.modified.length)}`); console.log(` Deleted files: ${chalk_1.default.red(result.deleted.length)}`); console.log(` Moved files: ${chalk_1.default.blue(result.moved.length)}`); // Performance metrics console.log(chalk_1.default.cyan('\\nPerformance Metrics:')); console.log(` Scan time: ${chalk_1.default.blue(formatDuration(result.scanTime / 1000))}`); console.log(` Hashing time: ${chalk_1.default.blue(formatDuration(result.hashingTime / 1000))}`); console.log(` Files/second: ${chalk_1.default.blue(calculateFilesPerSecond(stats.totalFiles, result.scanTime))}`); if (result.scanTime > 0) { const efficiency = ((result.hashingTime / result.scanTime) * 100).toFixed(1); console.log(` Hashing efficiency: ${chalk_1.default.blue(efficiency)}%`); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to get detection statistics')); throw error; } } async function checkFileChanges(options, spinner) { if (!options.check) { throw new error_handler_1.ValidationError('File path is required for checking changes'); } const targetPath = options.path || process.cwd(); const filePath = options.check; if (spinner) spinner.setText(`Checking changes for ${filePath}...`); try { const detector = await (0, change_detector_1.createChangeDetector)(targetPath, { enableCache: options.enableCache ?? true, useContentHashing: options.useHashing ?? true }); const hasChanged = await detector.hasFileChanged(filePath); const changes = await detector.getFileChanges(filePath); const fileHash = await detector.getFileHash(filePath); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ file: filePath, hasChanged, changes, currentHash: fileHash }, null, 2)); return; } console.log(chalk_1.default.cyan('\\nšŸ” File Change Analysis')); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(`File: ${chalk_1.default.blue(filePath)}`); console.log(`Changed: ${hasChanged ? chalk_1.default.red('Yes') : chalk_1.default.green('No')}`); if (fileHash) { console.log(`Current hash: ${chalk_1.default.gray(fileHash.hash.substring(0, 16) + '...')}`); console.log(`Size: ${chalk_1.default.blue(formatBytes(fileHash.size))}`); console.log(`Modified: ${chalk_1.default.gray(new Date(fileHash.mtime).toLocaleString())}`); } if (changes) { console.log(`\\nChange type: ${getChangeTypeIcon(changes.type)} ${chalk_1.default.blue(changes.type)}`); if (changes.oldHash && changes.hash) { console.log(`Hash changed: ${chalk_1.default.red(changes.oldHash.substring(0, 16) + '...')} → ${chalk_1.default.green(changes.hash.substring(0, 16) + '...')}`); } if (changes.metadata) { console.log(`Timestamp: ${chalk_1.default.gray(new Date(changes.timestamp).toLocaleString())}`); } } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to check file changes')); throw error; } } async function clearCache(options, spinner) { const targetPath = options.path || process.cwd(); if (spinner) spinner.setText('Clearing change detection cache...'); try { const detector = await (0, change_detector_1.createChangeDetector)(targetPath, { enableCache: options.enableCache ?? true, cacheLocation: options.cacheLocation }); await detector.clearCache(); if (spinner) spinner.stop(); console.log(chalk_1.default.green('\\nāœ… Change detection cache cleared successfully!')); console.log(chalk_1.default.gray('Next scan will rebuild the entire cache.')); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to clear cache')); throw error; } } async function watchForChanges(options, spinner) { const targetPath = options.path || process.cwd(); if (spinner) spinner.setText('Starting change monitoring...'); try { const detector = await (0, change_detector_1.createChangeDetector)(targetPath, { useContentHashing: options.useHashing ?? true, trackMoves: options.trackMoves ?? true, enableCache: options.enableCache ?? true }); if (spinner) spinner.stop(); console.log(chalk_1.default.cyan('\\nšŸ‘€ Watching for changes... (Press Ctrl+C to stop)')); console.log(chalk_1.default.gray(`Monitoring: ${targetPath}`)); console.log(chalk_1.default.gray('═'.repeat(50))); let scanCount = 0; const startTime = Date.now(); const watchInterval = setInterval(async () => { try { const result = await detector.detectChanges(); scanCount++; if (result.totalChanges > 0) { const timestamp = new Date().toLocaleTimeString(); console.log(`\\n${chalk_1.default.blue('šŸ”„')} ${chalk_1.default.gray(timestamp)} Changes detected: ${chalk_1.default.blue(result.totalChanges)}`); for (const file of result.added.slice(0, 3)) { console.log(` ${chalk_1.default.green('+')} ${file}`); } for (const file of result.modified.slice(0, 3)) { console.log(` ${chalk_1.default.yellow('~')} ${file}`); } for (const file of result.deleted.slice(0, 3)) { console.log(` ${chalk_1.default.red('-')} ${file}`); } if (result.totalChanges > 6) { console.log(` ... and ${result.totalChanges - 6} more changes`); } } else if (options.verbose) { const timestamp = new Date().toLocaleTimeString(); console.log(`${chalk_1.default.gray(timestamp)} No changes`); } } catch (error) { console.error(chalk_1.default.red(`Watch error: ${error}`)); } }, 5000); // Check every 5 seconds // Handle Ctrl+C process.on('SIGINT', () => { clearInterval(watchInterval); const duration = (Date.now() - startTime) / 1000; console.log(chalk_1.default.yellow('\\n\\nā¹ļø Change monitoring stopped')); console.log(chalk_1.default.gray(`Duration: ${formatDuration(duration)}`)); console.log(chalk_1.default.gray(`Scans performed: ${scanCount}`)); process.exit(0); }); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to start change monitoring')); throw error; } } async function compareChanges(options, spinner) { const targetPath = options.path || process.cwd(); if (spinner) spinner.setText('Comparing changes...'); try { const detector = await (0, change_detector_1.createChangeDetector)(targetPath, { useContentHashing: options.useHashing ?? true, trackMoves: options.trackMoves ?? true }); // Perform two scans with a small delay to capture any changes const firstScan = await detector.detectChanges(); console.log(chalk_1.default.yellow('\\nā±ļø Waiting 2 seconds for changes...')); await new Promise(resolve => setTimeout(resolve, 2000)); const secondScan = await detector.detectChanges(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ firstScan, secondScan, comparison: { changesInSecondScan: secondScan.totalChanges, timeWindow: '2 seconds' } }, null, 2)); return; } console.log(chalk_1.default.cyan('\\nšŸ“Š Change Comparison')); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(`First scan changes: ${chalk_1.default.blue(firstScan.totalChanges)}`); console.log(`Second scan changes: ${chalk_1.default.blue(secondScan.totalChanges)}`); if (secondScan.totalChanges > 0) { console.log(chalk_1.default.yellow('\\nāš ļø Changes detected during comparison window:')); for (const file of secondScan.added) { console.log(` ${chalk_1.default.green('+')} ${file}`); } for (const file of secondScan.modified) { console.log(` ${chalk_1.default.yellow('~')} ${file}`); } for (const file of secondScan.deleted) { console.log(` ${chalk_1.default.red('-')} ${file}`); } } else { console.log(chalk_1.default.green('\\nāœ… No additional changes detected')); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to compare changes')); throw error; } } async function interactiveChangeDetection(options, spinner) { if (spinner) spinner.stop(); const response = await (0, prompts_1.default)([ { type: 'select', name: 'action', message: 'What would you like to do?', choices: [ { title: 'šŸ” Scan for changes', value: 'scan' }, { title: 'šŸ“Š Show status', value: 'status' }, { title: 'šŸ“ˆ Show statistics', value: 'stats' }, { title: 'šŸ”Ž Check specific file', value: 'check' }, { title: 'šŸ‘€ Watch for changes', value: 'watch' }, { title: 'šŸ“‹ Compare changes', value: 'compare' }, { title: 'šŸ—‘ļø Clear cache', value: 'clear' } ] } ]); if (!response.action) return; switch (response.action) { case 'scan': await scanInteractive(options); break; case 'status': await showDetectorStatus({ ...options, interactive: false }); break; case 'stats': await showDetectorStats({ ...options, interactive: false }); break; case 'check': await checkFileInteractive(options); break; case 'watch': await watchInteractive(options); break; case 'compare': await compareChanges({ ...options, interactive: false }); break; case 'clear': await clearCacheInteractive(options); break; } } async function scanInteractive(options) { const response = await (0, prompts_1.default)([ { type: 'text', name: 'path', message: 'Path to scan:', initial: process.cwd() }, { type: 'confirm', name: 'useHashing', message: 'Use content hashing?', initial: true }, { type: 'confirm', name: 'trackMoves', message: 'Track file moves?', initial: true }, { type: 'confirm', name: 'skipBinary', message: 'Skip binary files?', initial: false }, { type: 'number', name: 'maxDepth', message: 'Maximum scan depth:', initial: 10, min: 1, max: 50 } ]); if (!response.path) return; await scanForChanges({ ...options, ...response, interactive: false }); } async function checkFileInteractive(options) { const response = await (0, prompts_1.default)([ { type: 'text', name: 'check', message: 'File path to check:', validate: (value) => value.length > 0 ? true : 'File path is required' }, { type: 'text', name: 'path', message: 'Root directory:', initial: process.cwd() }, { type: 'confirm', name: 'useHashing', message: 'Use content hashing?', initial: true } ]); if (!response.check) return; await checkFileChanges({ ...options, ...response, interactive: false }); } async function watchInteractive(options) { const response = await (0, prompts_1.default)([ { type: 'text', name: 'path', message: 'Path to watch:', initial: process.cwd() }, { type: 'confirm', name: 'useHashing', message: 'Use content hashing?', initial: true }, { type: 'confirm', name: 'verbose', message: 'Show verbose output?', initial: false } ]); await watchForChanges({ ...options, ...response, interactive: false }); } async function clearCacheInteractive(options) { const response = await (0, prompts_1.default)([ { type: 'text', name: 'path', message: 'Root directory:', initial: process.cwd() }, { type: 'confirm', name: 'confirm', message: 'Are you sure you want to clear the cache?', initial: false } ]); if (!response.confirm) { console.log(chalk_1.default.yellow('Cache clear cancelled')); return; } await clearCache({ ...options, ...response, interactive: false }); } // Utility functions function formatDuration(seconds) { if (seconds < 1) { return `${(seconds * 1000).toFixed(0)}ms`; } else if (seconds < 60) { return `${seconds.toFixed(1)}s`; } else { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}m ${remainingSeconds.toFixed(0)}s`; } } function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; } function calculateFilesPerSecond(fileCount, timeMs) { if (timeMs === 0) return 'āˆž'; const filesPerSecond = (fileCount / (timeMs / 1000)); return filesPerSecond < 1 ? '< 1' : filesPerSecond.toFixed(0); } function getChangeTypeIcon(type) { switch (type) { case 'added': return 'āž•'; case 'modified': return 'šŸ“'; case 'deleted': return 'āž–'; case 'moved': return 'šŸ”„'; default: return 'šŸ“„'; } }