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

709 lines (708 loc) 30.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.PluginCommandRegistry = void 0; exports.createPluginCommandRegistry = createPluginCommandRegistry; exports.validateCommandName = validateCommandName; exports.normalizeCommandName = normalizeCommandName; const path = __importStar(require("path")); const events_1 = require("events"); const chalk_1 = __importDefault(require("chalk")); const commander_1 = require("commander"); const error_handler_1 = require("./error-handler"); const plugin_command_middleware_1 = require("./plugin-command-middleware"); const plugin_command_conflicts_1 = require("./plugin-command-conflicts"); // Plugin command registry class PluginCommandRegistry extends events_1.EventEmitter { constructor(program, config = {}) { super(); this.commands = new Map(); this.aliases = new Map(); // alias -> commandId this.conflicts = new Map(); // commandName -> conflicting commandIds this.isInitialized = false; this.middlewareManager = (0, plugin_command_middleware_1.createMiddlewareChainManager)(); this.conflictResolver = (0, plugin_command_conflicts_1.createConflictResolver)(); this.program = program; this.config = { allowConflicts: false, conflictResolution: 'priority', enableMiddleware: true, validatePermissions: true, trackUsage: true, logCommands: true, ...config }; } // Initialize the command registry async initialize() { if (this.isInitialized) return; this.emit('registry-initializing'); try { // Setup command tracking if (this.config.trackUsage) { this.setupUsageTracking(); } this.isInitialized = true; this.emit('registry-initialized'); } catch (error) { this.emit('registry-error', error); throw error; } } // Register a command from a plugin async registerCommand(plugin, definition) { if (!this.isInitialized) { throw new error_handler_1.ValidationError('Command registry not initialized'); } const commandId = this.generateCommandId(plugin.manifest.name, definition.name); this.emit('command-registering', { pluginName: plugin.manifest.name, definition }); try { // Validate command definition this.validateCommandDefinition(definition); // Check for conflicts const conflicts = this.checkForConflicts(definition); if (conflicts.length > 0 && !this.config.allowConflicts) { // Try auto-resolution if enabled if (this.config.conflictResolution === 'priority') { try { // Update conflict resolver with current commands this.conflictResolver.registerCommands(Array.from(this.commands.values())); await this.conflictResolver.autoResolveConflicts(); } catch (error) { // Auto-resolution failed, report conflict const result = { success: false, commandId, conflicts, warnings: [], errors: [`Command conflicts detected: ${conflicts.join(', ')}`] }; this.emit('command-registration-failed', result); return result; } } else { const result = { success: false, commandId, conflicts, warnings: [], errors: [`Command conflicts detected: ${conflicts.join(', ')}`] }; this.emit('command-registration-failed', result); return result; } } // Create Commander command const commanderCommand = this.createCommanderCommand(plugin, definition); // Register command const registeredCommand = { id: commandId, pluginName: plugin.manifest.name, definition, commanderCommand, registeredAt: Date.now(), usageCount: 0, isActive: true, conflicts }; this.commands.set(commandId, registeredCommand); // Register aliases if (definition.aliases) { definition.aliases.forEach(alias => { this.aliases.set(alias, commandId); }); } // Update conflict tracking if (conflicts.length > 0) { this.updateConflictTracking(definition.name, commandId); } const result = { success: true, commandId, conflicts, warnings: conflicts.length > 0 ? [`Command has conflicts: ${conflicts.join(', ')}`] : [], errors: [] }; this.emit('command-registered', { pluginName: plugin.manifest.name, commandId, definition, result }); return result; } catch (error) { const result = { success: false, commandId, conflicts: [], warnings: [], errors: [error instanceof Error ? error.message : String(error)] }; this.emit('command-registration-failed', result); return result; } } // Unregister a command async unregisterCommand(commandId) { const command = this.commands.get(commandId); if (!command) { return false; } this.emit('command-unregistering', { commandId, command }); try { // Remove from Commander const parent = command.commanderCommand.parent; if (parent) { // Remove from parent's commands (cast to any to access internal properties) const parentAny = parent; if (parentAny.commands && Array.isArray(parentAny.commands)) { const index = parentAny.commands.indexOf(command.commanderCommand); if (index !== -1) { parentAny.commands.splice(index, 1); } } } // Remove aliases if (command.definition.aliases) { command.definition.aliases.forEach(alias => { this.aliases.delete(alias); }); } // Remove from conflicts this.removeFromConflictTracking(command.definition.name, commandId); // Remove command this.commands.delete(commandId); this.emit('command-unregistered', { commandId, command }); return true; } catch (error) { this.emit('command-unregistration-failed', { commandId, error }); return false; } } // Unregister all commands from a plugin async unregisterPluginCommands(pluginName) { const pluginCommands = Array.from(this.commands.values()) .filter(cmd => cmd.pluginName === pluginName); let unregisteredCount = 0; for (const command of pluginCommands) { const success = await this.unregisterCommand(command.id); if (success) { unregisteredCount++; } } this.emit('plugin-commands-unregistered', { pluginName, count: unregisteredCount }); return unregisteredCount; } // Get registered command by ID getCommand(commandId) { return this.commands.get(commandId); } // Get all registered commands getCommands() { return Array.from(this.commands.values()); } // Get commands by plugin getPluginCommands(pluginName) { return Array.from(this.commands.values()) .filter(cmd => cmd.pluginName === pluginName); } // Get command by name or alias findCommand(nameOrAlias) { // Check direct command names for (const command of this.commands.values()) { if (command.definition.name === nameOrAlias) { return command; } } // Check aliases const commandId = this.aliases.get(nameOrAlias); if (commandId) { return this.commands.get(commandId); } return undefined; } // List all conflicts getConflicts() { return new Map(this.conflicts); } // Resolve command conflicts async resolveConflicts(commandName, resolution) { const conflictingIds = this.conflicts.get(commandName); if (!conflictingIds || conflictingIds.length <= 1) { return false; } this.emit('conflict-resolving', { commandName, conflictingIds, resolution }); try { if (resolution === 'disable') { // Disable all but the first command for (let i = 1; i < conflictingIds.length; i++) { const command = this.commands.get(conflictingIds[i]); if (command) { command.isActive = false; command.commanderCommand.hidden = true; } } } else if (resolution === 'priority') { // Sort by priority and disable lower priority commands const commands = conflictingIds .map(id => this.commands.get(id)) .filter(cmd => cmd !== undefined) .sort((a, b) => (b.definition.priority || 0) - (a.definition.priority || 0)); for (let i = 1; i < commands.length; i++) { commands[i].isActive = false; commands[i].commanderCommand.hidden = true; } } this.emit('conflict-resolved', { commandName, conflictingIds, resolution }); return true; } catch (error) { this.emit('conflict-resolution-failed', { commandName, conflictingIds, resolution, error }); return false; } } // Validate command definition validateCommandDefinition(definition) { if (!definition.name || typeof definition.name !== 'string') { throw new error_handler_1.ValidationError('Command name is required and must be a string'); } if (!definition.description || typeof definition.description !== 'string') { throw new error_handler_1.ValidationError('Command description is required and must be a string'); } if (definition.name.includes(' ')) { throw new error_handler_1.ValidationError('Command name cannot contain spaces'); } if (!/^[a-z][a-z0-9-]*$/.test(definition.name)) { throw new error_handler_1.ValidationError('Command name must be lowercase and contain only letters, numbers, and hyphens'); } if (typeof definition.handler !== 'function') { throw new error_handler_1.ValidationError('Command handler must be a function'); } // Validate arguments if (definition.arguments) { definition.arguments.forEach((arg, index) => { if (!arg.name || typeof arg.name !== 'string') { throw new error_handler_1.ValidationError(`Argument ${index} name is required and must be a string`); } if (typeof arg.required !== 'boolean') { throw new error_handler_1.ValidationError(`Argument ${index} required property must be a boolean`); } }); } // Validate options if (definition.options) { definition.options.forEach((opt, index) => { if (!opt.flag || typeof opt.flag !== 'string') { throw new error_handler_1.ValidationError(`Option ${index} flag is required and must be a string`); } if (!opt.flag.startsWith('-')) { throw new error_handler_1.ValidationError(`Option ${index} flag must start with '-'`); } }); } } // Check for command conflicts checkForConflicts(definition) { const conflicts = []; // Check command name conflicts const existingCommand = this.findCommand(definition.name); if (existingCommand) { conflicts.push(existingCommand.id); } // Check alias conflicts if (definition.aliases) { definition.aliases.forEach(alias => { const existingCommand = this.findCommand(alias); if (existingCommand && !conflicts.includes(existingCommand.id)) { conflicts.push(existingCommand.id); } }); } return conflicts; } // Create Commander command from definition createCommanderCommand(plugin, definition) { const command = new commander_1.Command(definition.name); command.description(definition.description); // Add aliases if (definition.aliases) { definition.aliases.forEach(alias => { command.alias(alias); }); } // Add arguments if (definition.arguments) { definition.arguments.forEach(arg => { const argString = arg.required ? `<${arg.name}>` : `[${arg.name}]`; command.argument(argString, arg.description, arg.defaultValue); }); } // Add options if (definition.options) { definition.options.forEach(opt => { command.option(opt.flag, opt.description, opt.defaultValue); }); } // Set action handler command.action(async (...args) => { const commandArgs = args.slice(0, -1); // Remove options object const options = args[args.length - 1]; // Last argument is options // Track usage if (this.config.trackUsage) { this.trackCommandUsage(this.generateCommandId(plugin.manifest.name, definition.name)); } // Create context const context = this.createCommandContext(plugin, definition); let processedArgs = {}; let processedOptions = {}; try { // Process arguments processedArgs = this.processArguments(definition, commandArgs); processedOptions = this.processOptions(definition, options); // Execute middleware chain if (this.config.enableMiddleware) { // Execute pre-validation middleware await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.PRE_VALIDATION, processedArgs, processedOptions, context); // Execute validation middleware await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.VALIDATION, processedArgs, processedOptions, context); // Execute authorization middleware await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.AUTHORIZATION, processedArgs, processedOptions, context); // Execute pre-execution middleware await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.PRE_EXECUTION, processedArgs, processedOptions, context); // Execute command-specific middleware if (definition.middleware) { for (const middleware of definition.middleware) { await new Promise((resolve) => { middleware(processedArgs, processedOptions, context, async () => { resolve(); }); }); } } } // Execute command handler await definition.handler(processedArgs, processedOptions, context); // Execute post-execution middleware if (this.config.enableMiddleware) { await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.POST_EXECUTION, processedArgs, processedOptions, context); // Execute logging middleware await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.LOGGER, processedArgs, processedOptions, context); } } catch (error) { // Execute error handling middleware if (this.config.enableMiddleware && processedArgs && processedOptions) { try { await this.middlewareManager.executeChain(plugin_command_middleware_1.MiddlewareType.ERROR_HANDLER, processedArgs, processedOptions, context); } catch (middlewareError) { // Log middleware error but continue with original error context.logger.error(`Error handling middleware failed: ${middlewareError instanceof Error ? middlewareError.message : String(middlewareError)}`); } } this.emit('command-execution-error', { pluginName: plugin.manifest.name, commandName: definition.name, error }); context.logger.error(`Command execution failed: ${error instanceof Error ? error.message : String(error)}`); throw error; } }); // Hide if deprecated or hidden if (definition.hidden || definition.deprecated) { command.hidden = true; } // Add to parent program this.program.addCommand(command); return command; } // Process command arguments processArguments(definition, args) { const processed = {}; if (definition.arguments) { definition.arguments.forEach((argDef, index) => { const value = args[index]; if (argDef.required && (value === undefined || value === null)) { throw new error_handler_1.ValidationError(`Required argument '${argDef.name}' is missing`); } if (value !== undefined) { // Type conversion let convertedValue = value; if (argDef.type === 'number') { convertedValue = Number(value); if (isNaN(convertedValue)) { throw new error_handler_1.ValidationError(`Argument '${argDef.name}' must be a number`); } } else if (argDef.type === 'boolean') { convertedValue = Boolean(value); } // Choice validation if (argDef.choices && !argDef.choices.includes(convertedValue)) { throw new error_handler_1.ValidationError(`Argument '${argDef.name}' must be one of: ${argDef.choices.join(', ')}`); } // Custom validation if (argDef.validation) { const validationResult = argDef.validation(convertedValue); if (validationResult !== true) { const errorMsg = typeof validationResult === 'string' ? validationResult : `Argument '${argDef.name}' is invalid`; throw new error_handler_1.ValidationError(errorMsg); } } processed[argDef.name] = convertedValue; } else if (argDef.defaultValue !== undefined) { processed[argDef.name] = argDef.defaultValue; } }); } return processed; } // Process command options processOptions(definition, options) { const processed = { ...options }; if (definition.options) { definition.options.forEach(optDef => { const flagName = this.extractOptionName(optDef.flag); const value = options[flagName]; if (optDef.required && (value === undefined || value === null)) { throw new error_handler_1.ValidationError(`Required option '${optDef.flag}' is missing`); } if (value !== undefined) { // Type conversion let convertedValue = value; if (optDef.type === 'number') { convertedValue = Number(value); if (isNaN(convertedValue)) { throw new error_handler_1.ValidationError(`Option '${optDef.flag}' must be a number`); } } else if (optDef.type === 'boolean') { convertedValue = Boolean(value); } // Choice validation if (optDef.choices && !optDef.choices.includes(convertedValue)) { throw new error_handler_1.ValidationError(`Option '${optDef.flag}' must be one of: ${optDef.choices.join(', ')}`); } // Custom validation if (optDef.validation) { const validationResult = optDef.validation(convertedValue); if (validationResult !== true) { const errorMsg = typeof validationResult === 'string' ? validationResult : `Option '${optDef.flag}' is invalid`; throw new error_handler_1.ValidationError(errorMsg); } } processed[flagName] = convertedValue; } }); // Check option conflicts and implications this.validateOptionRelationships(definition.options, processed); } return processed; } // Validate option conflicts and implications validateOptionRelationships(options, processedOptions) { for (const option of options) { const flagName = this.extractOptionName(option.flag); if (processedOptions[flagName] !== undefined) { // Check conflicts if (option.conflicts) { for (const conflictFlag of option.conflicts) { const conflictName = this.extractOptionName(conflictFlag); if (processedOptions[conflictName] !== undefined) { throw new error_handler_1.ValidationError(`Option '${option.flag}' conflicts with '${conflictFlag}'`); } } } // Check implications if (option.implies) { for (const impliedFlag of option.implies) { const impliedName = this.extractOptionName(impliedFlag); if (processedOptions[impliedName] === undefined) { throw new error_handler_1.ValidationError(`Option '${option.flag}' requires '${impliedFlag}' to be specified`); } } } } } } // Get middleware manager getMiddlewareManager() { return this.middlewareManager; } // Get conflict resolver getConflictResolver() { return this.conflictResolver; } // Update conflict resolver with current commands updateConflictResolver() { this.conflictResolver.registerCommands(Array.from(this.commands.values())); } // Create command execution context createCommandContext(plugin, definition) { return { command: definition, plugin, cli: { program: this.program, rootPath: process.cwd(), configPath: path.join(process.cwd(), '.re-shell'), version: '0.7.0' // Would get from package.json }, logger: this.createLogger(plugin.manifest.name, definition.name), utils: { path, chalk: chalk_1.default, spinner: null // Would inject spinner utility } }; } // Create command logger createLogger(pluginName, commandName) { const prefix = `[${pluginName}:${commandName}]`; 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) }; } // Extract option name from flag extractOptionName(flag) { const match = flag.match(/--?([a-zA-Z][a-zA-Z0-9-]*)/); return match ? match[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : flag; } // Generate unique command ID generateCommandId(pluginName, commandName) { return `${pluginName}:${commandName}`; } // Setup usage tracking setupUsageTracking() { // Would implement persistent usage tracking } // Track command usage trackCommandUsage(commandId) { const command = this.commands.get(commandId); if (command) { command.usageCount++; command.lastUsed = Date.now(); } } // Update conflict tracking updateConflictTracking(commandName, commandId) { if (!this.conflicts.has(commandName)) { this.conflicts.set(commandName, []); } this.conflicts.get(commandName).push(commandId); } // Remove from conflict tracking removeFromConflictTracking(commandName, commandId) { const conflicts = this.conflicts.get(commandName); if (conflicts) { const index = conflicts.indexOf(commandId); if (index !== -1) { conflicts.splice(index, 1); if (conflicts.length === 0) { this.conflicts.delete(commandName); } } } } // Get registry statistics getStats() { const stats = { totalCommands: this.commands.size, activeCommands: Array.from(this.commands.values()).filter(cmd => cmd.isActive).length, totalAliases: this.aliases.size, totalConflicts: this.conflicts.size, commandsByPlugin: {}, mostUsedCommands: [], recentCommands: [] }; // Commands by plugin for (const command of this.commands.values()) { stats.commandsByPlugin[command.pluginName] = (stats.commandsByPlugin[command.pluginName] || 0) + 1; } // Most used commands stats.mostUsedCommands = Array.from(this.commands.values()) .filter(cmd => cmd.usageCount > 0) .sort((a, b) => b.usageCount - a.usageCount) .slice(0, 10) .map(cmd => ({ id: cmd.id, name: cmd.definition.name, plugin: cmd.pluginName, usageCount: cmd.usageCount })); // Recent commands stats.recentCommands = Array.from(this.commands.values()) .filter(cmd => cmd.lastUsed) .sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0)) .slice(0, 10) .map(cmd => ({ id: cmd.id, name: cmd.definition.name, plugin: cmd.pluginName, lastUsed: cmd.lastUsed || 0 })); return stats; } } exports.PluginCommandRegistry = PluginCommandRegistry; // Utility functions function createPluginCommandRegistry(program, config) { return new PluginCommandRegistry(program, config); } function validateCommandName(name) { return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes(' '); } function normalizeCommandName(name) { return name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-'); }