UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

298 lines • 9.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProblemStream = void 0; exports.prepareZodIssues = prepareZodIssues; exports.prepareZodError = prepareZodError; exports.default = prepareError; exports.sanitizeValue = sanitizeValue; exports.withSanitizer = withSanitizer; exports.validateLogLevel = validateLogLevel; exports.sanitizeUrls = sanitizeUrls; exports.getEnv = getEnv; exports.getMessage = getMessage; exports.toMeta = toMeta; const tslib_1 = require("tslib"); const node_stream_1 = require("node:stream"); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const bunyan_1 = tslib_1.__importDefault(require("bunyan")); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const got_1 = require("got"); const zod_1 = require("zod"); const regex_1 = require("../util/regex"); const sanitize_1 = require("../util/sanitize"); const excludeProps = ['pid', 'time', 'v', 'hostname']; class ProblemStream extends node_stream_1.Stream { _problems = []; readable; writable; constructor() { super(); this.readable = false; this.writable = true; } write(data) { const problem = { ...data }; for (const prop of excludeProps) { delete problem[prop]; } this._problems.push(problem); return true; } getProblems() { return this._problems; } clearProblems() { this._problems = []; } } exports.ProblemStream = ProblemStream; const contentFields = [ 'content', 'contents', 'packageLockParsed', 'yarnLockParsed', ]; function prepareZodIssues(input) { if (!is_1.default.plainObject(input)) { return null; } let err = null; if (is_1.default.array(input._errors, is_1.default.string)) { if (input._errors.length === 1) { err = input._errors[0]; } else if (input._errors.length > 1) { err = input._errors; } else { err = null; } } delete input._errors; if (is_1.default.emptyObject(input)) { return err; } const output = {}; const entries = Object.entries(input); for (const [key, value] of entries.slice(0, 3)) { const child = prepareZodIssues(value); if (child !== null) { output[key] = child; } } if (entries.length > 3) { output.___ = `... ${entries.length - 3} more`; } return output; } function prepareZodError(err) { Object.defineProperty(err, 'message', { get: () => 'Schema error', /* v8 ignore next 3 -- TODO: drop set? */ set: () => { /* intentionally empty */ }, }); return { message: err.message, stack: err.stack, issues: prepareZodIssues(err.format()), }; } function prepareError(err) { if (err instanceof zod_1.ZodError) { return prepareZodError(err); } const response = { ...err, }; // Required as message is non-enumerable if (!response.message && err.message) { response.message = err.message; } // Required as stack is non-enumerable if (!response.stack && err.stack) { response.stack = err.stack; } if (err instanceof AggregateError) { response.errors = err.errors.map((error) => prepareError(error)); } // handle got error if (err instanceof got_1.RequestError) { const options = { headers: structuredClone(err.options.headers), url: err.options.url?.toString(), hostType: err.options.context.hostType, }; response.options = options; options.username = err.options.username; options.password = err.options.password; options.method = err.options.method; options.http2 = err.options.http2; if (err.response) { response.response = { statusCode: err.response.statusCode, statusMessage: err.response.statusMessage, body: err.name === 'TimeoutError' ? undefined : structuredClone(err.response.body), headers: structuredClone(err.response.headers), httpVersion: err.response.httpVersion, retryCount: err.response.retryCount, }; } } return response; } function isNested(value) { return is_1.default.array(value) || is_1.default.object(value); } function sanitizeValue(value, seen = new WeakMap()) { if (is_1.default.string(value)) { return (0, sanitize_1.sanitize)(sanitizeUrls(value)); } if (is_1.default.date(value)) { return value; } if (is_1.default.function(value)) { return '[function]'; } if (is_1.default.buffer(value)) { return '[content]'; } if (is_1.default.error(value)) { const err = prepareError(value); return sanitizeValue(err, seen); } if (is_1.default.array(value)) { const length = value.length; const arrayResult = Array(length); seen.set(value, arrayResult); for (let idx = 0; idx < length; idx += 1) { const val = value[idx]; arrayResult[idx] = isNested(val) && seen.has(val) ? seen.get(val) : sanitizeValue(val, seen); } return arrayResult; } if (is_1.default.object(value)) { const objectResult = {}; seen.set(value, objectResult); for (const [key, val] of Object.entries(value)) { let curValue; if (!val) { curValue = val; } else if (sanitize_1.redactedFields.includes(key)) { // Do not mask/sanitize secrets templates if (is_1.default.string(val) && (0, regex_1.regEx)(/^{{\s*secrets\..*}}$/).test(val)) { curValue = val; } else { curValue = '***********'; } } else if (contentFields.includes(key)) { curValue = '[content]'; } else if (key === 'secrets') { curValue = {}; Object.keys(val).forEach((secretKey) => { curValue[secretKey] = '***********'; }); } else { curValue = seen.has(val) ? seen.get(val) : sanitizeValue(val, seen); } objectResult[key] = curValue; } return objectResult; } return value; } function withSanitizer(streamConfig) { if (streamConfig.type === 'rotating-file') { throw new Error("Rotating files aren't supported"); } const stream = streamConfig.stream; if (stream?.writable) { const write = (chunk, enc, cb) => { const raw = sanitizeValue(chunk); const result = streamConfig.type === 'raw' ? raw : JSON.stringify(raw, bunyan_1.default.safeCycles()).replace(/\n?$/, '\n'); // TODO #12874 stream.write(result, enc, cb); }; return { ...streamConfig, type: 'raw', stream: { write }, }; } if (streamConfig.path) { const fileStream = fs_extra_1.default.createWriteStream(streamConfig.path, { flags: 'a', encoding: 'utf8', }); return withSanitizer({ ...streamConfig, stream: fileStream }); } throw new Error("Missing 'stream' or 'path' for bunyan stream"); } /** * A function that terminates execution if the log level that was entered is * not a valid value for the Bunyan logger. * @param logLevelToCheck * @returns returns the logLevel when the logLevelToCheck is valid or the defaultLevel passed as argument when it is undefined. Else it stops execution. */ function validateLogLevel(logLevelToCheck, defaultLevel) { const allowedValues = [ 'trace', 'debug', 'info', 'warn', 'error', 'fatal', ]; if (is_1.default.undefined(logLevelToCheck) || (is_1.default.string(logLevelToCheck) && allowedValues.includes(logLevelToCheck))) { // log level is in the allowed values or its undefined return logLevelToCheck ?? defaultLevel; } const logger = bunyan_1.default.createLogger({ name: 'renovate', streams: [ { level: 'fatal', stream: process.stdout, }, ], }); logger.fatal({ logLevel: logLevelToCheck }, 'Invalid log level'); process.exit(1); } // Can't use `util/regex` because of circular reference to logger const urlRe = /[a-z]{3,9}:\/\/[^@/]+@[a-z0-9.-]+/gi; const urlCredRe = /\/\/[^@]+@/g; const dataUriCredRe = /^(data:[0-9a-z-]+\/[0-9a-z-]+;).+/i; function sanitizeUrls(text) { return text .replace(urlRe, (url) => { return url.replace(urlCredRe, '//**redacted**@'); }) .replace(dataUriCredRe, '$1**redacted**'); } function getEnv(key) { return [process.env[`RENOVATE_${key}`], process.env[key]] .map((v) => v?.toLowerCase().trim()) .find(is_1.default.nonEmptyStringAndNotWhitespace); } function getMessage(p1, p2) { return is_1.default.string(p1) ? p1 : p2; } function toMeta(p1) { return is_1.default.object(p1) ? p1 : {}; } //# sourceMappingURL=utils.js.map