epd
Version:
Enhanced peer dependency resolution for npm, yarn, and pnpm
556 lines โข 21.5 kB
JavaScript
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