From 23f8467d1e9486bbfd17ace6b9ec29568378e16c Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@keylocation.sg> Date: Fri, 29 Sep 2017 08:08:52 +0200 Subject: [PATCH] feat: support non-root yarn workspaces (#852) Previously, Renovate assumed that any yarn workspaces configuration would be in the root of the repository. Now, workspaces can be located within a subdirectory, e.g. `frontend/`. Note: Renovate still supports only one workspace per repository, please file an issue if you require more than one. Closes #842 --- lib/workers/branch/lock-files.js | 14 ++++-- lib/workers/repository/apis.js | 44 ++++++++++--------- test/workers/branch/lock-files.spec.js | 3 +- .../__snapshots__/apis.spec.js.snap | 9 ++++ test/workers/repository/apis.spec.js | 31 ++++++++++++- test/workers/repository/index.spec.js | 1 + 6 files changed, 76 insertions(+), 26 deletions(-) diff --git a/lib/workers/branch/lock-files.js b/lib/workers/branch/lock-files.js index fc01f65e84..c7f78385f6 100644 --- a/lib/workers/branch/lock-files.js +++ b/lib/workers/branch/lock-files.js @@ -69,9 +69,17 @@ function determineLockFileDirs(config) { } } - // If yarn workspaces are in use, then we need to generate yarn.lock from root dir - if (config.updatedPackageFiles.length && config.hasYarnWorkspaces) { - yarnLockFileDirs.push(path.dirname('package.json')); + // If yarn workspaces are in use, then we need to generate yarn.lock from the workspaces dir + if (config.updatedPackageFiles.length && config.workspaceDir) { + const updatedPackageFileNames = config.updatedPackageFiles.map(p => p.name); + for (const packageFile of config.packageFiles) { + if ( + updatedPackageFileNames.includes(packageFile.packageFile) && + packageFile.workspaceDir && + !yarnLockFileDirs.includes(packageFile.workspaceDir) + ) + yarnLockFileDirs.push(packageFile.workspaceDir); + } } return { yarnLockFileDirs, packageLockFileDirs }; diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js index b4498493b4..8a0645c39e 100644 --- a/lib/workers/repository/apis.js +++ b/lib/workers/repository/apis.js @@ -42,20 +42,30 @@ async function checkMonorepos(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); - } + let workspaces = []; + for (const packageFile of config.packageFiles) { + if ( + packageFile.packageFile && + packageFile.packageFile.endsWith('package.json') && + packageFile.content.workspaces + ) { + config.workspaceDir = path.dirname(packageFile.packageFile); + logger.info(`workspaceDir=${config.workspaceDir}`); + ({ workspaces } = packageFile.content); } - 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); - } + } + if (workspaces.length) { + logger.debug({ workspaces }, 'Found yarn workspaces'); + } + for (const workspace of workspaces) { + const basePath = path.join(config.workspaceDir, workspace); + logger.info(`basePath=${basePath}`); + for (const packageFile of config.packageFiles) { + if (minimatch(path.dirname(packageFile.packageFile), basePath)) { + logger.info(`Matched ${packageFile.packageFile}`); + const depName = packageFile.content.name; + config.monorepoPackages.push(depName); + packageFile.workspaceDir = config.workspaceDir; } } } @@ -315,14 +325,6 @@ async function resolvePackageFiles(inputConfig) { delete packageFile.yarnrc; } if (packageFile.content) { - // check for workspaces - if ( - packageFile.packageFile === 'package.json' && - packageFile.content.workspaces - ) { - logger.info('Found yarn workspaces configuration'); - config.hasYarnWorkspaces = true; - } // hoist renovate config if exists if (packageFile.content.renovate) { config.hasPackageJsonRenovateConfig = true; diff --git a/test/workers/branch/lock-files.spec.js b/test/workers/branch/lock-files.spec.js index 60d479d1f0..4a7fad10ff 100644 --- a/test/workers/branch/lock-files.spec.js +++ b/test/workers/branch/lock-files.spec.js @@ -150,7 +150,7 @@ describe('workers/branch/lock-files', () => { expect(res).toMatchSnapshot(); }); it('returns root directory if using yarn workspaces', () => { - config.hasYarnWorkspaces = true; + config.workspaceDir = '.'; config.upgrades = [{}]; config.packageFiles = [ { @@ -159,6 +159,7 @@ describe('workers/branch/lock-files', () => { }, { packageFile: 'backend/package.json', + workspaceDir: '.', }, ]; config.updatedPackageFiles = [ diff --git a/test/workers/repository/__snapshots__/apis.spec.js.snap b/test/workers/repository/__snapshots__/apis.spec.js.snap index add163b03e..90daea4501 100644 --- a/test/workers/repository/__snapshots__/apis.spec.js.snap +++ b/test/workers/repository/__snapshots__/apis.spec.js.snap @@ -14,6 +14,13 @@ Array [ ] `; +exports[`workers/repository/apis checkMonorepos adds nested yarn workspaces 1`] = ` +Array [ + "@a/b", + "@a/c", +] +`; + exports[`workers/repository/apis checkMonorepos adds yarn workspaces 1`] = ` Array [ "@a/b", @@ -21,6 +28,8 @@ Array [ ] `; +exports[`workers/repository/apis checkMonorepos skips if no lerna packages 1`] = `Array []`; + 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 6b0b2e0b4e..3db1f3c716 100644 --- a/test/workers/repository/apis.spec.js +++ b/test/workers/repository/apis.spec.js @@ -57,7 +57,6 @@ describe('workers/repository/apis', () => { }; }); it('adds yarn workspaces', async () => { - config.hasYarnWorkspaces = true; config.packageFiles = [ { packageFile: 'package.json', @@ -75,10 +74,29 @@ describe('workers/repository/apis', () => { const res = await apis.checkMonorepos(config); expect(res.monorepoPackages).toMatchSnapshot(); }); + it('adds nested yarn workspaces', async () => { + config.packageFiles = [ + { + packageFile: 'frontend/package.json', + content: { workspaces: ['packages/*'] }, + }, + { + packageFile: 'frontend/packages/something/package.json', + content: { name: '@a/b' }, + }, + { + packageFile: 'frontend/packages/something-else/package.json', + content: { name: '@a/c' }, + }, + ]; + const res = await apis.checkMonorepos(config); + expect(res.monorepoPackages).toMatchSnapshot(); + }); it('adds lerna packages', async () => { config.packageFiles = [ { packageFile: 'package.json', + content: {}, }, { packageFile: 'packages/something/package.json', @@ -93,6 +111,17 @@ describe('workers/repository/apis', () => { const res = await apis.checkMonorepos(config); expect(res.monorepoPackages).toMatchSnapshot(); }); + it('skips if no lerna packages', async () => { + config.packageFiles = [ + { + packageFile: 'package.json', + content: {}, + }, + ]; + config.api.getFileJson.mockReturnValue({}); + const res = await apis.checkMonorepos(config); + expect(res.monorepoPackages).toMatchSnapshot(); + }); }); describe('detectSemanticCommits', () => { it('disables semantic commits', async () => { diff --git a/test/workers/repository/index.spec.js b/test/workers/repository/index.spec.js index f6b87e2af8..f653f35bd7 100644 --- a/test/workers/repository/index.spec.js +++ b/test/workers/repository/index.spec.js @@ -47,6 +47,7 @@ describe('workers/repository', () => { apis.mergeRenovateJson = jest.fn(input => input); apis.detectPackageFiles = jest.fn(); apis.resolvePackageFiles = jest.fn(input => input); + apis.checkMonorepos = jest.fn(input => input); onboarding.getOnboardingStatus = jest.fn(); onboarding.ensurePr = jest.fn(); upgrades.determineRepoUpgrades = jest.fn(() => []); -- GitLab