@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
JavaScript
;
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 'š';
}
}