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

663 lines (662 loc) β€’ 25.8 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.manageFileWatcher = manageFileWatcher; const chalk_1 = __importDefault(require("chalk")); const prompts_1 = __importDefault(require("prompts")); const fs = __importStar(require("fs-extra")); const file_watcher_1 = require("../utils/file-watcher"); const error_handler_1 = require("../utils/error-handler"); const DEFAULT_WORKSPACE_FILE = 're-shell.workspaces.yaml'; let globalWatcher = null; async function manageFileWatcher(options = {}) { const { spinner, verbose, json } = options; try { if (options.start) { await startFileWatcher(options, spinner); return; } if (options.stop) { await stopFileWatcher(options, spinner); return; } if (options.status) { await showWatcherStatus(options, spinner); return; } if (options.stats) { await showWatcherStats(options, spinner); return; } if (options.rules) { await showPropagationRules(options, spinner); return; } if (options.addRule) { await addPropagationRule(options, spinner); return; } if (options.removeRule) { await removePropagationRule(options, spinner); return; } if (options.interactive) { await interactiveFileWatcher(options, spinner); return; } // Default: show status await showWatcherStatus(options, spinner); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('File watcher operation failed')); throw error; } } async function startFileWatcher(options, spinner) { const workspaceFile = options.workspaceFile || DEFAULT_WORKSPACE_FILE; if (!(await fs.pathExists(workspaceFile))) { throw new error_handler_1.ValidationError(`Workspace file not found: ${workspaceFile}`); } if (globalWatcher && globalWatcher.isWatching()) { throw new error_handler_1.ValidationError('File watcher is already running. Stop it first with --stop'); } if (spinner) spinner.setText('Starting file watcher...'); try { const watchOptions = { usePolling: options.usePolling, interval: options.interval || 1000, depth: options.depth, persistent: options.persistent ?? true, ignored: options.ignored || [ '**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/.re-shell/**' ] }; globalWatcher = await (0, file_watcher_1.startWorkspaceWatcher)(workspaceFile, watchOptions); // Set up event handlers setupWatcherEventHandlers(globalWatcher, options); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ status: 'started', timestamp: Date.now(), stats: globalWatcher.getStats() }, null, 2)); return; } console.log(chalk_1.default.green('\\nβœ… File watcher started successfully!')); console.log(chalk_1.default.gray('═'.repeat(50))); const stats = globalWatcher.getStats(); console.log(`Watching: ${chalk_1.default.blue(stats.watchedPaths.length)} paths`); console.log(`Active rules: ${chalk_1.default.blue(stats.activeRules)}`); if (options.verbose) { console.log(chalk_1.default.cyan('\\nπŸ“ Watched paths:')); for (const watchPath of stats.watchedPaths) { console.log(` β€’ ${watchPath}`); } } if (options.follow) { console.log(chalk_1.default.cyan('\\nπŸ‘€ Watching for changes... (Press Ctrl+C to stop)')); // Keep the process alive and show events process.on('SIGINT', async () => { console.log(chalk_1.default.yellow('\\n\\n⏹️ Stopping file watcher...')); if (globalWatcher) { await globalWatcher.stopWatching(); } process.exit(0); }); // Prevent the process from exiting setInterval(() => { }, 1000); } else { console.log(chalk_1.default.cyan('\\nπŸ› οΈ Commands:')); console.log(' β€’ Show status: re-shell file-watcher status'); console.log(' β€’ Show stats: re-shell file-watcher stats'); console.log(' β€’ Stop watching: re-shell file-watcher stop'); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to start file watcher')); throw error; } } async function stopFileWatcher(options, spinner) { if (!globalWatcher || !globalWatcher.isWatching()) { throw new error_handler_1.ValidationError('No active file watcher found'); } if (spinner) spinner.setText('Stopping file watcher...'); try { await globalWatcher.stopWatching(); const finalStats = globalWatcher.getStats(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ status: 'stopped', timestamp: Date.now(), finalStats }, null, 2)); return; } console.log(chalk_1.default.green('\\nβœ… File watcher stopped successfully!')); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(`Uptime: ${formatDuration(finalStats.uptime / 1000)}`); console.log(`Total events: ${chalk_1.default.blue(finalStats.totalEvents)}`); console.log(`Propagated events: ${chalk_1.default.blue(finalStats.propagatedEvents)}`); if (options.verbose) { console.log(chalk_1.default.cyan('\\nπŸ“Š Event breakdown:')); for (const [type, count] of Object.entries(finalStats.eventsByType)) { if (count > 0) { console.log(` β€’ ${type}: ${count}`); } } } globalWatcher = null; } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to stop file watcher')); throw error; } } async function showWatcherStatus(options, spinner) { if (spinner) spinner.setText('Checking watcher status...'); try { const isActive = globalWatcher && globalWatcher.isWatching(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify({ active: isActive, stats: isActive ? globalWatcher.getStats() : null, timestamp: Date.now() }, null, 2)); return; } if (!isActive) { console.log(chalk_1.default.yellow('\\n⏸️ File watcher is not running')); console.log(chalk_1.default.gray('Start it with: re-shell file-watcher start')); return; } const stats = globalWatcher.getStats(); console.log(chalk_1.default.green('\\nβœ… File watcher is active')); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(`Status: ${chalk_1.default.green('Running')}`); console.log(`Uptime: ${formatDuration((Date.now() - stats.startTime) / 1000)}`); console.log(`Watched paths: ${chalk_1.default.blue(stats.watchedPaths.length)}`); console.log(`Active rules: ${chalk_1.default.blue(stats.activeRules)}`); console.log(`Total events: ${chalk_1.default.blue(stats.totalEvents)}`); console.log(`Propagated events: ${chalk_1.default.blue(stats.propagatedEvents)}`); if (options.verbose && stats.watchedPaths.length > 0) { console.log(chalk_1.default.cyan('\\nπŸ“ Watched paths:')); for (const watchPath of stats.watchedPaths) { console.log(` β€’ ${watchPath}`); } } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to get watcher status')); throw error; } } async function showWatcherStats(options, spinner) { if (!globalWatcher || !globalWatcher.isWatching()) { throw new error_handler_1.ValidationError('No active file watcher found'); } if (spinner) spinner.setText('Gathering statistics...'); try { const stats = globalWatcher.getStats(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(stats, null, 2)); return; } console.log(chalk_1.default.cyan('\\nπŸ“Š File Watcher Statistics')); console.log(chalk_1.default.gray('═'.repeat(50))); // Overview console.log(chalk_1.default.cyan('\\nOverview:')); console.log(` Total events: ${chalk_1.default.blue(stats.totalEvents)}`); console.log(` Propagated events: ${chalk_1.default.blue(stats.propagatedEvents)}`); console.log(` Watched paths: ${chalk_1.default.blue(stats.watchedPaths.length)}`); console.log(` Active rules: ${chalk_1.default.blue(stats.activeRules)}`); console.log(` Uptime: ${formatDuration(stats.uptime / 1000)}`); // Events by type if (stats.totalEvents > 0) { console.log(chalk_1.default.cyan('\\nEvents by type:')); for (const [type, count] of Object.entries(stats.eventsByType)) { if (count > 0) { const percentage = ((count / stats.totalEvents) * 100).toFixed(1); console.log(` β€’ ${type}: ${chalk_1.default.blue(count)} (${percentage}%)`); } } } // Events by workspace if (Object.keys(stats.eventsByWorkspace).length > 0) { console.log(chalk_1.default.cyan('\\nEvents by workspace:')); const sortedWorkspaces = Object.entries(stats.eventsByWorkspace) .sort(([, a], [, b]) => b - a) .slice(0, 10); // Top 10 for (const [workspace, count] of sortedWorkspaces) { const percentage = ((count / stats.totalEvents) * 100).toFixed(1); console.log(` β€’ ${workspace}: ${chalk_1.default.blue(count)} (${percentage}%)`); } if (Object.keys(stats.eventsByWorkspace).length > 10) { console.log(` ... and ${Object.keys(stats.eventsByWorkspace).length - 10} more`); } } // Performance metrics if (stats.totalEvents > 0 && stats.uptime > 0) { const eventsPerSecond = (stats.totalEvents / (stats.uptime / 1000)).toFixed(2); const propagationRate = ((stats.propagatedEvents / stats.totalEvents) * 100).toFixed(1); console.log(chalk_1.default.cyan('\\nPerformance:')); console.log(` Events/second: ${chalk_1.default.blue(eventsPerSecond)}`); console.log(` Propagation rate: ${chalk_1.default.blue(propagationRate)}%`); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to get watcher statistics')); throw error; } } async function showPropagationRules(options, spinner) { if (!globalWatcher) { throw new error_handler_1.ValidationError('No file watcher instance available'); } if (spinner) spinner.setText('Loading propagation rules...'); try { // Access private propagationRules through reflection (for demo purposes) // In production, you'd add a public getter method const rules = Array.from(globalWatcher.propagationRules.values()); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(rules, null, 2)); return; } if (rules.length === 0) { console.log(chalk_1.default.yellow('\\nπŸ“‹ No propagation rules configured')); console.log(chalk_1.default.gray('Add rules with: re-shell file-watcher add-rule')); return; } console.log(chalk_1.default.cyan('\\nπŸ“‹ Change Propagation Rules')); console.log(chalk_1.default.gray('═'.repeat(50))); for (let i = 0; i < rules.length; i++) { const rule = rules[i]; console.log(`\\n${i + 1}. ${chalk_1.default.bold(rule.name)}`); console.log(` ID: ${chalk_1.default.gray(rule.id)}`); console.log(` Pattern: ${chalk_1.default.blue(rule.sourcePattern.toString())}`); console.log(` Action: ${chalk_1.default.green(rule.actionType)}`); console.log(` Targets: ${formatTargetWorkspaces(rule.targetWorkspaces)}`); if (rule.debounceMs) { console.log(` Debounce: ${chalk_1.default.yellow(rule.debounceMs)}ms`); } if (options.verbose) { console.log(` Description: ${chalk_1.default.gray(rule.description)}`); } } console.log(chalk_1.default.cyan('\\nπŸ› οΈ Commands:')); console.log(' β€’ Add rule: re-shell file-watcher add-rule'); console.log(' β€’ Remove rule: re-shell file-watcher remove-rule <id>'); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to load propagation rules')); throw error; } } async function addPropagationRule(options, spinner) { if (!globalWatcher) { throw new error_handler_1.ValidationError('No file watcher instance available'); } if (spinner) spinner.stop(); const response = await (0, prompts_1.default)([ { type: 'text', name: 'id', message: 'Rule ID:', validate: (value) => value.length > 0 ? true : 'ID is required' }, { type: 'text', name: 'name', message: 'Rule name:', validate: (value) => value.length > 0 ? true : 'Name is required' }, { type: 'text', name: 'description', message: 'Description:' }, { type: 'text', name: 'pattern', message: 'Source pattern (regex or string):', validate: (value) => value.length > 0 ? true : 'Pattern is required' }, { type: 'select', name: 'actionType', message: 'Action type:', choices: [ { title: 'Rebuild', value: 'rebuild' }, { title: 'Restart dev server', value: 'restart-dev' }, { title: 'Run tests', value: 'run-tests' }, { title: 'Invalidate cache', value: 'invalidate-cache' }, { title: 'Notify only', value: 'notify' }, { title: 'Custom', value: 'custom' } ] }, { type: 'select', name: 'targetType', message: 'Target workspaces:', choices: [ { title: 'All workspaces', value: 'all' }, { title: 'Specific workspaces', value: 'specific' }, { title: 'By condition', value: 'condition' } ] }, { type: prev => prev === 'specific' ? 'text' : null, name: 'targetWorkspaces', message: 'Workspace names (comma-separated):' }, { type: 'number', name: 'debounceMs', message: 'Debounce time (ms):', initial: 1000, min: 0 } ]); if (!response.id || !response.name || !response.pattern) return; try { // Parse pattern as regex if it looks like one let sourcePattern = response.pattern; if (response.pattern.startsWith('/') && response.pattern.endsWith('/')) { sourcePattern = new RegExp(response.pattern.slice(1, -1)); } else if (response.pattern.includes('\\\\')) { try { sourcePattern = new RegExp(response.pattern); } catch { // Keep as string if regex parsing fails } } // Resolve target workspaces let targetWorkspaces = 'all'; if (response.targetType === 'specific' && response.targetWorkspaces) { targetWorkspaces = response.targetWorkspaces.split(',').map((s) => s.trim()); } else if (response.targetType === 'condition') { targetWorkspaces = () => true; // Simple condition for demo } const rule = { id: response.id, name: response.name, description: response.description || '', sourcePattern, targetWorkspaces, actionType: response.actionType, debounceMs: response.debounceMs > 0 ? response.debounceMs : undefined }; globalWatcher.addPropagationRule(rule); console.log(chalk_1.default.green('\\nβœ… Propagation rule added successfully!')); console.log(`Rule ID: ${chalk_1.default.blue(rule.id)}`); } catch (error) { throw new error_handler_1.ValidationError(`Failed to add propagation rule: ${error.message}`); } } async function removePropagationRule(options, spinner) { if (!globalWatcher) { throw new error_handler_1.ValidationError('No file watcher instance available'); } if (!options.removeRule) { throw new error_handler_1.ValidationError('Rule ID is required for removal'); } if (spinner) spinner.setText('Removing propagation rule...'); try { const removed = globalWatcher.removePropagationRule(options.removeRule); if (spinner) spinner.stop(); if (removed) { console.log(chalk_1.default.green('\\nβœ… Propagation rule removed successfully!')); console.log(`Removed rule: ${chalk_1.default.blue(options.removeRule)}`); } else { console.log(chalk_1.default.yellow('\\n⚠️ Rule not found')); console.log(`Rule ID: ${chalk_1.default.blue(options.removeRule)}`); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Failed to remove propagation rule')); throw error; } } async function interactiveFileWatcher(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: '▢️ Start file watcher', value: 'start' }, { title: '⏹️ Stop file watcher', value: 'stop' }, { title: 'πŸ“Š Show status', value: 'status' }, { title: 'πŸ“ˆ Show statistics', value: 'stats' }, { title: 'πŸ“‹ Show propagation rules', value: 'rules' }, { title: 'βž• Add propagation rule', value: 'add-rule' }, { title: 'βž– Remove propagation rule', value: 'remove-rule' } ] } ]); if (!response.action) return; switch (response.action) { case 'start': await startInteractive(options); break; case 'stop': await stopFileWatcher({ ...options, interactive: false }); break; case 'status': await showWatcherStatus({ ...options, interactive: false }); break; case 'stats': await showWatcherStats({ ...options, interactive: false }); break; case 'rules': await showPropagationRules({ ...options, interactive: false }); break; case 'add-rule': await addPropagationRule({ ...options, interactive: false }); break; case 'remove-rule': await removeRuleInteractive(options); break; } } async function startInteractive(options) { const response = await (0, prompts_1.default)([ { type: 'text', name: 'workspaceFile', message: 'Workspace file:', initial: 're-shell.workspaces.yaml' }, { type: 'confirm', name: 'usePolling', message: 'Use polling (for network drives)?', initial: false }, { type: prev => prev ? 'number' : null, name: 'interval', message: 'Polling interval (ms):', initial: 1000, min: 100 }, { type: 'confirm', name: 'follow', message: 'Follow changes in real-time?', initial: true } ]); if (!response.workspaceFile) return; await startFileWatcher({ ...options, ...response, interactive: false }); } async function removeRuleInteractive(options) { if (!globalWatcher) { throw new error_handler_1.ValidationError('No file watcher instance available'); } const rules = Array.from(globalWatcher.propagationRules.values()); if (rules.length === 0) { console.log(chalk_1.default.yellow('\\nπŸ“‹ No propagation rules to remove')); return; } const response = await (0, prompts_1.default)([ { type: 'select', name: 'ruleId', message: 'Select rule to remove:', choices: rules.map(rule => ({ title: `${rule.name} (${rule.id})`, value: rule.id, description: rule.description })) } ]); if (!response.ruleId) return; await removePropagationRule({ ...options, removeRule: response.ruleId, interactive: false }); } // Set up event handlers for the watcher function setupWatcherEventHandlers(watcher, options) { watcher.on('file-event', (event) => { if (options.verbose || options.follow) { const icon = getEventIcon(event.type); const timestamp = new Date(event.timestamp).toLocaleTimeString(); console.log(`${icon} ${chalk_1.default.gray(timestamp)} ${chalk_1.default.blue(event.workspace)} ${event.path}`); } }); watcher.on('propagate', (event) => { if (options.verbose || options.follow) { const timestamp = new Date(event.timestamp).toLocaleTimeString(); console.log(`πŸ”„ ${chalk_1.default.gray(timestamp)} ${chalk_1.default.green(event.actionType)} β†’ ${event.targetWorkspaces.join(', ')}`); } }); watcher.on('error', (error) => { console.error(chalk_1.default.red(`❌ Watcher error: ${error.message}`)); }); watcher.on('warning', (message) => { if (options.verbose) { console.warn(chalk_1.default.yellow(`⚠️ ${message}`)); } }); } // Utility functions function formatDuration(seconds) { if (seconds < 60) { return `${seconds.toFixed(0)}s`; } else if (seconds < 3600) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}m ${remainingSeconds.toFixed(0)}s`; } else { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${hours}h ${minutes}m`; } } function formatTargetWorkspaces(target) { if (target === 'all') { return chalk_1.default.blue('all'); } else if (Array.isArray(target)) { return chalk_1.default.blue(target.join(', ')); } else if (typeof target === 'function') { return chalk_1.default.blue('conditional'); } return chalk_1.default.gray('unknown'); } function getEventIcon(type) { switch (type) { case 'add': return 'βž•'; case 'change': return 'πŸ“'; case 'unlink': return 'βž–'; case 'addDir': return 'πŸ“'; case 'unlinkDir': return 'πŸ—‚οΈ'; default: return 'πŸ“„'; } }