diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index e5c6b2aa13a5c83051fbfa80ff9fdefaa14c6921..d3f0ed28e127ac7a59594e018c5d227412f908f0 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -184,6 +184,11 @@ Note that if `commitMessagePrefix` or `semanticCommits` values are defined then ## onboardingConfig +## onboardingConfigFileName + +If set to one of the valid [config file names](https://docs.renovatebot.com/configuration-options/), the onboarding PR will create a configuration file with the provided name instead of `renovate.json`. +Falls back to `renovate.json` if the name provided is not valid. + ## onboardingPrTitle Similarly to `onboardingBranch`, if you have an existing Renovate installation and you change `onboardingPrTitle` then it's possible that you'll get onboarding PRs for repositories that had previously closed the onboarding PR unmerged. diff --git a/lib/config/common.ts b/lib/config/common.ts index 3db93010aac79762747b0047f6ff696c48cc4ac0..f841fd90fda3b5300f1c79f708faafa4fc92a26f 100644 --- a/lib/config/common.ts +++ b/lib/config/common.ts @@ -98,6 +98,7 @@ export interface RenovateAdminConfig { onboardingCommitMessage?: string; onboardingPrTitle?: string; onboardingConfig?: RenovateSharedConfig; + onboardingConfigFileName?: string; platform?: string; postUpdateOptions?: string[]; diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts index a2085e088e207656b2b329e00db4630fcc5ed12b..6c60eec6ea82deec4ed4673e0e9e5421fc85733a 100644 --- a/lib/config/definitions.ts +++ b/lib/config/definitions.ts @@ -156,6 +156,15 @@ const options: RenovateOptions[] = [ admin: true, cli: false, }, + { + name: 'onboardingConfigFileName', + description: + 'Change this value in order to override the default onboarding config file name.', + type: 'string', + default: 'renovate.json', + admin: true, + cli: false, + }, { name: 'onboardingPrTitle', description: diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 695d958be27b78a323dc77d91c9c145bba7df50e..1fabfc59eb22943e7f49d7ffe85097ef63e8b6e6 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -7,6 +7,7 @@ import Git, { StatusResult as StatusResult_, } from 'simple-git'; import { join } from 'upath'; +import { configFileNames } from '../../config/app-strings'; import { REPOSITORY_CHANGED, REPOSITORY_DISABLED, @@ -606,7 +607,7 @@ export async function commitFiles({ } } // istanbul ignore if - if (fileNames.length === 1 && fileNames[0] === 'renovate.json') { + if (fileNames.length === 1 && configFileNames.includes(fileNames[0])) { fileNames.unshift('-f'); } if (fileNames.length) { diff --git a/lib/workers/repository/init/apis.spec.ts b/lib/workers/repository/init/apis.spec.ts index b373d18549769f38845f561def05ce3b65c63177..46401b2af820fe819a6be074648154c5586b6240 100644 --- a/lib/workers/repository/init/apis.spec.ts +++ b/lib/workers/repository/init/apis.spec.ts @@ -16,6 +16,9 @@ describe('workers/repository/init/apis', () => { delete config.optimizeForDisabled; delete config.includeForks; }); + afterEach(() => { + jest.resetAllMocks(); + }); it('runs', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -50,5 +53,55 @@ describe('workers/repository/init/apis', () => { }) ).rejects.toThrow(REPOSITORY_FORKED); }); + it('uses the onboardingConfigFileName if set', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + }); + platform.getJsonFile.mockResolvedValueOnce({ includeForks: false }); + const workerPlatformConfig = await initApis({ + ...config, + optimizeForDisabled: true, + onboardingConfigFileName: '.github/renovate.json', + }); + expect(workerPlatformConfig).toBeTruthy(); + expect(workerPlatformConfig.onboardingConfigFileName).toBe( + '.github/renovate.json' + ); + expect(platform.getJsonFile).toHaveBeenCalledWith( + '.github/renovate.json' + ); + expect(platform.getJsonFile).not.toHaveBeenCalledWith('renovate.json'); + }); + it('falls back to "renovate.json" if onboardingConfigFileName is not set', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + }); + platform.getJsonFile.mockResolvedValueOnce({ includeForks: false }); + const workerPlatformConfig = await initApis({ + ...config, + optimizeForDisabled: true, + onboardingConfigFileName: undefined, + }); + expect(workerPlatformConfig).toBeTruthy(); + expect(workerPlatformConfig.onboardingConfigFileName).toBeUndefined(); + expect(platform.getJsonFile).toHaveBeenCalledWith('renovate.json'); + }); + it('falls back to "renovate.json" if onboardingConfigFileName is not valid', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + }); + platform.getJsonFile.mockResolvedValueOnce({ includeForks: false }); + const workerPlatformConfig = await initApis({ + ...config, + optimizeForDisabled: true, + onboardingConfigFileName: 'foo.bar', + }); + expect(workerPlatformConfig).toBeTruthy(); + expect(workerPlatformConfig.onboardingConfigFileName).toBe('foo.bar'); + expect(platform.getJsonFile).toHaveBeenCalledWith('renovate.json'); + }); }); }); diff --git a/lib/workers/repository/init/apis.ts b/lib/workers/repository/init/apis.ts index 22d948fa163ae613bbc85c09906c5c96241c54b0..0b122aeab7199931a1fdf541498a5015abeb48f4 100644 --- a/lib/workers/repository/init/apis.ts +++ b/lib/workers/repository/init/apis.ts @@ -12,13 +12,18 @@ export type WorkerPlatformConfig = RepoResult & RenovateConfig & Record<string, any>; -const defaultConfigFile = configFileNames[0]; +const defaultConfigFile = (config: RenovateConfig): string => + configFileNames.includes(config.onboardingConfigFileName) + ? config.onboardingConfigFileName + : configFileNames[0]; async function validateOptimizeForDisabled( config: RenovateConfig ): Promise<void> { if (config.optimizeForDisabled) { - const renovateConfig = await platform.getJsonFile(defaultConfigFile); + const renovateConfig = await platform.getJsonFile( + defaultConfigFile(config) + ); if (renovateConfig?.enabled === false) { throw new Error(REPOSITORY_DISABLED); } @@ -27,7 +32,9 @@ async function validateOptimizeForDisabled( async function validateIncludeForks(config: RenovateConfig): Promise<void> { if (!config.includeForks && config.isFork) { - const renovateConfig = await platform.getJsonFile(defaultConfigFile); + const renovateConfig = await platform.getJsonFile( + defaultConfigFile(config) + ); if (!renovateConfig?.includeForks) { throw new Error(REPOSITORY_FORKED); } diff --git a/lib/workers/repository/onboarding/branch/create.spec.ts b/lib/workers/repository/onboarding/branch/create.spec.ts index 410f42bcc43c87e6abc72fdb5549d23d23cc4427..7f11597188c6f21844f8bd7b567774d3aff53c4f 100644 --- a/lib/workers/repository/onboarding/branch/create.spec.ts +++ b/lib/workers/repository/onboarding/branch/create.spec.ts @@ -11,11 +11,14 @@ jest.mock('./config', () => ({ }), })); -const buildExpectedCommitFilesArgument = (message: string) => ({ +const buildExpectedCommitFilesArgument = ( + message: string, + filename = 'renovate.json' +) => ({ branchName: 'renovate/configure', files: [ { - name: 'renovate.json', + name: filename, contents: '{"foo":"bar"}', }, ], @@ -94,5 +97,53 @@ describe('workers/repository/onboarding/branch', () => { ); }); }); + describe('setting the onboarding configuration file name', () => { + it('falls back to the default option if not present', async () => { + const prefix = 'chore(deps)'; + config.semanticCommits = 'enabled'; + config.onboardingConfigFileName = undefined; + await createOnboardingBranch(config); + expect(commitFiles).toHaveBeenCalledWith( + buildExpectedCommitFilesArgument( + `${prefix}${COMMIT_MESSAGE_PREFIX_SEPARATOR} add renovate.json` + ) + ); + }); + it('falls back to the default option if in list of allowed names', async () => { + const prefix = 'chore(deps)'; + config.semanticCommits = 'enabled'; + config.onboardingConfigFileName = 'superConfigFile.yaml'; + await createOnboardingBranch(config); + expect(commitFiles).toHaveBeenCalledWith( + buildExpectedCommitFilesArgument( + `${prefix}${COMMIT_MESSAGE_PREFIX_SEPARATOR} add renovate.json` + ) + ); + }); + it('uses the given name if valid', async () => { + const prefix = 'chore(deps)'; + config.semanticCommits = 'enabled'; + config.onboardingConfigFileName = '.gitlab/renovate.json'; + await createOnboardingBranch(config); + expect(commitFiles).toHaveBeenCalledWith( + buildExpectedCommitFilesArgument( + `${prefix}${COMMIT_MESSAGE_PREFIX_SEPARATOR} add ${config.onboardingConfigFileName}`, + config.onboardingConfigFileName + ) + ); + }); + it('applies to the default commit message', async () => { + const prefix = 'chore(deps)'; + config.semanticCommits = 'enabled'; + config.onboardingConfigFileName = `.renovaterc`; + await createOnboardingBranch(config); + expect(commitFiles).toHaveBeenCalledWith( + buildExpectedCommitFilesArgument( + `${prefix}${COMMIT_MESSAGE_PREFIX_SEPARATOR} add ${config.onboardingConfigFileName}`, + config.onboardingConfigFileName + ) + ); + }); + }); }); }); diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts index 9df2f009ac7e1fe878a11b453b33025a2c575d12..8fa32831b3090fdbd23a88c8815dd0484fe01685 100644 --- a/lib/workers/repository/onboarding/branch/create.ts +++ b/lib/workers/repository/onboarding/branch/create.ts @@ -14,6 +14,10 @@ export async function createOnboardingBranch( const contents = await getOnboardingConfig(config); logger.debug('Creating onboarding branch'); + const configFile = configFileNames.includes(config.onboardingConfigFileName) + ? config.onboardingConfigFileName + : defaultConfigFile; + let commitMessagePrefix = ''; if (config.commitMessagePrefix) { commitMessagePrefix = config.commitMessagePrefix; @@ -33,7 +37,7 @@ export async function createOnboardingBranch( } else { onboardingCommitMessage = `${ commitMessagePrefix ? 'add' : 'Add' - } ${defaultConfigFile}`; + } ${configFile}`; } const commitMessage = `${commitMessagePrefix} ${onboardingCommitMessage}`.trim(); @@ -47,7 +51,7 @@ export async function createOnboardingBranch( branchName: config.onboardingBranch, files: [ { - name: defaultConfigFile, + name: configFile, contents, }, ], diff --git a/lib/workers/repository/onboarding/branch/rebase.spec.ts b/lib/workers/repository/onboarding/branch/rebase.spec.ts index 383e37a19f7dc32d4785c521eb8f1404815e59d3..4d3ea90d7d84a79179601dfa2aa6f1aa52c1c977 100644 --- a/lib/workers/repository/onboarding/branch/rebase.spec.ts +++ b/lib/workers/repository/onboarding/branch/rebase.spec.ts @@ -32,5 +32,33 @@ describe('workers/repository/onboarding/branch/rebase', () => { await rebaseOnboardingBranch(config); expect(git.commitFiles).toHaveBeenCalledTimes(1); }); + it('uses the onboardingConfigFileName if set', async () => { + git.isBranchStale.mockResolvedValueOnce(true); + await rebaseOnboardingBranch({ + ...config, + onboardingConfigFileName: '.github/renovate.json', + }); + expect(git.commitFiles).toHaveBeenCalledTimes(1); + expect(git.commitFiles.mock.calls[0][0].message).toContain( + '.github/renovate.json' + ); + expect(git.commitFiles.mock.calls[0][0].files[0].name).toBe( + '.github/renovate.json' + ); + }); + it('falls back to "renovate.json" if onboardingConfigFileName is not set', async () => { + git.isBranchStale.mockResolvedValueOnce(true); + await rebaseOnboardingBranch({ + ...config, + onboardingConfigFileName: undefined, + }); + expect(git.commitFiles).toHaveBeenCalledTimes(1); + expect(git.commitFiles.mock.calls[0][0].message).toContain( + 'renovate.json' + ); + expect(git.commitFiles.mock.calls[0][0].files[0].name).toBe( + 'renovate.json' + ); + }); }); }); diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts index 5abec25e16712a93d0631234dd095e03e4c7f022..b2ca91a56daf61483274ccdf4093f493796e5858 100644 --- a/lib/workers/repository/onboarding/branch/rebase.ts +++ b/lib/workers/repository/onboarding/branch/rebase.ts @@ -9,9 +9,13 @@ import { } from '../../../../util/git'; import { getOnboardingConfig } from './config'; -const defaultConfigFile = configFileNames[0]; +const defaultConfigFile = (config: RenovateConfig): string => + configFileNames.includes(config.onboardingConfigFileName) + ? config.onboardingConfigFileName + : configFileNames[0]; function getCommitMessage(config: RenovateConfig): string { + const configFile = defaultConfigFile(config); let commitMessage: string; // istanbul ignore if if (config.semanticCommits === 'enabled') { @@ -20,9 +24,9 @@ function getCommitMessage(config: RenovateConfig): string { commitMessage += `(${config.semanticCommitScope})`; } commitMessage += ': '; - commitMessage += 'add ' + defaultConfigFile; + commitMessage += 'add ' + configFile; } else { - commitMessage = 'Add ' + defaultConfigFile; + commitMessage = 'Add ' + configFile; } return commitMessage; } @@ -35,10 +39,8 @@ export async function rebaseOnboardingBranch( logger.debug('Onboarding branch has been edited and cannot be rebased'); return null; } - const existingContents = await getFile( - defaultConfigFile, - config.onboardingBranch - ); + const configFile = defaultConfigFile(config); + const existingContents = await getFile(configFile, config.onboardingBranch); const contents = await getOnboardingConfig(config); if ( contents === existingContents && @@ -60,7 +62,7 @@ export async function rebaseOnboardingBranch( branchName: config.onboardingBranch, files: [ { - name: defaultConfigFile, + name: configFile, contents, }, ], diff --git a/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap b/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap index d6386d3a946ddfba87fffa74d8630aaf41d89bb9..9215cf1a0a0dce6ee263b55f3950266309828dd2 100644 --- a/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap +++ b/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap @@ -15,6 +15,51 @@ Based on the default config's presets, Renovate will: " `; +exports[`workers/repository/onboarding/pr/config-description getConfigDesc() contains the onboardingConfigFileName if set 1`] = ` +" +### Configuration Summary + +Based on the default config's presets, Renovate will: + + - Start dependency updates only once this onboarding PR is merged + - Run Renovate on following schedule: before 5am + +:abcd: Would you like to change the way Renovate is upgrading your dependencies? Simply edit the \`.github/renovate.json\` in this branch with your custom config and the list of Pull Requests in the \\"What to Expect\\" section below will be updated the next time Renovate runs. + +--- +" +`; + +exports[`workers/repository/onboarding/pr/config-description getConfigDesc() falls back to "renovate.json" if onboardingConfigFileName is not set 1`] = ` +" +### Configuration Summary + +Based on the default config's presets, Renovate will: + + - Start dependency updates only once this onboarding PR is merged + - Run Renovate on following schedule: before 5am + +:abcd: Would you like to change the way Renovate is upgrading your dependencies? Simply edit the \`renovate.json\` in this branch with your custom config and the list of Pull Requests in the \\"What to Expect\\" section below will be updated the next time Renovate runs. + +--- +" +`; + +exports[`workers/repository/onboarding/pr/config-description getConfigDesc() falls back to "renovate.json" if onboardingConfigFileName is not valid 1`] = ` +" +### Configuration Summary + +Based on the default config's presets, Renovate will: + + - Start dependency updates only once this onboarding PR is merged + - Run Renovate on following schedule: before 5am + +:abcd: Would you like to change the way Renovate is upgrading your dependencies? Simply edit the \`renovate.json\` in this branch with your custom config and the list of Pull Requests in the \\"What to Expect\\" section below will be updated the next time Renovate runs. + +--- +" +`; + exports[`workers/repository/onboarding/pr/config-description getConfigDesc() returns a full list 1`] = ` " ### Configuration Summary diff --git a/lib/workers/repository/onboarding/pr/config-description.spec.ts b/lib/workers/repository/onboarding/pr/config-description.spec.ts index fc5dd3f78a24509fa00bcb4d0907a0572b654093..610854f25ccc270437edcf58e97ee235f901be09 100644 --- a/lib/workers/repository/onboarding/pr/config-description.spec.ts +++ b/lib/workers/repository/onboarding/pr/config-description.spec.ts @@ -38,5 +38,30 @@ describe('workers/repository/onboarding/pr/config-description', () => { const res = getConfigDesc(config); expect(res).toMatchSnapshot(); }); + it('contains the onboardingConfigFileName if set', () => { + delete config.description; + config.schedule = ['before 5am']; + config.onboardingConfigFileName = '.github/renovate.json'; + const res = getConfigDesc(config); + expect(res).toMatchSnapshot(); + expect(res.indexOf('`.github/renovate.json`')).not.toBe(-1); + expect(res.indexOf('`renovate.json`')).toBe(-1); + }); + it('falls back to "renovate.json" if onboardingConfigFileName is not set', () => { + delete config.description; + config.schedule = ['before 5am']; + config.onboardingConfigFileName = undefined; + const res = getConfigDesc(config); + expect(res).toMatchSnapshot(); + expect(res.indexOf('`renovate.json`')).not.toBe(-1); + }); + it('falls back to "renovate.json" if onboardingConfigFileName is not valid', () => { + delete config.description; + config.schedule = ['before 5am']; + config.onboardingConfigFileName = 'foo.bar'; + const res = getConfigDesc(config); + expect(res).toMatchSnapshot(); + expect(res.indexOf('`renovate.json`')).not.toBe(-1); + }); }); }); diff --git a/lib/workers/repository/onboarding/pr/config-description.ts b/lib/workers/repository/onboarding/pr/config-description.ts index 6812a9ec976b71de3e460c87fc52452db5732c81..795fe6219e1a16b6ff68ef32b81ac89aa887cd03 100644 --- a/lib/workers/repository/onboarding/pr/config-description.ts +++ b/lib/workers/repository/onboarding/pr/config-description.ts @@ -33,6 +33,9 @@ export function getConfigDesc( config: RenovateConfig, packageFiles?: Record<string, PackageFile[]> ): string { + const configFile = configFileNames.includes(config.onboardingConfigFileName) + ? config.onboardingConfigFileName + : defaultConfigFile; logger.debug('getConfigDesc()'); logger.trace({ config }); const descriptionArr = getDescriptionArray(config); @@ -50,7 +53,7 @@ export function getConfigDesc( desc += emojify( `:abcd: Would you like to change the way Renovate is upgrading your dependencies?` ); - desc += ` Simply edit the \`${defaultConfigFile}\` in this branch with your custom config and the list of Pull Requests in the "What to Expect" section below will be updated the next time Renovate runs.`; + desc += ` Simply edit the \`${configFile}\` in this branch with your custom config and the list of Pull Requests in the "What to Expect" section below will be updated the next time Renovate runs.`; desc += '\n\n---\n'; return desc; }