@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
JavaScript
"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, '-');
}