-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(check_peer_dependencies): Added a script which checks that all p…
…eer dependencies are satisfied npx check_peer_dependencies (--yarn | --npm) [--install]
- Loading branch information
1 parent
a3574bd
commit 63e6c97
Showing
2 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
#!/usr/bin/env node | ||
|
||
const fs = require('fs'); | ||
const semver = require('semver'); | ||
const util = require('./util'); | ||
const yargs = require('yargs') | ||
.option('help', { | ||
alias: 'h', | ||
boolean: true, | ||
description: `Print usage information`, | ||
}) | ||
.option('yarn', { | ||
boolean: true, | ||
description: `Use yarn package manager`, | ||
}) | ||
.option('npm', { | ||
boolean: true, | ||
description: `Use npm package manager`, | ||
}) | ||
.option('install', { | ||
boolean: true, | ||
description: 'Install missing or incorrect peerDependencies' | ||
}) | ||
.check(argv => { | ||
if (argv.yarn && argv.npm) { | ||
throw new Error('Specify either --yarn or --npm but not both'); | ||
} else if (!argv.yarn && !argv.npm) { | ||
throw new Error('Specify either --yarn or --npm'); | ||
} | ||
return true; | ||
}); | ||
|
||
if (yargs.argv.help) { | ||
process.exit(-2); | ||
} | ||
|
||
const PACKAGEJSON = 'package.json'; | ||
const NODEMODULES = 'node_modules'; | ||
|
||
const readFile = filename => JSON.parse(fs.readFileSync(filename).toString('utf-8')); | ||
|
||
const visitedDeps = []; | ||
const peerDeps = []; | ||
|
||
checkInstalledPackage(readFile(PACKAGEJSON)); | ||
|
||
function getDependencies(packageJson) { | ||
const { name, dependencies = {}, peerDependencies = {} } = packageJson; | ||
return { | ||
name, | ||
dependencies: Object.keys(dependencies).map(name => ({ name, version: dependencies[ name ] })), | ||
peerDependencies: Object.keys(peerDependencies).map(name => ({ name, version: peerDependencies[ name ] })), | ||
}; | ||
} | ||
|
||
|
||
function checkInstalledPackage(packageJson) { | ||
const { name, dependencies, peerDependencies } = getDependencies(packageJson); | ||
if (visitedDeps.includes(name)) { | ||
return; | ||
} | ||
visitedDeps.push(name); | ||
|
||
peerDependencies.forEach(peer => peerDeps.push({ ...peer, depender: `${packageJson.name}@${packageJson.version}` })); | ||
dependencies.forEach(dependency => { | ||
const dependencyName = dependency.name; | ||
const installedPkgDir = `${NODEMODULES}/${dependencyName}`; | ||
const nestedPkgJson = `${installedPkgDir}/${PACKAGEJSON}`; | ||
const nestedPkg = readFile(nestedPkgJson); | ||
checkInstalledPackage(nestedPkg) | ||
}); | ||
} | ||
|
||
console.log('Peer Dependencies:'); | ||
peerDeps.forEach(dep => console.log(`${dep.depender} requires ${dep.name} ${dep.version}`)); | ||
console.log(''); | ||
|
||
const missingPeers = []; | ||
const incorrectPeers = []; | ||
|
||
function handleMissingPeerDependency(peerDependency) { | ||
missingPeers.push(peerDependency); | ||
} | ||
|
||
function handleIncorrectPeerDependency(peerDependency, installedVersion) { | ||
incorrectPeers.push({ ...peerDependency, installedVersion }); | ||
} | ||
|
||
/** | ||
* Given a peerDependency, checks that the installed version of the package satisfies the required version | ||
*/ | ||
function checkPeerDependency(peerDependency) { | ||
const pkgJsonFile = `${NODEMODULES}/${peerDependency.name}/${PACKAGEJSON}`; | ||
const exists = fs.existsSync(pkgJsonFile); | ||
if (!exists) { | ||
return handleMissingPeerDependency(peerDependency); | ||
} | ||
|
||
const installedVersion = readFile(pkgJsonFile).version; | ||
|
||
if (!semver.satisfies(installedVersion, peerDependency.version)) { | ||
handleIncorrectPeerDependency(peerDependency, installedVersion); | ||
} | ||
} | ||
|
||
peerDeps.forEach(checkPeerDependency); | ||
|
||
if (missingPeers.length || incorrectPeers.length) { | ||
console.log('Problems found:'); | ||
missingPeers.forEach(peer => console.error(`Missing peer dependency: ${peer.name}@${peer.version} depended on by ${peer.depender}`)); | ||
incorrectPeers.forEach(peer => | ||
console.error(`Incorrect peer dependency: ${peer.name}@${peer.installedVersion} installed but ${peer.depender} requires ${peer.version}.`)); | ||
console.error(); | ||
} | ||
|
||
function semverReverseSort(a, b) { | ||
const lt = semver.lt(a, b); | ||
const gt = semver.gt(a, b); | ||
if (!lt && !gt) { | ||
return 0; | ||
} else if (lt) { | ||
return 1; | ||
} | ||
return -1; | ||
} | ||
|
||
const adds = []; | ||
const upgrades = []; | ||
|
||
function findPossibleResolution(packageName, allPeerDeps, isMissing = false) { | ||
const requiredPeerVersions = allPeerDeps.filter(dep => dep.name === packageName); | ||
const rawVersionsInfo = util._exec(`npm view ${packageName} versions`, true).stdout; | ||
const availableVersions = JSON.parse(rawVersionsInfo.replace(/'/g, '"')).sort(semverReverseSort); | ||
|
||
const foundVer = availableVersions.find(ver => requiredPeerVersions.every(peerVer => semver.satisfies(ver, peerVer.version))); | ||
if (!foundVer) { | ||
console.error(`Unable to find a version for ${packageName} that satisfies the following peerDependencies:`); | ||
requiredPeerVersions.forEach(peer => console.error(`${peer.depender} requires ${peer.name} ${peer.version}`)); | ||
throw new Error(`Unable to find a version for ${packageName} that satisfies ` + | ||
`the following peerDependencies: ${requiredPeerVersions.map(x => x.version).join()}`); | ||
} | ||
|
||
console.log(`found ${packageName}@${foundVer} which satisfies ${requiredPeerVersions.join(',')}`); | ||
|
||
(isMissing ? adds : upgrades).push(`${packageName}@${foundVer}`); | ||
} | ||
|
||
|
||
const missingPackages = missingPeers.map(x => x.name).reduce((acc, x) => acc.includes(x) ? acc : acc.concat(x), []); | ||
const incorrectPackages = incorrectPeers.map(x => x.name).reduce((acc, x) => acc.includes(x) ? acc : acc.concat(x), []); | ||
if (missingPackages.length || incorrectPackages.length) { | ||
console.log('Searching for solutions:'); | ||
missingPackages.forEach(peer => findPossibleResolution(peer, peerDeps, true)); | ||
incorrectPackages.forEach(peer => findPossibleResolution(peer, peerDeps, false)); | ||
console.log(); | ||
} | ||
|
||
function getCommandLines() { | ||
const commands = []; | ||
if (yargs.argv.yarn) { | ||
if (adds.length) { | ||
commands.push(`yarn add ${adds.join(' ')}`); | ||
} | ||
if (upgrades.length) { | ||
commands.push(`yarn upgrade ${upgrades.join(' ')}`); | ||
} | ||
} else { | ||
commands.push(`npm install ${adds.concat(upgrades).join(' ')}`) | ||
} | ||
return commands; | ||
} | ||
|
||
const commandLines = getCommandLines(); | ||
if (commandLines.length) { | ||
console.log('Commands:'); | ||
commandLines.forEach(command => { | ||
if (yargs.argv.install) { | ||
util._exec(command); | ||
} else { | ||
console.log(command); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters