diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index bdb2999b39afff..e842e566a4af88 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -247,6 +247,24 @@ If left as default (null), a random short ID will be selected. ## logFileLevel +## migratePresets + +Use this if you have repositories that extend from a particular preset, which has now been renamed or removed. +This is handy if you have a large number of repositories that all extend from a particular preset which you want to rename, without the hassle of manually updating every repository individually. +Use an empty string to indicate that the preset should be ignored rather than replaced. + +Example: + +```js +modules.exports = { + migratePresets: { + '@company': 'local>org/renovate-config', + }, +}; +``` + +In the above example any reference to the `@company` preset will be replaced with `local>org/renovate-config`. + ## onboarding Set this to `false` only if all three statements are true: diff --git a/lib/config/__snapshots__/migration.spec.ts.snap b/lib/config/__snapshots__/migration.spec.ts.snap index 1b0c191d80cf35..6245413a4a0b72 100644 --- a/lib/config/__snapshots__/migration.spec.ts.snap +++ b/lib/config/__snapshots__/migration.spec.ts.snap @@ -51,6 +51,14 @@ Object { } `; +exports[`config/migration it migrates presets 1`] = ` +Object { + "extends": Array [ + "local>org/renovate-config", + ], +} +`; + exports[`config/migration migrateConfig(config, parentConfig) does not migrate multi days 1`] = ` Object { "schedule": "after 5:00pm on wednesday and thursday", diff --git a/lib/config/admin.ts b/lib/config/admin.ts index 85960857864a2f..73f289d2094761 100644 --- a/lib/config/admin.ts +++ b/lib/config/admin.ts @@ -14,6 +14,7 @@ const repoAdminOptions = [ 'dockerUser', 'dryRun', 'exposeAllEnv', + 'migratePresets', 'privateKey', 'localDir', 'cacheDir', diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts index 70d2a78d2c8fa7..8cff7fab64a1b9 100644 --- a/lib/config/definitions.ts +++ b/lib/config/definitions.ts @@ -148,6 +148,17 @@ const options: RenovateOptions[] = [ allowString: true, cli: false, }, + { + name: 'migratePresets', + description: + 'Define presets here which have been removed or renamed and should be migrated automatically.', + type: 'object', + admin: true, + default: {}, + additionalProperties: { + type: 'string', + }, + }, { name: 'description', description: 'Plain text description for a config or preset.', diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index 8c386d4486a6eb..3bbf422c0a34a5 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -1,5 +1,6 @@ import { getName } from '../../test/util'; import { PLATFORM_TYPE_GITHUB } from '../constants/platforms'; +import { setAdminConfig } from './admin'; import { getConfig } from './defaults'; import * as configMigration from './migration'; import type { @@ -682,4 +683,21 @@ describe(getName(), () => { expect(isMigrated).toBe(true); expect(migratedConfig).toMatchSnapshot(); }); + it('it migrates presets', () => { + setAdminConfig({ + migratePresets: { + '@org': 'local>org/renovate-config', + '@org2/foo': '', + }, + }); + const config: RenovateConfig = { + extends: ['@org', '@org2/foo'], + } as any; + const { isMigrated, migratedConfig } = configMigration.migrateConfig( + config, + defaultConfig + ); + expect(isMigrated).toBe(true); + expect(migratedConfig).toMatchSnapshot(); + }); }); diff --git a/lib/config/migration.ts b/lib/config/migration.ts index 3e866f252fc630..4affb3a91bbd71 100644 --- a/lib/config/migration.ts +++ b/lib/config/migration.ts @@ -3,6 +3,7 @@ import is from '@sindresorhus/is'; import { dequal } from 'dequal'; import { logger } from '../logger'; import { clone } from '../util/clone'; +import { getAdminConfig } from './admin'; import { getOptions } from './definitions'; import { removedPresets } from './presets/common'; import type { @@ -54,6 +55,7 @@ export function migrateConfig( 'optionalDependencies', 'peerDependencies', ]; + const { migratePresets } = getAdminConfig(); for (const [key, val] of Object.entries(config)) { if (removedOptions.includes(key)) { delete migratedConfig[key]; @@ -260,7 +262,11 @@ export function migrateConfig( for (let i = 0; i < presets.length; i += 1) { const preset = presets[i]; if (is.string(preset)) { - const newPreset = removedPresets[preset]; + let newPreset = removedPresets[preset]; + if (newPreset !== undefined) { + presets[i] = newPreset; + } + newPreset = migratePresets?.[preset]; if (newPreset !== undefined) { presets[i] = newPreset; } diff --git a/lib/config/types.ts b/lib/config/types.ts index 5b00e514397d4a..bd56a58505b444 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -96,6 +96,7 @@ export interface RepoAdminConfig { dockerUser?: string; dryRun?: boolean; exposeAllEnv?: boolean; + migratePresets?: Record; privateKey?: string | Buffer; localDir?: string; cacheDir?: string; diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 4893046903ad34..9e876c1b9ab0d4 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -514,7 +514,7 @@ export async function validateConfig( message: `Invalid \`${currentPath}.${key}.${res}\` configuration: value is not a url`, }); } - } else if (key === 'customEnvVariables') { + } else if (['customEnvVariables', 'migratePresets'].includes(key)) { const res = validatePlainObject(val); if (res !== true) { errors.push({