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

508 lines (507 loc) 18.7 kB
"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; } }