diff --git a/lib/config/defaults.ts b/lib/config/defaults.ts index 1fd65e07b619c3..620d94c6d79efc 100644 --- a/lib/config/defaults.ts +++ b/lib/config/defaults.ts @@ -1,4 +1,4 @@ -import { getOptions } from './options'; +import { getOptions, isTopLevelOnlyOption } from './options'; import type { AllConfig, RenovateOptions } from './types'; // Use functions instead of direct values to avoid introducing global references. @@ -23,7 +23,7 @@ export function getConfig(): AllConfig { const options = getOptions(); const config: AllConfig = {}; options.forEach((option) => { - if (!option.parents) { + if (!option.parents || isTopLevelOnlyOption(option)) { config[option.name] = getDefault(option); } }); diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 25131c290e21c8..49ddab51ffc160 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -210,6 +210,7 @@ const options: RenovateOptions[] = [ experimentalDescription: 'Config migration PRs are still being improved, in particular to reduce the amount of reordering and whitespace changes.', experimentalIssues: [16359], + parents: ['.'], }, { name: 'productLinks', @@ -269,6 +270,7 @@ const options: RenovateOptions[] = [ subType: 'string', allowString: true, cli: false, + parents: ['.'], }, { name: 'migratePresets', @@ -401,6 +403,7 @@ const options: RenovateOptions[] = [ 'If enabled, Renovate logs the fully resolved config for each repository, plus the fully resolved presets.', type: 'boolean', default: false, + parents: ['.'], }, { name: 'binarySource', @@ -471,6 +474,7 @@ const options: RenovateOptions[] = [ experimentalIssues: [23286], default: {}, mergeable: true, + parents: ['.'], }, { name: 'dockerChildPrefix', @@ -580,6 +584,7 @@ const options: RenovateOptions[] = [ type: 'string', allowedValues: ['auto', 'enabled', 'disabled'], default: 'auto', + parents: ['.'], }, { name: 'includeMirrors', @@ -686,6 +691,7 @@ const options: RenovateOptions[] = [ 'Whether to create a "Dependency Dashboard" issue in the repository.', type: 'boolean', default: false, + parents: ['.'], }, { name: 'dependencyDashboardApproval', @@ -700,12 +706,14 @@ const options: RenovateOptions[] = [ 'Set to `true` to let Renovate close the Dependency Dashboard issue if there are no more updates.', type: 'boolean', default: false, + parents: ['.'], }, { name: 'dependencyDashboardTitle', description: 'Title for the Dependency Dashboard issue.', type: 'string', default: `Dependency Dashboard`, + parents: ['.'], }, { name: 'dependencyDashboardHeader', @@ -714,12 +722,14 @@ const options: RenovateOptions[] = [ type: 'string', default: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more.', + parents: ['.'], }, { name: 'dependencyDashboardFooter', description: 'Any text added here will be placed last in the Dependency Dashboard issue body, with a divider separator before it.', type: 'string', + parents: ['.'], }, { name: 'dependencyDashboardLabels', @@ -728,6 +738,7 @@ const options: RenovateOptions[] = [ type: 'array', subType: 'string', default: null, + parents: ['.'], }, { name: 'dependencyDashboardOSVVulnerabilitySummary', @@ -737,6 +748,7 @@ const options: RenovateOptions[] = [ allowedValues: ['none', 'all', 'unresolved'], default: 'none', experimental: true, + parents: ['.'], }, { name: 'configWarningReuseIssue', @@ -744,6 +756,7 @@ const options: RenovateOptions[] = [ 'Set this to `false` to make Renovate create a new issue for each config warning, instead of reopening or reusing an existing issue.', type: 'boolean', default: true, + parents: ['.'], }, // encryption @@ -1024,6 +1037,7 @@ const options: RenovateOptions[] = [ subType: 'string', stage: 'package', cli: false, + parents: ['.'], }, { name: 'useBaseBranchConfig', @@ -1032,6 +1046,7 @@ const options: RenovateOptions[] = [ type: 'string', allowedValues: ['merge', 'none'], default: 'none', + parents: ['.'], }, { name: 'gitAuthor', @@ -1071,6 +1086,7 @@ const options: RenovateOptions[] = [ subType: 'string', mergeable: false, stage: 'repository', + parents: ['.'], }, { name: 'includePaths', @@ -1999,6 +2015,7 @@ const options: RenovateOptions[] = [ 'Rate limit PRs to maximum x created per hour. 0 means no limit.', type: 'integer', default: 2, + parents: ['.'], }, { name: 'prConcurrentLimit', @@ -2006,6 +2023,7 @@ const options: RenovateOptions[] = [ 'Limit to a maximum of x concurrent branches/PRs. 0 means no limit.', type: 'integer', default: 10, + parents: ['.'], }, { name: 'branchConcurrentLimit', @@ -2013,6 +2031,7 @@ const options: RenovateOptions[] = [ 'Limit to a maximum of x concurrent branches. 0 means no limit, `null` (default) inherits value from `prConcurrentLimit`.', type: 'integer', default: null, // inherit prConcurrentLimit + parents: ['.'], }, { name: 'prPriority', @@ -2111,6 +2130,7 @@ const options: RenovateOptions[] = [ cli: false, env: false, supportedPlatforms: ['github'], + parents: ['.'], }, { name: 'osvVulnerabilityAlerts', @@ -2119,6 +2139,7 @@ const options: RenovateOptions[] = [ default: false, experimental: true, experimentalIssues: [20542], + parents: ['.'], }, { name: 'pruneBranchAfterAutomerge', @@ -2266,6 +2287,7 @@ const options: RenovateOptions[] = [ additionalProperties: { type: 'string', }, + parents: ['.'], }, { name: 'lockFileMaintenance', @@ -2495,6 +2517,7 @@ const options: RenovateOptions[] = [ stage: 'repository', cli: true, mergeable: true, + parents: ['.'], }, { name: 'hostType', @@ -2796,6 +2819,7 @@ const options: RenovateOptions[] = [ stage: 'package', cli: true, mergeable: true, + parents: ['.'], }, { name: 'customType', @@ -3014,6 +3038,7 @@ const options: RenovateOptions[] = [ experimental: true, globalOnly: true, default: [], + parents: ['.'], }, { name: 'maxRetryAfter', @@ -3034,6 +3059,7 @@ const options: RenovateOptions[] = [ stage: 'repository', cli: false, env: false, + parents: ['.'], }, { name: 'matchMessage', @@ -3073,6 +3099,10 @@ export function getOptions(): RenovateOptions[] { return options; } +export function isTopLevelOnlyOption(option: RenovateOptions): boolean { + return option.parents?.length === 1 && option.parents[0] === '.'; +} + function loadManagerOptions(): void { const allManagers = new Map([...getManagers(), ...getCustomManagers()]); for (const [name, config] of allManagers.entries()) { diff --git a/lib/config/types.ts b/lib/config/types.ts index 2aa2cd155230eb..32f3ae9e0e429b 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -394,6 +394,7 @@ export interface ValidationMessage { } export type AllowedParents = + | '.' // Root | 'customManagers' | 'customDatasources' | 'hostRules' diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 80f007f20a224a..b8b7864b51cf06 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -276,9 +276,13 @@ export async function validateConfig( !optionParents[key].includes(parentName as AllowedParents) ) { // TODO: types (#22198) - const message = `${key} should only be configured within one of "${optionParents[ + let message = `${key} should only be configured within one of "${optionParents[ key ]?.join(' or ')}" objects. Was found in ${parentName}`; + message = message.replace( + 'within one of "." objects', + 'at the top level', + ); warnings.push({ topic: `${parentPath ? `${parentPath}.` : ''}${key}`, message, diff --git a/test/documentation.spec.ts b/test/documentation.spec.ts index 6d1d54ce339926..2e053b30259c87 100644 --- a/test/documentation.spec.ts +++ b/test/documentation.spec.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra'; import { glob } from 'glob'; -import { getOptions } from '../lib/config/options'; +import { getOptions, isTopLevelOnlyOption } from '../lib/config/options'; import { regEx } from '../lib/util/regex'; const options = getOptions(); @@ -29,7 +29,7 @@ describe('documentation', () => { function getRequiredConfigOptions(): string[] { return options .filter((option) => !option.globalOnly) - .filter((option) => !option.parents) + .filter((option) => !option.parents || isTopLevelOnlyOption(option)) .filter((option) => !option.autogenerated) .map((option) => option.name) .sort(); @@ -59,7 +59,7 @@ describe('documentation', () => { return options .filter((option) => option.stage !== 'global') .filter((option) => !option.globalOnly) - .filter((option) => option.parents) + .filter((option) => option.parents && !isTopLevelOnlyOption(option)) .map((option) => option.name) .sort(); } diff --git a/tools/docs/schema.ts b/tools/docs/schema.ts index 293e713af91ba0..db1fc9b6fb8bbc 100644 --- a/tools/docs/schema.ts +++ b/tools/docs/schema.ts @@ -75,7 +75,7 @@ function createSingleConfig(option: RenovateOptions): Record { function createSchemaForParentConfigs(): void { for (const option of options) { - if (!option.parents) { + if (!option.parents || option.parents.includes('.')) { properties[option.name] = createSingleConfig(option); } } @@ -85,6 +85,9 @@ function addChildrenArrayInParents(): void { for (const option of options) { if (option.parents) { for (const parent of option.parents) { + if (parent === '.') { + continue; + } properties[parent].items = { allOf: [ { @@ -120,6 +123,9 @@ function createSchemaForChildConfigs(): void { for (const option of options) { if (option.parents) { for (const parent of option.parents) { + if (parent === '.') { + continue; + } properties[parent].items.allOf[0].properties[option.name] = createSingleConfig(option); }