Skip to content

Commit

Permalink
Initial commit of the code
Browse files Browse the repository at this point in the history
First version, it works locally so what could go wrong?
:stuck_out_tongue:
  • Loading branch information
Arcanemagus committed Oct 18, 2017
1 parent 854989e commit eea2f2f
Show file tree
Hide file tree
Showing 5 changed files with 1,798 additions and 1 deletion.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,40 @@
# check-peer-deps
Verifies that the peerDependency requirements of all top level dependencies are satisfied.
Verifies that the `peerDependency` requirements of all top level dependencies
are satisfied.

## Installation

You can install this on your system with a simple:

```sh
npm install check-peer-deps
```

Please note that this utility requires `npm` to be available.

## Usage

Simply change into the directory of the project you wish to check the
`peerDependencies` of and run the program.

```sh
> cd foobar
> check-peer-deps
```

If the minimum versions of all your top level `peerDependencies` are satisfied
then there will be no output, otherwise you will see something similar to this:

```
> check-peer-deps
A [dev]Dependency satisfying eslint-config-airbnb-base's peerDependency of 'eslint@^4.9.0' was not found!
Current: eslint@^4.6.0
Required version is allowed? Yes
```

This tells you that `eslint-config-airbnb-base` is requiring `eslint@^4.9.0` as
a `peerDependency`, but the project currently only specifies `eslint@^4.6.0`,
allowing a potential issue to arise if `[email protected]` was installed and not
updated before installing. The output also tells you that although the
_minimum_ allowed version is too low, the _maximum_ allowed version does
satisfy the `peerDependencies` requirement.
7 changes: 7 additions & 0 deletions bin/check-peer-deps-cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node

process.title = 'check-peer-deps';

const checkPeerDeps = require('../check-peer-deps');

checkPeerDeps();
184 changes: 184 additions & 0 deletions check-peer-deps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const { exec } = require('sb-exec');
const semver = require('semver');

// Flags
// Enable debug output
const DEBUG = false;
// Include development packages when checking whether a peerDependency has been
// satisfied.
const INCLUDE_DEV = true;
// Use the local package.json files or check NPM to determine the peerDependencies
const USE_LOCAL_PEERDEPS = true;

// Internal vars
const deps = new Map();
const npmVers = new Map();
const peerDeps = new Map();

const log = (value) => {
if (DEBUG) {
console.log(value);
}
};

const addDeps = (dependencies) => {
Object.entries(dependencies).forEach((entry) => {
const [name, range] = entry;
deps.set(name, range);
});
};

const gatherNpmVer = async (range, name) => {
log(`Getting versions for ${name}@${range}...`);
const opts = ['view', '--json', name, 'versions'];
const versions = JSON.parse(await exec('npm', opts));
const ranges = {
versions,
minimum: semver.minSatisfying(versions, range),
maximum: semver.maxSatisfying(versions, range),
};
log(`${name}@${range}: '${ranges.minimum}' to '${ranges.maximum}'`);
npmVers.set(name, ranges);
};

const getNpmVersions = async () => {
// Gather the unique package names
const toCheck = new Set();
peerDeps.forEach((peerDependencies) => {
peerDependencies.forEach((range, name) => {
toCheck.add(name);
});
});

// Grab the versions from NPM
return Promise.all(Array.from(toCheck.values()).map(async (name) => {
if (deps.has(name)) {
await gatherNpmVer(deps.get(name), name);
}
}));
};

// Get the peerDependencies
const getNpmPeerDep = async (range, name) => {
log(`Getting peerDependencies for ${name}`);
const opts = ['view', '--json', name, 'peerDependencies'];
const npmPeerDeps = JSON.parse(await exec('npm', opts));
if (!peerDeps.has(name)) {
peerDeps.set(name, new Map());
}
const currDeps = peerDeps.get(name);
Object.entries(npmPeerDeps).forEach((entry) => {
const [depName, depRange] = entry;
log(`${depName}@${depRange}`);
currDeps.set(depName, depRange);
});
};

const getLocalPeerDep = async (range, name) => {
log(`Getting peerDependencies for ${name}`);
let packageInfo;
try {
// Hacktown, USA.
// eslint-disable-next-line import/no-dynamic-require
packageInfo = require(`${process.cwd()}/node_modules/${name}/package.json`);
} catch (e) {
return;
}
if (!packageInfo.peerDependencies) {
return;
}
if (!peerDeps.has(name)) {
peerDeps.set(name, new Map());
}
const currDeps = peerDeps.get(name);
Object.entries(packageInfo.peerDependencies).forEach((entry) => {
const [depName, depRange] = entry;
log(`${depName}@${depRange}`);
currDeps.set(depName, depRange);
});
};

const getPeerDeps = async () => {
const promises = [];
if (USE_LOCAL_PEERDEPS) {
log("Using local package.json's to determine peerDependencies.");
} else {
log('Using NPM to determine peerDependencies.');
}
deps.forEach((range, name) => {
if (USE_LOCAL_PEERDEPS) {
promises.push(getLocalPeerDep(range, name));
} else {
promises.push(getNpmPeerDep(range, name));
}
});
return Promise.all(promises);
};

// peerDependencies checks
const checkPeerDependencies = async (peerDependencies, name) =>
Promise.all(Array.from(peerDependencies.entries()).map(async (entry) => {
const [peerDepName, peerDepRange] = entry;
log(`Checking ${name}'s peerDependency of '${peerDepName}@${peerDepRange}'`);
let found = false;
if (deps.has(peerDepName)) {
// Verify that the minimum allowed version still satisfies the peerDep
const minAllowedVer = npmVers.get(peerDepName).minimum;
if (semver.satisfies(minAllowedVer, peerDepRange)) {
found = true;
}
}

if (!found) {
console.error(`A ${INCLUDE_DEV ? '[dev]D' : 'd'}ependency satisfying ${name}'s peerDependency of '${peerDepName}@${peerDepRange}' was not found!`);

if (deps.has(peerDepName)) {
console.log(`Current: ${peerDepName}@${deps.get(peerDepName)}`);
const { versions } = npmVers.get(peerDepName);
const maxUsabe = semver.maxSatisfying(versions, peerDepRange);
console.log(`Required version is allowed? ${maxUsabe ? 'Yes' : 'No'}`);
}
}
}));

const checkAllPeerDeps = async () => {
const promises = [];
peerDeps.forEach((peerDependencies, name) => {
promises.push(checkPeerDependencies(peerDependencies, name));
});
return Promise.all(promises);
};

// Main function
async function checkPeerDeps() {
// eslint-disable-next-line import/no-dynamic-require
const packageConfig = require(`${process.cwd()}/package.json`);
if (!packageConfig.dependencies) {
console.error('No dependencies in the current pacakge!');
}

// Get the dependencies to process
addDeps(packageConfig.dependencies);

if (INCLUDE_DEV && packageConfig.devDependencies) {
addDeps(packageConfig.devDependencies);
}

log('Dependencies:');
deps.forEach((range, name) => { log(`${name}: ${range}`); });

log('Determining peerDependencies...');
await getPeerDeps();
log('Done.');

// Get the NPM versions required to check the peerDependencies
log('Determining version ranges from NPM...');
await getNpmVersions();
log('Done.');

log('Checking versions...');
await checkAllPeerDeps();
log('Done.');
}

module.exports = checkPeerDeps;
Loading

0 comments on commit eea2f2f

Please sign in to comment.