Skip to content

Commit

Permalink
Respect peer dependencies (#869)
Browse files Browse the repository at this point in the history
* respect peer dependencies

* add command-line option checkPeer

* unit tests for checkPeer option

* Remove internal options.dev, options.peer, etc

* Rename --checkPeer to --peer

* --peer tests: Clean up node_modules and package-lock.json

* pass all options to getCurrentDependencies

Co-authored-by: Raine Revere <raine@cybersemics.org>
  • Loading branch information
korelstar and raineorshine committed Apr 7, 2021
1 parent 02f7ca0 commit 791aa22
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 20 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ ncu "/^(?!react-).*$/" # windows
--deep Run recursively in current working directory.
Alias of (--packageFile '**/package.json').
--dep <value> Check one or more sections of dependencies only:
prod, dev, peer, optional, bundle
dev, optional, peer, prod, bundle
(comma-delimited).
--deprecated Include deprecated packages.
--doctor Iteratively installs upgrades and runs tests to
Expand Down Expand Up @@ -172,6 +172,8 @@ ncu "/^(?!react-).*$/" # windows
--packageFile <path|glob> Package file(s) location (default:
./package.json).
-p, --packageManager <name> npm, yarn (default: "npm")
--peer Check peer dependencies of installed packages
and filter updates to compatible versions.
--pre <n> Include -alpha, -beta, -rc. (default: 0; default
with --newest and --greatest: 1).
--prefix <path> Current working directory of npm.
Expand Down
7 changes: 6 additions & 1 deletion lib/cli-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ other version numbers that are higher. Includes prereleases.`])

// store CLI options separately from bin file so that they can be used to build type definitions
const cliOptions = [
{
long: 'peer',
description: 'Check peer dependencies of installed packages and filter updates to compatible versions.',
type: 'boolean'
},
{
long: 'color',
description: 'Force color in terminal',
Expand Down Expand Up @@ -67,7 +72,7 @@ const cliOptions = [
{
long: 'dep',
arg: 'value',
description: 'Check one or more sections of dependencies only: prod, dev, peer, optional, bundle (comma-delimited).'
description: 'Check one or more sections of dependencies only: dev, optional, peer, prod, bundle (comma-delimited).'
},
{
long: 'deprecated',
Expand Down
7 changes: 6 additions & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ declare namespace ncu {
deep?: boolean;

/**
* Check one or more sections of dependencies only: prod, dev, peer, optional, bundle (comma-delimited).
* Check one or more sections of dependencies only: dev, optional, peer, prod, bundle (comma-delimited).
*/
dep?: string;

Expand Down Expand Up @@ -140,6 +140,11 @@ declare namespace ncu {
*/
packageManager?: string;

/**
* Check peer dependencies of installed packages and filter updates to compatible versions.
*/
peer?: boolean;

/**
* Include -alpha, -beta, -rc. (default: 0; default with --newest and --greatest: 1).
*/
Expand Down
31 changes: 30 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ async function analyzeProjectDependencies(options, pkgData, pkgFile) {
options.enginesNode = _.get(pkg, 'engines.node')
}

if (options.peer) {
options.peerDependencies = getPeerDependencies(current, options)
}

print(options, '\nOptions:', 'verbose')
print(options, sortOptions(options), 'verbose')

Expand Down Expand Up @@ -224,6 +228,31 @@ async function analyzeProjectDependencies(options, pkgData, pkgFile) {
return output
}

/** Get peer dependencies from installed packages */
function getPeerDependencies(current, options) {
const basePath = options.cwd || './'
return Object.keys(current).map(pkgName => {
const path = basePath + 'node_modules/' + pkgName + '/package.json'
try {
const pkgData = fs.readFileSync(path)
const pkg = jph.parse(pkgData)
return vm.getCurrentDependencies(pkg, { ...options, dep: 'peer' })
}
catch (e) {
print(options, 'Could not read peer dependencies for package ' + pkgName + '. Is this package installed?', 'warn')
return {}
}
}).reduce((acc, peers) => {
Object.entries(peers).forEach(([pkgName, version]) => {
if (acc[pkgName] === undefined) {
acc[pkgName] = []
}
acc[pkgName][acc[pkgName].length] = version
})
return acc
}, {})
}

//
// Program
//
Expand Down Expand Up @@ -535,4 +564,4 @@ function getNcurc({ configFileName, configFilePath, packageFile } = {}) {
return result ? { ...result, args } : null
}

module.exports = { run, getNcurc, ...vm }
module.exports = { run, getNcurc, getPeerDependencies, ...vm }
15 changes: 15 additions & 0 deletions lib/package-managers/npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,27 @@ function satisfiesNodeEngine(versionResult, nodeEngine) {
return versionNodeEngine && semver.satisfies(minVersion, versionNodeEngine)
}

/**
* Returns true if the peer dependencies requirement is satisfied or not specified for a given package version.
*
* @param versionResult Version object returned by pacote.packument.
* @param peerDependencies The list of peer dependencies.
* @returns True if the peer dependencies are satisfied or not specified.
*/
function satisfiesPeerDependencies(versionResult, peerDependencies) {
if (!peerDependencies) return true
const pkgPeerDependencies = peerDependencies[versionResult.name]
if (!pkgPeerDependencies) return true
return pkgPeerDependencies.every(v => semver.satisfies(versionResult.version, v))
}

/** Returns a composite predicate that filters out deprecated, prerelease, and node engine incompatibilies from version objects returns by pacote.packument. */
function filterPredicate(options) {
return _.overEvery([
options.deprecated ? null : o => !o.deprecated,
options.pre ? null : o => !versionUtil.isPre(o.version),
options.enginesNode ? o => satisfiesNodeEngine(o, options.enginesNode) : null,
options.peerDependencies ? o => satisfiesPeerDependencies(o, options.peerDependencies) : null,
])
}

Expand Down
24 changes: 8 additions & 16 deletions lib/versionmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,25 +295,17 @@ async function upgradePackageData(pkgData, oldDependencies, newDependencies, new
*/
function getCurrentDependencies(pkgData = {}, options = {}) {

if (options.dep) {
const deps = (options.dep || '').split(',')
options.prod = deps.includes('prod')
options.dev = deps.includes('dev')
options.peer = deps.includes('peer')
options.optional = deps.includes('optional')
options.bundle = deps.includes('bundle')
}
else {
options.prod = options.dev = options.peer = options.optional = options.bundle = true
}
const deps = options.dep
? (options.dep || '').split(',')
: ['dev', 'optional', 'peer', 'prod', 'bundle']

const allDependencies = cint.filterObject(
{
...options.prod && pkgData.dependencies,
...options.dev && pkgData.devDependencies,
...options.peer && pkgData.peerDependencies,
...options.optional && pkgData.optionalDependencies,
...options.bundle && pkgData.bundleDependencies
...deps.includes('prod') && pkgData.dependencies,
...deps.includes('dev') && pkgData.devDependencies,
...deps.includes('peer') && pkgData.peerDependencies,
...deps.includes('optional') && pkgData.optionalDependencies,
...deps.includes('bundle') && pkgData.bundleDependencies
},
filterAndReject(options.filter, options.reject, options.filterVersion, options.rejectVersion)
)
Expand Down
35 changes: 35 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

const fs = require('fs')
const path = require('path')
const rimraf = require('rimraf')
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const chaiString = require('chai-string')
const ncu = require('../lib/')
const { npm: spawnNpm } = require('../lib/package-managers/npm')

chai.use(chaiAsPromised)
chai.use(chaiString)
Expand Down Expand Up @@ -708,4 +710,37 @@ describe('run', function () {

})

describe('peer dependencies', () => {
const peerPath = path.join(__dirname, '/peer/')

it('peer dependencies of installed packages are ignored by default', async () => {
try {
await spawnNpm('install', {}, { cwd: peerPath })
const upgrades = await ncu.run({ cwd: peerPath })
upgrades.should.deep.equal({
'ncu-test-return-version': '2.0.0'
})
}
finally {
rimraf.sync(path.join(peerPath, 'node_modules'))
rimraf.sync(path.join(peerPath, 'package-lock.json'))
}
})

it('peer dependencies of installed packages are checked when using option peer', async () => {
try {
await spawnNpm('install', {}, { cwd: peerPath })
const upgrades = await ncu.run({ cwd: peerPath, peer: true })
upgrades.should.deep.equal({
'ncu-test-return-version': '1.1.0'
})
}
finally {
rimraf.sync(path.join(peerPath, 'node_modules'))
rimraf.sync(path.join(peerPath, 'package-lock.json'))
}
})

})

})
6 changes: 6 additions & 0 deletions test/peer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"dependencies": {
"ncu-test-peer": "1.0.0",
"ncu-test-return-version": "1.0.0"
}
}

0 comments on commit 791aa22

Please sign in to comment.