diff --git a/lib/workers/repository/init/config.spec.ts b/lib/workers/repository/init/config.spec.ts deleted file mode 100644 index a119f7ff9898800a5679cfddd94ebd38b2bb184e..0000000000000000000000000000000000000000 --- a/lib/workers/repository/init/config.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { AllConfig } from '../../../config/types'; -import { mergeStaticRepoEnvConfig } from './config'; - -describe('workers/repository/init/config', () => { - describe('mergeRepoEnvConfig()', () => { - interface MergeRepoEnvTestCase { - name: string; - env: NodeJS.ProcessEnv; - currentConfig: AllConfig; - wantConfig: AllConfig; - } - - const testCases: MergeRepoEnvTestCase[] = [ - { - name: 'it does nothing', - env: {}, - currentConfig: { repositories: ['some/repo'] }, - wantConfig: { repositories: ['some/repo'] }, - }, - { - name: 'it merges env with the current config', - env: { RENOVATE_STATIC_REPO_CONFIG: '{"dependencyDashboard":true}' }, - currentConfig: { repositories: ['some/repo'] }, - wantConfig: { - dependencyDashboard: true, - repositories: ['some/repo'], - }, - }, - { - name: 'it ignores env with other renovate specific configuration options', - env: { RENOVATE_CONFIG: '{"dependencyDashboard":true}' }, - currentConfig: { repositories: ['some/repo'] }, - wantConfig: { repositories: ['some/repo'] }, - }, - ]; - - it.each(testCases)( - '$name', - async ({ env, currentConfig, wantConfig }: MergeRepoEnvTestCase) => { - const got = await mergeStaticRepoEnvConfig(currentConfig, env); - - expect(got).toEqual(wantConfig); - }, - ); - }); -}); diff --git a/lib/workers/repository/init/config.ts b/lib/workers/repository/init/config.ts index fe3122896b32aee3f819b32092c118f644e0800a..a8f9a14c6dfeceb8b5b3293cdccd72dce10ab32b 100644 --- a/lib/workers/repository/init/config.ts +++ b/lib/workers/repository/init/config.ts @@ -1,7 +1,4 @@ -import is from '@sindresorhus/is'; -import { mergeChildConfig } from '../../../config'; -import type { AllConfig, RenovateConfig } from '../../../config/types'; -import { parseAndValidateOrExit } from '../../global/config/parse/env'; +import type { RenovateConfig } from '../../../config/types'; import { checkOnboardingBranch } from '../onboarding/branch'; import { mergeInheritedConfig } from './inherited'; import { mergeRenovateConfig } from './merge'; @@ -13,24 +10,7 @@ export async function getRepoConfig( let config = { ...config_ }; config.baseBranch = config.defaultBranch; config = await mergeInheritedConfig(config); - config = await mergeStaticRepoEnvConfig(config, process.env); config = await checkOnboardingBranch(config); config = await mergeRenovateConfig(config); return config; } - -export async function mergeStaticRepoEnvConfig( - config: AllConfig, - env: NodeJS.ProcessEnv, -): Promise<AllConfig> { - const repoEnvConfig = await parseAndValidateOrExit( - env, - 'RENOVATE_STATIC_REPO_CONFIG', - ); - - if (!is.nonEmptyObject(repoEnvConfig)) { - return config; - } - - return mergeChildConfig(config, repoEnvConfig); -} diff --git a/lib/workers/repository/init/merge.spec.ts b/lib/workers/repository/init/merge.spec.ts index 00ee13353d132f9b7aa5e74442cdeef5ce79d40f..c975e3ee7d772c6a32827c8edf6c772e0ecc102b 100644 --- a/lib/workers/repository/init/merge.spec.ts +++ b/lib/workers/repository/init/merge.spec.ts @@ -11,6 +11,7 @@ import * as decrypt from '../../../config/decrypt'; import { getConfig } from '../../../config/defaults'; import * as _migrateAndValidate from '../../../config/migrate-validate'; import * as _migrate from '../../../config/migration'; +import type { AllConfig } from '../../../config/types'; import * as memCache from '../../../util/cache/memory'; import * as repoCache from '../../../util/cache/repository'; import { initRepoCache } from '../../../util/cache/repository/init'; @@ -21,6 +22,7 @@ import { checkForRepoConfigError, detectRepoFileConfig, mergeRenovateConfig, + mergeStaticRepoEnvConfig, setNpmTokenInNpmrc, } from './merge'; @@ -45,10 +47,13 @@ jest.mock('../../../config/migration'); jest.mock('../../../config/migrate-validate'); describe('workers/repository/init/merge', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + describe('detectRepoFileConfig()', () => { beforeEach(async () => { await initRepoCache({ repoFingerprint: '0123456789abcdef' }); - jest.restoreAllMocks(); }); it('returns config if not found', async () => { @@ -469,4 +474,188 @@ describe('workers/repository/init/merge', () => { expect(config).toMatchObject({ npmrc: 'something_auth=token\n' }); }); }); + + describe('static repository config', () => { + const repoStaticConfigKey = 'RENOVATE_STATIC_REPO_CONFIG'; + + beforeEach(() => { + migrate.migrateConfig.mockImplementation((c) => ({ + isMigrated: true, + migratedConfig: c, + })); + migrateAndValidate.migrateAndValidate.mockImplementationOnce((_, c) => { + return Promise.resolve({ + ...c, + warnings: [], + errors: [], + }); + }); + }); + + describe('mergeStaticRepoEnvConfig()', () => { + interface MergeRepoEnvTestCase { + name: string; + currentConfig: AllConfig; + env: NodeJS.ProcessEnv; + want: AllConfig; + } + + const testCases: MergeRepoEnvTestCase[] = [ + { + name: 'it does nothing', + env: {}, + currentConfig: { repositories: ['some/repo'] }, + want: { repositories: ['some/repo'] }, + }, + { + name: 'it merges env with the current config', + env: { [repoStaticConfigKey]: '{"dependencyDashboard":true}' }, + currentConfig: { repositories: ['some/repo'] }, + want: { + dependencyDashboard: true, + repositories: ['some/repo'], + }, + }, + { + name: 'it ignores env with other renovate specific configuration options', + env: { RENOVATE_CONFIG: '{"dependencyDashboard":true}' }, + currentConfig: { repositories: ['some/repo'] }, + want: { repositories: ['some/repo'] }, + }, + ]; + + it.each(testCases)( + '$name', + async ({ env, currentConfig, want }: MergeRepoEnvTestCase) => { + const got = await mergeStaticRepoEnvConfig(currentConfig, env); + + expect(got).toEqual(want); + }, + ); + }); + + describe('mergeRenovateConfig() with a static repository config', () => { + beforeEach(() => { + delete process.env[repoStaticConfigKey]; + + scm.getFileList.mockResolvedValueOnce(['renovate.json']); + }); + + interface MergeRepoFileAndEnvConfigTestCase { + name: string; + currentConfig: AllConfig; + repoFileConfig: AllConfig; + staticConfig: AllConfig; + wantConfig: AllConfig; + } + + it.each<MergeRepoFileAndEnvConfigTestCase>([ + { + name: 'it does nothing', + currentConfig: {}, + repoFileConfig: {}, + staticConfig: {}, + wantConfig: { + renovateJsonPresent: true, + warnings: [], + }, + }, + { + name: 'it should resolve and use the repo file config when the static config is not set', + currentConfig: {}, + repoFileConfig: { + extends: ['group:socketio'], + }, + staticConfig: {}, + wantConfig: { + description: ['Group socket.io packages.'], + packageRules: [ + { + groupName: 'socket.io packages', + matchPackageNames: ['socket.io**'], + }, + ], + renovateJsonPresent: true, + warnings: [], + }, + }, + { + name: 'it should resolve and use the static config when no repo file present', + currentConfig: {}, + repoFileConfig: {}, + staticConfig: { extends: ['group:socketio'] }, + wantConfig: { + description: ['Group socket.io packages.'], + packageRules: [ + { + groupName: 'socket.io packages', + matchPackageNames: ['socket.io**'], + }, + ], + renovateJsonPresent: true, + warnings: [], + }, + }, + { + name: 'it should merge a static repo config into the repo config by appending it', + currentConfig: {}, + repoFileConfig: { + extends: ['group:socketio'], + packageRules: [ + { + matchConfidence: ['high', 'very high'], + groupName: 'high merge confidence', + }, + ], + }, + staticConfig: { + dependencyDashboard: true, + packageRules: [ + { + groupName: 'my-custom-socketio-override', + matchPackageNames: ['socket.io**'], + }, + ], + }, + wantConfig: { + dependencyDashboard: true, + description: ['Group socket.io packages.'], + packageRules: [ + { + groupName: 'socket.io packages', + matchPackageNames: ['socket.io**'], + }, + { + groupName: 'high merge confidence', + matchConfidence: ['high', 'very high'], + }, + { + groupName: 'my-custom-socketio-override', + matchPackageNames: ['socket.io**'], + }, + ], + renovateJsonPresent: true, + warnings: [], + }, + }, + ])( + '$name', + async ({ + staticConfig, + repoFileConfig, + currentConfig, + wantConfig, + }: MergeRepoFileAndEnvConfigTestCase) => { + fs.readLocalFile.mockResolvedValueOnce( + JSON.stringify(repoFileConfig), + ); + process.env[repoStaticConfigKey] = JSON.stringify(staticConfig); + + const got = await mergeRenovateConfig(currentConfig); + + expect(got).toStrictEqual(wantConfig); + }, + ); + }); + }); }); diff --git a/lib/workers/repository/init/merge.ts b/lib/workers/repository/init/merge.ts index 4cf53b1395c3e516da1afc083dd87af960fe4279..454b1939652bcb5699f19beb2fcb7533a0fdf464 100644 --- a/lib/workers/repository/init/merge.ts +++ b/lib/workers/repository/init/merge.ts @@ -7,7 +7,7 @@ import { migrateConfig } from '../../../config/migration'; import { parseFileConfig } from '../../../config/parse'; import * as presets from '../../../config/presets'; import { applySecretsToConfig } from '../../../config/secrets'; -import type { RenovateConfig } from '../../../config/types'; +import type { AllConfig, RenovateConfig } from '../../../config/types'; import { CONFIG_VALIDATION, REPOSITORY_CHANGED, @@ -25,6 +25,7 @@ import * as queue from '../../../util/http/queue'; import * as throttle from '../../../util/http/throttle'; import { maskToken } from '../../../util/mask'; import { regEx } from '../../../util/regex'; +import { parseAndValidateOrExit } from '../../global/config/parse/env'; import { getOnboardingConfig } from '../onboarding/branch/config'; import { getDefaultConfigFileName } from '../onboarding/branch/create'; import { @@ -187,15 +188,19 @@ export async function mergeRenovateConfig( }; } const configFileParsed = repoConfig?.configFileParsed || {}; + const configFileAndEnv = await mergeStaticRepoEnvConfig( + configFileParsed, + process.env, + ); if (is.nonEmptyArray(returnConfig.extends)) { - configFileParsed.extends = [ + configFileAndEnv.extends = [ ...returnConfig.extends, - ...(configFileParsed.extends || []), + ...(configFileAndEnv.extends ?? []), ]; delete returnConfig.extends; } checkForRepoConfigError(repoConfig); - const migratedConfig = await migrateAndValidate(config, configFileParsed); + const migratedConfig = await migrateAndValidate(config, configFileAndEnv); if (migratedConfig.errors?.length) { const error = new Error(CONFIG_VALIDATION); error.validationSource = repoConfig.configFileName; @@ -312,3 +317,19 @@ export function setNpmTokenInNpmrc(config: RenovateConfig): void { delete config.npmToken; } + +export async function mergeStaticRepoEnvConfig( + config: AllConfig, + env: NodeJS.ProcessEnv, +): Promise<AllConfig> { + const repoEnvConfig = await parseAndValidateOrExit( + env, + 'RENOVATE_STATIC_REPO_CONFIG', + ); + + if (!is.nonEmptyObject(repoEnvConfig)) { + return config; + } + + return mergeChildConfig(config, repoEnvConfig); +}