@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
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.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 'π';
}
}