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

604 lines (603 loc) 24.9 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.FileWatcher = void 0; exports.createFileWatcher = createFileWatcher; exports.startWorkspaceWatcher = startWorkspaceWatcher; exports.createCrossPlatformWatcher = createCrossPlatformWatcher; exports.getPlatformCapabilities = getPlatformCapabilities; exports.testPlatformWatching = testPlatformWatching; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const events_1 = require("events"); const error_handler_1 = require("./error-handler"); const event_debouncer_1 = require("./event-debouncer"); const platform_watcher_1 = require("./platform-watcher"); // File watcher with change propagation class FileWatcher extends events_1.EventEmitter { constructor(rootPath = process.cwd(), fallbackOptions) { super(); this.watchers = new Map(); this.watchedPaths = new Set(); this.propagationRules = new Map(); this.debounceTimers = new Map(); this.workspaces = {}; this.isActive = false; this.watcherFailures = 0; this.rootPath = rootPath; // Initialize platform watcher with cross-platform capabilities this.platformWatcher = (0, platform_watcher_1.createPlatformWatcher)(fallbackOptions); // Set up platform watcher event listeners this.setupPlatformWatcherListeners(); // Initialize event debouncer with intelligent defaults this.eventDebouncer = (0, event_debouncer_1.createEventDebouncer)({ delay: 300, maxDelay: 2000, maxBatchSize: 100, enableDeduplication: true, enableBatching: true, groupByType: false, includeStats: true }); // Set up debouncer event listeners this.setupDebouncerListeners(); const platformCapabilities = this.platformWatcher.getPlatformCapabilities(); this.stats = { totalEvents: 0, eventsByType: { add: 0, change: 0, unlink: 0, addDir: 0, unlinkDir: 0 }, eventsByWorkspace: {}, propagatedEvents: 0, startTime: Date.now(), uptime: 0, watchedPaths: [], activeRules: 0, // Cross-platform stats platformCapabilities, activeWatchers: 0, fallbackWatchers: 0, healthyWatchers: 0, watcherFailures: 0 }; this.initializeDefaultRules(); } // Setup platform watcher event listeners setupPlatformWatcherListeners() { this.platformWatcher.on('watcher-created', ({ watcherId, watchPath, method }) => { this.emit('platform-watcher-created', { watcherId, watchPath, method }); }); this.platformWatcher.on('watcher-error', ({ watcherId, watchPath, error }) => { this.watcherFailures++; this.emit('platform-watcher-error', { watcherId, watchPath, error }); }); this.platformWatcher.on('fallback-activated', ({ watcherId, watchPath, method }) => { this.emit('platform-fallback-activated', { watcherId, watchPath, method }); }); this.platformWatcher.on('fallback-failed', ({ watcherId, watchPath, error }) => { this.watcherFailures++; this.emit('platform-fallback-failed', { watcherId, watchPath, error }); }); this.platformWatcher.on('watcher-unhealthy', ({ watcherId, watchPath, error, failureCount }) => { this.emit('platform-watcher-unhealthy', { watcherId, watchPath, error, failureCount }); }); this.platformWatcher.on('health-check-completed', ({ totalWatchers, healthyWatchers }) => { this.stats.activeWatchers = totalWatchers; this.stats.healthyWatchers = healthyWatchers; this.emit('platform-health-check', { totalWatchers, healthyWatchers }); }); } // Start watching workspace paths async startWatching(workspaces, options = {}) { if (this.isActive) { throw new error_handler_1.ValidationError('File watcher is already active'); } this.workspaces = workspaces; this.isActive = true; this.stats.startTime = Date.now(); const defaultOptions = { ignored: [ '**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/.re-shell/**', '**/.next/**', '**/.nuxt/**', '**/coverage/**', '**/.nyc_output/**', '**/*.log', '**/.DS_Store', '**/Thumbs.db' ], persistent: true, ignoreInitial: true, followSymlinks: true, usePolling: false, interval: 1000, binaryInterval: 3000, alwaysStat: true, depth: undefined, awaitWriteFinish: { stabilityThreshold: 2000, pollInterval: 100 }, ignorePermissionErrors: true, atomic: true, // Enable cross-platform features by default enableFallbacks: true, platformOptimizations: true, ...options }; // Watch each workspace directory using platform watcher for (const [workspaceName, workspace] of Object.entries(workspaces)) { if (!workspace.path) continue; const watchPath = path.resolve(this.rootPath, workspace.path); if (!(await fs.pathExists(watchPath))) { this.emit('warning', `Workspace path does not exist: ${watchPath}`); continue; } try { // Convert WatchOptions to PlatformWatchOptions const platformOptions = { usePolling: defaultOptions.usePolling, interval: defaultOptions.interval, binaryInterval: defaultOptions.binaryInterval, useNativeWatcher: !defaultOptions.usePolling, enableFallbacks: defaultOptions.enableFallbacks, fallbackOptions: defaultOptions.fallbackOptions, platformSpecific: defaultOptions.platformSpecific }; // Create platform-optimized watcher const watcher = await this.platformWatcher.createWatcher(watchPath, platformOptions); // Set up event handlers watcher .on('add', (filePath, stats) => this.handleFileEvent('add', filePath, workspaceName, stats)) .on('change', (filePath, stats) => this.handleFileEvent('change', filePath, workspaceName, stats)) .on('unlink', (filePath) => this.handleFileEvent('unlink', filePath, workspaceName)) .on('addDir', (dirPath, stats) => this.handleFileEvent('addDir', dirPath, workspaceName, stats)) .on('unlinkDir', (dirPath) => this.handleFileEvent('unlinkDir', dirPath, workspaceName)) .on('error', (error) => this.handleWatchError(error, workspaceName)) .on('ready', () => this.handleWatchReady(workspaceName, watchPath)); this.watchers.set(workspaceName, watcher); this.watchedPaths.add(watchPath); } catch (error) { this.watcherFailures++; this.emit('error', new error_handler_1.ValidationError(`Failed to start watching ${workspaceName}: ${error instanceof Error ? error.message : String(error)}`)); } } // Watch root configuration files using platform watcher await this.watchRootFiles(defaultOptions); // Update stats this.stats.activeWatchers = this.platformWatcher.getActiveWatchersCount(); this.emit('started', { watchedWorkspaces: Object.keys(workspaces).length, watchedPaths: Array.from(this.watchedPaths), activeRules: this.propagationRules.size, platformCapabilities: this.stats.platformCapabilities }); } // Stop watching all paths async stopWatching() { if (!this.isActive) return; this.isActive = false; // Clear debounce timers for (const timer of this.debounceTimers.values()) { clearTimeout(timer); } this.debounceTimers.clear(); // Flush and clear the event debouncer this.eventDebouncer.flush(); this.eventDebouncer.clear(); // Close platform watcher (handles all watchers) await this.platformWatcher.closeAll(); this.watchers.clear(); this.watchedPaths.clear(); this.stats.uptime = Date.now() - this.stats.startTime; this.stats.activeWatchers = 0; this.stats.healthyWatchers = 0; this.emit('stopped', { uptime: this.stats.uptime, totalEvents: this.stats.totalEvents, propagatedEvents: this.stats.propagatedEvents, watcherFailures: this.watcherFailures }); } // Add change propagation rule addPropagationRule(rule) { this.propagationRules.set(rule.id, rule); this.stats.activeRules = this.propagationRules.size; this.emit('rule-added', rule); } // Remove change propagation rule removePropagationRule(ruleId) { const removed = this.propagationRules.delete(ruleId); this.stats.activeRules = this.propagationRules.size; if (removed) { this.emit('rule-removed', ruleId); } return removed; } // Get current watcher statistics getStats() { return { ...this.stats, uptime: this.isActive ? Date.now() - this.stats.startTime : this.stats.uptime, watchedPaths: Array.from(this.watchedPaths), activeWatchers: this.platformWatcher.getActiveWatchersCount(), watcherFailures: this.watcherFailures }; } // Check if currently watching isWatching() { return this.isActive; } // Get platform capabilities getPlatformCapabilities() { return this.platformWatcher.getPlatformCapabilities(); } // Get platform watcher health getPlatformWatcherHealth(watcherId) { return this.platformWatcher.getWatcherHealth(watcherId); } // Test platform capabilities async testPlatformCapabilities() { return await this.platformWatcher.testPlatformCapabilities(); } // Setup debouncer event listeners setupDebouncerListeners() { // Handle individual debounced events this.eventDebouncer.on('debounced-event', (debouncedEvent) => { const event = { type: debouncedEvent.type, path: debouncedEvent.path, workspace: this.getWorkspaceForPath(debouncedEvent.path), timestamp: debouncedEvent.timestamp, size: debouncedEvent.stats?.size, stats: debouncedEvent.stats }; // Emit the debounced file event this.emit('file-event', event); // Process propagation rules this.processPropagationRules(event); }); // Handle batched events this.eventDebouncer.on('batched-events', (batch) => { const events = batch.events.map(debouncedEvent => ({ type: debouncedEvent.type, path: debouncedEvent.path, workspace: this.getWorkspaceForPath(debouncedEvent.path), timestamp: debouncedEvent.timestamp, size: debouncedEvent.stats?.size, stats: debouncedEvent.stats })); // Emit batch event this.emit('batched-file-events', { events, batch, totalEvents: batch.totalEvents, timespan: batch.endTime - batch.startTime }); // Process each event for propagation rules events.forEach(event => this.processPropagationRules(event)); }); // Handle raw event additions for debugging this.eventDebouncer.on('event-added', (event) => { this.emit('raw-event', event); }); } // Get workspace for file path getWorkspaceForPath(filePath) { for (const [workspaceName, workspace] of Object.entries(this.workspaces)) { const workspacePath = path.resolve(this.rootPath, workspace.path); if (filePath.startsWith(workspacePath)) { return workspaceName; } } return 'unknown'; } // Handle file system events handleFileEvent(type, filePath, workspace, stats) { // Update statistics for raw events this.stats.totalEvents++; this.stats.eventsByType[type]++; this.stats.eventsByWorkspace[workspace] = (this.stats.eventsByWorkspace[workspace] || 0) + 1; // Add event to debouncer for intelligent processing this.eventDebouncer.addEvent(type, filePath, stats); } // Process change propagation rules processPropagationRules(event) { for (const rule of this.propagationRules.values()) { if (this.matchesRule(event, rule)) { this.propagateChange(event, rule); } } } // Check if event matches propagation rule matchesRule(event, rule) { // Check source pattern const sourceMatch = typeof rule.sourcePattern === 'string' ? event.path.includes(rule.sourcePattern) : rule.sourcePattern.test(event.path); if (!sourceMatch) return false; // Check condition if provided if (rule.condition && !rule.condition(event, this.workspaces)) { return false; } return true; } // Propagate change to target workspaces propagateChange(event, rule) { const targetWorkspaces = this.resolveTargetWorkspaces(rule.targetWorkspaces, event); if (targetWorkspaces.length === 0) return; const propagationEvent = { rule, sourceEvent: rule.transform ? rule.transform(event) : event, targetWorkspaces, timestamp: Date.now(), actionType: rule.actionType }; // Handle debouncing if specified if (rule.debounceMs && rule.debounceMs > 0) { this.debouncePropagate(propagationEvent, rule); } else { this.emitPropagation(propagationEvent); } } // Handle debounced propagation debouncePropagate(event, rule) { const timerId = `${rule.id}-${event.sourceEvent.path}`; // Clear existing timer if (this.debounceTimers.has(timerId)) { clearTimeout(this.debounceTimers.get(timerId)); } // Set new timer const timer = setTimeout(() => { this.debounceTimers.delete(timerId); this.emitPropagation(event); }, rule.debounceMs); this.debounceTimers.set(timerId, timer); } // Emit propagation event emitPropagation(event) { this.stats.propagatedEvents++; this.emit('propagate', event); } // Resolve target workspaces from rule definition resolveTargetWorkspaces(target, event) { if (target === 'all') { return Object.keys(this.workspaces); } if (Array.isArray(target)) { return target.filter(ws => this.workspaces[ws]); } if (typeof target === 'function') { return Object.keys(this.workspaces).filter(target); } return []; } // Watch root configuration files async watchRootFiles(options) { const rootFiles = [ 're-shell.workspaces.yaml', 're-shell.config.yaml', 'package.json', 'tsconfig.json', '.env', '.env.local' ]; for (const file of rootFiles) { const filePath = path.join(this.rootPath, file); if (await fs.pathExists(filePath)) { try { // Convert to platform watch options const platformOptions = { usePolling: options.usePolling, interval: options.interval, binaryInterval: options.binaryInterval, useNativeWatcher: !options.usePolling, enableFallbacks: options.enableFallbacks, fallbackOptions: options.fallbackOptions, platformSpecific: options.platformSpecific }; const watcher = await this.platformWatcher.createWatcher(filePath, platformOptions); watcher .on('change', (changedPath, stats) => this.handleFileEvent('change', changedPath, 'root', stats)) .on('unlink', (changedPath) => this.handleFileEvent('unlink', changedPath, 'root')) .on('error', (error) => this.handleWatchError(error, 'root')); this.watchers.set(`root-${file}`, watcher); this.watchedPaths.add(filePath); } catch (error) { this.emit('warning', `Failed to watch root file ${file}: ${error instanceof Error ? error.message : String(error)}`); } } } } // Handle watch errors handleWatchError(error, workspace) { const message = error instanceof Error ? error.message : String(error); this.emit('error', new error_handler_1.ValidationError(`File watcher error in ${workspace}: ${message}`)); } // Handle watcher ready state handleWatchReady(workspace, path) { this.emit('watcher-ready', { workspace, path }); } // Configure event debouncer configureDebouncer(options) { this.eventDebouncer.updateOptions(options); } // Add debouncer filter addDebouncerFilter(filter) { this.eventDebouncer.addFilter(filter); } // Get debouncer statistics getDebouncerStats() { return this.eventDebouncer.getStatistics(); } // Flush pending debounced events flushDebouncedEvents() { this.eventDebouncer.flush(); } // Initialize default propagation rules initializeDefaultRules() { // Package.json changes trigger dependency updates this.addPropagationRule({ id: 'package-json-changed', name: 'Package.json Dependencies', description: 'Propagate package.json changes to dependent workspaces', sourcePattern: /package\.json$/, targetWorkspaces: 'all', actionType: 'invalidate-cache', debounceMs: 3000, condition: (event) => event.type === 'change' }); // TypeScript config changes this.addPropagationRule({ id: 'tsconfig-changed', name: 'TypeScript Configuration', description: 'Rebuild TypeScript workspaces when config changes', sourcePattern: /tsconfig.*\.json$/, targetWorkspaces: (workspace) => { const ws = this.workspaces[workspace]; return ws?.type === 'app' || ws?.type === 'lib'; }, actionType: 'rebuild', debounceMs: 2000 }); // Source code changes in libraries this.addPropagationRule({ id: 'lib-source-changed', name: 'Library Source Changes', description: 'Rebuild dependent workspaces when library source changes', sourcePattern: /\.(ts|tsx|js|jsx)$/, targetWorkspaces: [], actionType: 'rebuild', debounceMs: 1000, condition: (event, workspaces) => { const workspace = workspaces[event.workspace]; return workspace?.type === 'lib' && event.type === 'change'; } }); // Environment file changes this.addPropagationRule({ id: 'env-changed', name: 'Environment Variables', description: 'Restart development servers when environment changes', sourcePattern: /\.env/, targetWorkspaces: 'all', actionType: 'restart-dev', debounceMs: 1000 }); // Test file changes this.addPropagationRule({ id: 'test-changed', name: 'Test Files', description: 'Run tests when test files change', sourcePattern: /\.(test|spec)\.(ts|tsx|js|jsx)$/, targetWorkspaces: (workspace) => !!workspace, actionType: 'run-tests', debounceMs: 500, condition: (event) => event.type === 'change' || event.type === 'add' }); // Configuration file changes this.addPropagationRule({ id: 'config-changed', name: 'Configuration Files', description: 'Restart when configuration files change', sourcePattern: /\.(config|rc)\.(js|json|yaml|yml)$/, targetWorkspaces: 'all', actionType: 'restart-dev', debounceMs: 2000 }); } } exports.FileWatcher = FileWatcher; // Utility functions async function createFileWatcher(rootPath, fallbackOptions) { return new FileWatcher(rootPath, fallbackOptions); } // Start watching with workspace definition async function startWorkspaceWatcher(workspaceFile, options, fallbackOptions) { const watcher = new FileWatcher(process.cwd(), fallbackOptions); // Load workspace definition const definition = await loadWorkspaceDefinition(workspaceFile); // Start watching await watcher.startWatching(definition.workspaces, options); return watcher; } // Create cross-platform file watcher with optimized settings async function createCrossPlatformWatcher(rootPath, enableFallbacks = true) { const fallbackOptions = { enableFallbackLogging: true, platformOptimizations: true, adaptivePolling: true, maxRetries: 3, fallbackDelay: 2000 }; const watcher = new FileWatcher(rootPath, fallbackOptions); // Test platform capabilities const capabilities = await watcher.testPlatformCapabilities(); if (capabilities.recommendations.length > 0) { console.log('Platform recommendations:', capabilities.recommendations.join(', ')); } return watcher; } // Helper function to load workspace definition async function loadWorkspaceDefinition(filePath) { if (!(await fs.pathExists(filePath))) { throw new error_handler_1.ValidationError(`Workspace file not found: ${filePath}`); } const content = await fs.readFile(filePath, 'utf8'); const yaml = await Promise.resolve().then(() => __importStar(require('yaml'))); return yaml.parse(content); } // Export platform capabilities functions function getPlatformCapabilities() { return (0, platform_watcher_1.getPlatformCapabilities)(); } async function testPlatformWatching() { return await (0, platform_watcher_1.testPlatformWatching)(); }