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