UNPKG

npm-ls-flat

Version:

detects dependency version mismatches for streamlined dependency management

75 lines (74 loc) 3.44 kB
#!/usr/bin/env node import { exec } from 'node:child_process'; import path from 'node:path'; import chalk from 'chalk'; import fs from 'fs-extra'; import lodash from 'lodash'; import semver from 'semver'; import { sortPackageVersions, traverseDependencyTree } from './utils.js'; const readPackageJsonFile = async (path, allowDev = false) => { try { const { dependencies, devDependencies } = await fs.readJson(path); let allPackages = { ...dependencies }; if (allowDev && devDependencies) allPackages = { ...allPackages, ...devDependencies }; return allPackages; } catch (err) { console.error('Error reading package.json:', err); throw err; } }; const getDependencyTree = (packages = []) => new Promise((resolve, reject) => { const command = packages.length ? `npm ls ${packages.join(' ')} --all --json` : 'npm ls --all --json'; exec(command, (_, stdout) => { try { const { dependencies } = JSON.parse(stdout); resolve(dependencies); } catch (parseError) { reject(parseError); } }); }); const findMismatchedVersions = (deps) => Object.entries(deps).reduce((acc, [name, dependencies]) => { const root = dependencies.find((dependency) => !dependency.path); if (!root) return acc; const differentVersions = dependencies.filter((dependency) => dependency.version !== root.version); const versions = differentVersions.length ? [root, dependencies.filter((dependency) => dependency.version !== root.version)] : [root, []]; if (versions[1].length > 0 || root.invalid) acc[name] = versions; return acc; }, {}); (async () => { const packageJsonPath = path.resolve(process.cwd(), 'package.json'); try { const packageNames = Object.keys(await readPackageJsonFile(packageJsonPath, true)); console.log(`Looking for mismatches in ${chalk.greenBright(packageNames.length)} ${packageNames.length > 1 ? 'packages' : 'package'}...`); const dependencyTree = await getDependencyTree(packageNames); const groupedDependencyTree = lodash.groupBy(traverseDependencyTree(dependencyTree ?? {}).filter((dependency) => packageNames.some((name) => dependency.name === name)), 'name'); const mismatches = Object.values(findMismatchedVersions(groupedDependencyTree)) //sort first packages that have invalid versions .sort((a) => (a[0].invalid ? -1 : 1)); if (!mismatches.length) { console.log(chalk.green('All packages have consistent versions.')); return; } for (const [root, pkgs] of mismatches) { console.log(`\n${root.name} ${chalk.cyanBright(root.version)} ${root.invalid ? chalk.redBright('(invalid)') : ''}`); if (root.invalid) console.log(` ${chalk.redBright(root.invalid)}`); const sortedPackages = sortPackageVersions(pkgs, 'DESC'); const packageVersionColor = (version) => semver.gt(root.version, version) ? chalk.redBright(version) : chalk.greenBright(version); for (const pkg of sortedPackages) { console.log(` ${packageVersionColor(pkg.version)} (${chalk.yellow(pkg.path?.map(({ name }) => name)?.join(' > '))})`); } } } catch (err) { console.error('Failed to get packages:', err); } })();