diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js index fca3ca157818b04df2bbf671626a6c9f6d52a408..327e00bb2c6cfe087b0f55baa18d3bbbc74b418f 100644 --- a/lib/workers/repository/apis.js +++ b/lib/workers/repository/apis.js @@ -322,6 +322,9 @@ async function resolvePackageFiles(inputConfig) { const { logger } = config; logger.trace({ config }, 'resolvePackageFiles()'); const packageFiles = []; + const contentBranch = config.repoIsOnboarded + ? config.baseBranch + : config.onboardingBranch; for (let packageFile of config.packageFiles) { packageFile = typeof packageFile === 'string' ? { packageFile } : packageFile; @@ -329,7 +332,7 @@ async function resolvePackageFiles(inputConfig) { logger.debug(`Resolving packageFile ${JSON.stringify(packageFile)}`); const pFileRaw = await config.api.getFileContent( packageFile.packageFile, - config.baseBranch + contentBranch ); if (!pFileRaw) { logger.info( @@ -357,7 +360,7 @@ async function resolvePackageFiles(inputConfig) { if (!inputConfig.ignoreNpmrcFile) { packageFile.npmrc = await config.api.getFileContent( path.join(path.dirname(packageFile.packageFile), '.npmrc'), - config.baseBranch + contentBranch ); } if (!packageFile.npmrc) { @@ -365,7 +368,7 @@ async function resolvePackageFiles(inputConfig) { } packageFile.yarnrc = await config.api.getFileContent( path.join(path.dirname(packageFile.packageFile), '.yarnrc'), - config.baseBranch + contentBranch ); if (!packageFile.yarnrc) { delete packageFile.yarnrc; @@ -412,7 +415,7 @@ async function resolvePackageFiles(inputConfig) { ); packageFile.yarnLock = await config.api.getFileContent( yarnLockFileName, - config.baseBranch + contentBranch ); if (packageFile.yarnLock) { logger.debug( @@ -426,7 +429,7 @@ async function resolvePackageFiles(inputConfig) { ); packageFile.packageLock = await config.api.getFileContent( packageLockFileName, - config.baseBranch + contentBranch ); if (packageFile.packageLock) { logger.debug( @@ -446,7 +449,7 @@ async function resolvePackageFiles(inputConfig) { logger.debug(`Resolving packageFile ${JSON.stringify(packageFile)}`); packageFile.content = await config.api.getFileContent( packageFile.packageFile, - config.baseBranch + contentBranch ); const strippedComment = packageFile.content.replace(/^(#.*?\n)+/, ''); const fromMatch = strippedComment.match(/^FROM (.*)\n/); diff --git a/lib/workers/repository/index.js b/lib/workers/repository/index.js index a1c840fe566c04880aa6ebf26a73643bf29e50e8..6dcd5efbfe4b7d9a1f4f8c012f115398aaf47b5e 100644 --- a/lib/workers/repository/index.js +++ b/lib/workers/repository/index.js @@ -1,6 +1,5 @@ const convertHrTime = require('convert-hrtime'); const tmp = require('tmp'); -const presets = require('../../config/presets'); // Workers const branchWorker = require('../branch'); // children @@ -64,6 +63,7 @@ async function renovateRepository(repoConfig, token) { logger.warn(message); } } + config = await onboarding.getOnboardingStatus(config); // Detect package files in default branch if not manually provisioned if (config.packageFiles.length === 0) { logger.debug('Detecting package files'); @@ -84,37 +84,6 @@ async function renovateRepository(repoConfig, token) { logger.trace({ config }, 'post-packageFiles config'); // TODO: why is this fix needed?! config.logger = logger; - config = await onboarding.getOnboardingStatus(config); - if (!config.repoIsOnboarded) { - config.contentBaseBranch = `${config.branchPrefix}configure`; - // Remove packageFile list in case they are provisioned in renovate.json - const packageFiles = config.packageFiles.map( - packageFile => packageFile.packageFile - ); - config.packageFiles = []; - config = await apis.mergeRenovateJson(config, config.contentBaseBranch); - // Restore previous packageFile list if not provisioned manually - if (config.packageFiles.length === 0) { - config.packageFiles = packageFiles; - } - if (config.baseBranch) { - if (await config.api.branchExists(config.baseBranch)) { - config.contentBaseBranch = config.baseBranch; - } else { - const message = `The configured baseBranch "${config.baseBranch}" is not present. Ignoring`; - config.errors.push({ - depName: 'baseBranch', - message, - }); - logger.warn(message); - } - } - config = await apis.resolvePackageFiles(config); - config = await apis.checkMonorepos(config); - config = await presets.resolveConfigPresets(config); - config.logger = logger; - logger.trace({ config }, 'onboarding config'); - } config = decryptConfig(config); logger.trace({ config }, 'post-decrypt config'); const allUpgrades = await upgrades.determineRepoUpgrades(config); @@ -168,6 +137,8 @@ async function renovateRepository(repoConfig, token) { // Swallow this error so that other repositories can be processed if (err.message === 'uninitiated') { logger.info('Repository is uninitiated - skipping'); + } else if (err.message === 'no package files') { + logger.info('Repository has no package files - skipping'); } else { logger.error(`Failed to process repository: ${err.message}`); logger.debug({ err }); diff --git a/lib/workers/repository/onboarding.js b/lib/workers/repository/onboarding.js index 2d034e6d5ada686fab11ad1c2e87b5c7e8c3ef54..d44f00d46b51d4263f23d66c4a4a8c68d665493d 100644 --- a/lib/workers/repository/onboarding.js +++ b/lib/workers/repository/onboarding.js @@ -1,8 +1,10 @@ +const apis = require('./apis'); + const onboardPrTitle = 'Configure Renovate'; module.exports = { isRepoPrivate, - createBranch, + createOnboardingBranch, ensurePr, getOnboardingStatus, }; @@ -20,45 +22,48 @@ async function isRepoPrivate(config) { return repoIsPrivate === true; } -async function createBranch(config) { +async function createOnboardingBranch(inputConfig) { + let config = { ...inputConfig }; const { logger } = config; - const onboardBranchName = `${config.branchPrefix}configure`; + config.meteor.enabled = true; + config.docker.enabled = true; + config = await apis.detectPackageFiles(config); + if (config.packageFiles.length === 0) { + throw new Error('no package files'); + } const repoIsPrivate = await module.exports.isRepoPrivate(config); - let onboardingConfigString; - if (repoIsPrivate) { - logger.debug('Repo is private - setting to app type'); - onboardingConfigString = `{\n "extends": ["config:js-app"]\n}\n`; + const renovateJson = { + extends: [], + }; + if (config.types.npm) { + if (repoIsPrivate) { + logger.debug('Repo is private - setting to app type'); + renovateJson.extends.push('config:js-app'); + } else { + logger.debug('Repo is not private - setting to library'); + renovateJson.extends.push('config:js-lib'); + } } else { - logger.debug('Repo is not private - setting to library'); - onboardingConfigString = `{\n "extends": ["config:js-lib"]\n}\n`; + renovateJson.extends.push('config:base'); } - const existingContent = await config.api.getFileContent( - 'renovate.json', - onboardBranchName - ); - if (existingContent === onboardingConfigString) { - logger.debug('Onboarding branch is already up-to-date'); - return; + if (config.types.meteor) { + renovateJson.extends.push(':meteor'); } - if (existingContent) { - logger.debug( - { existingContent, onboardingConfigString }, - 'Updating onboarding branch' - ); - } else { - logger.debug('Creating onboarding branch'); + if (config.types.docker) { + renovateJson.extends.push(':docker'); } - + logger.info({ renovateJson }, 'Creating onboarding branch'); await config.api.commitFilesToBranch( - onboardBranchName, + config.onboardingBranch, [ { name: 'renovate.json', - contents: onboardingConfigString, + contents: `${JSON.stringify(renovateJson, null, 2)}\n`, }, ], 'Add renovate.json' ); + return config; } async function ensurePr(config, branchUpgrades) { @@ -229,39 +234,33 @@ With your current configuration, renovate will initially create the following Pu } async function getOnboardingStatus(inputConfig) { - const config = { ...inputConfig }; + let config = { ...inputConfig }; const { logger } = config; - logger.debug('Checking if repo is configured'); + logger.debug('Checking if repo is onboarded'); // Check if repository is configured if (config.onboarding === false) { logger.debug('Repo onboarding is disabled'); return { ...config, repoIsOnboarded: true }; } - if (config.renovateJsonPresent || config.hasPackageJsonRenovateConfig) { - logger.debug('Repo has been configured'); + if (config.renovateJsonPresent) { + logger.debug('Repo has renovate.json'); return { ...config, repoIsOnboarded: true }; } + config.onboardingBranch = `${config.branchPrefix}configure`; const pr = await config.api.findPr( - `${config.branchPrefix}configure`, + config.onboardingBranch, 'Configure Renovate' ); + if (pr && pr.isClosed) { + logger.debug('Found closed Configure Renovate PR'); + return { ...config, repoIsOnboarded: true }; + } if (pr) { - logger.debug(`Found existing onboarding PR#${pr.number}`); - if (pr.isClosed) { - logger.debug('Found closed Configure Renovate PR'); - return { ...config, repoIsOnboarded: true }; - } - // PR exists but hasn't been closed yet - logger.debug( - `PR #${pr.displayNumber} needs to be closed to enable renovate to continue` - ); - const prDetails = await config.api.getPr(pr.number); - if (!prDetails.canRebase) { - // Cannot update files if rebasing not possible - return { ...config, repoIsOnboarded: false }; - } + logger.debug(`Found existing onboarding PR #${pr.number}`); + } else { + config = await module.exports.createOnboardingBranch(config); } - // Create or update files, then return - await module.exports.createBranch(config); + logger.debug('Merging renovate.json from onboarding branch'); + config = await apis.mergeRenovateJson(config, config.onboardingBranch); return { ...config, repoIsOnboarded: false }; } diff --git a/test/workers/repository/__snapshots__/apis.spec.js.snap b/test/workers/repository/__snapshots__/apis.spec.js.snap index c3c5bfe3c7e9d64465303f568be35772e09b63a4..3e22cd325917f38026889b1247d209937a387058 100644 --- a/test/workers/repository/__snapshots__/apis.spec.js.snap +++ b/test/workers/repository/__snapshots__/apis.spec.js.snap @@ -115,11 +115,9 @@ Array [ "content": Object { "workspaces": Array [], }, - "errors": Array [], "npmrc": "npmrc-1", "packageFile": "package.json", "packageLock": "packageLock-1", - "warnings": Array [], "yarnLock": "yarnLock-1", "yarnrc": "yarnrc-1", }, diff --git a/test/workers/repository/__snapshots__/onboarding.spec.js.snap b/test/workers/repository/__snapshots__/onboarding.spec.js.snap index 33e6062351fe7bca14feb3e3252e298e6c75ac14..3eff54e6c81d694920115f7affa416901d4b5e35 100644 --- a/test/workers/repository/__snapshots__/onboarding.spec.js.snap +++ b/test/workers/repository/__snapshots__/onboarding.spec.js.snap @@ -374,7 +374,9 @@ Array [ Array [ Object { "contents": "{ - \\"extends\\": [\\"config:js-lib\\"] + \\"extends\\": [ + \\"config:js-lib\\" + ] } ", "name": "renovate.json", @@ -384,13 +386,15 @@ Array [ ] `; -exports[`lib/workers/repository/onboarding getOnboardingStatus(config) commits files if existing content does not match 1`] = ` +exports[`lib/workers/repository/onboarding getOnboardingStatus(config) pins private repos 1`] = ` Array [ "renovate/configure", Array [ Object { "contents": "{ - \\"extends\\": [\\"config:js-lib\\"] + \\"extends\\": [ + \\"config:js-app\\" + ] } ", "name": "renovate.json", @@ -400,13 +404,19 @@ Array [ ] `; -exports[`lib/workers/repository/onboarding getOnboardingStatus(config) pins private repos 1`] = ` +exports[`lib/workers/repository/onboarding getOnboardingStatus(config) throws if no packageFiles 1`] = `[Error: no package files]`; + +exports[`lib/workers/repository/onboarding getOnboardingStatus(config) uses base + docker + meteor 1`] = ` Array [ "renovate/configure", Array [ Object { "contents": "{ - \\"extends\\": [\\"config:js-app\\"] + \\"extends\\": [ + \\"config:base\\", + \\":meteor\\", + \\":docker\\" + ] } ", "name": "renovate.json", diff --git a/test/workers/repository/apis.spec.js b/test/workers/repository/apis.spec.js index 0f574b4bc90c56b1a26f41191dbc25c86b425c52..b6b2173b9016d58af1ce1b2dcf44413f4a393c34 100644 --- a/test/workers/repository/apis.spec.js +++ b/test/workers/repository/apis.spec.js @@ -369,6 +369,7 @@ describe('workers/repository/apis', () => { expect(res.packageFiles).toEqual([]); }); it('includes files with content', async () => { + config.repoIsOnboarded = true; config.api.getFileContent.mockReturnValueOnce( JSON.stringify({ renovate: {}, diff --git a/test/workers/repository/index.spec.js b/test/workers/repository/index.spec.js index 3edb86e43ffd4847889c81098153a94c1ca53a95..511c6eb95df882251921ae3c67f98a2023729f61 100644 --- a/test/workers/repository/index.spec.js +++ b/test/workers/repository/index.spec.js @@ -101,7 +101,7 @@ describe('workers/repository', () => { ...{ packageFiles: [] }, })); await repositoryWorker.renovateRepository(config); - expect(onboarding.getOnboardingStatus.mock.calls.length).toBe(0); + expect(apis.resolvePackageFiles.mock.calls.length).toBe(0); expect(config.logger.error.mock.calls.length).toBe(0); }); it('does not skip repository if package.json', async () => { @@ -265,5 +265,18 @@ describe('workers/repository', () => { await repositoryWorker.renovateRepository(config); expect(config.logger.error.mock.calls.length).toBe(0); }); + it('handles special no package files error', async () => { + apis.initApis.mockImplementationOnce(() => { + // Create a new object, that prototypically inherits from the Error constructor + function MyError() { + this.message = 'no package files'; + } + MyError.prototype = Object.create(Error.prototype); + MyError.prototype.constructor = MyError; + throw new MyError(); + }); + await repositoryWorker.renovateRepository(config); + expect(config.logger.error.mock.calls.length).toBe(0); + }); }); }); diff --git a/test/workers/repository/onboarding.spec.js b/test/workers/repository/onboarding.spec.js index 8b432bacf3ec5e6672cdc4173cd315d39a83a18b..8132c0151061c316ea795f90053eea7282e60d36 100644 --- a/test/workers/repository/onboarding.spec.js +++ b/test/workers/repository/onboarding.spec.js @@ -1,4 +1,5 @@ const onboarding = require('../../../lib/workers/repository/onboarding'); +const apis = require('../../../lib/workers/repository/apis'); const logger = require('../../_fixtures/logger'); const defaultConfig = require('../../../lib/config/defaults').getConfig(); @@ -225,8 +226,10 @@ describe('lib/workers/repository/onboarding', () => { config.api = { commitFilesToBranch: jest.fn(), createPr: jest.fn(() => ({ displayNumber: 1 })), + findFilePaths: jest.fn(() => []), findPr: jest.fn(), getFileContent: jest.fn(), + getFileJson: jest.fn(() => ({})), getPr: jest.fn(() => {}), getCommitMessages: jest.fn(), }; @@ -256,17 +259,8 @@ describe('lib/workers/repository/onboarding', () => { expect(config.api.findPr.mock.calls.length).toBe(1); expect(config.api.commitFilesToBranch.mock.calls.length).toBe(0); }); - it('commits files if pr is not closed and is rebaseable', async () => { - config.api.findPr.mockReturnValueOnce({}); - config.api.getPr.mockReturnValueOnce({ canRebase: true }); - const res = await onboarding.getOnboardingStatus(config); - expect(res.repoIsOnboarded).toEqual(false); - expect(config.api.findPr.mock.calls.length).toBe(1); - expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1); - }); - it('skips file update if existing pr is not rebaseable', async () => { - config.api.findPr.mockReturnValueOnce({}); - config.api.getPr.mockReturnValueOnce({ canRebase: false }); + it('skips commit files and returns false if open pr', async () => { + config.api.findPr.mockReturnValueOnce({ isClosed: false }); const res = await onboarding.getOnboardingStatus(config); expect(res.repoIsOnboarded).toEqual(false); expect(config.api.findPr.mock.calls.length).toBe(1); @@ -287,21 +281,32 @@ describe('lib/workers/repository/onboarding', () => { expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1); expect(config.api.commitFilesToBranch.mock.calls[0]).toMatchSnapshot(); }); - it('commits files if existing content does not match', async () => { - config.api.getFileContent.mockReturnValueOnce('some-different-content'); + it('uses base + docker + meteor', async () => { + apis.detectPackageFiles = jest.fn(input => ({ + ...input, + packageFiles: [{}, {}], + types: { + meteor: true, + docker: true, + }, + })); const res = await onboarding.getOnboardingStatus(config); expect(res.repoIsOnboarded).toEqual(false); expect(config.api.findPr.mock.calls.length).toBe(1); expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1); expect(config.api.commitFilesToBranch.mock.calls[0]).toMatchSnapshot(); }); - it('skips commit files if existing content matches', async () => { - const existingContent = `{\n "extends": ["config:js-lib"]\n}\n`; - config.api.getFileContent.mockReturnValueOnce(existingContent); - const res = await onboarding.getOnboardingStatus(config); - expect(res.repoIsOnboarded).toEqual(false); - expect(config.api.findPr.mock.calls.length).toBe(1); - expect(config.api.commitFilesToBranch.mock.calls.length).toBe(0); + it('throws if no packageFiles', async () => { + apis.detectPackageFiles = jest.fn(input => ({ + ...input, + })); + let e; + try { + await onboarding.getOnboardingStatus(config); + } catch (err) { + e = err; + } + expect(e).toMatchSnapshot(); }); }); });