From 1258630faa1d0d4a7eb4abe4eac0a66651477842 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Thu, 7 Mar 2019 16:37:07 +0100 Subject: [PATCH] feat(npm): dedupe (#3322) Allows dedupe options for npm and yarn. Closes #2883 --- lib/config/definitions.js | 9 ++++-- lib/manager/npm/post-update/index.js | 3 +- lib/manager/npm/post-update/npm.js | 34 +++++++++++++++------ lib/manager/npm/post-update/yarn.js | 34 +++++++++++++++++++-- test/workers/branch/lock-files/npm.spec.js | 13 +++++--- test/workers/branch/lock-files/yarn.spec.js | 14 ++++++++- website/docs/configuration-options.md | 5 ++- 7 files changed, 89 insertions(+), 23 deletions(-) diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 069eece75a..4b9dc1ff5f 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -1080,10 +1080,15 @@ const options = [ { name: 'postUpdateOptions', description: - 'Enable various post-update options to be run after package/artifact updating', + 'Enable post-update options to be run after package/artifact updating', type: 'list', default: [], - allowedValues: ['gomodTidy'], + allowedValues: [ + 'gomodTidy', + 'npmDedupe', + 'yarnDedupeFewer', + 'yarnDedupeHighest', + ], cli: false, env: false, mergeable: true, diff --git a/lib/manager/npm/post-update/index.js b/lib/manager/npm/post-update/index.js index e26868e8b1..720f235b2f 100644 --- a/lib/manager/npm/post-update/index.js +++ b/lib/manager/npm/post-update/index.js @@ -378,8 +378,7 @@ async function getAdditionalFiles(config, packageFiles) { upath.join(config.localDir, lockFileDir), env, fileName, - config.skipInstalls, - config.binarySource, + config, upgrades ); if (res.error) { diff --git a/lib/manager/npm/post-update/npm.js b/lib/manager/npm/post-update/npm.js index 4469042a4d..9c6dc96d06 100644 --- a/lib/manager/npm/post-update/npm.js +++ b/lib/manager/npm/post-update/npm.js @@ -11,15 +11,16 @@ async function generateLockFile( cwd, env, filename, - skipInstalls, - binarySource, + config = {}, upgrades = [] ) { logger.debug(`Spawning npm install to create ${cwd}/${filename}`); + const { skipInstalls, binarySource, postUpdateOptions } = config; let lockFile = null; let stdout; let stderr; let cmd; + let args = ''; try { const startTime = process.hrtime(); try { @@ -62,15 +63,15 @@ async function generateLockFile( if (binarySource === 'global') { cmd = 'npm'; } - cmd = `${cmd} --version && ${cmd} install`; + args = `install`; if (skipInstalls) { - cmd += ' --package-lock-only --no-audit'; + args += ' --package-lock-only --no-audit'; } else { - cmd += ' --ignore-scripts --no-audit'; + args += ' --ignore-scripts --no-audit'; } - logger.debug(`Using npm: ${cmd}`); + logger.debug(`Using npm: ${cmd} ${args}`); // TODO: Switch to native util.promisify once using only node 8 - ({ stdout, stderr } = await exec(cmd, { + ({ stdout, stderr } = await exec(`${cmd} ${args}`, { cwd, shell: true, env, @@ -81,15 +82,27 @@ async function generateLockFile( if (lockUpdates.length) { logger.info('Performing lockfileUpdate (npm)'); const updateCmd = - cmd + + `${cmd} ${args}` + lockUpdates .map(update => ` ${update.depName}@${update.toVersion}`) .join(''); - ({ stdout, stderr } = await exec(updateCmd, { + const updateRes = await exec(updateCmd, { cwd, shell: true, env, - })); + }); + stdout += updateRes.stdout ? updateRes.stdout : ''; + stderr += updateRes.stderr ? updateRes.stderr : ''; + } + if (postUpdateOptions && postUpdateOptions.includes('npmDedupe')) { + logger.info('Performing npm dedupe'); + const dedupeRes = await exec(`${cmd} dedupe`, { + cwd, + shell: true, + env, + }); + stdout += dedupeRes.stdout ? dedupeRes.stdout : ''; + stderr += dedupeRes.stderr ? dedupeRes.stderr : ''; } const duration = process.hrtime(startTime); const seconds = Math.round(duration[0] + duration[1] / 1e9); @@ -102,6 +115,7 @@ async function generateLockFile( logger.info( { cmd, + args, err, stdout, stderr, diff --git a/lib/manager/npm/post-update/yarn.js b/lib/manager/npm/post-update/yarn.js index 821cad6fdf..361a3ab9ec 100644 --- a/lib/manager/npm/post-update/yarn.js +++ b/lib/manager/npm/post-update/yarn.js @@ -101,11 +101,41 @@ async function generateLockFile(cwd, env, config = {}, upgrades = []) { ' upgrade' + lockUpdates.map(depName => ` ${depName}`).join('') + cmdExtras; - ({ stdout, stderr } = await exec(updateCmd, { + const updateRes = await exec(updateCmd, { cwd, shell: true, env, - })); + }); + stdout += updateRes.stdout ? updateRes.stdout : ''; + stderr += updateRes.stderr ? updateRes.stderr : ''; + } + if ( + config.postUpdateOptions && + config.postUpdateOptions.includes('yarnDedupeFewer') + ) { + logger.info('Performing yarn dedupe fewer'); + const dedupeCommand = 'npx yarn-deduplicate@1.1.1 --strategy fewer'; + const dedupeRes = await exec(dedupeCommand, { + cwd, + shell: true, + env, + }); + stdout += dedupeRes.stdout ? dedupeRes.stdout : ''; + stderr += dedupeRes.stderr ? dedupeRes.stderr : ''; + } + if ( + config.postUpdateOptions && + config.postUpdateOptions.includes('yarnDedupeHighest') + ) { + logger.info('Performing yarn dedupe highest'); + const dedupeCommand = 'npx yarn-deduplicate@1.1.1 --strategy highest'; + const dedupeRes = await exec(dedupeCommand, { + cwd, + shell: true, + env, + }); + stdout += dedupeRes.stdout ? dedupeRes.stdout : ''; + stderr += dedupeRes.stderr ? dedupeRes.stderr : ''; } const duration = process.hrtime(startTime); const seconds = Math.round(duration[0] + duration[1] / 1e9); diff --git a/test/workers/branch/lock-files/npm.spec.js b/test/workers/branch/lock-files/npm.spec.js index 1e36155ff5..404e3679bb 100644 --- a/test/workers/branch/lock-files/npm.spec.js +++ b/test/workers/branch/lock-files/npm.spec.js @@ -17,13 +17,18 @@ describe('generateLockFile', () => { stdout: '', stderror: '', }); + exec.mockReturnValueOnce({ + stdout: '', + stderror: '', + }); fs.readFile = jest.fn(() => 'package-lock-contents'); const skipInstalls = true; + const postUpdateOptions = ['npmDedupe']; const res = await npmHelper.generateLockFile( 'some-dir', {}, 'package-lock.json', - skipInstalls + { skipInstalls, postUpdateOptions } ); expect(fs.readFile.mock.calls.length).toEqual(1); expect(res.error).not.toBeDefined(); @@ -48,8 +53,7 @@ describe('generateLockFile', () => { 'some-dir', {}, 'package-lock.json', - skipInstalls, - null, + { skipInstalls }, updates ); expect(fs.readFile.mock.calls.length).toEqual(1); @@ -69,8 +73,7 @@ describe('generateLockFile', () => { 'some-dir', {}, 'package-lock.json', - skipInstalls, - binarySource + { skipInstalls, binarySource } ); expect(fs.readFile.mock.calls.length).toEqual(1); expect(res.error).not.toBeDefined(); diff --git a/test/workers/branch/lock-files/yarn.spec.js b/test/workers/branch/lock-files/yarn.spec.js index a583500ff0..69f41b89f4 100644 --- a/test/workers/branch/lock-files/yarn.spec.js +++ b/test/workers/branch/lock-files/yarn.spec.js @@ -17,8 +17,20 @@ describe('generateLockFile', () => { stdout: '', stderror: '', }); + exec.mockReturnValueOnce({ + stdout: '', + stderror: '', + }); + exec.mockReturnValueOnce({ + stdout: '', + stderror: '', + }); fs.readFile = jest.fn(() => 'package-lock-contents'); - const res = await yarnHelper.generateLockFile('some-dir'); + const env = {}; + const config = { + postUpdateOptions: ['yarnDedupeFewer', 'yarnDedupeHighest'], + }; + const res = await yarnHelper.generateLockFile('some-dir', env, config); expect(fs.readFile.mock.calls.length).toEqual(1); expect(res.lockFile).toEqual('package-lock-contents'); }); diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index a8400bb730..601416e335 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -670,7 +670,10 @@ Warning: 'pipenv' support is currently in beta, so it is not enabled by default. ## postUpdateOptions -`gomodTidy`: Enable to run `go mod tidy` after Go module updates +`gomodTidy`: Run `go mod tidy` after Go module updates +`npmDedupe`: Run `npm dedupe` after `package-lock.json` updates +`yarnDedupeFewer`: Run `yarn-deduplicate --strategy fewer` after `yarn.lock` updates +`yarnDedupeHighest`: Run `yarn-deduplicate --strategy highest` after `yarn.lock` updates ## prBodyColumns -- GitLab