From feffa774d89e2047365c23aaa46cb68e4f162ffa Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@keylocation.sg> Date: Thu, 27 Jul 2017 09:36:36 +0200 Subject: [PATCH] feat(lerna): Add lerna support (#535) If a `lerna.json` is present in the root of the repository, then Renovate will automatically ignore (i.e. not renovate) all package names found in the `packages/*` path. It does not require an explicit configuration to work. --- lib/api/github.js | 13 +++++++++ lib/api/gitlab.js | 15 ++++++++++ lib/workers/dep-type/index.js | 7 ++++- lib/workers/repository/apis.js | 16 +++++++++++ test/api/__snapshots__/github.spec.js.snap | 28 +++++++++++++++++++ test/api/__snapshots__/gitlab.spec.js.snap | 23 +++++++++++++++ test/api/github.spec.js | 12 ++++++++ test/api/gitlab.spec.js | 12 ++++++++ test/workers/dep-type/index.spec.js | 2 ++ .../__snapshots__/apis.spec.js.snap | 11 ++++++++ test/workers/repository/apis.spec.js | 24 ++++++++++++++++ 11 files changed, 162 insertions(+), 1 deletion(-) diff --git a/lib/api/github.js b/lib/api/github.js index f47cfb6861..036c188b07 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -35,6 +35,7 @@ module.exports = { updatePr, mergePr, // file + getSubDirectories, commitFilesToBranch, getFile, getFileContent, @@ -602,6 +603,18 @@ async function getFileJson(filePath, branchName) { return fileJson; } +async function getSubDirectories(path) { + logger.trace(`getSubDirectories(path=${path})`); + const res = await ghGot(`repos/${config.repoName}/contents/${path}`); + const directoryList = []; + res.body.forEach(item => { + if (item.type === 'dir') { + directoryList.push(item.name); + } + }); + return directoryList; +} + // Add a new commit, create branch if not existing async function commitFilesToBranch( branchName, diff --git a/lib/api/gitlab.js b/lib/api/gitlab.js index 568755a7f6..ecce630629 100644 --- a/lib/api/gitlab.js +++ b/lib/api/gitlab.js @@ -28,6 +28,7 @@ module.exports = { updatePr, mergePr, // file + getSubDirectories, commitFilesToBranch, getFile, getFileContent, @@ -436,6 +437,20 @@ async function updateFile(branchName, filePath, fileContents, message) { await glGot.put(url, opts); } +async function getSubDirectories(path) { + logger.trace(`getSubDirectories(path=${path})`); + const res = await glGot( + `projects/${config.repoName}/repository/tree?path=${path}` + ); + const directoryList = []; + res.body.forEach(item => { + if (item.type === 'tree') { + directoryList.push(item.name); + } + }); + return directoryList; +} + // Add a new commit, create branch if not existing async function commitFilesToBranch( branchName, diff --git a/lib/workers/dep-type/index.js b/lib/workers/dep-type/index.js index a3d653a8fa..746ba1c70e 100644 --- a/lib/workers/dep-type/index.js +++ b/lib/workers/dep-type/index.js @@ -26,9 +26,14 @@ async function renovateDepType(packageContent, config) { logger.debug(`currentDeps length is ${currentDeps.length}`); logger.debug({ currentDeps }, `currentDeps`); // Filter out ignored dependencies - const filteredDeps = currentDeps.filter( + let filteredDeps = currentDeps.filter( dependency => config.ignoreDeps.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 266dad5161..d558eca848 100644 --- a/lib/workers/repository/apis.js +++ b/lib/workers/repository/apis.js @@ -10,6 +10,7 @@ module.exports = { setNpmrc, initApis, mergeRenovateJson, + checkForLerna, detectPackageFiles, }; @@ -28,6 +29,19 @@ async function setNpmrc(config) { } } +async function checkForLerna(config) { + const lernaJson = await config.api.getFileContent('lerna.json'); + if (!lernaJson) { + return {}; + } + config.logger.debug('Found lerna config'); + const lernaPackages = await config.api.getSubDirectories('packages'); + if (lernaPackages.length === 0) { + return {}; + } + return { lernaPackages }; +} + async function initApis(inputConfig, token) { function getPlatformApi(platform) { if (platform === 'github') { @@ -48,6 +62,8 @@ async function initApis(inputConfig, token) { ); // Check for presence of .npmrc in repository Object.assign(config, platformConfig); + const lernaConfig = await module.exports.checkForLerna(config); + Object.assign(config, lernaConfig); await module.exports.setNpmrc(config); return config; } diff --git a/test/api/__snapshots__/github.spec.js.snap b/test/api/__snapshots__/github.spec.js.snap index b87fdc7ff7..a4459f8fe5 100644 --- a/test/api/__snapshots__/github.spec.js.snap +++ b/test/api/__snapshots__/github.spec.js.snap @@ -908,6 +908,34 @@ Array [ ] `; +exports[`api/github getSubDirectories(path) should return subdirectories 1`] = ` +Array [ + Array [ + "repos/some/repo", + ], + Array [ + "repos/some/repo/git/refs/heads/master", + ], + Array [ + "repos/some/repo/branches/master/protection/required_status_checks", + Object { + "headers": Object { + "accept": "application/vnd.github.loki-preview+json", + }, + }, + ], + Array [ + "repos/some/repo/contents/some-path", + ], +] +`; + +exports[`api/github getSubDirectories(path) should return subdirectories 2`] = ` +Array [ + "a", +] +`; + exports[`api/github initRepo should detect repoForceRebase 1`] = ` Object { "repoForceRebase": true, diff --git a/test/api/__snapshots__/gitlab.spec.js.snap b/test/api/__snapshots__/gitlab.spec.js.snap index eb2b7dce45..4471d38360 100644 --- a/test/api/__snapshots__/gitlab.spec.js.snap +++ b/test/api/__snapshots__/gitlab.spec.js.snap @@ -85,6 +85,29 @@ Array [ ] `; +exports[`api/gitlab createFile(branchName, filePath, fileContents, message) getSubDirectories(path) should return subdirectories 1`] = ` +Array [ + Array [ + "projects/owned", + ], + Array [ + "projects/some%2Frepo", + ], + Array [ + "user", + ], + Array [ + "projects/some%2Frepo/repository/tree?path=some-path", + ], +] +`; + +exports[`api/gitlab createFile(branchName, filePath, fileContents, message) getSubDirectories(path) should return subdirectories 2`] = ` +Array [ + "a", +] +`; + exports[`api/gitlab createFile(branchName, filePath, fileContents, message) updateFile(branchName, filePath, fileContents, message) creates file with v3 1`] = ` Array [ Array [ diff --git a/test/api/github.spec.js b/test/api/github.spec.js index dbf992b5ff..4aa8f16b0f 100644 --- a/test/api/github.spec.js +++ b/test/api/github.spec.js @@ -1291,6 +1291,18 @@ describe('api/github', () => { expect(content).toBeNull(); }); }); + describe('getSubDirectories(path)', () => { + it('should return subdirectories', async () => { + await initRepo('some/repo', 'token'); + ghGot.mockImplementationOnce(() => ({ + body: [{ type: 'dir', name: 'a' }, { type: 'file', name: 'b' }], + })); + const dirList = await github.getSubDirectories('some-path'); + expect(ghGot.mock.calls).toMatchSnapshot(); + expect(dirList).toHaveLength(1); + expect(dirList).toMatchSnapshot(); + }); + }); describe('commitFilesToBranch(branchName, files, message, parentBranch)', () => { beforeEach(async () => { await initRepo('some/repo', 'token'); diff --git a/test/api/gitlab.spec.js b/test/api/gitlab.spec.js index 7082b9acb0..48813649d7 100644 --- a/test/api/gitlab.spec.js +++ b/test/api/gitlab.spec.js @@ -639,6 +639,18 @@ describe('api/gitlab', () => { expect(glGot.post.mock.calls[0][1].body.branch_name).toBeDefined(); }); }); + describe('getSubDirectories(path)', () => { + it('should return subdirectories', async () => { + await initRepo('some/repo', 'token'); + glGot.mockImplementationOnce(() => ({ + body: [{ type: 'tree', name: 'a' }, { type: 'file', name: 'b' }], + })); + const dirList = await gitlab.getSubDirectories('some-path'); + expect(glGot.mock.calls).toMatchSnapshot(); + expect(dirList).toHaveLength(1); + expect(dirList).toMatchSnapshot(); + }); + }); describe('commitFilesToBranch(branchName, files, message, parentBranch)', () => { it('creates branch', async () => { glGot.mockReturnValueOnce({ statusCode: 404 }); diff --git a/test/workers/dep-type/index.spec.js b/test/workers/dep-type/index.spec.js index de5b480edf..cb04b3b513 100644 --- a/test/workers/dep-type/index.spec.js +++ b/test/workers/dep-type/index.spec.js @@ -15,6 +15,7 @@ describe('lib/workers/dep-type/index', () => { beforeEach(() => { config = { ignoreDeps: ['a', 'b'], + lernaPackages: ['e'], }; }); it('returns empty if config is disabled', async () => { @@ -31,6 +32,7 @@ describe('lib/workers/dep-type/index', () => { packageJson.extractDependencies.mockReturnValueOnce([ { depName: 'a' }, { depName: 'b' }, + { depName: 'e' }, ]); const res = await depTypeWorker.renovateDepType({}, config); expect(res).toMatchObject([]); diff --git a/test/workers/repository/__snapshots__/apis.spec.js.snap b/test/workers/repository/__snapshots__/apis.spec.js.snap index 9cfb0ef22e..c7d31a9b5f 100644 --- a/test/workers/repository/__snapshots__/apis.spec.js.snap +++ b/test/workers/repository/__snapshots__/apis.spec.js.snap @@ -1,5 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`workers/repository/apis checkForLerna(config) ignores zero length lerna 1`] = `Object {}`; + +exports[`workers/repository/apis checkForLerna(config) returns lerna package names 1`] = ` +Object { + "lernaPackages": Array [ + "a", + "b", + ], +} +`; + 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 c86a30be32..3368686add 100644 --- a/test/workers/repository/apis.spec.js +++ b/test/workers/repository/apis.spec.js @@ -42,6 +42,30 @@ describe('workers/repository/apis', () => { await apis.setNpmrc(config); }); }); + describe('checkForLerna(config)', () => { + it('ignores zero length lerna', async () => { + const config = { + api: { + getFileContent: jest.fn(() => 'some content'), + getSubDirectories: jest.fn(() => []), + }, + logger, + }; + const res = await apis.checkForLerna(config); + expect(res).toMatchSnapshot(); + }); + it('returns lerna package names', async () => { + const config = { + api: { + getFileContent: jest.fn(() => 'some content'), + getSubDirectories: jest.fn(() => ['a', 'b']), + }, + logger, + }; + const res = await apis.checkForLerna(config); + expect(res).toMatchSnapshot(); + }); + }); describe('initApis(config)', () => { beforeEach(() => { jest.resetAllMocks(); -- GitLab