0
0
Fork 0
mirror of https://github.com/renovatebot/renovate.git synced 2025-05-12 15:41:58 +00:00
renovatebot_renovate/lib/util/interpolator.ts
2025-05-09 16:42:18 +00:00

133 lines
3.5 KiB
TypeScript

import is from '@sindresorhus/is';
import type { RenovateConfig } from '../config/types';
import {
CONFIG_SECRETS_INVALID,
CONFIG_VALIDATION,
} from '../constants/error-messages';
import { logger } from '../logger';
import { capitalize } from './string';
export interface InterpolatorOptions {
name: 'secrets' | 'variables';
templateRegex: RegExp;
nameRegex: RegExp;
}
export function validateInterpolatedValues(
input: unknown,
options: InterpolatorOptions,
): void {
if (!input) {
return;
}
const { name, nameRegex } = options;
const validationErrors: string[] = [];
if (is.plainObject(input)) {
for (const [key, value] of Object.entries(input)) {
if (!nameRegex.test(key)) {
validationErrors.push(`Invalid ${name} name "${key}"`);
}
if (!is.string(value)) {
validationErrors.push(
`${capitalize(name)} values must be strings. Found type ${typeof value} for ${name} ${key}`,
);
}
}
} else {
validationErrors.push(
`Config ${name}s must be a plain object. Found: ${typeof input}`,
);
}
if (validationErrors.length) {
logger.error({ validationErrors }, `Invalid ${name}s configured`);
throw new Error(CONFIG_SECRETS_INVALID);
}
}
function replaceInterpolatedValuesInString(
key: string,
value: string,
input: Record<string, string>,
options: InterpolatorOptions,
): string {
const { name, templateRegex } = options;
// do nothing if no interpolator template found
if (!templateRegex.test(value)) {
return value;
}
const disallowedPrefixes = ['branch', 'commit', 'group', 'pr', 'semantic'];
if (disallowedPrefixes.some((prefix) => key.startsWith(prefix))) {
const error = new Error(CONFIG_VALIDATION);
error.validationSource = 'config';
error.validationError = `Disallowed ${name} substitution`;
error.validationMessage = `The field \`${key}\` may not use ${name} substitution`;
throw error;
}
return value.replace(templateRegex, (_, key) => {
if (input?.[key]) {
return input[key];
}
const error = new Error(CONFIG_VALIDATION);
error.validationSource = 'config';
error.validationError = `Unknown ${name} name`;
error.validationMessage = `The following ${name} name was not found in config: ${String(
key,
)}`;
throw error;
});
}
export function replaceInterpolatedValuesInObject(
config_: RenovateConfig,
input: Record<string, string>,
options: InterpolatorOptions,
deleteValues: boolean,
): RenovateConfig {
const config = { ...config_ };
const { name } = options;
if (deleteValues) {
delete config[name];
}
for (const [key, value] of Object.entries(config)) {
if (is.plainObject(value)) {
config[key] = replaceInterpolatedValuesInObject(
value,
input,
options,
deleteValues,
);
}
if (is.string(value)) {
config[key] = replaceInterpolatedValuesInString(
key,
value,
input,
options,
);
}
if (is.array(value)) {
for (const [arrayIndex, arrayItem] of value.entries()) {
if (is.plainObject(arrayItem)) {
value[arrayIndex] = replaceInterpolatedValuesInObject(
arrayItem,
input,
options,
deleteValues,
);
} else if (is.string(arrayItem)) {
value[arrayIndex] = replaceInterpolatedValuesInString(
key,
arrayItem,
input,
options,
);
}
}
}
}
return config;
}