diff --git a/lib/workers/repository/init/merge.ts b/lib/workers/repository/init/merge.ts index 111a98cd47cd38b7d0b7ca98cbaf753df5b18896..56bfca3cfe913be041926edbafba48b3609a55b9 100644 --- a/lib/workers/repository/init/merge.ts +++ b/lib/workers/repository/init/merge.ts @@ -170,9 +170,10 @@ export async function mergeRenovateConfig( throw error; } if (migratedConfig.warnings) { - returnConfig.warnings = returnConfig.warnings.concat( - migratedConfig.warnings - ); + returnConfig.warnings = [ + ...(returnConfig.warnings || []), + ...migratedConfig.warnings, + ]; } delete migratedConfig.errors; delete migratedConfig.warnings; diff --git a/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap b/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap index c9031a0ff8a37ec81b01d8b96528364b49c3aadd..b94efadcf3a66c90cd448d86435cd8680c3a676c 100644 --- a/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap +++ b/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap @@ -6,3 +6,11 @@ exports[`workers/repository/onboarding/branch/index checkOnboardingBranch has de } " `; + +exports[`workers/repository/onboarding/branch/index checkOnboardingBranch uses discovered onboarding config 1`] = ` +"{ + \\"$schema\\": \\"https://docs.renovatebot.com/renovate-schema.json\\", + \\"extends: [\\"some/renovate-config\\"] +} +" +`; diff --git a/lib/workers/repository/onboarding/branch/config.spec.ts b/lib/workers/repository/onboarding/branch/config.spec.ts index 0ace4010ddfb54a4d8e3a597284406601be0d9ad..caa8982abc124ff1d0025ad395dd48d7fb57905c 100644 --- a/lib/workers/repository/onboarding/branch/config.spec.ts +++ b/lib/workers/repository/onboarding/branch/config.spec.ts @@ -1,7 +1,7 @@ import { RenovateConfig, getConfig, getName } from '../../../../../test/util'; import * as presets from '../../../../config/presets/local'; import { PRESET_DEP_NOT_FOUND } from '../../../../config/presets/util'; -import { getOnboardingConfig } from './config'; +import { getOnboardingConfig, getOnboardingConfigContents } from './config'; jest.mock('../../../../config/presets/local'); @@ -9,56 +9,72 @@ const mockedPresets = presets as jest.Mocked<typeof presets>; describe(getName(), () => { let config: RenovateConfig; - let onboardingConfig: string; beforeEach(() => { jest.clearAllMocks(); config = getConfig(); config.platform = 'github'; config.repository = 'some/repo'; }); + describe('getOnboardingConfigContents', () => { + it('returns the JSON stringified onboarding config', async () => { + mockedPresets.getPreset.mockResolvedValueOnce({ enabled: true }); + const contents = await getOnboardingConfigContents(config); + expect(mockedPresets.getPreset).toHaveBeenCalledTimes(1); + expect(contents).toEqual( + '{\n' + + ' "$schema": "https://docs.renovatebot.com/renovate-schema.json",\n' + + ' "extends": [\n' + + ' "local>some/renovate-config"\n' + + ' ]\n' + + '}\n' + ); + }); + }); describe('getOnboardingConfig', () => { it('handles finding an organization preset', async () => { mockedPresets.getPreset.mockResolvedValueOnce({ enabled: true }); - onboardingConfig = await getOnboardingConfig(config); + const onboardingConfig = await getOnboardingConfig(config); expect(mockedPresets.getPreset).toHaveBeenCalledTimes(1); - expect(JSON.parse(onboardingConfig).extends[0]).toEqual( - 'local>some/renovate-config' - ); + expect(onboardingConfig).toEqual({ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: ['local>some/renovate-config'], + }); }); it('handles finding an organization dot platform preset', async () => { mockedPresets.getPreset.mockRejectedValueOnce( new Error(PRESET_DEP_NOT_FOUND) ); mockedPresets.getPreset.mockResolvedValueOnce({ enabled: true }); - onboardingConfig = await getOnboardingConfig(config); + const onboardingConfig = await getOnboardingConfig(config); expect(mockedPresets.getPreset).toHaveBeenCalledTimes(2); - expect(JSON.parse(onboardingConfig).extends[0]).toEqual( - 'local>some/.github:renovate-config' - ); + expect(onboardingConfig).toEqual({ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: ['local>some/.github:renovate-config'], + }); }); it('handles not finding an organization preset', async () => { mockedPresets.getPreset.mockRejectedValue( new Error(PRESET_DEP_NOT_FOUND) ); - onboardingConfig = await getOnboardingConfig(config); + const onboardingConfig = await getOnboardingConfig(config); expect(mockedPresets.getPreset).toHaveBeenCalledTimes(2); - expect(JSON.parse(onboardingConfig)).toEqual(config.onboardingConfig); + expect(onboardingConfig).toEqual(config.onboardingConfig); }); it('ignores an unknown error', async () => { mockedPresets.getPreset.mockRejectedValue( new Error('unknown error for test') ); - onboardingConfig = await getOnboardingConfig(config); + const onboardingConfig = await getOnboardingConfig(config); expect(mockedPresets.getPreset).toHaveBeenCalledTimes(2); - expect(JSON.parse(onboardingConfig)).toEqual(config.onboardingConfig); + expect(onboardingConfig).toEqual(config.onboardingConfig); }); it('ignores unsupported platform', async () => { mockedPresets.getPreset.mockRejectedValue( new Error(`Unsupported platform 'dummy' for local preset.`) ); - onboardingConfig = await getOnboardingConfig(config); + const onboardingConfig = await getOnboardingConfig(config); expect(mockedPresets.getPreset).toHaveBeenCalledTimes(2); - expect(JSON.parse(onboardingConfig)).toEqual(config.onboardingConfig); + expect(onboardingConfig).toEqual(config.onboardingConfig); }); }); }); diff --git a/lib/workers/repository/onboarding/branch/config.ts b/lib/workers/repository/onboarding/branch/config.ts index 224f495b01d6696c9ced5eae8e4d8c0f738c0c2d..b96efdc78b735778e069f3f21b36773885fc0456 100644 --- a/lib/workers/repository/onboarding/branch/config.ts +++ b/lib/workers/repository/onboarding/branch/config.ts @@ -1,12 +1,15 @@ import { getPreset } from '../../../../config/presets/local'; import { PRESET_DEP_NOT_FOUND } from '../../../../config/presets/util'; -import type { RenovateConfig } from '../../../../config/types'; +import type { + RenovateConfig, + RenovateSharedConfig, +} from '../../../../config/types'; import { logger } from '../../../../logger'; import { clone } from '../../../../util/clone'; -export async function getOnboardingConfig( +async function getOnboardingConfig( config: RenovateConfig -): Promise<string> { +): Promise<RenovateSharedConfig> { let onboardingConfig = clone(config.onboardingConfig); let orgPreset: string; @@ -65,5 +68,14 @@ export async function getOnboardingConfig( } logger.debug({ config: onboardingConfig }, 'onboarding config'); + return onboardingConfig; +} + +async function getOnboardingConfigContents( + config: RenovateConfig +): Promise<string> { + const onboardingConfig = await getOnboardingConfig(config); return JSON.stringify(onboardingConfig, null, 2) + '\n'; } + +export { getOnboardingConfig, getOnboardingConfigContents }; diff --git a/lib/workers/repository/onboarding/branch/create.spec.ts b/lib/workers/repository/onboarding/branch/create.spec.ts index 6005cafb01dac023c59ed6b50ff6a3cc84b9d369..decbee29aec66891cfd9cd872fdf5a1d10c8781a 100644 --- a/lib/workers/repository/onboarding/branch/create.spec.ts +++ b/lib/workers/repository/onboarding/branch/create.spec.ts @@ -5,7 +5,7 @@ import { createOnboardingBranch } from './create'; jest.mock('../../../../util/git'); jest.mock('./config', () => ({ - getOnboardingConfig: () => + getOnboardingConfigContents: () => JSON.stringify({ foo: 'bar', }), diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts index dc4c306fabd720d27947fe47e42e7d14fa06d785..e670134c6871484ae0a831cee38174c06bb9fa1a 100644 --- a/lib/workers/repository/onboarding/branch/create.ts +++ b/lib/workers/repository/onboarding/branch/create.ts @@ -4,7 +4,7 @@ import type { RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import { commitFiles } from '../../../../util/git'; import { formatCommitMessagePrefix } from '../../util/commit-message'; -import { getOnboardingConfig } from './config'; +import { getOnboardingConfigContents } from './config'; const defaultConfigFile = configFileNames[0]; @@ -12,7 +12,7 @@ export async function createOnboardingBranch( config: Partial<RenovateConfig> ): Promise<string | null> { logger.debug('createOnboardingBranch()'); - const contents = await getOnboardingConfig(config); + const contents = await getOnboardingConfigContents(config); logger.debug('Creating onboarding branch'); const configFile = configFileNames.includes(config.onboardingConfigFileName) diff --git a/lib/workers/repository/onboarding/branch/index.spec.ts b/lib/workers/repository/onboarding/branch/index.spec.ts index f2e56792bbcf11ab2a3e70248f9bd3eaf46b5108..10074a91a136f9f0887fc7f8da74e0a1e67bc723 100644 --- a/lib/workers/repository/onboarding/branch/index.spec.ts +++ b/lib/workers/repository/onboarding/branch/index.spec.ts @@ -7,16 +7,23 @@ import { git, platform, } from '../../../../../test/util'; +import { + REPOSITORY_FORKED, + REPOSITORY_NO_PACKAGE_FILES, +} from '../../../../constants/error-messages'; import { Pr } from '../../../../platform'; import { PrState } from '../../../../types'; +import * as _config from './config'; import * as _rebase from './rebase'; import { checkOnboardingBranch } from '.'; const rebase: any = _rebase; +const configModule: any = _config; jest.mock('../../../../workers/repository/onboarding/branch/rebase'); jest.mock('../../../../util/fs'); jest.mock('../../../../util/git'); +jest.mock('./config'); describe(getName(), () => { describe('checkOnboardingBranch', () => { @@ -28,16 +35,51 @@ describe(getName(), () => { git.getFileList.mockResolvedValue([]); }); it('throws if no package files', async () => { - await expect(checkOnboardingBranch(config)).rejects.toThrow(); + await expect(checkOnboardingBranch(config)).rejects.toThrow( + REPOSITORY_NO_PACKAGE_FILES + ); }); it('throws if fork', async () => { config.isFork = true; - await expect(checkOnboardingBranch(config)).rejects.toThrow(); + await expect(checkOnboardingBranch(config)).rejects.toThrow( + REPOSITORY_FORKED + ); }); it('has default onboarding config', async () => { + configModule.getOnboardingConfig.mockResolvedValue( + config.onboardingConfig + ); + configModule.getOnboardingConfigContents.mockResolvedValue( + '{\n' + + ' "$schema": "https://docs.renovatebot.com/renovate-schema.json"\n' + + '}\n' + ); + git.getFileList.mockResolvedValue(['package.json']); + fs.readLocalFile.mockResolvedValue('{}'); + await checkOnboardingBranch(config); + expect( + git.commitFiles.mock.calls[0][0].files[0].contents + ).toMatchSnapshot(); + }); + it('uses discovered onboarding config', async () => { + configModule.getOnboardingConfig.mockResolvedValue({ + onboardingBranch: 'test', + }); + configModule.getOnboardingConfigContents.mockResolvedValue( + '{\n' + + ' "$schema": "https://docs.renovatebot.com/renovate-schema.json",\n' + + ' "extends: ["some/renovate-config"]\n' + + '}\n' + ); git.getFileList.mockResolvedValue(['package.json']); fs.readLocalFile.mockResolvedValue('{}'); await checkOnboardingBranch(config); + expect(configModule.getOnboardingConfigContents).toHaveBeenCalledWith({ + ...config, + onboardingBranch: 'test', + renovateJsonPresent: true, + warnings: [], + }); expect( git.commitFiles.mock.calls[0][0].files[0].contents ).toMatchSnapshot(); diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts index d9f63474bc54ac2d1a50626423832620a91af1b6..c38584c1f22e1066497e9dee7a6b5bff387aa626 100644 --- a/lib/workers/repository/onboarding/branch/index.ts +++ b/lib/workers/repository/onboarding/branch/index.ts @@ -1,3 +1,4 @@ +import { mergeChildConfig } from '../../../../config'; import { getAdminConfig } from '../../../../config/admin'; import type { RenovateConfig } from '../../../../config/types'; import { @@ -8,7 +9,9 @@ import { logger } from '../../../../logger'; import { platform } from '../../../../platform'; import { checkoutBranch } from '../../../../util/git'; import { extractAllDependencies } from '../../extract'; +import { mergeRenovateConfig } from '../../init/merge'; import { isOnboarded, onboardingPrExists } from './check'; +import { getOnboardingConfig } from './config'; import { createOnboardingBranch } from './create'; import { rebaseOnboardingBranch } from './rebase'; @@ -17,6 +20,7 @@ export async function checkOnboardingBranch( ): Promise<RenovateConfig> { logger.debug('checkOnboarding()'); logger.trace({ config }); + let onboardingBranch = config.onboardingBranch; const repoIsOnboarded = await isOnboarded(config); if (repoIsOnboarded) { logger.debug('Repo is onboarded'); @@ -42,22 +46,29 @@ export async function checkOnboardingBranch( } } else { logger.debug('Onboarding PR does not exist'); - if (Object.entries(await extractAllDependencies(config)).length === 0) { + const onboardingConfig = await getOnboardingConfig(config); + let mergedConfig = mergeChildConfig(config, onboardingConfig); + mergedConfig = await mergeRenovateConfig(mergedConfig); + onboardingBranch = mergedConfig.onboardingBranch; + + if ( + Object.entries(await extractAllDependencies(mergedConfig)).length === 0 + ) { throw new Error(REPOSITORY_NO_PACKAGE_FILES); } logger.debug('Need to create onboarding PR'); - const commit = await createOnboardingBranch(config); + const commit = await createOnboardingBranch(mergedConfig); // istanbul ignore if if (commit) { logger.info( - { branch: config.onboardingBranch, commit, onboarding: true }, + { branch: onboardingBranch, commit, onboarding: true }, 'Branch created' ); } } if (!getAdminConfig().dryRun) { - await checkoutBranch(config.onboardingBranch); + await checkoutBranch(onboardingBranch); } - const branchList = [config.onboardingBranch]; - return { ...config, repoIsOnboarded, branchList }; + const branchList = [onboardingBranch]; + return { ...config, repoIsOnboarded, onboardingBranch, branchList }; } diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts index 63354f51670ba6a1c4ed394ea215bd062da4782b..5080736251d3e8a4bce64b90fafdea811d76a066 100644 --- a/lib/workers/repository/onboarding/branch/rebase.ts +++ b/lib/workers/repository/onboarding/branch/rebase.ts @@ -8,7 +8,7 @@ import { isBranchModified, isBranchStale, } from '../../../../util/git'; -import { getOnboardingConfig } from './config'; +import { getOnboardingConfigContents } from './config'; const defaultConfigFile = (config: RenovateConfig): string => configFileNames.includes(config.onboardingConfigFileName) @@ -42,7 +42,7 @@ export async function rebaseOnboardingBranch( } const configFile = defaultConfigFile(config); const existingContents = await getFile(configFile, config.onboardingBranch); - const contents = await getOnboardingConfig(config); + const contents = await getOnboardingConfigContents(config); if ( contents === existingContents && !(await isBranchStale(config.onboardingBranch))