'use strict'; const yargs = require('yargs/yargs'); const node_child_process = require('node:child_process'); const semver = require('semver'); const path = require('node:path'); const fse = require('fs-extra'); const resolve = require('resolve'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const yargs__default = /*#__PURE__*/_interopDefaultCompat(yargs); const semver__default = /*#__PURE__*/_interopDefaultCompat(semver); const path__default = /*#__PURE__*/_interopDefaultCompat(path); const fse__default = /*#__PURE__*/_interopDefaultCompat(fse); const resolve__default = /*#__PURE__*/_interopDefaultCompat(resolve); var OrderBy = /* @__PURE__ */ ((OrderBy2) => { OrderBy2[OrderBy2["Depender"] = 0] = "Depender"; OrderBy2[OrderBy2["Dependee"] = 1] = "Dependee"; return OrderBy2; })(OrderBy || {}); const DEFAULT_VALUE = Object.freeze({ orderBy: 1 /* Dependee */, debug: false, verbose: false, ignore: [], runOnlyOnRootDependencies: false, findSolutions: false, install: false, includePrerelease: false }); function packageFilter() { let packagePath; return { getPackagePath: () => packagePath, filter: (pkg, pkgdir) => { if (!packagePath || pkg.version) { packagePath = pkgdir; } return pkg; } }; } function resolvePackageDir(basedir, packageName) { const { getPackagePath, filter } = packageFilter(); try { resolve__default.sync(packageName, { basedir, packageFilter: filter }); } catch { } return getPackagePath(); } function walkPackageDependencyTree(packagePath, isAncestorDevDependency, visitor, visitedPaths, options) { const isRootPackage = visitedPaths.length === 0; if (visitedPaths.includes(packagePath)) { return; } visitedPaths.push(packagePath); const packageJsonPath = path__default.join(packagePath, "package.json"); if (!fse__default.existsSync(packageJsonPath)) { throw new Error(`package.json missing at ${packageJsonPath}.`); } const packageJson = fse__default.readJsonSync(packageJsonPath); const packageDependencies = getPackageMeta( packagePath, packageJson, isAncestorDevDependency ); if (options.debug) { console.log(packageJsonPath); for (const dep of packageDependencies.peerDependencies) { console.log(dep); } } visitor(packagePath, packageJson, packageDependencies); function walkDependency(dependency, isAncestorDevDependency2) { if (resolve__default.isCore(dependency.name)) { return; } const dependencyPath = resolvePackageDir(packagePath, dependency.name); if (!dependencyPath) { if (packageDependencies.optionalDependencies.some( (x) => x.name === dependency.name )) { if (options.debug) { console.log( `Ignoring missing optional dependency ${dependency.name} from ${packagePath}` ); } return; } else { throw new Error( `WARN: Unable to resolve package ${dependency.name} from ${packagePath}` ); } } walkPackageDependencyTree( dependencyPath, isAncestorDevDependency2, visitor, visitedPaths, options ); } let dependenciesToWalk = []; if (isRootPackage) { dependenciesToWalk = packageDependencies.devDependencies; } else if (!options.runOnlyOnRootDependencies) { dependenciesToWalk = packageDependencies.dependencies; } for (const dep of dependenciesToWalk) { walkDependency(dep, isRootPackage || isAncestorDevDependency); } } function buildDependencyArray(type, pkgJson, depender, isAncestorDevDependency) { const dependenciesObject = pkgJson[type] || {}; const peerDependenciesMeta = pkgJson.peerDependenciesMeta || {}; const peerDevDependencies = pkgJson.peerDevDependencies || []; const packageNames = Object.keys(dependenciesObject); return packageNames.map((name) => { const isPeerOptionalDependency = !!peerDependenciesMeta[name]?.optional; const isPeerDevDependency = isAncestorDevDependency || !!peerDependenciesMeta[name]?.dev || !!peerDevDependencies.includes(name); return { name, type, version: dependenciesObject[name], isPeerDevDependency, isPeerOptionalDependency, depender }; }); } function getPackageMeta(packagePath, packageJson, isAncestorDevDependency) { const { name, version } = packageJson; const packageMeta = { name, version, packagePath, dependencies: [], devDependencies: [], optionalDependencies: [], peerDependencies: [] }; const dependencyTypes = [ "dependencies", "devDependencies", "optionalDependencies", "peerDependencies" ]; for (const type of dependencyTypes) { packageMeta[type] = buildDependencyArray( type, packageJson, packageMeta, isAncestorDevDependency ); } return packageMeta; } function gatherPeerDependencies(packagePath, options) { let peerDeps = []; const visitor = (_path, _json, deps) => { peerDeps = [...peerDeps, ...deps.peerDependencies]; }; walkPackageDependencyTree(packagePath, false, visitor, [], options); const uniqueDeps = []; for (const peerDep of peerDeps) { if (!uniqueDeps.some((dep2) => isSameDep(peerDep, dep2))) { uniqueDeps.push(peerDep); } } return uniqueDeps; } function isSameDep(a, b) { const keys = [ "name", "version", "installedVersion", "semverSatisfies", "isYalc", "isPeerDevDependency" ]; return keys.every((key) => a[key] === b[key]) && a.depender.name === b.depender.name && a.depender.version === b.depender.version && a.depender.packagePath === b.depender.packagePath; } function getInstalledVersion(dep) { const peerDependencyDir = resolvePackageDir(".", dep.name); if (!peerDependencyDir) { return void 0; } const { readJSONSync: readJson, existsSync } = fse__default; const packageJson = readJson(path__default.resolve(peerDependencyDir, "package.json")); const isYalc = existsSync(path__default.resolve(peerDependencyDir, "yalc.sig")); return isYalc ? `${packageJson.version}-yalc` : packageJson.version; } function getUniqueProblems(problems) { const uniqueNames = new Set(problems.map((problem) => problem.name)); return [...uniqueNames].map((name) => { const problem = problems.find((problem2) => problem2.name === name); if (!problem) { throw new Error("This should never happen"); } return problem; }); } function determineResolutionType(problem) { if (problem.installedVersion) { return "upgrade"; } if (problem.isPeerDevDependency) { return "devInstall"; } return "install"; } function semverReverseSort(a, b) { const lt = semver__default.lt(a, b); const gt = semver__default.gt(a, b); if (!lt && !gt) { return 0; } else if (lt) { return 1; } return -1; } function findPossibleResolution(packageName, allPeerDeps, options) { const requiredPeerVersions = allPeerDeps.filter( (dep) => dep.name === packageName ); const command = `npm view ${packageName} versions`; let rawVersionsInfo; try { rawVersionsInfo = node_child_process.execSync(command, { stdio: "pipe" }).toString(); const availableVersions = JSON.parse( rawVersionsInfo.replace(/'/g, '"') ).sort(semverReverseSort); return availableVersions.find( (ver) => requiredPeerVersions.every((peerVer) => { return semver__default.satisfies(ver, peerVer.version, { includePrerelease: options.includePrerelease }); }) ); } catch (error) { console.error(`Error while running command: '${command}'`); console.error(error); console.error(); console.error("npm output:"); console.error(rawVersionsInfo); } } function determineResolutionVersion(problem, allPeerDependencies, options) { return findPossibleResolution(problem.name, allPeerDependencies, options); } function formatResolution(problem, resolutionVersion) { return resolutionVersion ? `${problem.name}@${resolutionVersion}` : ""; } function findPossibleResolutions(problems, allPeerDependencies, options) { const uniqProblems = getUniqueProblems(problems); return uniqProblems.map((problem) => { const resolutionType = determineResolutionType(problem); const resolutionVersion = determineResolutionVersion( problem, allPeerDependencies, options ); const resolution = formatResolution(problem, resolutionVersion); return { problem, resolution, resolutionType }; }); } function filterAndJoin(resolutions, resolutionType) { const filteredResolutions = resolutions.filter((r) => r.resolution && r.resolutionType === resolutionType).map((r) => r.resolution); return filteredResolutions.join(" "); } function getCommandLines(resolutions) { const installs = filterAndJoin(resolutions, "install"); const devInstalls = filterAndJoin(resolutions, "devInstall"); const upgrades = filterAndJoin(resolutions, "upgrade"); const installCommand = installs ? `yarn add ${installs}` : ""; const devInstallCommand = devInstalls ? `yarn add -D ${devInstalls}` : ""; const upgradesCommand = upgrades ? `yarn upgrade ${upgrades}` : ""; const commands = [installCommand, devInstallCommand, upgradesCommand].filter( Boolean ); return commands; } function reportPeerDependencyStatus(dep, byDepender, showSatisfiedDep, verbose) { const message = byDepender ? `${dep.depender.name}@${dep.depender.version} requires ${dep.name} ${dep.version}` : `${dep.name} ${dep.version} is required by ${dep.depender.name}@${dep.depender.version}`; let statusIcon = ""; let statusMessage = ""; if (dep.semverSatisfies) { if (!showSatisfiedDep) { return; } statusIcon = "\u2705"; statusMessage = `(${dep.installedVersion} is installed)`; } else if (dep.isYalc) { statusIcon = "\u2611\uFE0F"; statusMessage = `(${dep.installedVersion} is installed via yalc)`; } else if (dep.isIgnored) { if (!verbose) { return; } statusIcon = "\u2611\uFE0F"; statusMessage = `IGNORED (${dep.name} is not installed)`; } else if (dep.installedVersion) { statusIcon = "\u274C"; statusMessage = dep.isPeerOptionalDependency ? `OPTIONAL (${dep.installedVersion} is installed)` : `(${dep.installedVersion} is installed)`; } else if (dep.isPeerOptionalDependency) { if (!verbose) { return; } statusIcon = "\u2611\uFE0F"; statusMessage = `OPTIONAL (${dep.name} is not installed)`; } else { statusIcon = "\u274C"; statusMessage = `(${dep.name} is not installed)`; } console.log(` ${statusIcon} ${message} ${statusMessage}`); } function sortDependencies(allNestedPeerDependencies, orderBy) { const allNestedPeerDependenciesCopy = [...allNestedPeerDependencies]; if (orderBy === OrderBy.Depender) { allNestedPeerDependenciesCopy.sort( (a, b) => a.depender.name.localeCompare(b.depender.name) ); } else if (orderBy === OrderBy.Dependee) { allNestedPeerDependenciesCopy.sort((a, b) => a.name.localeCompare(b.name)); } return allNestedPeerDependenciesCopy; } function report(options, allNestedPeerDependencies) { const sortedDependencies = sortDependencies( allNestedPeerDependencies, options.orderBy ); for (const dep of sortedDependencies) { const relatedPeerDeps = sortedDependencies.filter( (other) => other.name === dep.name && other !== dep ); const showIfSatisfied = options.verbose || relatedPeerDeps.some((dep2) => isProblem(dep2)); reportPeerDependencyStatus( dep, options.orderBy === OrderBy.Depender, showIfSatisfied, options.verbose ); } } const isProblem = (dep) => !dep.semverSatisfies && !dep.isIgnored && !dep.isYalc && (!dep.isPeerOptionalDependency || !!dep.installedVersion); function getAllNestedPeerDependencies(options) { const gatheredDependencies = gatherPeerDependencies(".", options); function applySemverInformation(dep) { const installedVersion = getInstalledVersion(dep) ?? ""; const semverSatisfies = installedVersion ? semver__default.satisfies(installedVersion, dep.version, { includePrerelease: true }) : false; const isYalc = !!/-[\da-f]+-yalc$/.test(installedVersion); return { ...dep, installedVersion, semverSatisfies, isYalc }; } function applyIgnoreInformation(dep) { const isIgnored = options.ignore.includes(dep.name); return { ...dep, isIgnored }; } return gatheredDependencies.map((element) => applySemverInformation(element)).map((element) => applyIgnoreInformation(element)); } function findSolutions(problems, allNestedPeerDependencies, options) { printSearchMessage(problems.length); const resolutions = findPossibleResolutions( problems, allNestedPeerDependencies, options ); const resolutionsWithSolutions = getResolutionsWithSolutions(resolutions); const nosolution = getNoSolutionResolutions(resolutions); printNoSolutionErrors(nosolution, allNestedPeerDependencies); return { resolutionsWithSolutions, nosolution }; } function printSearchMessage(problemCount) { console.log(); console.log( `Searching for solutions for ${problemCount} missing dependencies...` ); console.log(); } function getResolutionsWithSolutions(resolutions) { return resolutions.filter((r) => r.resolution); } function getNoSolutionResolutions(resolutions) { return resolutions.filter((r) => !r.resolution); } function printNoSolutionErrors(nosolution, allNestedPeerDependencies) { for (const solution of nosolution) { printSingleNoSolutionError(solution, allNestedPeerDependencies); } if (nosolution.length > 0) { console.error(); } } function printSingleNoSolutionError(solution, allNestedPeerDependencies) { const name = solution.problem.name; const errorPrefix = `Unable to find a version of ${name} that satisfies the following peerDependencies:`; const peerDepRanges = getPeerDepRanges(name, allNestedPeerDependencies); console.error(` \u274C ${errorPrefix} ${peerDepRanges.join(" and ")}`); } function getPeerDepRanges(name, allNestedPeerDependencies) { const peerDepRanges = []; for (const dep of allNestedPeerDependencies) { if (dep.name === name && !peerDepRanges.includes(dep.version)) { peerDepRanges.push(dep.version); } } return peerDepRanges; } function checkPeerDependencies(options) { const allNestedPeerDependencies = getAllNestedPeerDependencies(options); report(options, allNestedPeerDependencies); const problems = allNestedPeerDependencies.filter((dep) => isProblem(dep)); if (problems.length === 0) { console.log(" \u2705 All peer dependencies are met"); return; } if (options.install) { const { nosolution, resolutionsWithSolutions } = findSolutions( problems, allNestedPeerDependencies, options ); const commandLines = getCommandLines(resolutionsWithSolutions); if (commandLines.length > 0) { return installPeerDependencies(commandLines, options, nosolution); } } else if (options.findSolutions) { const { resolutionsWithSolutions } = findSolutions( problems, allNestedPeerDependencies, options ); const commandLines = getCommandLines(resolutionsWithSolutions); if (commandLines.length > 0) { console.log(); console.log( `Install peerDependencies using ${commandLines.length > 1 ? "these commands:" : "this command"}:` ); console.log(); for (const command of commandLines) { console.log(command); } console.log(); } } else { console.log(); console.log(`Search for solutions using this command:`); console.log(); console.log(`npx peer-gear --findSolutions`); console.log(); console.log(`Install peerDependencies using this command:`); console.log(); console.log(`npx peer-gear --install`); console.log(); } process.exit(1); } let recursiveCount = 0; function installPeerDependencies(commandLines, options, nosolution) { console.log("Installing peerDependencies..."); console.log(); for (const command of commandLines) { console.log(`$ ${command}`); node_child_process.execSync(command); console.log(); } const newProblems = getAllNestedPeerDependencies(options).filter((dep) => isProblem(dep)).filter((dep) => !nosolution.some((x) => isSameDep(x.problem, dep))); if (nosolution.length === 0 && newProblems.length === 0) { console.log("All peer dependencies are met"); } if (newProblems.length > 0) { console.log(`Found ${newProblems.length} new unmet peerDependencies...`); if (++recursiveCount < 5) { return checkPeerDependencies(options); } else { console.error("Recursion limit reached (5)"); process.exit(5); } } } function getCliArgv() { const argv = yargs__default(process.argv.slice(2)).usage( 'Options may also be stored in package.json under the "checkPeerDependencies" key' ).option({ help: { type: "boolean", alias: "h", description: `Print usage information` } }).option({ orderBy: { choices: [OrderBy.Dependee, OrderBy.Depender], default: DEFAULT_VALUE.orderBy, description: "Order the output by depender or dependee" } }).option({ debug: { boolean: true, default: DEFAULT_VALUE.debug, description: "Print debugging information" } }).option({ verbose: { boolean: true, default: DEFAULT_VALUE.verbose, description: "Prints every peer dependency, even those that are met" } }).option({ ignore: { string: true, array: true, default: DEFAULT_VALUE.ignore, description: "package name to ignore (may specify multiple)" } }).option({ runOnlyOnRootDependencies: { boolean: true, default: DEFAULT_VALUE.runOnlyOnRootDependencies, description: "Run tool only on package root dependencies" } }).option({ findSolutions: { boolean: true, default: DEFAULT_VALUE.findSolutions, description: "Search for solutions and print package installation commands" } }).option({ install: { boolean: true, default: DEFAULT_VALUE.install, description: "Install missing or incorrect peerDependencies" } }).option({ includePrerelease: { type: "boolean", default: DEFAULT_VALUE.includePrerelease } }).parseSync(); return argv; } function main() { const argv = getCliArgv(); if (argv.help) { process.exit(-2); } checkPeerDependencies(argv); } exports.getCliArgv = getCliArgv; exports.main = main;