2019-07-17 08:14:56 +00:00
import is from '@sindresorhus/is' ;
2023-12-12 08:48:22 +00:00
import { logger } from '../logger' ;
2023-08-15 17:12:54 +00:00
import { allManagersList , getManagerList } from '../modules/manager' ;
2023-09-12 14:16:01 +00:00
import { isCustomManager } from '../modules/manager/custom' ;
2023-09-13 05:31:48 +00:00
import type {
RegexManagerConfig ,
RegexManagerTemplates ,
} from '../modules/manager/custom/regex/types' ;
import type { CustomManager } from '../modules/manager/custom/types' ;
2024-01-17 09:22:19 +00:00
import type { HostRule } from '../types/host-rules' ;
2024-02-06 21:51:41 +00:00
import { regEx } from '../util/regex' ;
import {
2024-02-18 15:46:24 +00:00
getRegexPredicate ,
isRegexMatch ,
matchRegexOrGlobList ,
2024-02-06 21:51:41 +00:00
} from '../util/string-match' ;
2020-04-05 08:09:55 +00:00
import * as template from '../util/template' ;
2022-03-03 09:53:23 +00:00
import {
hasValidSchedule ,
hasValidTimezone ,
} from '../workers/repository/update/branch/schedule' ;
2024-02-27 07:24:49 +00:00
import { configFileNames } from './app-strings' ;
2024-01-17 09:22:19 +00:00
import { GlobalConfig } from './global' ;
2021-05-04 06:02:39 +00:00
import { migrateConfig } from './migration' ;
2021-08-15 05:25:30 +00:00
import { getOptions } from './options' ;
2022-08-06 05:27:07 +00:00
import { resolveConfigPresets } from './presets' ;
2023-12-14 16:20:50 +00:00
import {
2024-01-22 08:23:59 +00:00
AllowedParents ,
2023-12-14 16:20:50 +00:00
type RenovateConfig ,
type RenovateOptions ,
type StatusCheckKey ,
type ValidationMessage ,
type ValidationResult ,
allowedStatusCheckStrings ,
2021-03-02 20:44:55 +00:00
} from './types' ;
2020-05-01 16:03:48 +00:00
import * as managerValidator from './validation-helpers/managers' ;
2024-05-05 07:45:58 +00:00
import * as regexOrGlobValidator from './validation-helpers/regex-glob-matchers' ;
2019-07-17 08:14:56 +00:00
2019-08-23 13:46:31 +00:00
const options = getOptions ( ) ;
2017-07-28 19:15:27 +00:00
2024-05-05 07:45:58 +00:00
let optionsInitialized = false ;
2019-08-23 13:46:31 +00:00
let optionTypes : Record < string , RenovateOptions [ ' type ' ] > ;
2024-01-22 08:23:59 +00:00
let optionParents : Record < string , AllowedParents [ ] > ;
2024-01-16 10:42:26 +00:00
let optionGlobals : Set < string > ;
2024-03-23 08:12:36 +00:00
let optionInherits : Set < string > ;
2024-05-05 07:45:58 +00:00
let optionRegexOrGlob : Set < string > ;
2017-07-28 19:15:27 +00:00
2020-10-18 05:56:16 +00:00
const managerList = getManagerList ( ) ;
2024-02-22 05:05:05 +00:00
const topLevelObjects = [ . . . managerList , 'env' ] ;
2021-05-20 10:25:22 +00:00
const ignoredNodes = [
'$schema' ,
2024-01-17 09:22:19 +00:00
'headers' ,
2021-05-20 10:25:22 +00:00
'depType' ,
'npmToken' ,
'packageFile' ,
'forkToken' ,
'repository' ,
'vulnerabilityAlertsOnly' ,
'vulnerabilityAlert' ,
'isVulnerabilityAlert' ,
'copyLocalLibs' , // deprecated - functionality is now enabled by default
'prBody' , // deprecated
2021-06-23 20:19:14 +00:00
'minimumConfidence' , // undocumented feature flag
2021-05-20 10:25:22 +00:00
] ;
2021-12-29 06:26:13 +00:00
const tzRe = regEx ( /^:timezone\((.+)\)$/ ) ;
const rulesRe = regEx ( /p.*Rules\[\d+\]$/ ) ;
2022-12-26 18:30:44 +00:00
2020-10-18 05:56:16 +00:00
function isManagerPath ( parentPath : string ) : boolean {
return (
2023-09-24 08:55:56 +00:00
regEx ( /^customManagers\[[0-9]+]$/ ) . test ( parentPath ) ||
2020-10-18 05:56:16 +00:00
managerList . includes ( parentPath )
) ;
}
2021-05-20 10:25:22 +00:00
function isIgnored ( key : string ) : boolean {
return ignoredNodes . includes ( key ) ;
}
function validatePlainObject ( val : Record < string , unknown > ) : true | string {
for ( const [ key , value ] of Object . entries ( val ) ) {
if ( ! is . string ( value ) ) {
return key ;
}
}
return true ;
}
function getUnsupportedEnabledManagers ( enabledManagers : string [ ] ) : string [ ] {
return enabledManagers . filter (
2023-11-08 13:13:26 +00:00
( manager ) = > ! allManagersList . includes ( manager . replace ( 'custom.' , '' ) ) ,
2021-05-20 10:25:22 +00:00
) ;
}
2022-04-24 22:48:54 +00:00
function getDeprecationMessage ( option : string ) : string | undefined {
const deprecatedOptions : Record < string , string | undefined > = {
2021-05-20 10:25:22 +00:00
branchName : ` Direct editing of branchName is now deprecated. Please edit branchPrefix, additionalBranchPrefix, or branchTopic instead ` ,
commitMessage : ` Direct editing of commitMessage is now deprecated. Please edit commitMessage's subcomponents instead. ` ,
prTitle : ` Direct editing of prTitle is now deprecated. Please edit commitMessage subcomponents instead as they will be passed through to prTitle. ` ,
} ;
return deprecatedOptions [ option ] ;
}
2024-03-23 08:12:36 +00:00
function isInhertConfigOption ( key : string ) : boolean {
return optionInherits . has ( key ) ;
}
2024-05-05 07:45:58 +00:00
function isRegexOrGlobOption ( key : string ) : boolean {
return optionRegexOrGlob . has ( key ) ;
}
2024-02-27 07:24:49 +00:00
function isGlobalOption ( key : string ) : boolean {
2024-05-05 07:45:58 +00:00
return optionGlobals . has ( key ) ;
}
function initOptions ( ) : void {
if ( optionsInitialized ) {
return ;
}
optionParents = { } ;
optionInherits = new Set ( ) ;
optionTypes = { } ;
optionRegexOrGlob = new Set ( ) ;
optionGlobals = new Set ( ) ;
for ( const option of options ) {
optionTypes [ option . name ] = option . type ;
if ( option . parents ) {
optionParents [ option . name ] = option . parents ;
}
if ( option . inheritConfigSupport ) {
optionInherits . add ( option . name ) ;
}
if ( option . patternMatch ) {
optionRegexOrGlob . add ( option . name ) ;
}
if ( option . globalOnly ) {
optionGlobals . add ( option . name ) ;
2024-02-27 07:24:49 +00:00
}
}
2024-05-05 07:45:58 +00:00
optionsInitialized = true ;
2024-02-27 07:24:49 +00:00
}
2022-04-24 22:48:54 +00:00
export function getParentName ( parentPath : string | undefined ) : string {
2021-04-05 10:41:31 +00:00
return parentPath
? parentPath
2021-10-19 12:53:34 +00:00
. replace ( regEx ( /\.?encrypted$/ ) , '' )
. replace ( regEx ( /\[\d+\]$/ ) , '' )
2021-04-05 10:41:31 +00:00
. split ( '.' )
2022-04-24 22:48:54 +00:00
. pop ( ) !
2021-04-05 10:41:31 +00:00
: '.' ;
}
2019-08-23 13:46:31 +00:00
export async function validateConfig (
2024-03-23 08:12:36 +00:00
configType : 'global' | 'inherit' | 'repo' ,
2019-08-23 13:46:31 +00:00
config : RenovateConfig ,
isPreset? : boolean ,
2023-11-07 15:50:29 +00:00
parentPath? : string ,
2019-08-23 13:46:31 +00:00
) : Promise < ValidationResult > {
2024-05-05 07:45:58 +00:00
initOptions ( ) ;
2019-08-23 13:46:31 +00:00
let errors : ValidationMessage [ ] = [ ] ;
let warnings : ValidationMessage [ ] = [ ] ;
2017-07-28 19:15:27 +00:00
2017-11-10 12:46:16 +00:00
for ( const [ key , val ] of Object . entries ( config ) ) {
2018-04-11 19:38:31 +00:00
const currentPath = parentPath ? ` ${ parentPath } . ${ key } ` : key ;
2020-10-16 08:23:50 +00:00
// istanbul ignore if
if ( key === '__proto__' ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Config security error' ,
2020-10-16 08:23:50 +00:00
message : '__proto__' ,
} ) ;
2021-11-08 19:20:03 +00:00
continue ;
2020-10-16 08:23:50 +00:00
}
2024-01-11 16:18:24 +00:00
if (
parentPath &&
parentPath !== 'onboardingConfig' &&
topLevelObjects . includes ( key )
) {
2021-04-12 04:11:25 +00:00
errors . push ( {
topic : 'Configuration Error' ,
message : ` The " ${ key } " object can only be configured at the top level of a config but was found inside " ${ parentPath } " ` ,
} ) ;
}
2024-01-16 10:42:26 +00:00
2024-03-23 08:12:36 +00:00
if ( isGlobalOption ( key ) ) {
if ( configType === 'global' ) {
await validateGlobalConfig (
key ,
val ,
optionTypes [ key ] ,
warnings ,
errors ,
currentPath ,
config ,
) ;
continue ;
} else if (
! isFalseGlobal ( key , parentPath ) &&
! ( configType === 'inherit' && isInhertConfigOption ( key ) )
) {
2024-01-16 10:42:26 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
2024-03-23 08:12:36 +00:00
message : ` The " ${ key } " option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file. ` ,
2024-01-16 10:42:26 +00:00
} ) ;
continue ;
}
}
2021-04-15 09:18:51 +00:00
if ( key === 'enabledManagers' && val ) {
const unsupportedManagers = getUnsupportedEnabledManagers (
2023-11-07 15:50:29 +00:00
val as string [ ] ,
2021-04-15 09:18:51 +00:00
) ;
if ( is . nonEmptyArray ( unsupportedManagers ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` The following managers configured in enabledManagers are not supported: " ${ unsupportedManagers . join (
2023-11-07 15:50:29 +00:00
', ' ,
2021-04-15 09:18:51 +00:00
) } " ` ,
} ) ;
}
}
2020-10-18 05:56:16 +00:00
if ( key === 'fileMatch' ) {
if ( parentPath === undefined ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Config error' ,
2020-10-18 05:56:16 +00:00
message : ` "fileMatch" may not be defined at the top level of a config and must instead be within a manager block ` ,
} ) ;
} else if ( ! isManagerPath ( parentPath ) ) {
warnings . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Config warning' ,
2020-10-18 05:56:16 +00:00
message : ` "fileMatch" must be configured in a manager block and not here: ${ parentPath } ` ,
} ) ;
}
}
2017-08-02 06:54:42 +00:00
if (
2017-07-28 19:15:27 +00:00
! isIgnored ( key ) && // We need to ignore some reserved keys
2019-08-28 04:46:48 +00:00
! ( is as any ) . function ( val ) // Ignore all functions
2017-07-28 19:15:27 +00:00
) {
2018-04-17 06:39:26 +00:00
if ( getDeprecationMessage ( key ) ) {
warnings . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Deprecation Warning' ,
2022-04-24 22:48:54 +00:00
message : getDeprecationMessage ( key ) ! ,
2018-04-17 06:39:26 +00:00
} ) ;
}
2020-01-27 11:48:08 +00:00
const templateKeys = [
'branchName' ,
'commitBody' ,
'commitMessage' ,
'prTitle' ,
'semanticCommitScope' ,
] ;
2020-03-06 08:07:55 +00:00
if ( ( key . endsWith ( 'Template' ) || templateKeys . includes ( key ) ) && val ) {
2020-01-27 11:48:08 +00:00
try {
2023-08-15 09:31:15 +00:00
// TODO: validate string #22198
2022-04-24 22:48:54 +00:00
let res = template . compile ( ( val as string ) . toString ( ) , config , false ) ;
2021-02-03 15:33:28 +00:00
res = template . compile ( res , config , false ) ;
template . compile ( res , config , false ) ;
2020-01-27 11:48:08 +00:00
} catch ( err ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2020-04-05 08:09:55 +00:00
message : ` Invalid template in config path: ${ currentPath } ` ,
2020-01-27 11:48:08 +00:00
} ) ;
}
}
2021-04-05 10:41:31 +00:00
const parentName = getParentName ( parentPath ) ;
if (
! isPreset &&
optionParents [ key ] &&
2024-01-22 08:23:59 +00:00
! optionParents [ key ] . includes ( parentName as AllowedParents )
2021-04-05 10:41:31 +00:00
) {
2023-08-15 09:31:15 +00:00
// TODO: types (#22198)
2024-01-22 08:23:59 +00:00
const message = ` ${ key } should only be configured within one of " ${ optionParents [
key
] ? . join ( ' or ' ) } " objects . Was found in $ { parentName } ` ;
2021-04-05 10:41:31 +00:00
warnings . push ( {
topic : ` ${ parentPath ? ` ${ parentPath } . ` : '' } ${ key } ` ,
message ,
} ) ;
}
2017-07-28 19:15:27 +00:00
if ( ! optionTypes [ key ] ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2019-03-17 06:21:25 +00:00
message : ` Invalid configuration option: ${ currentPath } ` ,
2017-07-28 19:15:27 +00:00
} ) ;
2017-08-14 09:09:14 +00:00
} else if ( key === 'schedule' ) {
2020-03-02 11:06:16 +00:00
const [ validSchedule , errorMessage ] = hasValidSchedule ( val as string [ ] ) ;
2017-08-14 09:09:14 +00:00
if ( ! validSchedule ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-11 19:38:31 +00:00
message : ` Invalid ${ currentPath } : \` ${ errorMessage } \` ` ,
2017-08-14 09:09:14 +00:00
} ) ;
}
2020-11-12 07:21:05 +00:00
} else if (
[ 'allowedVersions' , 'matchCurrentVersion' ] . includes ( key ) &&
2024-02-18 15:46:24 +00:00
isRegexMatch ( val )
2020-11-12 07:21:05 +00:00
) {
2024-02-18 15:46:24 +00:00
if ( ! getRegexPredicate ( val ) ) {
2020-04-15 20:07:53 +00:00
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2020-04-15 20:07:53 +00:00
message : ` Invalid regExp for ${ currentPath } : \` ${ val } \` ` ,
} ) ;
}
2022-09-25 06:56:02 +00:00
} else if (
key === 'matchCurrentValue' &&
is . string ( val ) &&
2024-02-18 15:46:24 +00:00
! getRegexPredicate ( val )
2022-09-25 06:56:02 +00:00
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid regExp for ${ currentPath } : \` ${ val } \` ` ,
} ) ;
2024-02-27 19:43:33 +00:00
} else if (
key === 'matchNewValue' &&
is . string ( val ) &&
! getRegexPredicate ( val )
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid regExp for ${ currentPath } : \` ${ val } \` ` ,
} ) ;
2018-03-12 03:24:45 +00:00
} else if ( key === 'timezone' && val !== null ) {
2020-03-02 11:06:16 +00:00
const [ validTimezone , errorMessage ] = hasValidTimezone ( val as string ) ;
2018-03-12 03:24:45 +00:00
if ( ! validTimezone ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-11 19:38:31 +00:00
message : ` ${ currentPath } : ${ errorMessage } ` ,
2018-03-12 03:24:45 +00:00
} ) ;
}
2021-11-09 07:02:59 +00:00
} else if ( val !== null ) {
2017-08-14 05:49:33 +00:00
const type = optionTypes [ key ] ;
2017-07-28 19:15:27 +00:00
if ( type === 'boolean' ) {
if ( val !== true && val !== false ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-11 19:38:31 +00:00
message : ` Configuration option \` ${ currentPath } \` should be boolean. Found: ${ JSON . stringify (
2023-11-07 15:50:29 +00:00
val ,
2017-08-22 06:12:42 +00:00
) } ( $ { typeof val } ) ` ,
2017-07-28 19:15:27 +00:00
} ) ;
}
2022-04-20 20:44:19 +00:00
} else if ( type === 'integer' ) {
if ( ! is . number ( val ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Configuration option \` ${ currentPath } \` should be an integer. Found: ${ JSON . stringify (
2023-11-07 15:50:29 +00:00
val ,
2022-04-20 20:44:19 +00:00
) } ( $ { typeof val } ) ` ,
} ) ;
}
2019-03-31 06:01:06 +00:00
} else if ( type === 'array' && val ) {
2021-03-04 05:21:55 +00:00
if ( is . array ( val ) ) {
2018-04-11 19:38:31 +00:00
for ( const [ subIndex , subval ] of val . entries ( ) ) {
2018-06-04 18:07:22 +00:00
if ( is . object ( subval ) ) {
2021-12-22 13:20:58 +00:00
const subValidation = await validateConfig (
2024-03-13 05:11:40 +00:00
configType ,
2021-12-22 13:20:58 +00:00
subval as RenovateConfig ,
2018-04-12 10:13:39 +00:00
isPreset ,
2023-11-07 15:50:29 +00:00
` ${ currentPath } [ ${ subIndex } ] ` ,
2018-03-28 08:04:07 +00:00
) ;
2018-03-06 14:54:27 +00:00
warnings = warnings . concat ( subValidation . warnings ) ;
errors = errors . concat ( subValidation . errors ) ;
2017-08-02 05:52:28 +00:00
}
2018-03-28 07:37:19 +00:00
}
2024-05-05 07:45:58 +00:00
if ( isRegexOrGlobOption ( key ) ) {
errors . push (
. . . regexOrGlobValidator . check ( {
val ,
currentPath ,
} ) ,
) ;
}
2018-03-28 07:37:19 +00:00
if ( key === 'extends' ) {
for ( const subval of val ) {
2020-10-27 15:39:11 +00:00
if ( is . string ( subval ) ) {
2021-05-05 15:46:34 +00:00
if (
parentName === 'packageRules' &&
subval . startsWith ( 'group:' )
) {
2021-05-04 06:02:39 +00:00
warnings . push ( {
topic : 'Configuration Warning' ,
message : ` ${ currentPath } : you should not extend "group:" presets ` ,
} ) ;
}
2020-10-27 15:39:11 +00:00
if ( tzRe . test ( subval ) ) {
2022-04-24 22:48:54 +00:00
const [ , timezone ] = tzRe . exec ( subval ) ! ;
2021-05-17 08:06:24 +00:00
const [ validTimezone , errorMessage ] =
hasValidTimezone ( timezone ) ;
2020-10-27 15:39:11 +00:00
if ( ! validTimezone ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2020-10-27 15:39:11 +00:00
message : ` ${ currentPath } : ${ errorMessage } ` ,
} ) ;
}
2018-03-28 07:37:19 +00:00
}
2020-10-27 15:39:11 +00:00
} else {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Warning' ,
2020-10-27 15:39:11 +00:00
message : ` ${ currentPath } : preset value is not a string ` ,
} ) ;
2018-03-28 07:37:19 +00:00
}
}
}
2018-04-12 10:13:39 +00:00
const selectors = [
2023-05-25 15:46:28 +00:00
'matchFileNames' ,
2021-01-29 10:43:42 +00:00
'matchLanguages' ,
2023-07-04 09:41:19 +00:00
'matchCategories' ,
2021-01-29 10:43:42 +00:00
'matchBaseBranches' ,
'matchManagers' ,
'matchDatasources' ,
'matchDepTypes' ,
2022-12-26 18:30:44 +00:00
'matchDepNames' ,
'matchDepPatterns' ,
2024-04-20 15:05:27 +00:00
'matchDepPrefixes' ,
2021-01-29 10:43:42 +00:00
'matchPackageNames' ,
'matchPackagePatterns' ,
2021-04-03 05:18:25 +00:00
'matchPackagePrefixes' ,
2022-12-26 18:30:44 +00:00
'excludeDepNames' ,
'excludeDepPatterns' ,
2024-04-20 15:05:27 +00:00
'excludeDepPrefixes' ,
2018-04-12 10:13:39 +00:00
'excludePackageNames' ,
'excludePackagePatterns' ,
2021-04-03 05:18:25 +00:00
'excludePackagePrefixes' ,
2023-07-21 04:21:36 +00:00
'excludeRepositories' ,
2022-09-25 06:56:02 +00:00
'matchCurrentValue' ,
2020-07-22 06:32:01 +00:00
'matchCurrentVersion' ,
2021-01-29 10:43:42 +00:00
'matchSourceUrlPrefixes' ,
2022-03-28 07:58:20 +00:00
'matchSourceUrls' ,
2021-01-29 10:43:42 +00:00
'matchUpdateTypes' ,
2023-03-21 18:37:38 +00:00
'matchConfidence' ,
2024-01-25 05:12:39 +00:00
'matchCurrentAge' ,
2023-07-21 04:21:36 +00:00
'matchRepositories' ,
2024-02-27 19:43:33 +00:00
'matchNewValue' ,
2018-04-12 10:13:39 +00:00
] ;
2018-03-28 08:04:07 +00:00
if ( key === 'packageRules' ) {
2021-04-11 16:38:25 +00:00
for ( const [ subIndex , packageRule ] of val . entries ( ) ) {
2018-06-04 18:07:22 +00:00
if ( is . object ( packageRule ) ) {
2021-05-04 06:02:39 +00:00
const resolvedRule = migrateConfig ( {
packageRules : [
await resolveConfigPresets (
packageRule as RenovateConfig ,
2023-11-07 15:50:29 +00:00
config ,
2021-05-04 06:02:39 +00:00
) ,
] ,
2022-04-24 22:48:54 +00:00
} ) . migratedConfig . packageRules ! [ 0 ] ;
2019-02-20 21:29:38 +00:00
errors . push (
2023-11-07 15:50:29 +00:00
. . . managerValidator . check ( { resolvedRule , currentPath } ) ,
2019-02-20 21:29:38 +00:00
) ;
2021-05-17 08:06:24 +00:00
const selectorLength = Object . keys ( resolvedRule ) . filter (
2023-11-07 15:50:29 +00:00
( ruleKey ) = > selectors . includes ( ruleKey ) ,
2021-05-17 08:06:24 +00:00
) . length ;
2021-04-11 16:40:01 +00:00
if ( ! selectorLength ) {
2021-04-11 16:38:25 +00:00
const message = ` ${ currentPath } [ ${ subIndex } ]: Each packageRule must contain at least one match* or exclude* selector. Rule: ${ JSON . stringify (
2023-11-07 15:50:29 +00:00
packageRule ,
2021-04-11 16:38:25 +00:00
) } ` ;
2018-03-28 13:13:32 +00:00
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-03-28 08:04:07 +00:00
message ,
} ) ;
}
2021-04-11 17:26:20 +00:00
if ( selectorLength === Object . keys ( resolvedRule ) . length ) {
const message = ` ${ currentPath } [ ${ subIndex } ]: Each packageRule must contain at least one non-match* or non-exclude* field. Rule: ${ JSON . stringify (
2023-11-07 15:50:29 +00:00
packageRule ,
2021-04-11 17:26:20 +00:00
) } ` ;
warnings . push ( {
topic : 'Configuration Error' ,
message ,
} ) ;
}
2021-04-22 07:16:40 +00:00
// It's too late to apply any of these options once you already have updates determined
const preLookupOptions = [
2021-04-22 07:55:14 +00:00
'allowedVersions' ,
2021-04-22 07:16:40 +00:00
'extractVersion' ,
'followTag' ,
'ignoreDeps' ,
'ignoreUnstable' ,
'rangeStrategy' ,
'registryUrls' ,
'respectLatest' ,
'rollbackPrs' ,
'separateMajorMinor' ,
'separateMinorPatch' ,
'separateMultipleMajor' ,
2024-03-24 07:59:30 +00:00
'separateMultipleMinor' ,
2021-04-22 07:16:40 +00:00
'versioning' ,
] ;
if ( is . nonEmptyArray ( resolvedRule . matchUpdateTypes ) ) {
for ( const option of preLookupOptions ) {
if ( resolvedRule [ option ] !== undefined ) {
const message = ` ${ currentPath } [ ${ subIndex } ]: packageRules cannot combine both matchUpdateTypes and ${ option } . Rule: ${ JSON . stringify (
2023-11-07 15:50:29 +00:00
packageRule ,
2021-04-22 07:16:40 +00:00
) } ` ;
errors . push ( {
topic : 'Configuration Error' ,
message ,
} ) ;
}
}
}
2018-03-28 08:04:07 +00:00
} else {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-11 19:38:31 +00:00
message : ` ${ currentPath } must contain JSON objects ` ,
2018-03-28 08:04:07 +00:00
} ) ;
}
}
}
2023-09-24 08:55:56 +00:00
if ( key === 'customManagers' ) {
2020-03-06 08:07:55 +00:00
const allowedKeys = [
2023-08-22 14:01:03 +00:00
'customType' ,
2021-02-01 06:03:38 +00:00
'description' ,
2020-03-06 08:07:55 +00:00
'fileMatch' ,
'matchStrings' ,
2020-11-27 05:55:57 +00:00
'matchStringsStrategy' ,
2020-03-06 08:07:55 +00:00
'depNameTemplate' ,
2022-03-03 15:08:43 +00:00
'packageNameTemplate' ,
2020-03-06 08:07:55 +00:00
'datasourceTemplate' ,
'versioningTemplate' ,
2021-03-22 06:18:34 +00:00
'registryUrlTemplate' ,
2021-06-11 07:49:09 +00:00
'currentValueTemplate' ,
2021-06-15 11:10:23 +00:00
'extractVersionTemplate' ,
2021-10-05 12:21:11 +00:00
'autoReplaceStringTemplate' ,
2021-10-25 05:25:35 +00:00
'depTypeTemplate' ,
2020-03-06 08:07:55 +00:00
] ;
2023-09-24 08:55:56 +00:00
for ( const customManager of val as CustomManager [ ] ) {
2020-03-06 08:07:55 +00:00
if (
2023-09-24 08:55:56 +00:00
Object . keys ( customManager ) . some (
2023-11-07 15:50:29 +00:00
( k ) = > ! allowedKeys . includes ( k ) ,
2020-04-12 16:09:36 +00:00
)
2020-03-06 08:07:55 +00:00
) {
2023-09-24 08:55:56 +00:00
const disallowedKeys = Object . keys ( customManager ) . filter (
2023-11-07 15:50:29 +00:00
( k ) = > ! allowedKeys . includes ( k ) ,
2020-03-06 08:07:55 +00:00
) ;
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2023-09-24 08:55:56 +00:00
message : ` Custom Manager contains disallowed fields: ${ disallowedKeys . join (
2023-11-07 15:50:29 +00:00
', ' ,
2020-03-06 08:07:55 +00:00
) } ` ,
} ) ;
2023-09-12 14:16:01 +00:00
} else if (
2023-09-24 08:55:56 +00:00
is . nonEmptyString ( customManager . customType ) &&
isCustomManager ( customManager . customType )
2023-09-12 14:16:01 +00:00
) {
2023-09-24 08:55:56 +00:00
if ( is . nonEmptyArray ( customManager . fileMatch ) ) {
switch ( customManager . customType ) {
2023-09-12 14:16:01 +00:00
case 'regex' :
validateRegexManagerFields (
2023-09-24 08:55:56 +00:00
customManager ,
2023-09-12 14:16:01 +00:00
currentPath ,
2023-11-07 15:50:29 +00:00
errors ,
2023-09-12 14:16:01 +00:00
) ;
break ;
2021-11-22 20:08:10 +00:00
}
2021-11-23 14:17:49 +00:00
} else {
errors . push ( {
topic : 'Configuration Error' ,
2023-09-24 08:55:56 +00:00
message : ` Each Custom Manager must contain a non-empty fileMatch array ` ,
2021-11-23 14:17:49 +00:00
} ) ;
2020-03-06 08:07:55 +00:00
}
2021-03-04 05:21:55 +00:00
} else {
2023-09-12 14:16:01 +00:00
if (
2023-09-24 08:55:56 +00:00
is . emptyString ( customManager . customType ) ||
is . undefined ( customManager . customType )
2023-09-12 14:16:01 +00:00
) {
errors . push ( {
topic : 'Configuration Error' ,
2023-09-24 08:55:56 +00:00
message : ` Each Custom Manager must contain a non-empty customType string ` ,
2023-09-12 14:16:01 +00:00
} ) ;
} else {
errors . push ( {
topic : 'Configuration Error' ,
2023-09-24 08:55:56 +00:00
message : ` Invalid customType: ${ customManager . customType } . Key is not a custom manager ` ,
2023-09-12 14:16:01 +00:00
} ) ;
}
2020-03-06 08:07:55 +00:00
}
}
}
2021-01-29 10:43:42 +00:00
if (
2022-12-26 18:30:44 +00:00
[
'matchPackagePatterns' ,
'excludePackagePatterns' ,
'matchDepPatterns' ,
'excludeDepPatterns' ,
] . includes ( key )
2021-01-29 10:43:42 +00:00
) {
2020-07-30 04:54:20 +00:00
for ( const pattern of val as string [ ] ) {
2019-10-22 06:48:40 +00:00
if ( pattern !== '*' ) {
try {
regEx ( pattern ) ;
} catch ( e ) {
2019-10-15 08:14:49 +00:00
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2019-10-22 06:48:40 +00:00
message : ` Invalid regExp for ${ currentPath } : \` ${ pattern } \` ` ,
2019-10-15 08:14:49 +00:00
} ) ;
}
2018-04-30 11:18:51 +00:00
}
2019-10-22 06:48:40 +00:00
}
}
if ( key === 'fileMatch' ) {
2020-07-30 04:54:20 +00:00
for ( const fileMatch of val as string [ ] ) {
2019-10-22 06:48:40 +00:00
try {
regEx ( fileMatch ) ;
} catch ( e ) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2019-10-22 06:48:40 +00:00
message : ` Invalid regExp for ${ currentPath } : \` ${ fileMatch } \` ` ,
} ) ;
}
2018-04-30 11:18:51 +00:00
}
}
2023-02-20 23:32:08 +00:00
if ( key === 'baseBranches' ) {
for ( const baseBranch of val as string [ ] ) {
if (
2024-02-18 15:46:24 +00:00
isRegexMatch ( baseBranch ) &&
! getRegexPredicate ( baseBranch )
2023-02-20 23:32:08 +00:00
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid regExp for ${ currentPath } : \` ${ baseBranch } \` ` ,
} ) ;
}
}
}
2018-04-12 10:13:39 +00:00
if (
2022-09-25 06:56:02 +00:00
( selectors . includes ( key ) ||
key === 'matchCurrentVersion' ||
key === 'matchCurrentValue' ) &&
2023-08-15 09:31:15 +00:00
// TODO: can be undefined ? #22198
2022-04-24 22:48:54 +00:00
! rulesRe . test ( parentPath ! ) && // Inside a packageRule
2023-08-02 15:07:49 +00:00
( is . string ( parentPath ) || ! isPreset ) // top level in a preset
2018-04-12 10:13:39 +00:00
) {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-12 10:13:39 +00:00
message : ` ${ currentPath } : ${ key } should be inside a \` packageRule \` only ` ,
} ) ;
}
2021-03-04 05:21:55 +00:00
} else {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2021-03-04 05:21:55 +00:00
message : ` Configuration option \` ${ currentPath } \` should be a list (Array) ` ,
} ) ;
2017-07-28 19:15:27 +00:00
}
} else if ( type === 'string' ) {
2018-06-04 18:07:22 +00:00
if ( ! is . string ( val ) ) {
2017-07-28 19:15:27 +00:00
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-11 19:38:31 +00:00
message : ` Configuration option \` ${ currentPath } \` should be a string ` ,
2017-07-28 19:15:27 +00:00
} ) ;
}
2020-09-30 09:02:25 +00:00
} else if (
type === 'object' &&
currentPath !== 'compatibility' &&
2020-10-27 07:13:23 +00:00
currentPath !== 'constraints' &&
currentPath !== 'force.constraints'
2020-09-30 09:02:25 +00:00
) {
2020-08-26 12:59:50 +00:00
if ( is . plainObject ( val ) ) {
2022-06-10 05:14:49 +00:00
if ( key === 'registryAliases' ) {
2023-06-16 08:38:47 +00:00
const res = validatePlainObject ( val ) ;
2021-05-20 10:25:22 +00:00
if ( res !== true ) {
errors . push ( {
topic : 'Configuration Error' ,
2023-06-16 08:38:47 +00:00
message : ` Invalid \` ${ currentPath } . ${ key } . ${ res } \` configuration: value is not a string ` ,
2021-05-20 10:25:22 +00:00
} ) ;
}
2024-02-22 05:05:05 +00:00
} else if ( key === 'env' ) {
2024-03-13 05:11:40 +00:00
const allowedEnvVars =
configType === 'global'
? ( config . allowedEnv as string [ ] ) ? ? [ ]
: GlobalConfig . get ( 'allowedEnv' , [ ] ) ;
2024-02-22 05:05:05 +00:00
for ( const [ envVarName , envVarValue ] of Object . entries ( val ) ) {
if ( ! is . string ( envVarValue ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid env variable value: \` ${ currentPath } . ${ envVarName } \` must be a string. ` ,
} ) ;
}
if ( ! matchRegexOrGlobList ( envVarName , allowedEnvVars ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Env variable name \` ${ envVarName } \` is not allowed by this bot's \` allowedEnv \` . ` ,
} ) ;
}
}
2023-12-14 16:20:50 +00:00
} else if ( key === 'statusCheckNames' ) {
for ( const [ statusCheckKey , statusCheckValue ] of Object . entries (
val ,
) ) {
if (
! allowedStatusCheckStrings . includes (
statusCheckKey as StatusCheckKey ,
)
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid \` ${ currentPath } . ${ key } . ${ statusCheckKey } \` configuration: key is not allowed. ` ,
} ) ;
}
if (
! ( is . string ( statusCheckValue ) || is . null_ ( statusCheckValue ) )
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid \` ${ currentPath } . ${ statusCheckKey } \` configuration: status check is not a string. ` ,
} ) ;
continue ;
}
}
2023-07-10 18:22:35 +00:00
} else if ( key === 'customDatasources' ) {
const allowedKeys = [
'description' ,
'defaultRegistryUrlTemplate' ,
'format' ,
'transformTemplates' ,
] ;
for ( const [
customDatasourceName ,
customDatasourceValue ,
] of Object . entries ( val ) ) {
if ( ! is . plainObject ( customDatasourceValue ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid \` ${ currentPath } . ${ customDatasourceName } \` configuration: customDatasource is not an object ` ,
} ) ;
continue ;
}
for ( const [ subKey , subValue ] of Object . entries (
2023-11-07 15:50:29 +00:00
customDatasourceValue ,
2023-07-10 18:22:35 +00:00
) ) {
if ( ! allowedKeys . includes ( subKey ) ) {
errors . push ( {
topic : 'Configuration Error' ,
2024-04-18 06:30:53 +00:00
message : ` Invalid \` ${ currentPath } . ${ subKey } \` configuration: key is not allowed ` ,
2023-07-10 18:22:35 +00:00
} ) ;
} else if ( subKey === 'transformTemplates' ) {
if ( ! is . array ( subValue , is . string ) ) {
errors . push ( {
topic : 'Configuration Error' ,
2024-04-18 06:30:53 +00:00
message : ` Invalid \` ${ currentPath } . ${ subKey } \` configuration: is not an array of string ` ,
} ) ;
}
} else if ( subKey === 'description' ) {
if (
! ( is . string ( subValue ) || is . array ( subValue , is . string ) )
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid \` ${ currentPath } . ${ subKey } \` configuration: is not an array of strings ` ,
2023-07-10 18:22:35 +00:00
} ) ;
}
} else if ( ! is . string ( subValue ) ) {
errors . push ( {
topic : 'Configuration Error' ,
2024-04-18 06:30:53 +00:00
message : ` Invalid \` ${ currentPath } . ${ subKey } \` configuration: is a string ` ,
2023-07-10 18:22:35 +00:00
} ) ;
}
}
}
2020-06-04 13:47:56 +00:00
} else {
const ignoredObjects = options
. filter ( ( option ) = > option . freeChoice )
. map ( ( option ) = > option . name ) ;
if ( ! ignoredObjects . includes ( key ) ) {
2021-12-22 13:20:58 +00:00
const subValidation = await validateConfig (
2024-03-13 05:11:40 +00:00
configType ,
2020-06-04 13:47:56 +00:00
val ,
isPreset ,
2023-11-07 15:50:29 +00:00
currentPath ,
2020-06-04 13:47:56 +00:00
) ;
warnings = warnings . concat ( subValidation . warnings ) ;
errors = errors . concat ( subValidation . errors ) ;
}
2019-03-31 07:16:29 +00:00
}
2017-07-28 19:15:27 +00:00
} else {
errors . push ( {
2021-04-01 13:50:17 +00:00
topic : 'Configuration Error' ,
2018-04-11 19:38:31 +00:00
message : ` Configuration option \` ${ currentPath } \` should be a json object ` ,
2017-07-28 19:15:27 +00:00
} ) ;
}
}
}
}
2024-01-17 09:22:19 +00:00
if ( key === 'hostRules' && is . array ( val ) ) {
2024-03-13 05:11:40 +00:00
const allowedHeaders =
configType === 'global'
? ( config . allowedHeaders as string [ ] ) ? ? [ ]
: GlobalConfig . get ( 'allowedHeaders' , [ ] ) ;
2024-01-17 09:22:19 +00:00
for ( const rule of val as HostRule [ ] ) {
if ( ! rule . headers ) {
continue ;
}
for ( const [ header , value ] of Object . entries ( rule . headers ) ) {
if ( ! is . string ( value ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid hostRules headers value configuration: header must be a string. ` ,
} ) ;
}
2024-02-18 15:46:24 +00:00
if ( ! matchRegexOrGlobList ( header , allowedHeaders ) ) {
2024-01-17 09:22:19 +00:00
errors . push ( {
topic : 'Configuration Error' ,
message : ` hostRules header \` ${ header } \` is not allowed by this bot's \` allowedHeaders \` . ` ,
} ) ;
}
}
}
}
2017-07-28 19:15:27 +00:00
}
2022-12-26 18:30:44 +00:00
2019-08-23 13:46:31 +00:00
function sortAll ( a : ValidationMessage , b : ValidationMessage ) : number {
2020-03-07 10:27:10 +00:00
// istanbul ignore else: currently never happen
2021-04-01 13:50:17 +00:00
if ( a . topic === b . topic ) {
2019-04-16 14:03:37 +00:00
return a . message > b . message ? 1 : - 1 ;
2018-04-28 06:48:12 +00:00
}
2020-03-07 10:27:10 +00:00
// istanbul ignore next: currently never happen
2021-04-01 13:50:17 +00:00
return a . topic > b . topic ? 1 : - 1 ;
2018-04-28 06:48:12 +00:00
}
2022-12-26 18:30:44 +00:00
2018-04-28 06:48:12 +00:00
errors . sort ( sortAll ) ;
warnings . sort ( sortAll ) ;
2017-07-31 12:50:44 +00:00
return { errors , warnings } ;
2017-07-28 19:15:27 +00:00
}
2023-09-12 14:16:01 +00:00
2024-05-04 14:53:31 +00:00
function hasField (
customManager : Partial < RegexManagerConfig > ,
field : string ,
) : boolean {
const templateField = ` ${ field } Template ` as keyof RegexManagerTemplates ;
return ! ! (
customManager [ templateField ] ? ?
customManager . matchStrings ? . some ( ( matchString ) = >
matchString . includes ( ` (?< ${ field } > ` ) ,
)
) ;
}
2023-09-12 14:16:01 +00:00
function validateRegexManagerFields (
2023-09-24 08:55:56 +00:00
customManager : Partial < RegexManagerConfig > ,
2023-09-12 14:16:01 +00:00
currentPath : string ,
2023-11-07 15:50:29 +00:00
errors : ValidationMessage [ ] ,
2023-09-12 14:16:01 +00:00
) : void {
2023-09-24 08:55:56 +00:00
if ( is . nonEmptyArray ( customManager . matchStrings ) ) {
for ( const matchString of customManager . matchStrings ) {
2023-09-12 14:16:01 +00:00
try {
regEx ( matchString ) ;
2023-12-12 08:48:22 +00:00
} catch ( err ) {
logger . debug (
{ err } ,
'customManager.matchStrings regEx validation error' ,
) ;
2023-09-12 14:16:01 +00:00
errors . push ( {
topic : 'Configuration Error' ,
message : ` Invalid regExp for ${ currentPath } : \` ${ matchString } \` ` ,
} ) ;
}
}
} else {
errors . push ( {
topic : 'Configuration Error' ,
2023-09-24 08:55:56 +00:00
message : ` Each Custom Manager must contain a non-empty matchStrings array ` ,
2023-09-12 14:16:01 +00:00
} ) ;
}
2024-05-05 12:14:55 +00:00
const mandatoryFields = [ 'currentValue' , 'datasource' ] ;
2023-09-12 14:16:01 +00:00
for ( const field of mandatoryFields ) {
2024-05-04 14:53:31 +00:00
if ( ! hasField ( customManager , field ) ) {
2023-09-12 14:16:01 +00:00
errors . push ( {
topic : 'Configuration Error' ,
message : ` Regex Managers must contain ${ field } Template configuration or regex group named ${ field } ` ,
} ) ;
}
}
2024-05-05 12:14:55 +00:00
const nameFields = [ 'depName' , 'packageName' ] ;
if ( ! nameFields . some ( ( field ) = > hasField ( customManager , field ) ) ) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` Regex Managers must contain depName or packageName regex groups or templates ` ,
} ) ;
}
2023-09-12 14:16:01 +00:00
}
2024-01-16 10:42:26 +00:00
2024-02-27 07:24:49 +00:00
/ * *
* Basic validation for global config options
* /
async function validateGlobalConfig (
key : string ,
val : unknown ,
type : string ,
warnings : ValidationMessage [ ] ,
2024-03-17 09:22:42 +00:00
errors : ValidationMessage [ ] ,
2024-02-27 07:24:49 +00:00
currentPath : string | undefined ,
2024-03-17 09:22:42 +00:00
config : RenovateConfig ,
2024-02-27 07:24:49 +00:00
) : Promise < void > {
2024-02-28 15:28:20 +00:00
if ( val !== null ) {
if ( type === 'string' ) {
if ( is . string ( val ) ) {
if (
key === 'onboardingConfigFileName' &&
! configFileNames . includes ( val )
) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value \` ${ val } \` for \` ${ currentPath } \` . The allowed values are ${ configFileNames . join ( ', ' ) } . ` ,
} ) ;
} else if (
key === 'repositoryCache' &&
! [ 'enabled' , 'disabled' , 'reset' ] . includes ( val )
) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value \` ${ val } \` for \` ${ currentPath } \` . The allowed values are ${ [ 'enabled' , 'disabled' , 'reset' ] . join ( ', ' ) } . ` ,
} ) ;
} else if (
key === 'dryRun' &&
! [ 'extract' , 'lookup' , 'full' ] . includes ( val )
) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value \` ${ val } \` for \` ${ currentPath } \` . The allowed values are ${ [ 'extract' , 'lookup' , 'full' ] . join ( ', ' ) } . ` ,
} ) ;
} else if (
key === 'binarySource' &&
! [ 'docker' , 'global' , 'install' , 'hermit' ] . includes ( val )
) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value \` ${ val } \` for \` ${ currentPath } \` . The allowed values are ${ [ 'docker' , 'global' , 'install' , 'hermit' ] . join ( ', ' ) } . ` ,
} ) ;
} else if (
key === 'requireConfig' &&
! [ 'required' , 'optional' , 'ignored' ] . includes ( val )
) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value \` ${ val } \` for \` ${ currentPath } \` . The allowed values are ${ [ 'required' , 'optional' , 'ignored' ] . join ( ', ' ) } . ` ,
} ) ;
} else if (
key === 'gitUrl' &&
! [ 'default' , 'ssh' , 'endpoint' ] . includes ( val )
) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value \` ${ val } \` for \` ${ currentPath } \` . The allowed values are ${ [ 'default' , 'ssh' , 'endpoint' ] . join ( ', ' ) } . ` ,
} ) ;
}
2024-03-17 09:22:42 +00:00
if (
key === 'reportType' &&
[ 's3' , 'file' ] . includes ( val ) &&
! is . string ( config . reportPath )
) {
errors . push ( {
topic : 'Configuration Error' ,
message : ` reportType ' ${ val } ' requires a configured reportPath ` ,
} ) ;
}
2024-02-28 15:28:20 +00:00
} else {
2024-02-27 07:24:49 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
2024-02-28 15:28:20 +00:00
message : ` Configuration option \` ${ currentPath } \` should be a string. ` ,
2024-02-27 07:24:49 +00:00
} ) ;
2024-02-28 15:28:20 +00:00
}
} else if ( type === 'integer' ) {
if ( ! is . number ( val ) ) {
2024-02-27 07:24:49 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
2024-02-28 15:28:20 +00:00
message : ` Configuration option \` ${ currentPath } \` should be an integer. Found: ${ JSON . stringify (
val ,
) } ( $ { typeof val } ) . ` ,
2024-02-27 07:24:49 +00:00
} ) ;
2024-02-28 15:28:20 +00:00
}
} else if ( type === 'boolean' ) {
if ( val !== true && val !== false ) {
2024-02-27 07:24:49 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
2024-02-28 15:28:20 +00:00
message : ` Configuration option \` ${ currentPath } \` should be a boolean. Found: ${ JSON . stringify (
val ,
) } ( $ { typeof val } ) . ` ,
2024-02-27 07:24:49 +00:00
} ) ;
2024-02-28 15:28:20 +00:00
}
} else if ( type === 'array' ) {
if ( is . array ( val ) ) {
2024-05-05 07:45:58 +00:00
if ( isRegexOrGlobOption ( key ) ) {
warnings . push (
. . . regexOrGlobValidator . check ( {
val ,
currentPath : currentPath ! ,
} ) ,
) ;
}
2024-02-28 15:28:20 +00:00
if ( key === 'gitNoVerify' ) {
const allowedValues = [ 'commit' , 'push' ] ;
for ( const value of val as string [ ] ) {
if ( ! allowedValues . includes ( value ) ) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid value for \` ${ currentPath } \` . The allowed values are ${ allowedValues . join ( ', ' ) } . ` ,
} ) ;
}
}
}
} else {
2024-02-27 07:24:49 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
2024-02-28 15:28:20 +00:00
message : ` Configuration option \` ${ currentPath } \` should be a list (Array). ` ,
2024-02-27 07:24:49 +00:00
} ) ;
}
2024-02-28 15:28:20 +00:00
} else if ( type === 'object' ) {
if ( is . plainObject ( val ) ) {
if ( key === 'onboardingConfig' ) {
2024-03-13 05:11:40 +00:00
const subValidation = await validateConfig ( 'repo' , val ) ;
2024-02-28 15:28:20 +00:00
for ( const warning of subValidation . warnings . concat (
subValidation . errors ,
) ) {
warnings . push ( warning ) ;
2024-02-27 07:24:49 +00:00
}
2024-02-28 15:28:20 +00:00
} else if ( key === 'force' ) {
2024-03-13 05:11:40 +00:00
const subValidation = await validateConfig ( 'global' , val ) ;
2024-02-28 15:28:20 +00:00
for ( const warning of subValidation . warnings . concat (
subValidation . errors ,
) ) {
warnings . push ( warning ) ;
}
} else if ( key === 'cacheTtlOverride' ) {
for ( const [ subKey , subValue ] of Object . entries ( val ) ) {
if ( ! is . number ( subValue ) ) {
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Invalid \` ${ currentPath } . ${ subKey } \` configuration: value must be an integer. ` ,
} ) ;
}
}
} else {
const res = validatePlainObject ( val ) ;
if ( res !== true ) {
2024-02-27 07:24:49 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
2024-02-28 15:28:20 +00:00
message : ` Invalid \` ${ currentPath } . ${ res } \` configuration: value must be a string. ` ,
2024-02-27 07:24:49 +00:00
} ) ;
}
}
} else {
2024-02-28 15:28:20 +00:00
warnings . push ( {
topic : 'Configuration Error' ,
message : ` Configuration option \` ${ currentPath } \` should be a JSON object. ` ,
} ) ;
2024-02-27 07:24:49 +00:00
}
}
}
}
2024-01-16 10:42:26 +00:00
/ * * A n o p t i o n i s a f a l s e g l o b a l i f i t h a s t h e s a m e n a m e a s a g l o b a l o n l y o p t i o n
* but is actually just the field of a non global option or field an children of the non global option
* eg . token : it 's global option used as the bot' s token as well and
* also it can be the token used for a platform inside the hostRules configuration
* /
function isFalseGlobal ( optionName : string , parentPath? : string ) : boolean {
if ( parentPath ? . includes ( 'hostRules' ) ) {
if (
optionName === 'token' ||
optionName === 'username' ||
optionName === 'password'
) {
return true ;
}
}
return false ;
}