diff --git a/lib/config/parse.ts b/lib/config/parse.ts new file mode 100644 index 00000000000000..45bbb8e651d434 --- /dev/null +++ b/lib/config/parse.ts @@ -0,0 +1,75 @@ +import jsonValidator from 'json-dup-key-validator'; +import JSON5 from 'json5'; +import upath from 'upath'; +import { logger } from '../logger'; +import { parseJson } from '../util/common'; + +export function parseFileConfig( + fileName: string, + fileContents: string, +): + | { success: true; parsedContents: unknown } + | { success: false; validationError: string; validationMessage: string } { + const fileType = upath.extname(fileName); + + if (fileType === '.json5') { + try { + return { success: true, parsedContents: JSON5.parse(fileContents) }; + } catch (err) /* istanbul ignore next */ { + logger.debug({ fileName, fileContents }, 'Error parsing JSON5 file'); + const validationError = 'Invalid JSON5 (parsing failed)'; + const validationMessage = `JSON5.parse error: \`${err.message.replaceAll( + '`', + "'", + )}\``; + return { + success: false, + validationError, + validationMessage, + }; + } + } else { + let allowDuplicateKeys = true; + let jsonValidationError = jsonValidator.validate( + fileContents, + allowDuplicateKeys, + ); + if (jsonValidationError) { + const validationError = 'Invalid JSON (parsing failed)'; + const validationMessage = jsonValidationError; + return { + success: false, + validationError, + validationMessage, + }; + } + allowDuplicateKeys = false; + jsonValidationError = jsonValidator.validate( + fileContents, + allowDuplicateKeys, + ); + if (jsonValidationError) { + const validationError = 'Duplicate keys in JSON'; + const validationMessage = JSON.stringify(jsonValidationError); + return { + success: false, + validationError, + validationMessage, + }; + } + try { + return { + success: true, + parsedContents: parseJson(fileContents, fileName), + }; + } catch (err) /* istanbul ignore next */ { + logger.debug({ fileContents }, 'Error parsing renovate config'); + const validationError = 'Invalid JSON (parsing failed)'; + const validationMessage = `JSON.parse error: \`${err.message.replaceAll( + '`', + "'", + )}\``; + return { success: false, validationError, validationMessage }; + } + } +} diff --git a/lib/workers/repository/init/merge.ts b/lib/workers/repository/init/merge.ts index f5acc402f63de3..c8ced5681b9e3a 100644 --- a/lib/workers/repository/init/merge.ts +++ b/lib/workers/repository/init/merge.ts @@ -1,12 +1,10 @@ import is from '@sindresorhus/is'; -import jsonValidator from 'json-dup-key-validator'; -import JSON5 from 'json5'; -import upath from 'upath'; import { mergeChildConfig } from '../../../config'; import { configFileNames } from '../../../config/app-strings'; import { decryptConfig } from '../../../config/decrypt'; import { migrateAndValidate } from '../../../config/migrate-validate'; 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'; @@ -131,71 +129,18 @@ export async function detectRepoFileConfig(): Promise { configFileRaw = '{}'; } - const fileType = upath.extname(configFileName); + const parseResult = parseFileConfig(configFileName, configFileRaw); - if (fileType === '.json5') { - try { - configFileParsed = JSON5.parse(configFileRaw); - } catch (err) /* istanbul ignore next */ { - logger.debug( - { renovateConfig: configFileRaw }, - 'Error parsing renovate config renovate.json5', - ); - const validationError = 'Invalid JSON5 (parsing failed)'; - const validationMessage = `JSON5.parse error: \`${err.message.replaceAll( - '`', - "'", - )}\``; - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } - } else { - let allowDuplicateKeys = true; - let jsonValidationError = jsonValidator.validate( - configFileRaw, - allowDuplicateKeys, - ); - if (jsonValidationError) { - const validationError = 'Invalid JSON (parsing failed)'; - const validationMessage = jsonValidationError; - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } - allowDuplicateKeys = false; - jsonValidationError = jsonValidator.validate( - configFileRaw, - allowDuplicateKeys, - ); - if (jsonValidationError) { - const validationError = 'Duplicate keys in JSON'; - const validationMessage = JSON.stringify(jsonValidationError); - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } - try { - configFileParsed = parseJson(configFileRaw, configFileName); - } catch (err) /* istanbul ignore next */ { - logger.debug( - { renovateConfig: configFileRaw }, - 'Error parsing renovate config', - ); - const validationError = 'Invalid JSON (parsing failed)'; - const validationMessage = `JSON.parse error: \`${err.message.replaceAll( - '`', - "'", - )}\``; - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } + if (!parseResult.success) { + return { + configFileName, + configFileParseError: { + validationError: parseResult.validationError, + validationMessage: parseResult.validationMessage, + }, + }; } + configFileParsed = parseResult.parsedContents; logger.debug( { fileName: configFileName, config: configFileParsed }, 'Repository config',