Skip to content

Commit

Permalink
feat: --mergeConfig option for --deep to inherit config settings (#897)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Shagin <saaivs@gmail.com>
  • Loading branch information
art-ws and saaivs committed May 30, 2021
1 parent 7f538ed commit 18358a4
Show file tree
Hide file tree
Showing 14 changed files with 176 additions and 4 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ ncu "/^(?!react-).*$/" # windows
--jsonUpgraded Output upgraded dependencies in json.
-l, --loglevel <n> Amount to log: silent, error, minimal, warn,
info, verbose, silly. (default: "warn")
--mergeConfig Merges nested configs with the root config file
for --deep or --packageFile options (default:
false)').
-m, --minimal Do not upgrade newer versions that are already
satisfied by the version range according to
semver.
Expand Down
6 changes: 3 additions & 3 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ program.version(pkg.version)

program.parse(process.argv)

const { configFileName, configFilePath, packageFile } = program
const { configFileName, configFilePath, packageFile, mergeConfig } = program

// load .ncurc
// Do not load when global option is set
// Do not load when tests are running (an be overridden if configFilePath is set explicitly)
const rcResult = !program.global && (!process.env.NCU_TESTS || configFilePath)
// Do not load when tests are running (an be overridden if configFilePath is set explicitly, or --mergeConfig option specified)
const rcResult = !program.global && (!process.env.NCU_TESTS || configFilePath || mergeConfig)
? ncu.getNcurc({ configFileName, configFilePath, packageFile })
: null

Expand Down
5 changes: 5 additions & 0 deletions lib/cli-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ As a comparison: without using the --peer option, ncu will suggest the latest ve
description: `Run recursively in current working directory. Alias of (--packageFile '${deepPatternPrefix}package.json').`,
type: 'boolean'
},
{
long: 'mergeConfig',
description: `Merges nested configs with the root config file for --deep or --packageFile options (default: false)').`,
type: 'boolean'
},
{
long: 'dep',
arg: 'value',
Expand Down
5 changes: 5 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ declare namespace ncu {
*/
loglevel?: string;

/**
* Merges nested configs with the root config file for --deep or --packageFile options (default: false)').
*/
mergeConfig?: boolean;

/**
* Do not upgrade newer versions that are already satisfied by the version range according to semver.
*/
Expand Down
8 changes: 7 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const packageManagers = require('./package-managers')
const { print, printJson, printUpgrades, printIgnoredUpdates } = require('./logging')
const { deepPatternPrefix, doctorHelpText } = require('./constants')
const cliOptions = require('./cli-options')
const mergeOptions = require('./merge-options')

// maps package managers to package file names
const packageFileNames = {
Expand Down Expand Up @@ -508,9 +509,14 @@ async function run(options = {}) {
const packages = await previousPromise
// copy object to prevent share .ncurc options between different packageFile, to prevent unpredictable behavior
const rcResult = getNcurc({ packageFile })
let rcConfig = rcResult && rcResult.config ? rcResult.config : {}
if (options.mergeConfig && Object.keys(rcConfig).length) {
// Merge config options.
rcConfig = mergeOptions(options, rcConfig)
}
const pkgOptions = {
...options,
...rcResult && rcResult.config,
...rcConfig,
packageFile,
}
const [pkgData, pkgFile] = await findPackage(pkgOptions)
Expand Down
21 changes: 21 additions & 0 deletions lib/merge-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function mergeArrays(arr1, arr2) {
return [...new Set([...arr1 || [], ...arr2 || []])]
}

/**
* Shallow merge (specific or all) properties.
* If some properties both are arrays, then merge them also.
*/
function mergeOptions(options1, options2) {
options1 = options1 || {}
options2 = options2 || {}
const result = { ...options1, ...options2 }
Object.keys(result).forEach(key => {
if (Array.isArray(options1[key]) && Array.isArray(options2[key])) {
result[key] = mergeArrays(options1[key], options2[key])
}
})
return result
}

module.exports = mergeOptions
5 changes: 5 additions & 0 deletions test/deep-ncurc/pkg/sub2/sub21/.ncurc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
reject: [
'fp-and-or'
]
}
7 changes: 7 additions & 0 deletions test/deep-ncurc/pkg/sub2/sub21/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"cute-animals": "^0.1.0",
"fp-and-or": "^0.1.0",
"ncu-test-return-version": "^0.1.0"
}
}
7 changes: 7 additions & 0 deletions test/deep-ncurc/pkg/sub2/sub22/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"cute-animals": "^0.1.0",
"fp-and-or": "^0.1.0",
"ncu-test-v2": "^0.1.0"
}
}
7 changes: 7 additions & 0 deletions test/deep-ncurc/pkg/sub3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"cute-animals": "^0.1.0",
"fp-and-or": "^0.1.0",
"ncu-test-v2": "^0.1.0"
}
}
5 changes: 5 additions & 0 deletions test/deep-ncurc/pkg/sub3/sub31/.ncurc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
reject: [
'fp-and-or'
]
}
7 changes: 7 additions & 0 deletions test/deep-ncurc/pkg/sub3/sub31/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"cute-animals": "^0.1.0",
"fp-and-or": "^0.1.0",
"ncu-test-return-version": "^0.1.0"
}
}
7 changes: 7 additions & 0 deletions test/deep-ncurc/pkg/sub3/sub32/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"cute-animals": "^0.1.0",
"fp-and-or": "^0.1.0",
"ncu-test-v2": "^0.1.0"
}
}
87 changes: 87 additions & 0 deletions test/deep.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const ncu = require('../lib/')
const spawn = require('spawn-please')
const mergeOptions = require('../lib/merge-options')

chai.should()
chai.use(chaiAsPromised)
Expand Down Expand Up @@ -144,6 +145,92 @@ describe('--deep with nested ncurc files', function () {
deepJsonOut['pkg/sub2/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub2/package.json'].should.have.property('fp-and-or')
deepJsonOut['pkg/sub2/package.json'].should.have.property('ncu-test-v2')

// pkg3: reject: ['cute-animals']
deepJsonOut.should.have.property('pkg/sub3/package.json')
deepJsonOut['pkg/sub3/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub3/package.json'].should.have.property('fp-and-or')
deepJsonOut['pkg/sub3/package.json'].should.have.property('ncu-test-v2')
})

it('use ncurc of nested packages with --mergeConfig option', async () => {

const deepJsonOut = await spawn('node', [bin, '--jsonUpgraded', '--deep', '--mergeConfig'], { cwd }).then(JSON.parse)

// root: reject: ['cute-animals']
deepJsonOut.should.have.property('package.json')
deepJsonOut['package.json'].should.not.have.property('cute-animals')
deepJsonOut['package.json'].should.have.property('fp-and-or')

// pkg1: reject: ['fp-ando-or', 'cute-animals']
deepJsonOut.should.have.property('pkg/sub1/package.json')
deepJsonOut['pkg/sub1/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub1/package.json'].should.not.have.property('fp-and-or')
deepJsonOut['pkg/sub1/package.json'].should.have.property('ncu-test-return-version')

// pkg2: reject: ['cute-animals']
deepJsonOut.should.have.property('pkg/sub2/package.json')
deepJsonOut['pkg/sub2/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub2/package.json'].should.have.property('fp-and-or')
deepJsonOut['pkg/sub2/package.json'].should.have.property('ncu-test-v2')

// pkg21: explicit reject: ['fp-ando-or'] and implicit reject ['cute-animals']
deepJsonOut.should.have.property('pkg/sub2/sub21/package.json')
deepJsonOut['pkg/sub2/sub21/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub2/sub21/package.json'].should.not.have.property('fp-and-or')
deepJsonOut['pkg/sub2/sub21/package.json'].should.have.property('ncu-test-return-version')

// pkg22: implicit reject: ['cute-animals']
deepJsonOut.should.have.property('pkg/sub2/sub22/package.json')
deepJsonOut['pkg/sub2/sub22/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub2/sub22/package.json'].should.have.property('fp-and-or')
deepJsonOut['pkg/sub2/sub22/package.json'].should.have.property('ncu-test-v2')

// pkg3: reject: ['cute-animals']
deepJsonOut.should.have.property('pkg/sub3/package.json')
deepJsonOut['pkg/sub3/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub3/package.json'].should.have.property('fp-and-or')
deepJsonOut['pkg/sub3/package.json'].should.have.property('ncu-test-v2')

// pkg31: explicit reject: ['fp-ando-or'] and implicit reject ['cute-animals']
deepJsonOut.should.have.property('pkg/sub3/sub31/package.json')
deepJsonOut['pkg/sub3/sub31/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub3/sub31/package.json'].should.not.have.property('fp-and-or')
deepJsonOut['pkg/sub3/sub31/package.json'].should.have.property('ncu-test-return-version')

// pkg32: implicit reject: ['cute-animals']
deepJsonOut.should.have.property('pkg/sub3/sub32/package.json')
deepJsonOut['pkg/sub3/sub32/package.json'].should.not.have.property('cute-animals')
deepJsonOut['pkg/sub3/sub32/package.json'].should.have.property('fp-and-or')
deepJsonOut['pkg/sub3/sub32/package.json'].should.have.property('ncu-test-v2')

})

it('merge options', () => {
const eq = (o1, o2, result, opts) => chai.expect(mergeOptions(o1, o2)).to.deep.equal(result)

// trivial cases
eq(null, null, {})
eq({}, {}, {})

// standard merge not broken
eq({ a: 1 }, {}, { a: 1 })
eq({}, { a: 1 }, { a: 1 })
eq({ a: 1 }, { a: 2 }, { a: 2 })

// merge arrays (non standard behavior)
eq({ a: [1] }, { a: [2] }, { a: [1, 2] })
eq({ a: [1, 2] }, { a: [2, 3] }, { a: [1, 2, 3] })

// if property types different, then apply standard merge behavior
eq({ a: 1 }, { a: [2] }, { a: [2] })

// all together
eq(
{ a: [1], b: true, c: 1, d1: 'd1' },
{ a: [2], b: false, c: ['1'], d2: 'd2' },
{ a: [1, 2], b: false, c: ['1'], d1: 'd1', d2: 'd2' }
)
})

})

0 comments on commit 18358a4

Please sign in to comment.