diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index e6288c3b78ea05fc487f9aab5744fc2eff56b203..7dfabbecbbb28c412dc7e2008fa904c310efa058 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -435,5 +435,26 @@ describe('config/migration', () => { expect(res.isMigrated).toBe(false); expect(res.migratedConfig).toMatchObject({ semanticCommits: 'disabled' }); }); + it('it migrates preset strings to array', () => { + let config: RenovateConfig; + let res: MigratedConfig; + + config = { extends: ':js-app' } as never; + res = configMigration.migrateConfig(config); + expect(res.isMigrated).toBe(true); + expect(res.migratedConfig).toMatchObject({ extends: ['config:js-app'] }); + + config = { extends: 'foo' } as never; + res = configMigration.migrateConfig(config); + expect(res.isMigrated).toBe(true); + expect(res.migratedConfig).toMatchObject({ extends: ['foo'] }); + + config = { extends: ['foo', ':js-app', 'bar'] } as never; + res = configMigration.migrateConfig(config); + expect(res.isMigrated).toBe(true); + expect(res.migratedConfig).toMatchObject({ + extends: ['foo', 'config:js-app', 'bar'], + }); + }); }); }); diff --git a/lib/config/migration.ts b/lib/config/migration.ts index 82cd988842494ac54a23bc3e44656156304be3e5..d98e552f11045780640b88cd7121984e123e6376 100644 --- a/lib/config/migration.ts +++ b/lib/config/migration.ts @@ -237,20 +237,29 @@ export function migrateConfig( } else { migratedConfig.semanticCommitScope = null; } - } else if (key === 'extends' && is.array<string>(val)) { - for (let i = 0; i < val.length; i += 1) { - if (val[i] === 'config:application' || val[i] === ':js-app') { - isMigrated = true; - migratedConfig.extends[i] = 'config:js-app'; - } else if (val[i] === ':library' || val[i] === 'config:library') { - isMigrated = true; - migratedConfig.extends[i] = 'config:js-lib'; - } else if (is.string(val[i]) && val[i].startsWith(':masterIssue')) { - isMigrated = true; - migratedConfig.extends[i] = val[i].replace( - 'masterIssue', - 'dependencyDashboard' - ); + } else if ( + key === 'extends' && + (is.array<string>(val) || is.string(val)) + ) { + if (is.string(migratedConfig.extends)) { + migratedConfig.extends = [migratedConfig.extends]; + isMigrated = true; + } + const presets = migratedConfig.extends; + for (let i = 0; i < presets.length; i += 1) { + let preset = presets[i]; + if (is.string(preset)) { + if (preset === 'config:application' || preset === ':js-app') { + isMigrated = true; + preset = 'config:js-app'; + } else if (preset === ':library' || preset === 'config:library') { + isMigrated = true; + preset = 'config:js-lib'; + } else if (preset.startsWith(':masterIssue')) { + isMigrated = true; + preset = preset.replace('masterIssue', 'dependencyDashboard'); + } + presets[i] = preset; } } } else if (key === 'versionScheme') { diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index d1f88c081dee95da9f17d65b4f0f0b045ada604c..258b392e724c1491523acd71910627d6f527e592 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -434,5 +434,17 @@ describe('config/validation', () => { expect(errors).toMatchSnapshot(); expect(warnings).toMatchSnapshot(); }); + + it('validates preset values', async () => { + const config = { + extends: ['foo', 'bar', 42] as never, + }; + const { warnings, errors } = await configValidation.validateConfig( + config, + true + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(1); + }); }); }); diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 58eb98421ea869634835d1c4eb12c08c3014acc1..a61a2805c4c908de57fde848cbc4ca1f8d165f53 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -214,17 +214,24 @@ export async function validateConfig( if (key === 'extends') { const tzRe = /^:timezone\((.+)\)$/; for (const subval of val) { - if (is.string(subval) && tzRe.test(subval)) { - const [, timezone] = tzRe.exec(subval); - const [validTimezone, errorMessage] = hasValidTimezone( - timezone - ); - if (!validTimezone) { - errors.push({ - depName: 'Configuration Error', - message: `${currentPath}: ${errorMessage}`, - }); + if (is.string(subval)) { + if (tzRe.test(subval)) { + const [, timezone] = tzRe.exec(subval); + const [validTimezone, errorMessage] = hasValidTimezone( + timezone + ); + if (!validTimezone) { + errors.push({ + depName: 'Configuration Error', + message: `${currentPath}: ${errorMessage}`, + }); + } } + } else { + errors.push({ + depName: 'Configuration Warning', + message: `${currentPath}: preset value is not a string`, + }); } } }