From 8fa94141a124a29aed1a5ee973ba5c4490607acf Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@keylocation.sg> Date: Tue, 12 Sep 2017 07:49:56 +0200 Subject: [PATCH] feat: use package names for ignoring when lerna or workspaces (#787) * add minimatch * feat: use package names for ignoring when lerna or workspaces Renovate will now: - Find all package.json files matching lerna or yarn workspaces glob pattern - Retrieve package names from within those package.json files - Implicitly ignore (not renvoate) any of those names Closes #781 --- lib/workers/dep-type/index.js | 11 +-- lib/workers/repository/apis.js | 71 +++++++++----- lib/workers/repository/index.js | 2 + package.json | 1 + test/workers/dep-type/index.spec.js | 2 +- .../__snapshots__/apis.spec.js.snap | 28 ++---- test/workers/repository/apis.spec.js | 97 ++++++++++--------- yarn.lock | 2 +- 8 files changed, 114 insertions(+), 100 deletions(-) diff --git a/lib/workers/dep-type/index.js b/lib/workers/dep-type/index.js index a089b82215..d19d0f600f 100644 --- a/lib/workers/dep-type/index.js +++ b/lib/workers/dep-type/index.js @@ -26,14 +26,11 @@ async function renovateDepType(packageContent, config) { logger.debug(`currentDeps length is ${currentDeps.length}`); logger.debug({ currentDeps }, `currentDeps`); // Filter out ignored dependencies - let filteredDeps = currentDeps.filter( - dependency => config.ignoreDeps.indexOf(dependency.depName) === -1 + const filteredDeps = currentDeps.filter( + dependency => + config.ignoreDeps.indexOf(dependency.depName) === -1 && + config.monorepoPackages.indexOf(dependency.depName) === -1 ); - if (config.lernaPackages) { - filteredDeps = filteredDeps.filter( - dependency => config.lernaPackages.indexOf(dependency.depName) === -1 - ); - } logger.debug(`filteredDeps length is ${filteredDeps.length}`); logger.debug({ filteredDeps }, `filteredDeps`); // Obtain full config for each dependency diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js index 4c915d7ff3..f18eddbdd6 100644 --- a/lib/workers/repository/apis.js +++ b/lib/workers/repository/apis.js @@ -1,3 +1,4 @@ +const minimatch = require('minimatch'); const conventionalCommitsDetector = require('conventional-commits-detector'); const path = require('path'); const jsonValidator = require('json-dup-key-validator'); @@ -12,10 +13,10 @@ const gitlabApi = require('../../api/gitlab'); module.exports = { detectSemanticCommits, + checkMonorepos, getNpmrc, initApis, mergeRenovateJson, - checkForLerna, detectPackageFiles, resolvePackageFiles, migrateAndValidate, @@ -35,6 +36,50 @@ async function detectSemanticCommits(config) { return true; } +async function checkMonorepos(input) { + const config = { ...input }; + const { logger } = config; + config.monorepoPackages = []; + // yarn workspaces + if (config.hasYarnWorkspaces) { + let workspaces = []; + for (const packageFile of config.packageFiles) { + if (packageFile.packageFile === 'package.json') { + workspaces = packageFile.content.workspaces; + } + } + logger.debug({ workspaces }, 'workspaces'); + for (const workspace of workspaces) { + for (const packageFile of config.packageFiles) { + if (minimatch(path.dirname(packageFile.packageFile), workspace)) { + const depName = packageFile.content.name; + config.monorepoPackages.push(depName); + } + } + } + } + // lerna + const lernaJson = await config.api.getFileJson('lerna.json'); + if (!lernaJson) { + return config; + } + config.logger.debug({ lernaJson }, 'Found lerna config'); + if (!lernaJson.packages) { + return config; + } + for (const packageGlob of lernaJson.packages) { + for (const packageFile of config.packageFiles) { + if (minimatch(path.dirname(packageFile.packageFile), packageGlob)) { + const depName = packageFile.content.name; + if (!config.monorepoPackages.includes(depName)) { + config.monorepoPackages.push(depName); + } + } + } + } + return config; +} + // Check for .npmrc in repository and pass it to npm api if found async function getNpmrc(config) { let npmrc; @@ -49,27 +94,6 @@ async function getNpmrc(config) { return { ...config, npmrc }; } -async function checkForLerna(config) { - const lernaJson = await config.api.getFileJson('lerna.json'); - if (!lernaJson) { - return {}; - } - config.logger.debug({ lernaJson }, 'Found lerna config'); - try { - const packagesPath = lernaJson.packages - ? lernaJson.packages[0].slice(0, -2) - : 'packages'; - const lernaPackages = await config.api.getSubDirectories(packagesPath); - if (lernaPackages.length === 0) { - return {}; - } - return { lernaPackages }; - } catch (err) { - config.logger.info('could not find any lerna subdirectories'); - return {}; - } -} - async function initApis(inputConfig, token) { function getPlatformApi(platform) { if (platform === 'github') { @@ -88,10 +112,7 @@ async function initApis(inputConfig, token) { config.endpoint, config.logger ); - // Check for presence of .npmrc in repository Object.assign(config, platformConfig); - const lernaConfig = await module.exports.checkForLerna(config); - Object.assign(config, lernaConfig); if (config.semanticCommits === null) { config.semanticCommits = await module.exports.detectSemanticCommits(config); } diff --git a/lib/workers/repository/index.js b/lib/workers/repository/index.js index 709a6ba7bd..053f6588e0 100644 --- a/lib/workers/repository/index.js +++ b/lib/workers/repository/index.js @@ -79,6 +79,7 @@ async function renovateRepository(repoConfig, token) { } logger.debug('Resolving package files and content'); config = await apis.resolvePackageFiles(config); + config = await apis.checkMonorepos(config); logger.trace({ config }, 'post-packageFiles config'); // TODO: why is this fix needed?! config.logger = logger; @@ -108,6 +109,7 @@ async function renovateRepository(repoConfig, token) { } } config = await apis.resolvePackageFiles(config); + config = await apis.checkMonorepos(config); config = await presets.resolveConfigPresets(config); config.logger = logger; logger.trace({ config }, 'onboarding config'); diff --git a/package.json b/package.json index 507c881fed..cb6afb01cb 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "jsonwebtoken": "7.4.3", "later": "1.2.0", "lodash": "4.17.4", + "minimatch": "3.0.4", "moment": "2.18.1", "moment-timezone": "0.5.13", "registry-auth-token": "3.3.1", diff --git a/test/workers/dep-type/index.spec.js b/test/workers/dep-type/index.spec.js index d97e55e328..65860a0271 100644 --- a/test/workers/dep-type/index.spec.js +++ b/test/workers/dep-type/index.spec.js @@ -15,7 +15,7 @@ describe('lib/workers/dep-type/index', () => { beforeEach(() => { config = { ignoreDeps: ['a', 'b'], - lernaPackages: ['e'], + monorepoPackages: ['e'], }; }); it('returns empty if config is disabled', async () => { diff --git a/test/workers/repository/__snapshots__/apis.spec.js.snap b/test/workers/repository/__snapshots__/apis.spec.js.snap index 7af498776c..13465b626c 100644 --- a/test/workers/repository/__snapshots__/apis.spec.js.snap +++ b/test/workers/repository/__snapshots__/apis.spec.js.snap @@ -7,28 +7,20 @@ Object { } `; -exports[`workers/repository/apis checkForLerna(config) ignores zero length lerna 1`] = `Object {}`; - -exports[`workers/repository/apis checkForLerna(config) implies lerna package path 1`] = ` -Object { - "lernaPackages": Array [ - "a", - "b", - ], -} +exports[`workers/repository/apis checkMonorepos adds lerna packages 1`] = ` +Array [ + "@a/b", + "@a/c", +] `; -exports[`workers/repository/apis checkForLerna(config) returns lerna package names 1`] = ` -Object { - "lernaPackages": Array [ - "a", - "b", - ], -} +exports[`workers/repository/apis checkMonorepos adds yarn workspaces 1`] = ` +Array [ + "@a/b", + "@a/c", +] `; -exports[`workers/repository/apis checkForLerna(config) swallows lerna 404 1`] = `Object {}`; - exports[`workers/repository/apis detectPackageFiles(config) adds package files to object 1`] = ` Array [ "package.json", diff --git a/test/workers/repository/apis.spec.js b/test/workers/repository/apis.spec.js index 24da3950d6..bada3eef81 100644 --- a/test/workers/repository/apis.spec.js +++ b/test/workers/repository/apis.spec.js @@ -45,75 +45,76 @@ describe('workers/repository/apis', () => { expect(await apis.getNpmrc(config)).toMatchObject(config); }); }); - describe('detectSemanticCommits', () => { - it('disables semantic commits', async () => { - const config = { + describe('checkMonorepos', () => { + let config; + beforeEach(() => { + config = { + ...defaultConfig, api: { - getCommitMessages: jest.fn(() => []), + getFileJson: jest.fn(), }, logger, }; - const res = await apis.detectSemanticCommits(config); - expect(res).toEqual(false); }); - it('enables semantic commits', async () => { - const config = { - api: { - getCommitMessages: jest.fn(() => []), + it('adds yarn workspaces', async () => { + config.hasYarnWorkspaces = true; + config.packageFiles = [ + { + packageFile: 'package.json', + content: { workspaces: ['packages/*'] }, }, - logger, - }; - config.api.getCommitMessages.mockReturnValueOnce(['fix: something']); - const res = await apis.detectSemanticCommits(config); - expect(res).toEqual(true); - }); - }); - describe('checkForLerna(config)', () => { - it('swallows lerna 404', async () => { - const config = { - api: { - getFileJson: jest.fn(() => ({})), - getSubDirectories: jest.fn(() => { - throw new Error('some-error'); - }), + { + packageFile: 'packages/something/package.json', + content: { name: '@a/b' }, }, - logger, - }; - const res = await apis.checkForLerna(config); - expect(res).toMatchSnapshot(); + { + packageFile: 'packages/something-else/package.json', + content: { name: '@a/c' }, + }, + ]; + const res = await apis.checkMonorepos(config); + expect(res.monorepoPackages).toMatchSnapshot(); }); - it('ignores zero length lerna', async () => { - const config = { - api: { - getFileJson: jest.fn(() => ({ packages: ['packages/*'] })), - getSubDirectories: jest.fn(() => []), + it('adds lerna packages', async () => { + config.packageFiles = [ + { + packageFile: 'package.json', }, - logger, - }; - const res = await apis.checkForLerna(config); - expect(res).toMatchSnapshot(); + { + packageFile: 'packages/something/package.json', + content: { name: '@a/b' }, + }, + { + packageFile: 'packages/something-else/package.json', + content: { name: '@a/c' }, + }, + ]; + config.api.getFileJson.mockReturnValue({ packages: ['packages/*'] }); + const res = await apis.checkMonorepos(config); + expect(res.monorepoPackages).toMatchSnapshot(); }); - it('implies lerna package path', async () => { + }); + describe('detectSemanticCommits', () => { + it('disables semantic commits', async () => { const config = { api: { - getFileJson: jest.fn(() => ({})), - getSubDirectories: jest.fn(() => ['a', 'b']), + getCommitMessages: jest.fn(() => []), }, logger, }; - const res = await apis.checkForLerna(config); - expect(res).toMatchSnapshot(); + const res = await apis.detectSemanticCommits(config); + expect(res).toEqual(false); }); - it('returns lerna package names', async () => { + it('enables semantic commits', async () => { const config = { api: { - getFileJson: jest.fn(() => ({ packages: ['packages/*'] })), - getSubDirectories: jest.fn(() => ['a', 'b']), + getCommitMessages: jest.fn(() => []), }, logger, }; - const res = await apis.checkForLerna(config); - expect(res).toMatchSnapshot(); + config.api.getCommitMessages.mockReturnValueOnce(['fix: something']); + const res = await apis.detectSemanticCommits(config); + expect(res).toEqual(true); }); }); describe('initApis(config)', () => { diff --git a/yarn.lock b/yarn.lock index e8ae67d2c4..036741cee2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2958,7 +2958,7 @@ mimic-response@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: -- GitLab