@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
631 lines (630 loc) • 26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandConflictResolver = exports.ConflictSeverity = exports.ConflictType = exports.ConflictResolutionStrategy = void 0;
exports.createConflictResolver = createConflictResolver;
exports.getConflictSeverityColor = getConflictSeverityColor;
exports.formatConflictType = formatConflictType;
const events_1 = require("events");
const error_handler_1 = require("./error-handler");
// Conflict resolution strategies
var ConflictResolutionStrategy;
(function (ConflictResolutionStrategy) {
ConflictResolutionStrategy["FIRST_WINS"] = "first-wins";
ConflictResolutionStrategy["LAST_WINS"] = "last-wins";
ConflictResolutionStrategy["PRIORITY"] = "priority";
ConflictResolutionStrategy["NAMESPACE"] = "namespace";
ConflictResolutionStrategy["INTERACTIVE"] = "interactive";
ConflictResolutionStrategy["AUTO_MERGE"] = "auto-merge";
ConflictResolutionStrategy["DISABLE_ALL"] = "disable-all";
})(ConflictResolutionStrategy || (exports.ConflictResolutionStrategy = ConflictResolutionStrategy = {}));
// Conflict types
var ConflictType;
(function (ConflictType) {
ConflictType["COMMAND_NAME"] = "command-name";
ConflictType["ALIAS"] = "alias";
ConflictType["SUBCOMMAND"] = "subcommand";
ConflictType["OPTION"] = "option";
ConflictType["DESCRIPTION"] = "description";
})(ConflictType || (exports.ConflictType = ConflictType = {}));
// Conflict severity levels
var ConflictSeverity;
(function (ConflictSeverity) {
ConflictSeverity["LOW"] = "low";
ConflictSeverity["MEDIUM"] = "medium";
ConflictSeverity["HIGH"] = "high";
ConflictSeverity["CRITICAL"] = "critical";
})(ConflictSeverity || (exports.ConflictSeverity = ConflictSeverity = {}));
// Command conflict resolver
class CommandConflictResolver extends events_1.EventEmitter {
constructor(priorityConfig, resolutionPolicy) {
super();
this.conflicts = new Map();
this.commands = new Map();
this.resolutionHistory = [];
this.autoResolutionCount = 0;
this.priorityConfig = {
pluginPriorities: new Map([
['core', 1000],
['system', 900],
['official', 800],
['verified', 700],
['community', 500],
['user', 300]
]),
categoryPriorities: new Map([
['system', 1000],
['core', 900],
['dev-tools', 800],
['productivity', 700],
['utility', 600],
['extension', 500]
]),
defaultPriority: 100,
userOverrides: new Map(),
systemCommands: new Set(['help', 'version', 'init', 'config']),
...priorityConfig
};
this.resolutionPolicy = {
defaultStrategy: ConflictResolutionStrategy.PRIORITY,
strategyByType: new Map([
[ConflictType.COMMAND_NAME, ConflictResolutionStrategy.PRIORITY],
[ConflictType.ALIAS, ConflictResolutionStrategy.NAMESPACE],
[ConflictType.SUBCOMMAND, ConflictResolutionStrategy.AUTO_MERGE],
[ConflictType.OPTION, ConflictResolutionStrategy.PRIORITY]
]),
allowAutoResolution: true,
requireConfirmation: false,
maxAutoResolutions: 10,
preserveSystemCommands: true,
namespacePrefix: 'plugin',
...resolutionPolicy
};
}
// Register commands for conflict detection
registerCommands(commands) {
this.commands.clear();
commands.forEach(cmd => {
this.commands.set(cmd.id, cmd);
});
this.detectConflicts();
}
// Detect all conflicts
detectConflicts() {
this.conflicts.clear();
const detectedConflicts = [];
// Detect command name conflicts
detectedConflicts.push(...this.detectCommandNameConflicts());
// Detect alias conflicts
detectedConflicts.push(...this.detectAliasConflicts());
// Detect option conflicts
detectedConflicts.push(...this.detectOptionConflicts());
// Store conflicts
detectedConflicts.forEach(conflict => {
this.conflicts.set(conflict.id, conflict);
});
this.emit('conflicts-detected', detectedConflicts);
return detectedConflicts;
}
// Detect command name conflicts
detectCommandNameConflicts() {
const conflicts = [];
const nameGroups = new Map();
// Group commands by name
Array.from(this.commands.values()).forEach(cmd => {
const name = cmd.definition.name;
if (!nameGroups.has(name)) {
nameGroups.set(name, []);
}
nameGroups.get(name).push(cmd);
});
// Find conflicts
nameGroups.forEach((commands, name) => {
if (commands.length > 1) {
const conflict = this.createCommandNameConflict(name, commands);
conflicts.push(conflict);
}
});
return conflicts;
}
// Detect alias conflicts
detectAliasConflicts() {
const conflicts = [];
const aliasMap = new Map();
// Collect all aliases
Array.from(this.commands.values()).forEach(cmd => {
if (cmd.definition.aliases) {
cmd.definition.aliases.forEach(alias => {
if (!aliasMap.has(alias)) {
aliasMap.set(alias, []);
}
aliasMap.get(alias).push(cmd);
});
}
});
// Find conflicts
aliasMap.forEach((commands, alias) => {
if (commands.length > 1) {
const conflict = this.createAliasConflict(alias, commands);
conflicts.push(conflict);
}
});
return conflicts;
}
// Detect option conflicts within commands
detectOptionConflicts() {
const conflicts = [];
Array.from(this.commands.values()).forEach(cmd => {
if (cmd.definition.options) {
const optionFlags = new Map();
cmd.definition.options.forEach(option => {
const flag = this.normalizeFlag(option.flag);
optionFlags.set(flag, (optionFlags.get(flag) || 0) + 1);
});
optionFlags.forEach((count, flag) => {
if (count > 1) {
const conflict = this.createOptionConflict(cmd, flag);
conflicts.push(conflict);
}
});
}
});
return conflicts;
}
// Create command name conflict
createCommandNameConflict(name, commands) {
const severity = this.priorityConfig.systemCommands.has(name)
? ConflictSeverity.CRITICAL
: ConflictSeverity.HIGH;
const suggestions = this.generateConflictSuggestions(name, commands, ConflictType.COMMAND_NAME);
return {
id: `cmd_${name}_${Date.now()}`,
type: ConflictType.COMMAND_NAME,
severity,
conflictingCommands: commands.map(c => c.id),
conflictingPlugins: [...new Set(commands.map(c => c.pluginName))],
conflictValue: name,
description: `Multiple commands registered with name '${name}'`,
suggestions,
autoResolvable: severity !== ConflictSeverity.CRITICAL && suggestions.some(s => s.autoApplicable),
priority: this.calculateConflictPriority(commands),
detectedAt: Date.now(),
resolved: false
};
}
// Create alias conflict
createAliasConflict(alias, commands) {
const suggestions = this.generateConflictSuggestions(alias, commands, ConflictType.ALIAS);
return {
id: `alias_${alias}_${Date.now()}`,
type: ConflictType.ALIAS,
severity: ConflictSeverity.MEDIUM,
conflictingCommands: commands.map(c => c.id),
conflictingPlugins: [...new Set(commands.map(c => c.pluginName))],
conflictValue: alias,
description: `Multiple commands registered with alias '${alias}'`,
suggestions,
autoResolvable: suggestions.some(s => s.autoApplicable),
priority: this.calculateConflictPriority(commands),
detectedAt: Date.now(),
resolved: false
};
}
// Create option conflict
createOptionConflict(command, flag) {
return {
id: `opt_${command.id}_${flag}_${Date.now()}`,
type: ConflictType.OPTION,
severity: ConflictSeverity.LOW,
conflictingCommands: [command.id],
conflictingPlugins: [command.pluginName],
conflictValue: flag,
description: `Duplicate option flag '${flag}' in command '${command.definition.name}'`,
suggestions: [{
type: 'rename',
description: 'Rename duplicate option flags',
action: `Rename conflicting '${flag}' options`,
impact: 'low',
autoApplicable: true,
confidence: 0.9
}],
autoResolvable: true,
priority: 1,
detectedAt: Date.now(),
resolved: false
};
}
// Generate conflict suggestions
generateConflictSuggestions(conflictValue, commands, type) {
const suggestions = [];
// Priority-based resolution
if (commands.length === 2) {
const priorities = commands.map(cmd => this.calculateCommandPriority(cmd));
const maxPriority = Math.max(...priorities);
const hasUniqueHighest = priorities.filter(p => p === maxPriority).length === 1;
if (hasUniqueHighest) {
suggestions.push({
type: 'priority',
description: 'Resolve based on plugin priority',
action: 'Keep highest priority command, disable others',
impact: 'medium',
autoApplicable: true,
confidence: 0.8
});
}
}
// Namespace resolution
suggestions.push({
type: 'namespace',
description: 'Add plugin namespace prefix',
action: `Rename to ${this.resolutionPolicy.namespacePrefix}:pluginname:${conflictValue}`,
impact: 'low',
autoApplicable: type !== ConflictType.COMMAND_NAME,
confidence: 0.9
});
// Rename suggestions
commands.forEach((cmd, index) => {
if (index > 0) { // Keep first command as-is
suggestions.push({
type: 'rename',
description: `Rename ${cmd.pluginName} command`,
action: `Rename to ${conflictValue}-${cmd.pluginName.toLowerCase()}`,
impact: 'medium',
autoApplicable: false,
confidence: 0.7
});
}
});
// Disable resolution
if (commands.length > 2) {
suggestions.push({
type: 'disable',
description: 'Disable lower priority commands',
action: 'Keep highest priority, disable others',
impact: 'high',
autoApplicable: false,
confidence: 0.6
});
}
return suggestions;
}
// Calculate command priority
calculateCommandPriority(command) {
let priority = this.resolutionPolicy.preserveSystemCommands &&
this.priorityConfig.systemCommands.has(command.definition.name)
? 10000 : 0;
// User overrides have highest priority
const userOverride = this.priorityConfig.userOverrides.get(command.id);
if (userOverride !== undefined) {
return priority + userOverride;
}
// Plugin-based priority
const pluginPriority = this.priorityConfig.pluginPriorities.get(command.pluginName) ||
this.priorityConfig.defaultPriority;
priority += pluginPriority;
// Category-based priority
if (command.definition.category) {
const categoryPriority = this.priorityConfig.categoryPriorities.get(command.definition.category) || 0;
priority += categoryPriority * 0.1; // Category has less weight than plugin
}
// Command-specific priority
priority += command.definition.priority || 0;
// Registration time (earlier = higher priority)
priority += Math.max(0, 1000 - (Date.now() - command.registeredAt) / 1000);
return priority;
}
// Calculate conflict priority
calculateConflictPriority(commands) {
const priorities = commands.map(cmd => this.calculateCommandPriority(cmd));
return Math.max(...priorities);
}
// Resolve conflict
async resolveConflict(conflictId, strategy, options = {}) {
const conflict = this.conflicts.get(conflictId);
if (!conflict) {
throw new error_handler_1.ValidationError(`Conflict '${conflictId}' not found`);
}
if (conflict.resolved) {
throw new error_handler_1.ValidationError(`Conflict '${conflictId}' already resolved`);
}
const resolveStrategy = strategy ||
this.resolutionPolicy.strategyByType.get(conflict.type) ||
this.resolutionPolicy.defaultStrategy;
if (this.resolutionPolicy.requireConfirmation && !options.userConfirmed && !options.dryRun) {
throw new error_handler_1.ValidationError('User confirmation required for conflict resolution');
}
if (this.autoResolutionCount >= this.resolutionPolicy.maxAutoResolutions && !options.userConfirmed) {
throw new error_handler_1.ValidationError('Maximum auto-resolution limit reached');
}
const resolution = {
strategy: resolveStrategy,
appliedAt: Date.now(),
appliedBy: options.userConfirmed ? 'user' : 'auto',
actions: [],
success: false,
errors: [],
reversible: true
};
this.emit('conflict-resolution-started', { conflictId, strategy: resolveStrategy });
try {
switch (resolveStrategy) {
case ConflictResolutionStrategy.PRIORITY:
resolution.actions = await this.resolveBypriority(conflict, options.dryRun);
break;
case ConflictResolutionStrategy.NAMESPACE:
resolution.actions = await this.resolveByNamespace(conflict, options.dryRun);
break;
case ConflictResolutionStrategy.FIRST_WINS:
resolution.actions = await this.resolveByFirstWins(conflict, options.dryRun);
break;
case ConflictResolutionStrategy.LAST_WINS:
resolution.actions = await this.resolveByLastWins(conflict, options.dryRun);
break;
case ConflictResolutionStrategy.DISABLE_ALL:
resolution.actions = await this.resolveByDisableAll(conflict, options.dryRun);
break;
default:
throw new error_handler_1.ValidationError(`Unsupported resolution strategy: ${resolveStrategy}`);
}
resolution.success = resolution.actions.every(action => action.applied);
if (resolution.success && !options.dryRun) {
conflict.resolved = true;
conflict.resolution = resolution;
this.autoResolutionCount++;
}
this.resolutionHistory.push(resolution);
this.emit('conflict-resolved', { conflictId, resolution });
}
catch (error) {
resolution.errors.push(error instanceof Error ? error.message : String(error));
this.emit('conflict-resolution-failed', { conflictId, error });
}
return resolution;
}
// Resolve by priority
async resolveBypriority(conflict, dryRun) {
const actions = [];
const commands = conflict.conflictingCommands.map(id => this.commands.get(id));
// Sort by priority (highest first)
const sortedCommands = commands.sort((a, b) => this.calculateCommandPriority(b) - this.calculateCommandPriority(a));
// Keep highest priority, disable others
for (let i = 1; i < sortedCommands.length; i++) {
const action = {
type: 'disable',
target: sortedCommands[i].id,
details: { reason: 'lower priority in conflict resolution' },
applied: false
};
if (!dryRun) {
try {
// In real implementation, would disable the command
sortedCommands[i].isActive = false;
action.applied = true;
}
catch (error) {
action.error = error instanceof Error ? error.message : String(error);
}
}
else {
action.applied = true; // Assume success for dry run
}
actions.push(action);
}
return actions;
}
// Resolve by namespace
async resolveByNamespace(conflict, dryRun) {
const actions = [];
const commands = conflict.conflictingCommands.map(id => this.commands.get(id));
for (let i = 1; i < commands.length; i++) { // Keep first command unchanged
const cmd = commands[i];
const newName = `${this.resolutionPolicy.namespacePrefix}:${cmd.pluginName}:${conflict.conflictValue}`;
const action = {
type: 'namespace',
target: cmd.id,
details: {
originalName: conflict.conflictValue,
newName,
prefix: `${this.resolutionPolicy.namespacePrefix}:${cmd.pluginName}`
},
applied: false
};
if (!dryRun) {
try {
// In real implementation, would rename the command
cmd.definition.name = newName;
action.applied = true;
}
catch (error) {
action.error = error instanceof Error ? error.message : String(error);
}
}
else {
action.applied = true;
}
actions.push(action);
}
return actions;
}
// Resolve by first wins
async resolveByFirstWins(conflict, dryRun) {
const actions = [];
const commands = conflict.conflictingCommands.map(id => this.commands.get(id));
// Sort by registration time (earliest first)
const sortedCommands = commands.sort((a, b) => a.registeredAt - b.registeredAt);
// Disable all except first
for (let i = 1; i < sortedCommands.length; i++) {
const action = {
type: 'disable',
target: sortedCommands[i].id,
details: { reason: 'first-wins policy' },
applied: false
};
if (!dryRun) {
try {
sortedCommands[i].isActive = false;
action.applied = true;
}
catch (error) {
action.error = error instanceof Error ? error.message : String(error);
}
}
else {
action.applied = true;
}
actions.push(action);
}
return actions;
}
// Resolve by last wins
async resolveByLastWins(conflict, dryRun) {
const actions = [];
const commands = conflict.conflictingCommands.map(id => this.commands.get(id));
// Sort by registration time (latest first)
const sortedCommands = commands.sort((a, b) => b.registeredAt - a.registeredAt);
// Disable all except last (first in sorted array)
for (let i = 1; i < sortedCommands.length; i++) {
const action = {
type: 'disable',
target: sortedCommands[i].id,
details: { reason: 'last-wins policy' },
applied: false
};
if (!dryRun) {
try {
sortedCommands[i].isActive = false;
action.applied = true;
}
catch (error) {
action.error = error instanceof Error ? error.message : String(error);
}
}
else {
action.applied = true;
}
actions.push(action);
}
return actions;
}
// Resolve by disabling all
async resolveByDisableAll(conflict, dryRun) {
const actions = [];
const commands = conflict.conflictingCommands.map(id => this.commands.get(id));
for (const cmd of commands) {
const action = {
type: 'disable',
target: cmd.id,
details: { reason: 'disable-all policy' },
applied: false
};
if (!dryRun) {
try {
cmd.isActive = false;
action.applied = true;
}
catch (error) {
action.error = error instanceof Error ? error.message : String(error);
}
}
else {
action.applied = true;
}
actions.push(action);
}
return actions;
}
// Auto-resolve all resolvable conflicts
async autoResolveConflicts() {
if (!this.resolutionPolicy.allowAutoResolution) {
throw new error_handler_1.ValidationError('Auto-resolution is disabled');
}
const resolutions = [];
const autoResolvableConflicts = Array.from(this.conflicts.values())
.filter(c => !c.resolved && c.autoResolvable)
.sort((a, b) => b.priority - a.priority); // Resolve highest priority first
for (const conflict of autoResolvableConflicts) {
if (this.autoResolutionCount >= this.resolutionPolicy.maxAutoResolutions) {
break;
}
try {
const resolution = await this.resolveConflict(conflict.id);
resolutions.push(resolution);
}
catch (error) {
this.emit('auto-resolution-failed', { conflictId: conflict.id, error });
}
}
return resolutions;
}
// Normalize flag for comparison
normalizeFlag(flag) {
return flag.replace(/^-+/, '').toLowerCase();
}
// Get all conflicts
getConflicts() {
return Array.from(this.conflicts.values());
}
// Get unresolved conflicts
getUnresolvedConflicts() {
return Array.from(this.conflicts.values()).filter(c => !c.resolved);
}
// Get conflicts by type
getConflictsByType(type) {
return Array.from(this.conflicts.values()).filter(c => c.type === type);
}
// Get conflicts by severity
getConflictsBySeverity(severity) {
return Array.from(this.conflicts.values()).filter(c => c.severity === severity);
}
// Get resolution history
getResolutionHistory() {
return [...this.resolutionHistory];
}
// Set user priority override
setUserPriorityOverride(commandId, priority) {
this.priorityConfig.userOverrides.set(commandId, priority);
this.emit('priority-override-set', { commandId, priority });
}
// Remove user priority override
removeUserPriorityOverride(commandId) {
this.priorityConfig.userOverrides.delete(commandId);
this.emit('priority-override-removed', { commandId });
}
// Get conflict statistics
getStats() {
const conflicts = Array.from(this.conflicts.values());
return {
total: conflicts.length,
resolved: conflicts.filter(c => c.resolved).length,
unresolved: conflicts.filter(c => !c.resolved).length,
autoResolvable: conflicts.filter(c => c.autoResolvable && !c.resolved).length,
byType: Object.values(ConflictType).reduce((acc, type) => {
acc[type] = conflicts.filter(c => c.type === type).length;
return acc;
}, {}),
bySeverity: Object.values(ConflictSeverity).reduce((acc, severity) => {
acc[severity] = conflicts.filter(c => c.severity === severity).length;
return acc;
}, {}),
resolutionHistory: this.resolutionHistory.length,
autoResolutionCount: this.autoResolutionCount,
priorityOverrides: this.priorityConfig.userOverrides.size
};
}
}
exports.CommandConflictResolver = CommandConflictResolver;
// Utility functions
function createConflictResolver(priorityConfig, resolutionPolicy) {
return new CommandConflictResolver(priorityConfig, resolutionPolicy);
}
function getConflictSeverityColor(severity) {
switch (severity) {
case ConflictSeverity.CRITICAL: return 'red';
case ConflictSeverity.HIGH: return 'magenta';
case ConflictSeverity.MEDIUM: return 'yellow';
case ConflictSeverity.LOW: return 'blue';
default: return 'gray';
}
}
function formatConflictType(type) {
return type.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}