@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
508 lines (507 loc) • 18.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginDependencyResolver = void 0;
exports.createDependencyResolver = createDependencyResolver;
exports.validateVersion = validateVersion;
exports.compareVersions = compareVersions;
exports.satisfiesConstraint = satisfiesConstraint;
const events_1 = require("events");
const semver_1 = __importDefault(require("semver"));
// Plugin dependency resolver
class PluginDependencyResolver extends events_1.EventEmitter {
constructor(options = {}) {
super();
this.options = options;
this.dependencyGraph = new Map();
this.versionCache = new Map();
this.resolutionCache = new Map();
this.plugins = new Map();
this.options = {
allowPrerelease: false,
preferStable: true,
ignoreOptional: false,
maxDepth: 10,
timeout: 30000,
strategy: 'strict',
allowConflicts: false,
autoInstall: false,
...options
};
}
// Register available plugins
registerPlugin(registration) {
this.plugins.set(registration.manifest.name, registration);
this.updateDependencyGraph(registration);
this.emit('plugin-registered', registration.manifest.name);
}
// Unregister a plugin
unregisterPlugin(name) {
this.plugins.delete(name);
this.dependencyGraph.delete(name);
this.clearCache();
this.emit('plugin-unregistered', name);
}
// Resolve dependencies for a plugin
async resolveDependencies(manifest, options = {}) {
const resolveOptions = { ...this.options, ...options };
const cacheKey = this.getCacheKey(manifest, resolveOptions);
// Check cache
if (this.resolutionCache.has(cacheKey)) {
const cached = this.resolutionCache.get(cacheKey);
this.emit('resolution-cache-hit', manifest.name);
return cached;
}
const startTime = Date.now();
this.emit('resolution-started', manifest.name);
try {
const result = await this.performResolution(manifest, resolveOptions);
// Cache result
this.resolutionCache.set(cacheKey, result);
const duration = Date.now() - startTime;
this.emit('resolution-completed', {
plugin: manifest.name,
success: result.success,
duration,
conflicts: result.conflicts.length,
missing: result.missing.length
});
return result;
}
catch (error) {
const duration = Date.now() - startTime;
this.emit('resolution-failed', {
plugin: manifest.name,
error,
duration
});
throw error;
}
}
// Perform actual dependency resolution
async performResolution(manifest, options) {
const result = {
resolved: [],
conflicts: [],
missing: [],
circular: [],
installationPlan: [],
success: true,
warnings: []
};
// Extract dependency specifications
const dependencySpecs = this.extractDependencySpecs(manifest);
// Build dependency tree
const dependencyTree = await this.buildDependencyTree(manifest.name, dependencySpecs, options, new Set(), 0);
// Detect circular dependencies
result.circular = this.detectCircularDependencies(dependencyTree);
if (result.circular.length > 0 && options.strategy === 'strict') {
result.success = false;
result.conflicts.push(...result.circular.map(cycle => ({
type: 'circular',
source: cycle[0],
target: cycle[cycle.length - 1],
requested: 'circular',
resolution: {
action: 'remove',
target: cycle[0],
reason: 'Break circular dependency'
}
})));
}
// Resolve version constraints
const constraintResolution = await this.resolveVersionConstraints(dependencyTree, options);
result.resolved = constraintResolution.resolved;
result.conflicts.push(...constraintResolution.conflicts);
result.missing = constraintResolution.missing;
// Create installation plan
if (result.success && options.autoInstall) {
result.installationPlan = this.createInstallationPlan(result.resolved);
}
// Generate warnings
result.warnings = this.generateWarnings(result);
result.success = result.conflicts.length === 0 && result.missing.length === 0;
return result;
}
// Extract dependency specifications from manifest
extractDependencySpecs(manifest) {
const specs = [];
// Plugin dependencies
if (manifest.reshell?.plugins) {
Object.entries(manifest.reshell.plugins).forEach(([name, version]) => {
specs.push({
name,
version: version,
required: true,
type: 'plugin'
});
});
}
// Regular dependencies
if (manifest.dependencies) {
Object.entries(manifest.dependencies).forEach(([name, version]) => {
specs.push({
name,
version,
required: true,
type: 'npm'
});
});
}
// Peer dependencies
if (manifest.peerDependencies) {
Object.entries(manifest.peerDependencies).forEach(([name, version]) => {
specs.push({
name,
version,
required: false,
type: 'peer'
});
});
}
return specs;
}
// Build dependency tree recursively
async buildDependencyTree(pluginName, specs, options, visited, depth) {
const tree = new Map();
if (depth > (options.maxDepth || 10)) {
this.emit('resolution-warning', {
type: 'max-depth',
plugin: pluginName,
depth
});
return tree;
}
if (visited.has(pluginName)) {
return tree; // Avoid infinite recursion
}
visited.add(pluginName);
// Create node for current plugin
const node = {
name: pluginName,
version: '1.0.0', // This would come from manifest
dependencies: new Set(),
dependents: new Set(),
resolved: this.plugins.has(pluginName),
depth,
installation: this.plugins.get(pluginName)
};
tree.set(pluginName, node);
// Process dependencies
for (const spec of specs) {
if (options.ignoreOptional && !spec.required) {
continue;
}
node.dependencies.add(spec.name);
// Get dependency manifest if it's a plugin
if (spec.type === 'plugin' && this.plugins.has(spec.name)) {
const depPlugin = this.plugins.get(spec.name);
const depSpecs = this.extractDependencySpecs(depPlugin.manifest);
// Recursively build tree for dependency
const subTree = await this.buildDependencyTree(spec.name, depSpecs, options, new Set(visited), depth + 1);
// Merge subtree
subTree.forEach((subNode, subName) => {
if (!tree.has(subName)) {
tree.set(subName, subNode);
}
// Update dependents
subNode.dependents.add(pluginName);
});
}
}
return tree;
}
// Detect circular dependencies
detectCircularDependencies(tree) {
const cycles = [];
const visited = new Set();
const recursionStack = new Set();
const dfs = (node, path) => {
if (recursionStack.has(node)) {
// Found cycle
const cycleStart = path.indexOf(node);
if (cycleStart !== -1) {
cycles.push([...path.slice(cycleStart), node]);
}
return;
}
if (visited.has(node)) {
return;
}
visited.add(node);
recursionStack.add(node);
const nodeData = tree.get(node);
if (nodeData) {
nodeData.dependencies.forEach(dep => {
dfs(dep, [...path, node]);
});
}
recursionStack.delete(node);
};
tree.forEach((_, nodeName) => {
if (!visited.has(nodeName)) {
dfs(nodeName, []);
}
});
return cycles;
}
// Resolve version constraints
async resolveVersionConstraints(tree, options) {
const resolved = [];
const conflicts = [];
const missing = [];
// Collect all version constraints
const constraints = new Map();
tree.forEach((node, name) => {
node.dependencies.forEach(depName => {
if (!constraints.has(depName)) {
constraints.set(depName, []);
}
// This would normally come from the dependency specification
constraints.get(depName).push({
constraint: '^1.0.0', // Placeholder
source: name,
type: 'range'
});
});
});
// Resolve each dependency
for (const [depName, depConstraints] of constraints.entries()) {
const resolution = await this.resolveVersionConstraint(depName, depConstraints, options);
if (resolution.success) {
resolved.push(resolution.dependency);
}
else {
if (resolution.missing) {
missing.push(depName);
}
if (resolution.conflicts) {
conflicts.push(...resolution.conflicts);
}
}
}
return { resolved, conflicts, missing };
}
// Resolve single version constraint
async resolveVersionConstraint(depName, constraints, options) {
// Check if plugin is available
const plugin = this.plugins.get(depName);
if (!plugin) {
return {
success: false,
dependency: {
name: depName,
version: 'unknown',
required: true,
type: 'plugin',
resolved: false
},
missing: true
};
}
// Get available versions
const availableVersions = await this.getAvailableVersions(depName);
// Find satisfying version
const satisfyingVersion = this.findSatisfyingVersion(constraints, availableVersions, options);
if (!satisfyingVersion) {
return {
success: false,
dependency: {
name: depName,
version: 'unknown',
required: true,
type: 'plugin',
resolved: false
},
conflicts: [{
type: 'version',
source: constraints[0].source,
target: depName,
requested: constraints[0].constraint,
available: availableVersions[0],
resolution: {
action: 'upgrade',
target: depName,
version: availableVersions[0],
reason: 'No version satisfies all constraints'
}
}]
};
}
return {
success: true,
dependency: {
name: depName,
version: satisfyingVersion,
required: true,
type: 'plugin',
resolved: true,
resolvedVersion: satisfyingVersion,
installation: plugin
}
};
}
// Find version that satisfies all constraints
findSatisfyingVersion(constraints, availableVersions, options) {
// Sort versions in descending order
const sortedVersions = availableVersions
.filter(v => semver_1.default.valid(v))
.sort((a, b) => semver_1.default.rcompare(a, b));
if (options.preferStable) {
// Filter out prerelease versions unless explicitly allowed
const stableVersions = sortedVersions.filter(v => options.allowPrerelease || !semver_1.default.prerelease(v));
if (stableVersions.length > 0) {
return this.findBestMatch(constraints, stableVersions, options);
}
}
return this.findBestMatch(constraints, sortedVersions, options);
}
// Find best matching version
findBestMatch(constraints, versions, options) {
for (const version of versions) {
if (this.satisfiesAllConstraints(version, constraints)) {
return version;
}
}
// If strict mode, return null
if (options.strategy === 'strict') {
return null;
}
// In loose mode, return the latest version
return versions[0] || null;
}
// Check if version satisfies all constraints
satisfiesAllConstraints(version, constraints) {
return constraints.every(constraint => {
try {
return semver_1.default.satisfies(version, constraint.constraint);
}
catch (error) {
return false;
}
});
}
// Get available versions for a plugin
async getAvailableVersions(pluginName) {
// Check cache
if (this.versionCache.has(pluginName)) {
return this.versionCache.get(pluginName);
}
// For now, return current version
// In a real implementation, this would query npm/marketplace
const plugin = this.plugins.get(pluginName);
const versions = plugin ? [plugin.manifest.version] : [];
this.versionCache.set(pluginName, versions);
return versions;
}
// Create installation plan
createInstallationPlan(dependencies) {
const plan = [];
const processed = new Set();
// Topological sort for installation order
const sorted = this.topologicalSort(dependencies);
sorted.forEach((dep, index) => {
if (!processed.has(dep.name)) {
plan.push({
action: 'install',
plugin: dep.name,
version: dep.resolvedVersion || dep.version,
dependencies: [], // Would be filled with actual dependencies
order: index,
optional: !dep.required
});
processed.add(dep.name);
}
});
return plan;
}
// Topological sort for dependencies
topologicalSort(dependencies) {
// Simple implementation - in practice would use dependency graph
return [...dependencies].sort((a, b) => {
// Required dependencies first
if (a.required !== b.required) {
return a.required ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
}
// Generate warnings
generateWarnings(result) {
const warnings = [];
if (result.conflicts.length > 0) {
warnings.push(`Found ${result.conflicts.length} dependency conflicts`);
}
if (result.missing.length > 0) {
warnings.push(`Missing ${result.missing.length} required dependencies`);
}
if (result.circular.length > 0) {
warnings.push(`Detected ${result.circular.length} circular dependencies`);
}
return warnings;
}
// Update dependency graph
updateDependencyGraph(registration) {
const specs = this.extractDependencySpecs(registration.manifest);
const node = {
name: registration.manifest.name,
version: registration.manifest.version,
dependencies: new Set(specs.map(s => s.name)),
dependents: new Set(),
resolved: true,
depth: 0,
installation: registration
};
this.dependencyGraph.set(registration.manifest.name, node);
}
// Clear caches
clearCache() {
this.resolutionCache.clear();
this.versionCache.clear();
this.emit('cache-cleared');
}
// Get cache key for resolution result
getCacheKey(manifest, options) {
const dependencyHash = JSON.stringify({
dependencies: manifest.dependencies,
peerDependencies: manifest.peerDependencies,
plugins: manifest.reshell?.plugins
});
const optionsHash = JSON.stringify(options);
return `${manifest.name}_${manifest.version}_${dependencyHash}_${optionsHash}`;
}
// Get dependency statistics
getStats() {
return {
totalPlugins: this.plugins.size,
dependencyNodes: this.dependencyGraph.size,
cacheSize: this.resolutionCache.size,
versionCacheSize: this.versionCache.size
};
}
// Get dependency graph
getDependencyGraph() {
return new Map(this.dependencyGraph);
}
}
exports.PluginDependencyResolver = PluginDependencyResolver;
// Utility functions
function createDependencyResolver(options) {
return new PluginDependencyResolver(options);
}
function validateVersion(version) {
return semver_1.default.valid(version) !== null;
}
function compareVersions(a, b) {
return semver_1.default.compare(a, b);
}
function satisfiesConstraint(version, constraint) {
try {
return semver_1.default.satisfies(version, constraint);
}
catch (error) {
return false;
}
}