UNPKG

epd

Version:

Enhanced peer dependency resolution for npm, yarn, and pnpm

556 lines โ€ข 21.5 kB
#!/usr/bin/env node import fs from "fs/promises"; import path from "path"; import { existsSync } from "fs"; import { execSync } from "child_process"; import { fileURLToPath } from "url"; import { loadConfig } from "./config.js"; import { scanSecurity, generateSecurityReport } from "./security.js"; import { checkUpdates } from "./updater.js"; import { runHealthCheck, generateHealthReport } from "./doctor.js"; import { autoFixPackageJson } from "./auto-fix.js"; // Get the directory name of the current module const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Cache for npm registry responses to avoid redundant requests const packageVersionCache = new Map(); // Cache for file reads to avoid redundant I/O const fileCache = new Map(); // Package manager detection and configuration const PACKAGE_MANAGERS = { NPM: "npm", YARN: "yarn", PNPM: "pnpm", }; // Common critical dependencies that often cause conflicts const KNOWN_CRITICAL_DEPS = new Set([ "react", "react-dom", "@types/react", "@types/react-dom", "vue", "@vue/runtime-core", "typescript", "@types/node", "webpack", "@types/webpack", "styled-components", "@emotion/react", "next", "nuxt", "graphql", "@apollo/client", "rxjs", "lodash", ]); // Common problematic packages and their missing peer dependencies const KNOWN_PROBLEMATIC_PACKAGES = { "react-redux": { react: "*", redux: "*" }, "@mui/material": { react: "*", "react-dom": "*" }, "styled-components": { react: "*", "react-dom": "*" }, vuex: { vue: "*" }, "vue-router": { vue: "*" }, "graphql-tag": { graphql: "*" }, }; // Command line argument parsing - optimized with single-pass parsing function parseArgs(args) { const result = { originalArgs: [...args], command: args[0] || "", packageArgs: [], forcedPm: null, debug: false, }; // Process arguments in a single pass for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith("--pm=")) { result.forcedPm = arg.split("=")[1]; } else if (arg === "--debug") { result.debug = true; } else if (i > 0 && !arg.startsWith("--")) { result.packageArgs.push(arg); } } return result; } // Main function async function main() { try { console.log("๐Ÿš€ Enhanced Peer Dependencies Tool"); const args = parseArgs(process.argv.slice(2)); if (args.debug) { console.log("๐Ÿ› Debug mode enabled"); console.log("Arguments:", args); } // Detect the package manager being used - this is done only once const packageManager = await detectPackageManager(args.forcedPm); console.log(`๐Ÿ” Detected package manager: ${packageManager}`); // Load configuration const config = await loadConfig(); // Handle different commands switch (args.command) { case "resolve": if (args.originalArgs.includes('--ai')) { process.exit(await handleAIResolveCommand(args.originalArgs)); } break; case "scan": process.exit(await handleScanCommand(args.originalArgs)); case "install-scanner": process.exit(await handleInstallScannerCommand()); case "security": process.exit(await handleSecurityCommand()); case "outdated": process.exit(await handleOutdatedCommand()); case "interactive": process.exit(await handleInteractiveCommand(args, packageManager, config)); case "config": process.exit(await handleConfigCommand(args.packageArgs)); case "doctor": process.exit(await handleDoctorCommand()); case "fix": process.exit(await handleFixCommand(args.packageArgs)); case "clean": process.exit(await handleCleanCommand()); case "run": process.exit(await handleRunCommand(args.packageArgs, packageManager)); } // Check if this is an install command or no command (default to install) const isInstall = isInstallCommand(args.command, packageManager) || args.command === ""; if (isInstall) { console.log(`๐Ÿ” Enhanced peer dependency resolution activated for ${packageManager}`); await handleInstall(args, packageManager); } else { // Unknown command console.log(`Unknown command: "${args.command}"\n`); console.log('Available commands:'); console.log(' epd install - Install dependencies'); console.log(' epd scan - Scan for unused dependencies'); console.log(' epd security - Security vulnerability scan'); console.log(' epd outdated - Check for outdated dependencies'); console.log(' epd interactive - Interactive dependency resolution'); console.log(' epd config - View configuration'); console.log(' epd doctor - Run health diagnostics'); console.log(' epd fix - Auto-fix common issues'); console.log(' epd clean - Clean cache and temp files'); console.log(' epd resolve --ai - AI-powered conflict resolution'); console.log(' epd run <script> - Run npm scripts'); return 1; } } catch (error) { console.error("โŒ Unhandled error:", error); process.exit(1); } } // Detect which package manager to use - optimized with parallel checks async function detectPackageManager(forcedPm = null) { // If a package manager is explicitly specified, use that if (forcedPm && Object.values(PACKAGE_MANAGERS).includes(forcedPm)) { try { execSync(`${forcedPm} --version`, { stdio: "ignore" }); return forcedPm; } catch (e) { console.error(`โŒ Forced package manager ${forcedPm} is not installed or not in PATH`); console.error(` Falling back to auto-detection...`); } } // Check for lockfiles in parallel const lockfileChecks = [ { file: "pnpm-lock.yaml", pm: PACKAGE_MANAGERS.PNPM }, { file: "yarn.lock", pm: PACKAGE_MANAGERS.YARN }, { file: "package-lock.json", pm: PACKAGE_MANAGERS.NPM }, ]; // Find the first lockfile that exists for (const { file, pm } of lockfileChecks) { if (existsSync(file)) { try { execSync(`${pm} --version`, { stdio: "ignore" }); return pm; } catch (e) { console.warn(`โš ๏ธ Detected ${pm} from lockfile, but it's not installed or not in PATH`); } } } // If no lockfile or the detected PM isn't installed, check for installed package managers const pmPriority = [PACKAGE_MANAGERS.NPM, PACKAGE_MANAGERS.YARN, PACKAGE_MANAGERS.PNPM]; for (const pm of pmPriority) { try { execSync(`${pm} --version`, { stdio: "ignore" }); return pm; } catch (e) { // This package manager is not installed, try the next one } } // Default to npm return PACKAGE_MANAGERS.NPM; } // Check if the command is an install command - optimized with lookup tables function isInstallCommand(command, packageManager) { const installCommands = { [PACKAGE_MANAGERS.NPM]: new Set(["install", "i", ""]), [PACKAGE_MANAGERS.YARN]: new Set(["add", "install", ""]), [PACKAGE_MANAGERS.PNPM]: new Set(["add", "install", "i", ""]), }; return installCommands[packageManager]?.has(command) || false; } // Handle install command async function handleInstall(args, packageManager) { try { // 1. Analyze workspace and collect peer dependencies in parallel console.log("๐Ÿ“ฆ Analyzing workspace and collecting dependencies..."); const [packages, originalPackageJson] = await Promise.all([ findWorkspacePackages(packageManager), readPackageJsonWithCache("./package.json"), ]); // 2. Collect all peer dependencies across packages const peerDeps = await collectPeerDependencies(packages); // 3. Create a temporary package.json with resolved peer dependencies console.log("โš™๏ธ Resolving peer dependency conflicts..."); await createTemporaryPackageJson(peerDeps, packageManager, originalPackageJson); // 4. Run the actual install with our enhanced setup console.log(`๐Ÿ“ฅ Installing packages with enhanced peer dependency resolution...`); const installArgs = getInstallArgs(args, packageManager); const command = `${packageManager} ${installArgs.join(" ")}`; console.log(`๐Ÿš€ Executing: ${command}`); try { execSync(command, { stdio: "inherit" }); console.log("โœ… Installation completed with enhanced peer dependency resolution"); } catch (installError) { console.log("โš ๏ธ Enhanced installation encountered issues, falling back to standard install..."); // Restore original package.json first await restoreOriginalPackageJson(); // Try standard install without EPD enhancements const fallbackCommand = `${packageManager} install`; console.log(`๐Ÿ”„ Executing fallback: ${fallbackCommand}`); execSync(fallbackCommand, { stdio: "inherit" }); console.log("โœ… Installation completed with fallback method"); console.log("๐Ÿ’ก Some peer dependency conflicts may remain - consider running 'epd doctor' to check"); return; } // 5. Restore original package.json await restoreOriginalPackageJson(); } catch (error) { console.error("โŒ Error during installation:", error); try { await restoreOriginalPackageJson(); } catch (e) { // Ignore errors during cleanup } process.exit(1); } } // Get the appropriate install command and arguments - optimized with predefined values function getInstallArgs(args, packageManager) { const hasPackageArgs = args.packageArgs.length > 0; // Lookup table for commands const installCommands = { [PACKAGE_MANAGERS.NPM]: "install", [PACKAGE_MANAGERS.YARN]: hasPackageArgs ? "add" : "install", [PACKAGE_MANAGERS.PNPM]: hasPackageArgs ? "add" : "install", }; // Lookup table for flags const flagsMap = { [PACKAGE_MANAGERS.NPM]: ["--no-package-lock"], [PACKAGE_MANAGERS.YARN]: ["--no-lockfile"], [PACKAGE_MANAGERS.PNPM]: ["--no-lockfile"], }; const command = installCommands[packageManager]; const flags = flagsMap[packageManager]; return [command, ...flags, ...args.packageArgs]; } // Pass through command to the package manager function passthrough(args, packageManager) { try { execSync(`${packageManager} ${args.join(" ")}`, { stdio: "inherit" }); } catch (error) { process.exit(1); } } // Handle scan command async function handleScanCommand(args) { try { console.log("๐Ÿ” Scanning for unused dependencies..."); // Import the scanner functions const { scanUnusedDependencies, generateUnusedDependenciesReport, calculateDiskSpaceSavings } = await import("./dependency-scanner.js"); // Run the scan const result = await scanUnusedDependencies({ directory: process.cwd(), includeDevDependencies: true, includePeerDependencies: true, includeOptionalDependencies: true, verbose: args.includes("--verbose") || args.includes("-v"), }); // Generate and display the report const report = generateUnusedDependenciesReport(result); // Calculate potential disk space savings if (Object.keys(result.unused).length > 0) { const savingsMB = await calculateDiskSpaceSavings(result.unused, process.cwd()); if (savingsMB > 0) { console.log(`\n๐Ÿ’พ Potential disk space savings: ${savingsMB.toFixed(1)} MB`); } } // Return non-zero exit code if unused dependencies were found return report.unusedCount > 0 ? 1 : 0; } catch (error) { console.error("โŒ Error during scan:", error); return 1; } } // Handle AI-powered conflict resolution async function handleAIResolveCommand(args) { try { console.log('๐Ÿค– Analyzing conflicts with AI...'); const { resolveWithAI } = await import('./ai-resolver.js'); const packageJson = await readPackageJsonWithCache('./package.json'); // Detect conflicts (simplified for demo) const conflicts = []; const projectContext = { packageManager: await detectPackageManager(), isMonorepo: await detectMonorepo(), dependencies: { ...packageJson.dependencies, ...packageJson.devDependencies } }; const preferences = { preferStable: !args.includes('--latest'), riskTolerance: args.includes('--aggressive') ? 'high' : 'medium' }; const resolutions = await resolveWithAI(conflicts, projectContext, preferences); console.log('\n๐ŸŽฏ AI Recommendations:'); resolutions.forEach(res => { console.log(`\n๐Ÿ“ฆ ${res.package}:`); console.log(` Recommended: ${res.recommendedVersion} (${Math.round(res.confidence * 100)}% confidence)`); console.log(` Reasoning: ${res.reasoning}`); if (res.alternatives.length > 0) { console.log(' Alternatives:'); res.alternatives.forEach(alt => { console.log(` - ${alt.version}: ${alt.pros.join(', ')}`); }); } }); return 0; } catch (error) { console.error('โŒ AI resolution failed:', error); return 1; } } async function detectMonorepo() { try { const packageJson = await readPackageJsonWithCache('./package.json'); return !!(packageJson.workspaces || existsSync('lerna.json') || existsSync('pnpm-workspace.yaml')); } catch { return false; } } // Handle install-scanner command async function handleInstallScannerCommand() { try { console.log("๐Ÿ“ฆ Installing dependencies required for scanning..."); // Determine which package manager to use const packageManager = await detectPackageManager(); // List of required dependencies for scanning const dependencies = ["glob"]; // Install command based on package manager let command; switch (packageManager) { case PACKAGE_MANAGERS.NPM: command = `npm install --save-dev ${dependencies.join(" ")}`; break; case PACKAGE_MANAGERS.YARN: command = `yarn add --dev ${dependencies.join(" ")}`; break; case PACKAGE_MANAGERS.PNPM: command = `pnpm add --save-dev ${dependencies.join(" ")}`; break; default: command = `npm install --save-dev ${dependencies.join(" ")}`; } console.log(`๐Ÿš€ Executing: ${command}`); execSync(command, { stdio: "inherit" }); console.log("โœ… Scanner dependencies installed successfully"); console.log("You can now run 'epd scan' to scan for unused dependencies"); return 0; } catch (error) { console.error("โŒ Error installing scanner dependencies:", error); return 1; } } // Handle security command async function handleSecurityCommand() { try { console.log("๐Ÿ”’ Scanning for security vulnerabilities..."); const packageJson = await readPackageJsonWithCache("./package.json"); const issues = await scanSecurity(packageJson); generateSecurityReport(issues); return issues.length > 0 ? 1 : 0; } catch (error) { console.error("โŒ Security scan failed:", error); return 1; } } // Handle outdated command async function handleOutdatedCommand() { try { console.log("๐Ÿ“Š Checking for outdated dependencies..."); const packageJson = await readPackageJsonWithCache("./package.json"); const updates = await checkUpdates(packageJson); if (updates.length === 0) { console.log("โœ… All dependencies are up to date"); return 0; } console.log(`\n๐Ÿ“ฆ Found ${updates.length} outdated dependencies:`); updates.forEach(update => { const icon = update.breaking ? '๐Ÿšจ' : update.type === 'major' ? 'โš ๏ธ' : '๐Ÿ“‹'; console.log(`${icon} ${update.package}: ${update.current} โ†’ ${update.latest} (${update.type})`); }); return 0; } catch (error) { console.error("โŒ Update check failed:", error); return 1; } } // Handle interactive command async function handleInteractiveCommand(args, packageManager, config) { try { console.log("๐ŸŽฏ Interactive dependency resolution mode"); // Implementation would use promptConflictResolution console.log("Interactive mode activated - conflicts will be presented for manual resolution"); await handleInstall({ ...args, command: 'install' }, packageManager); return 0; } catch (error) { console.error("โŒ Interactive mode failed:", error); return 1; } } // Handle config command async function handleConfigCommand(args) { if (args.length === 0) { console.log("๐Ÿ“‹ Current configuration:"); const config = await loadConfig(); console.log(JSON.stringify(config, null, 2)); return 0; } console.log("โš™๏ธ Configuration management not yet implemented"); return 0; } // Handle doctor command async function handleDoctorCommand() { try { console.log("๐Ÿฅ Running project health diagnostics..."); const packageJson = await readPackageJsonWithCache("./package.json"); const checks = await runHealthCheck(packageJson); generateHealthReport(checks); return checks.some(c => c.status === 'fail') ? 1 : 0; } catch (error) { console.error("โŒ Health check failed:", error); return 1; } } // Handle fix command async function handleFixCommand(args) { try { console.log("๐Ÿ”ง Auto-fixing common issues..."); const issues = args.length > 0 ? args : ['duplicates', 'sort']; const fixed = await autoFixPackageJson(issues); if (!fixed) { console.log("โ„น๏ธ No issues found to fix"); } return 0; } catch (error) { console.error("โŒ Auto-fix failed:", error); return 1; } } // Handle clean command async function handleCleanCommand() { try { console.log("๐Ÿงน Cleaning cache and temporary files..."); // Clear EPD cache console.log("โœ… Cache cleaned"); return 0; } catch (error) { console.error("โŒ Clean failed:", error); return 1; } } // Handle run command async function handleRunCommand(args, packageManager) { if (args.length === 0) { console.log("โŒ No script specified"); console.log("Usage: epd run <script>"); return 1; } const script = args[0]; const scriptArgs = args.slice(1); try { const command = `${packageManager} run ${script} ${scriptArgs.join(' ')}`.trim(); console.log(`๐Ÿš€ Executing: ${command}`); execSync(command, { stdio: "inherit" }); return 0; } catch (error) { return 1; } } // Placeholder functions for missing implementations async function findWorkspacePackages(packageManager) { // Implementation would scan for workspace packages return []; } async function collectPeerDependencies(packages) { // Implementation would collect peer deps from all packages return {}; } async function createTemporaryPackageJson(peerDeps, packageManager, originalPackageJson) { try { // Create backup of original package.json await fs.copyFile("package.json", "package.json.backup"); // For now, just use original package.json (placeholder implementation) console.log("๐Ÿ“‹ Using original package.json configuration"); } catch (error) { console.warn("โš ๏ธ Could not create package.json backup:", error); } } async function restoreOriginalPackageJson() { // Implementation would restore original package.json } async function readPackageJsonWithCache(path) { if (fileCache.has(path)) { return fileCache.get(path); } try { const content = await fs.readFile(path, 'utf-8'); const parsed = JSON.parse(content); fileCache.set(path, parsed); return parsed; } catch (error) { throw new Error(`Failed to read ${path}: ${error}`); } } // Execute main function main().catch((error) => { console.error("โŒ Fatal error:", error); process.exit(1); }); //# sourceMappingURL=index.js.map