Skip to content

Commit eea2f2f

Browse files
committed
Initial commit of the code
First version, it works locally so what could go wrong? :stuck_out_tongue:
1 parent 854989e commit eea2f2f

5 files changed

+1798
-1
lines changed

README.md

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,40 @@
11
# check-peer-deps
2-
Verifies that the peerDependency requirements of all top level dependencies are satisfied.
2+
Verifies that the `peerDependency` requirements of all top level dependencies
3+
are satisfied.
4+
5+
## Installation
6+
7+
You can install this on your system with a simple:
8+
9+
```sh
10+
npm install check-peer-deps
11+
```
12+
13+
Please note that this utility requires `npm` to be available.
14+
15+
## Usage
16+
17+
Simply change into the directory of the project you wish to check the
18+
`peerDependencies` of and run the program.
19+
20+
```sh
21+
> cd foobar
22+
> check-peer-deps
23+
```
24+
25+
If the minimum versions of all your top level `peerDependencies` are satisfied
26+
then there will be no output, otherwise you will see something similar to this:
27+
28+
```
29+
> check-peer-deps
30+
A [dev]Dependency satisfying eslint-config-airbnb-base's peerDependency of 'eslint@^4.9.0' was not found!
31+
Current: eslint@^4.6.0
32+
Required version is allowed? Yes
33+
```
34+
35+
This tells you that `eslint-config-airbnb-base` is requiring `eslint@^4.9.0` as
36+
a `peerDependency`, but the project currently only specifies `eslint@^4.6.0`,
37+
allowing a potential issue to arise if `[email protected]` was installed and not
38+
updated before installing. The output also tells you that although the
39+
_minimum_ allowed version is too low, the _maximum_ allowed version does
40+
satisfy the `peerDependencies` requirement.

bin/check-peer-deps-cli.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env node
2+
3+
process.title = 'check-peer-deps';
4+
5+
const checkPeerDeps = require('../check-peer-deps');
6+
7+
checkPeerDeps();

check-peer-deps.js

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
const { exec } = require('sb-exec');
2+
const semver = require('semver');
3+
4+
// Flags
5+
// Enable debug output
6+
const DEBUG = false;
7+
// Include development packages when checking whether a peerDependency has been
8+
// satisfied.
9+
const INCLUDE_DEV = true;
10+
// Use the local package.json files or check NPM to determine the peerDependencies
11+
const USE_LOCAL_PEERDEPS = true;
12+
13+
// Internal vars
14+
const deps = new Map();
15+
const npmVers = new Map();
16+
const peerDeps = new Map();
17+
18+
const log = (value) => {
19+
if (DEBUG) {
20+
console.log(value);
21+
}
22+
};
23+
24+
const addDeps = (dependencies) => {
25+
Object.entries(dependencies).forEach((entry) => {
26+
const [name, range] = entry;
27+
deps.set(name, range);
28+
});
29+
};
30+
31+
const gatherNpmVer = async (range, name) => {
32+
log(`Getting versions for ${name}@${range}...`);
33+
const opts = ['view', '--json', name, 'versions'];
34+
const versions = JSON.parse(await exec('npm', opts));
35+
const ranges = {
36+
versions,
37+
minimum: semver.minSatisfying(versions, range),
38+
maximum: semver.maxSatisfying(versions, range),
39+
};
40+
log(`${name}@${range}: '${ranges.minimum}' to '${ranges.maximum}'`);
41+
npmVers.set(name, ranges);
42+
};
43+
44+
const getNpmVersions = async () => {
45+
// Gather the unique package names
46+
const toCheck = new Set();
47+
peerDeps.forEach((peerDependencies) => {
48+
peerDependencies.forEach((range, name) => {
49+
toCheck.add(name);
50+
});
51+
});
52+
53+
// Grab the versions from NPM
54+
return Promise.all(Array.from(toCheck.values()).map(async (name) => {
55+
if (deps.has(name)) {
56+
await gatherNpmVer(deps.get(name), name);
57+
}
58+
}));
59+
};
60+
61+
// Get the peerDependencies
62+
const getNpmPeerDep = async (range, name) => {
63+
log(`Getting peerDependencies for ${name}`);
64+
const opts = ['view', '--json', name, 'peerDependencies'];
65+
const npmPeerDeps = JSON.parse(await exec('npm', opts));
66+
if (!peerDeps.has(name)) {
67+
peerDeps.set(name, new Map());
68+
}
69+
const currDeps = peerDeps.get(name);
70+
Object.entries(npmPeerDeps).forEach((entry) => {
71+
const [depName, depRange] = entry;
72+
log(`${depName}@${depRange}`);
73+
currDeps.set(depName, depRange);
74+
});
75+
};
76+
77+
const getLocalPeerDep = async (range, name) => {
78+
log(`Getting peerDependencies for ${name}`);
79+
let packageInfo;
80+
try {
81+
// Hacktown, USA.
82+
// eslint-disable-next-line import/no-dynamic-require
83+
packageInfo = require(`${process.cwd()}/node_modules/${name}/package.json`);
84+
} catch (e) {
85+
return;
86+
}
87+
if (!packageInfo.peerDependencies) {
88+
return;
89+
}
90+
if (!peerDeps.has(name)) {
91+
peerDeps.set(name, new Map());
92+
}
93+
const currDeps = peerDeps.get(name);
94+
Object.entries(packageInfo.peerDependencies).forEach((entry) => {
95+
const [depName, depRange] = entry;
96+
log(`${depName}@${depRange}`);
97+
currDeps.set(depName, depRange);
98+
});
99+
};
100+
101+
const getPeerDeps = async () => {
102+
const promises = [];
103+
if (USE_LOCAL_PEERDEPS) {
104+
log("Using local package.json's to determine peerDependencies.");
105+
} else {
106+
log('Using NPM to determine peerDependencies.');
107+
}
108+
deps.forEach((range, name) => {
109+
if (USE_LOCAL_PEERDEPS) {
110+
promises.push(getLocalPeerDep(range, name));
111+
} else {
112+
promises.push(getNpmPeerDep(range, name));
113+
}
114+
});
115+
return Promise.all(promises);
116+
};
117+
118+
// peerDependencies checks
119+
const checkPeerDependencies = async (peerDependencies, name) =>
120+
Promise.all(Array.from(peerDependencies.entries()).map(async (entry) => {
121+
const [peerDepName, peerDepRange] = entry;
122+
log(`Checking ${name}'s peerDependency of '${peerDepName}@${peerDepRange}'`);
123+
let found = false;
124+
if (deps.has(peerDepName)) {
125+
// Verify that the minimum allowed version still satisfies the peerDep
126+
const minAllowedVer = npmVers.get(peerDepName).minimum;
127+
if (semver.satisfies(minAllowedVer, peerDepRange)) {
128+
found = true;
129+
}
130+
}
131+
132+
if (!found) {
133+
console.error(`A ${INCLUDE_DEV ? '[dev]D' : 'd'}ependency satisfying ${name}'s peerDependency of '${peerDepName}@${peerDepRange}' was not found!`);
134+
135+
if (deps.has(peerDepName)) {
136+
console.log(`Current: ${peerDepName}@${deps.get(peerDepName)}`);
137+
const { versions } = npmVers.get(peerDepName);
138+
const maxUsabe = semver.maxSatisfying(versions, peerDepRange);
139+
console.log(`Required version is allowed? ${maxUsabe ? 'Yes' : 'No'}`);
140+
}
141+
}
142+
}));
143+
144+
const checkAllPeerDeps = async () => {
145+
const promises = [];
146+
peerDeps.forEach((peerDependencies, name) => {
147+
promises.push(checkPeerDependencies(peerDependencies, name));
148+
});
149+
return Promise.all(promises);
150+
};
151+
152+
// Main function
153+
async function checkPeerDeps() {
154+
// eslint-disable-next-line import/no-dynamic-require
155+
const packageConfig = require(`${process.cwd()}/package.json`);
156+
if (!packageConfig.dependencies) {
157+
console.error('No dependencies in the current pacakge!');
158+
}
159+
160+
// Get the dependencies to process
161+
addDeps(packageConfig.dependencies);
162+
163+
if (INCLUDE_DEV && packageConfig.devDependencies) {
164+
addDeps(packageConfig.devDependencies);
165+
}
166+
167+
log('Dependencies:');
168+
deps.forEach((range, name) => { log(`${name}: ${range}`); });
169+
170+
log('Determining peerDependencies...');
171+
await getPeerDeps();
172+
log('Done.');
173+
174+
// Get the NPM versions required to check the peerDependencies
175+
log('Determining version ranges from NPM...');
176+
await getNpmVersions();
177+
log('Done.');
178+
179+
log('Checking versions...');
180+
await checkAllPeerDeps();
181+
log('Done.');
182+
}
183+
184+
module.exports = checkPeerDeps;

0 commit comments

Comments
 (0)