UNPKG

renovate

Version:

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

189 lines (188 loc) • 7.55 kB
import { regEx } from "../regex.js"; import { logger } from "../../logger/index.js"; import { supportedDatasources as supportedDatasources$1 } from "../../config/presets/internal/merge-confidence.preset.js"; import { ensureTrailingSlash, joinUrlParts, parseUrl } from "../url.js"; import { ATTR_RENOVATE_DATASOURCE, ATTR_RENOVATE_PACKAGE_NAME } from "../../instrumentation/types.js"; import { instrument } from "../../instrumentation/index.js"; import { find } from "../host-rules.js"; import { ExternalHostError } from "../../types/errors/external-host-error.js"; import { get, set } from "../cache/package/index.js"; import { Http } from "../http/index.js"; import { memCacheProvider } from "../http/cache/memory-http-cache-provider.js"; import { MERGE_CONFIDENCE } from "./common.js"; import { MergeConfidenceResponse } from "./schema.js"; import { isNullOrUndefined } from "@sindresorhus/is"; //#region lib/util/merge-confidence/index.ts const hostType = "merge-confidence"; const http = new Http(hostType); let token; let apiBaseUrl; let supportedDatasources = []; const confidenceLevels = { low: -1, neutral: 0, high: 1, "very high": 2 }; function initConfig({ mergeConfidenceEndpoint, mergeConfidenceDatasources }) { apiBaseUrl = getApiBaseUrl(mergeConfidenceEndpoint); token = getApiToken(); supportedDatasources = mergeConfidenceDatasources ?? supportedDatasources$1; if (!isNullOrUndefined(token)) logger.debug(`Merge confidence token found for ${apiBaseUrl}`); } function isMergeConfidence(value) { return MERGE_CONFIDENCE.includes(value); } function isActiveConfidenceLevel(confidence) { return isMergeConfidence(confidence) && confidence !== "low"; } function satisfiesConfidenceLevel(confidence, minimumConfidence) { return confidenceLevels[confidence] >= confidenceLevels[minimumConfidence]; } const updateTypeConfidenceMapping = { pin: "high", digest: "neutral", pinDigest: "high", bump: "neutral", lockFileMaintenance: "neutral", lockfileUpdate: "neutral", rollback: "neutral", replacement: "neutral", major: null, minor: null, patch: null }; /** * Retrieves the merge confidence of a package update if the merge confidence API is enabled. Otherwise, undefined is returned. * * @param datasource * @param packageName * @param currentVersion * @param newVersion * @param updateType * * @returns The merge confidence level for the given package release. * @throws {ExternalHostError} If a request has been made and an error occurs during the request, such as a timeout, connection reset, authentication failure, or internal server error. */ async function getMergeConfidenceLevel(datasource, packageName, currentVersion, newVersion, updateType) { return await instrument("getMergeConfidenceLevel", async () => { if (isNullOrUndefined(apiBaseUrl) || isNullOrUndefined(token)) return; if (!supportedDatasources.includes(datasource)) return; if (!(currentVersion && newVersion && updateType)) return "neutral"; const mappedConfidence = updateTypeConfidenceMapping[updateType]; if (mappedConfidence) return mappedConfidence; return await queryApi(datasource, packageName, currentVersion, newVersion); }, { attributes: { [ATTR_RENOVATE_DATASOURCE]: datasource, [ATTR_RENOVATE_PACKAGE_NAME]: packageName } }); } /** * Queries the Merge Confidence API with the given package release information. * * @param datasource * @param packageName * @param currentVersion * @param newVersion * * @returns The merge confidence level for the given package release. * @throws {ExternalHostError} if a timeout or connection reset error, authentication failure, or internal server error occurs during the request. * * @remarks * Results are cached for 60 minutes to reduce the number of API calls. */ async function queryApi(datasource, packageName, currentVersion, newVersion) { // istanbul ignore if: defensive, already been validated before calling this function if (isNullOrUndefined(apiBaseUrl) || isNullOrUndefined(token)) return "neutral"; const escapedPackageName = packageName.replace(regEx(/\//g), "%2f").replace(regEx(/:/g), "%3A"); const url = joinUrlParts(apiBaseUrl, "api/mc/json", datasource, escapedPackageName, currentVersion, newVersion); const cacheKey = `${token}:${url}`; const cachedResult = await get(hostType, cacheKey); // istanbul ignore if if (cachedResult) { logger.debug({ datasource, packageName, currentVersion, newVersion, cachedResult }, "using merge confidence cached result"); return cachedResult; } let confidence = "neutral"; try { const res = (await http.getJson(url, { cacheProvider: memCacheProvider }, MergeConfidenceResponse)).body; if (isMergeConfidence(res.confidence)) confidence = res.confidence; } catch (err) { apiErrorHandler(err); } await set(hostType, cacheKey, confidence, 60); return confidence; } /** * Checks the health of the Merge Confidence API by attempting to authenticate with it. * * @returns Resolves when the API health check is completed successfully. * * @throws {ExternalHostError} if a timeout, connection reset error, authentication failure, or internal server error occurs during the request. * * @remarks * This function first checks that the API base URL and an authentication bearer token are defined before attempting to * authenticate with the API. If either the base URL or token is not defined, it will immediately return * without making a request. */ async function initMergeConfidence(config) { initConfig(config); if (isNullOrUndefined(apiBaseUrl) || isNullOrUndefined(token)) { logger.trace("merge confidence API usage is disabled"); return; } const url = joinUrlParts(apiBaseUrl, "api/mc/availability"); try { await http.get(url); } catch (err) { apiErrorHandler(err); } logger.debug({ supportedDatasources }, "merge confidence API - successfully authenticated"); } function getApiBaseUrl(mergeConfidenceEndpoint) { const defaultBaseUrl = "https://developer.mend.io/"; const baseFromEnv = mergeConfidenceEndpoint ?? defaultBaseUrl; const parsedBaseUrl = parseUrl(baseFromEnv)?.toString(); if (!parsedBaseUrl) { logger.warn({ baseFromEnv }, "invalid merge confidence API base URL found in environment variables - using default value instead"); return defaultBaseUrl; } logger.trace({ baseUrl: parsedBaseUrl }, "using merge confidence API base found in environment variables"); return ensureTrailingSlash(parsedBaseUrl); } function getApiToken() { return find({ url: apiBaseUrl, hostType })?.token; } /** * Handles errors returned by the Merge Confidence API. * * @param err - The error object returned by the API. * @throws {ExternalHostError} if a timeout or connection reset error, authentication failure, or internal server error occurs during the request. */ function apiErrorHandler(err) { if (err.code === "ETIMEDOUT" || err.code === "ECONNRESET") { logger.error({ err }, "merge confidence API request failed - aborting run"); throw new ExternalHostError(err, hostType); } if (err.statusCode === 403) { logger.error({ err }, "merge confidence API token rejected - aborting run"); throw new ExternalHostError(err, hostType); } if (err.statusCode >= 500 && err.statusCode < 600) { logger.error({ err }, "merge confidence API failure: 5xx - aborting run"); throw new ExternalHostError(err, hostType); } logger.warn({ err }, "error fetching merge confidence data"); } //#endregion export { getApiToken, getMergeConfidenceLevel, initMergeConfidence, isActiveConfidenceLevel, satisfiesConfidenceLevel }; //# sourceMappingURL=index.js.map