npm-ls-flat
Version:
detects dependency version mismatches for streamlined dependency management
75 lines (74 loc) • 3.44 kB
JavaScript
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);
}
})();