Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect peer dependencies #869

Merged
merged 7 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, { dep: 'peer', filter: options.filter, reject: options.reject })
raineorshine marked this conversation as resolved.
Show resolved Hide resolved
}
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"
}
}