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

671 lines (670 loc) 27.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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PluginLifecycleManager = exports.PluginState = void 0; exports.createPluginLifecycleManager = createPluginLifecycleManager; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const events_1 = require("events"); const chalk_1 = __importDefault(require("chalk")); const error_handler_1 = require("./error-handler"); const plugin_security_1 = require("./plugin-security"); // Plugin lifecycle states var PluginState; (function (PluginState) { PluginState["UNLOADED"] = "unloaded"; PluginState["LOADING"] = "loading"; PluginState["LOADED"] = "loaded"; PluginState["INITIALIZING"] = "initializing"; PluginState["INITIALIZED"] = "initialized"; PluginState["ACTIVATING"] = "activating"; PluginState["ACTIVE"] = "active"; PluginState["DEACTIVATING"] = "deactivating"; PluginState["DEACTIVATED"] = "deactivated"; PluginState["ERROR"] = "error"; })(PluginState || (exports.PluginState = PluginState = {})); // Plugin lifecycle manager class PluginLifecycleManager extends events_1.EventEmitter { constructor(config = {}) { super(); this.plugins = new Map(); this.dependencyGraph = new Map(); this.isInitialized = false; this.hotReloadWatchers = new Map(); this.config = { timeout: 30000, validateSecurity: true, sandboxed: false, enableHotReload: false, preloadDependencies: true, ...config }; } // Initialize the lifecycle manager async initialize() { if (this.isInitialized) return; this.emit('manager-initializing'); try { // Initialize dependency tracking this.buildDependencyGraph(); this.isInitialized = true; this.emit('manager-initialized'); } catch (error) { this.emit('manager-error', error); throw error; } } // Register a plugin for lifecycle management async registerPlugin(registration) { const managedRegistration = { ...registration, state: PluginState.UNLOADED, dependencies: [], dependents: [], lastStateChange: Date.now(), stateHistory: [], errors: [], permissions: registration.manifest.reshell?.permissions || [], performance: { loadDuration: 0, initDuration: 0, activationDuration: 0 } }; // Resolve dependencies managedRegistration.dependencies = await this.resolveDependencies(registration.manifest); this.plugins.set(registration.manifest.name, managedRegistration); this.updateDependencyGraph(registration.manifest.name, managedRegistration.dependencies); this.emit('plugin-registered', { pluginName: registration.manifest.name, registration: managedRegistration }); } // Load a plugin async loadPlugin(pluginName) { const registration = this.plugins.get(pluginName); if (!registration) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not registered`); } if (registration.state !== PluginState.UNLOADED) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' cannot be loaded from state '${registration.state}'`); } await this.transitionState(registration, PluginState.LOADING); try { const startTime = Date.now(); // Validate security permissions if (this.config.validateSecurity) { await this.validatePluginSecurity(registration); } // Load dependencies first if (this.config.preloadDependencies) { await this.loadDependencies(registration); } // Load the plugin module const pluginModule = await this.loadPluginModule(registration); // Validate plugin interface this.validatePluginInterface(pluginModule); registration.instance = pluginModule; registration.loadTime = Date.now(); registration.performance.loadDuration = registration.loadTime - startTime; // Setup hot reload if enabled if (this.config.enableHotReload) { await this.setupHotReload(registration); } await this.transitionState(registration, PluginState.LOADED); } catch (error) { registration.errors.push({ stage: 'load', error: error instanceof Error ? error : new Error(String(error)), timestamp: Date.now() }); await this.transitionState(registration, PluginState.ERROR); throw error; } } // Initialize a loaded plugin async initializePlugin(pluginName, context) { const registration = this.plugins.get(pluginName); if (!registration) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not registered`); } if (registration.state !== PluginState.LOADED) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' must be loaded before initialization. Current state: '${registration.state}'`); } await this.transitionState(registration, PluginState.INITIALIZING); try { const startTime = Date.now(); // Create or use provided context const pluginContext = context || this.createPluginContext(registration); registration.context = pluginContext; // Ensure plugin directories exist await this.ensurePluginDirectories(registration); // Initialize dependencies for (const dep of registration.dependencies) { if (dep.required && !dep.resolved) { throw new error_handler_1.ValidationError(`Required dependency '${dep.name}' is not available for plugin '${pluginName}'`); } } registration.initTime = Date.now(); registration.performance.initDuration = registration.initTime - startTime; await this.transitionState(registration, PluginState.INITIALIZED); } catch (error) { registration.errors.push({ stage: 'initialize', error: error instanceof Error ? error : new Error(String(error)), timestamp: Date.now() }); await this.transitionState(registration, PluginState.ERROR); throw error; } } // Activate an initialized plugin async activatePlugin(pluginName) { const registration = this.plugins.get(pluginName); if (!registration) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not registered`); } if (registration.state !== PluginState.INITIALIZED) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' must be initialized before activation. Current state: '${registration.state}'`); } await this.transitionState(registration, PluginState.ACTIVATING); try { const startTime = Date.now(); // Activate dependencies first await this.activateDependencies(registration); // Call plugin activation if (registration.instance && registration.context) { if (typeof registration.instance.activate === 'function') { await Promise.race([ registration.instance.activate(registration.context), new Promise((_, reject) => setTimeout(() => reject(new Error('Plugin activation timeout')), this.config.timeout)) ]); } } registration.activationTime = Date.now(); registration.performance.activationDuration = registration.activationTime - startTime; registration.isActive = true; // Track memory usage if (global.gc) { global.gc(); registration.memoryUsage = process.memoryUsage(); } await this.transitionState(registration, PluginState.ACTIVE); } catch (error) { registration.errors.push({ stage: 'activate', error: error instanceof Error ? error : new Error(String(error)), timestamp: Date.now() }); await this.transitionState(registration, PluginState.ERROR); throw error; } } // Deactivate an active plugin async deactivatePlugin(pluginName) { const registration = this.plugins.get(pluginName); if (!registration) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not registered`); } if (registration.state !== PluginState.ACTIVE) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not active. Current state: '${registration.state}'`); } await this.transitionState(registration, PluginState.DEACTIVATING); try { // Deactivate dependents first await this.deactivateDependents(registration); // Call plugin deactivation if (registration.instance && registration.context) { if (typeof registration.instance.deactivate === 'function') { await Promise.race([ registration.instance.deactivate(registration.context), new Promise((_, reject) => setTimeout(() => reject(new Error('Plugin deactivation timeout')), this.config.timeout)) ]); } } registration.isActive = false; await this.transitionState(registration, PluginState.DEACTIVATED); } catch (error) { registration.errors.push({ stage: 'deactivate', error: error instanceof Error ? error : new Error(String(error)), timestamp: Date.now() }); await this.transitionState(registration, PluginState.ERROR); throw error; } } // Unload a plugin (deactivate + cleanup) async unloadPlugin(pluginName) { const registration = this.plugins.get(pluginName); if (!registration) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not registered`); } // Deactivate if active if (registration.state === PluginState.ACTIVE) { await this.deactivatePlugin(pluginName); } try { // Cleanup hot reload watcher if (this.hotReloadWatchers.has(pluginName)) { const watcher = this.hotReloadWatchers.get(pluginName); await watcher.close(); this.hotReloadWatchers.delete(pluginName); } // Clear instance and context registration.instance = undefined; registration.context = undefined; registration.isLoaded = false; await this.transitionState(registration, PluginState.UNLOADED); } catch (error) { registration.errors.push({ stage: 'unload', error: error instanceof Error ? error : new Error(String(error)), timestamp: Date.now() }); await this.transitionState(registration, PluginState.ERROR); throw error; } } // Reload a plugin (unload + load + init + activate) async reloadPlugin(pluginName) { const registration = this.plugins.get(pluginName); if (!registration) { throw new error_handler_1.ValidationError(`Plugin '${pluginName}' is not registered`); } const wasActive = registration.state === PluginState.ACTIVE; await this.unloadPlugin(pluginName); await this.loadPlugin(pluginName); await this.initializePlugin(pluginName); if (wasActive) { await this.activatePlugin(pluginName); } } // Load plugin module async loadPluginModule(registration) { const mainFile = path.resolve(registration.pluginPath, registration.manifest.main); if (!await fs.pathExists(mainFile)) { throw new error_handler_1.ValidationError(`Plugin main file not found: ${mainFile}`); } // Clear require cache for hot reload if (require.cache[mainFile]) { delete require.cache[mainFile]; } try { const pluginModule = require(mainFile); // Handle different export patterns if (pluginModule.default) { return pluginModule.default; } else if (typeof pluginModule === 'function') { return pluginModule(); } else if (pluginModule.activate || pluginModule.manifest) { return pluginModule; } else { throw new error_handler_1.ValidationError('Invalid plugin export pattern'); } } catch (error) { throw new error_handler_1.ValidationError(`Failed to load plugin module: ${error instanceof Error ? error.message : String(error)}`); } } // Validate plugin interface validatePluginInterface(plugin) { if (!plugin || typeof plugin !== 'object') { throw new error_handler_1.ValidationError('Plugin must export an object'); } if (plugin.manifest && !this.isValidManifest(plugin.manifest)) { throw new error_handler_1.ValidationError('Plugin manifest is invalid'); } if (plugin.activate && typeof plugin.activate !== 'function') { throw new error_handler_1.ValidationError('Plugin activate must be a function'); } if (plugin.deactivate && typeof plugin.deactivate !== 'function') { throw new error_handler_1.ValidationError('Plugin deactivate must be a function'); } } // Validate plugin security async validatePluginSecurity(registration) { try { const securityValidator = (0, plugin_security_1.createSecurityValidator)((0, plugin_security_1.getDefaultSecurityPolicy)()); const securityResult = await securityValidator.scanPlugin(registration); // Check if plugin is blocked if (securityResult.securityLevel === plugin_security_1.SecurityLevel.BLOCKED || !securityResult.approved) { const criticalViolations = securityResult.violations.filter(v => v.severity === 'critical' || v.blocked); if (criticalViolations.length > 0) { const violationDescriptions = criticalViolations.map(v => v.description).join(', '); throw new error_handler_1.ValidationError(`Plugin blocked due to security violations: ${violationDescriptions}`); } } // Store security result in registration for later use registration.securityResult = securityResult; // Emit security validation event this.emit('security-validated', { pluginName: registration.manifest.name, securityLevel: securityResult.securityLevel, violations: securityResult.violations.length, approved: securityResult.approved }); } catch (error) { throw new error_handler_1.ValidationError(`Security validation failed for plugin '${registration.manifest.name}': ${error instanceof Error ? error.message : String(error)}`); } } // Resolve plugin dependencies async resolveDependencies(manifest) { const dependencies = []; if (manifest.dependencies) { for (const [name, version] of Object.entries(manifest.dependencies)) { dependencies.push({ name, version, required: true, resolved: false }); } } if (manifest.peerDependencies) { for (const [name, version] of Object.entries(manifest.peerDependencies)) { dependencies.push({ name, version, required: false, resolved: false }); } } // Check which dependencies are available for (const dep of dependencies) { if (this.plugins.has(dep.name)) { dep.resolved = true; dep.instance = this.plugins.get(dep.name)?.instance; } } return dependencies; } // Load plugin dependencies async loadDependencies(registration) { for (const dep of registration.dependencies) { if (dep.required && !dep.resolved) { const depRegistration = this.plugins.get(dep.name); if (depRegistration && depRegistration.state === PluginState.UNLOADED) { await this.loadPlugin(dep.name); dep.resolved = true; dep.instance = depRegistration.instance; } } } } // Activate plugin dependencies async activateDependencies(registration) { for (const dep of registration.dependencies) { if (dep.resolved) { const depRegistration = this.plugins.get(dep.name); if (depRegistration && depRegistration.state !== PluginState.ACTIVE) { if (depRegistration.state === PluginState.LOADED) { await this.initializePlugin(dep.name); } if (depRegistration.state === PluginState.INITIALIZED) { await this.activatePlugin(dep.name); } } } } } // Deactivate plugin dependents async deactivateDependents(registration) { for (const dependentName of registration.dependents) { const dependent = this.plugins.get(dependentName); if (dependent && dependent.state === PluginState.ACTIVE) { await this.deactivatePlugin(dependentName); } } } // Build dependency graph buildDependencyGraph() { this.dependencyGraph.clear(); for (const [name, registration] of this.plugins) { this.dependencyGraph.set(name, new Set()); for (const dep of registration.dependencies) { if (dep.resolved) { this.dependencyGraph.get(name).add(dep.name); // Update dependent tracking const depRegistration = this.plugins.get(dep.name); if (depRegistration) { if (!depRegistration.dependents.includes(name)) { depRegistration.dependents.push(name); } } } } } } // Update dependency graph for a plugin updateDependencyGraph(pluginName, dependencies) { if (!this.dependencyGraph.has(pluginName)) { this.dependencyGraph.set(pluginName, new Set()); } const deps = this.dependencyGraph.get(pluginName); deps.clear(); for (const dep of dependencies) { if (dep.resolved) { deps.add(dep.name); // Update dependent tracking const depRegistration = this.plugins.get(dep.name); if (depRegistration && !depRegistration.dependents.includes(pluginName)) { depRegistration.dependents.push(pluginName); } } } } // Setup hot reload for a plugin async setupHotReload(registration) { const chokidar = require('chokidar'); const watcher = chokidar.watch(registration.pluginPath, { ignored: /node_modules/, persistent: true }); watcher.on('change', async (filePath) => { try { this.emit('plugin-file-changed', { pluginName: registration.manifest.name, filePath }); await this.reloadPlugin(registration.manifest.name); this.emit('plugin-hot-reloaded', { pluginName: registration.manifest.name, filePath }); } catch (error) { this.emit('plugin-hot-reload-error', { pluginName: registration.manifest.name, filePath, error }); } }); this.hotReloadWatchers.set(registration.manifest.name, watcher); } // Ensure plugin directories exist async ensurePluginDirectories(registration) { if (registration.context) { await fs.ensureDir(registration.context.plugin.dataPath); await fs.ensureDir(registration.context.plugin.cachePath); } } // Create plugin context createPluginContext(registration) { // This would typically be injected from the main plugin system // For now, create a basic context return { cli: { version: '0.7.0', rootPath: process.cwd(), configPath: path.join(process.cwd(), '.re-shell'), workspaces: {} }, plugin: { name: registration.manifest.name, version: registration.manifest.version, config: {}, dataPath: path.join(process.cwd(), '.re-shell', 'data', registration.manifest.name), cachePath: path.join(process.cwd(), '.re-shell', 'cache', registration.manifest.name) }, logger: this.createLogger(registration.manifest.name), hooks: this.createHookSystem(), utils: this.createUtils() }; } // Create plugin logger createLogger(pluginName) { const prefix = `[${pluginName}]`; return { debug: (msg, ...args) => console.debug(chalk_1.default.gray(`${prefix} ${msg}`), ...args), info: (msg, ...args) => console.info(chalk_1.default.blue(`${prefix} ${msg}`), ...args), warn: (msg, ...args) => console.warn(chalk_1.default.yellow(`${prefix} ${msg}`), ...args), error: (msg, ...args) => console.error(chalk_1.default.red(`${prefix} ${msg}`), ...args) }; } // Create hook system (placeholder - will be injected from main system) createHookSystem() { return { register: () => 'placeholder', unregister: () => false, execute: async () => ({ success: true, results: [], errors: [] }), executeSync: () => [], onCommand: () => 'placeholder', onFileChange: () => 'placeholder', onWorkspaceBuild: () => 'placeholder', getHooks: () => [], registerCustomHook: () => 'placeholder' }; } // Create utils createUtils() { return { path, fs, chalk: chalk_1.default, exec: async () => ({ stdout: '', stderr: '' }), spawn: async () => 0 }; } // Transition plugin state async transitionState(registration, newState) { const oldState = registration.state; const event = { pluginName: registration.manifest.name, oldState, newState, timestamp: Date.now() }; registration.state = newState; registration.lastStateChange = event.timestamp; registration.stateHistory.push(event); this.emit('state-changed', event); this.emit(`state-${newState}`, event); } // Validate manifest isValidManifest(manifest) { return manifest && typeof manifest.name === 'string' && typeof manifest.version === 'string'; } // Get plugin by name getPlugin(name) { return this.plugins.get(name); } // Get all plugins getPlugins() { return Array.from(this.plugins.values()); } // Get plugins by state getPluginsByState(state) { return Array.from(this.plugins.values()).filter(p => p.state === state); } // Get dependency graph getDependencyGraph() { return new Map(this.dependencyGraph); } // Get lifecycle statistics getLifecycleStats() { const stats = { total: this.plugins.size, byState: {}, totalErrors: 0, avgLoadTime: 0, avgInitTime: 0, avgActivationTime: 0 }; let totalLoadTime = 0; let totalInitTime = 0; let totalActivationTime = 0; let loadedCount = 0; for (const plugin of this.plugins.values()) { stats.byState[plugin.state] = (stats.byState[plugin.state] || 0) + 1; stats.totalErrors += plugin.errors.length; if (plugin.performance.loadDuration > 0) { totalLoadTime += plugin.performance.loadDuration; totalInitTime += plugin.performance.initDuration; totalActivationTime += plugin.performance.activationDuration; loadedCount++; } } if (loadedCount > 0) { stats.avgLoadTime = totalLoadTime / loadedCount; stats.avgInitTime = totalInitTime / loadedCount; stats.avgActivationTime = totalActivationTime / loadedCount; } return stats; } } exports.PluginLifecycleManager = PluginLifecycleManager; // Utility functions function createPluginLifecycleManager(config) { return new PluginLifecycleManager(config); }