renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
311 lines (310 loc) • 12.7 kB
JavaScript
import "../../constants/error-messages.js";
import { get, set } from "../../util/cache/memory/index.js";
import { GlobalConfig } from "../../config/global.js";
import { logger } from "../../logger/index.js";
import { clone } from "../../util/clone.js";
import { trimTrailingSlash } from "../../util/url.js";
import { coerceArray } from "../../util/array.js";
import { ExternalHostError } from "../../types/errors/external-host-error.js";
import { ATTR_RENOVATE_DATASOURCE, ATTR_RENOVATE_PACKAGE_NAME, ATTR_RENOVATE_REGISTRY_URL } from "../../instrumentation/types.js";
import { filterMap } from "../../util/filter-map.js";
import { get as get$1 } from "../versioning/index.js";
import { DatasourceCacheStats } from "../../util/stats.js";
import { instrument } from "../../instrumentation/index.js";
import { get as get$2, set as set$1 } from "../../util/cache/package/index.js";
import { AsyncResult, Result } from "../../util/result.js";
import { resolveRegistryUrl, setNpmrc } from "./npm/npmrc.js";
import "./npm/index.js";
import { addMetaData } from "./metadata.js";
import api from "./api.js";
import { applyConstraintsFiltering, applyExtractVersion, applyVersionCompatibility, filterValidVersions, getDatasourceFor, isGetPkgReleasesConfig, sortAndRemoveDuplicates } from "./common.js";
import { isFunction, isNonEmptyArray, isString } from "@sindresorhus/is";
import { dequal } from "dequal";
import { ATTR_CODE_FUNCTION_NAME } from "@opentelemetry/semantic-conventions";
//#region lib/modules/datasource/index.ts
const getDatasources = () => api;
const getDatasourceList = () => Array.from(api.keys());
function logError(datasource, packageName, err) {
const { statusCode, code: errCode, url } = err;
if (statusCode === 404) logger.debug({
datasource,
packageName,
url
}, "Datasource 404");
else if (statusCode === 401 || statusCode === 403) logger.debug({
datasource,
packageName,
url
}, "Datasource unauthorized");
else if (errCode) logger.debug({
datasource,
packageName,
url,
errCode
}, "Datasource connection error");
else logger.debug({
datasource,
packageName,
err
}, "Datasource unknown error");
}
async function getRegistryReleases(datasource, config, registryUrl) {
const cacheNamespace = `datasource-releases-${datasource.id}`;
const cacheKey = `${registryUrl}:${config.packageName}`;
const cacheEnabled = !!datasource.caching;
const cacheForced = GlobalConfig.get("cachePrivatePackages");
if (cacheEnabled || cacheForced) {
const cachedResult = await get$2(cacheNamespace, cacheKey);
if (cachedResult) {
logger.trace({ cacheKey }, "Returning cached datasource response");
DatasourceCacheStats.hit(datasource.id, registryUrl, config.packageName);
return cachedResult;
}
DatasourceCacheStats.miss(datasource.id, registryUrl, config.packageName);
}
const res = await instrument("getReleases", () => datasource.getReleases({
...config,
registryUrl
}), { attributes: {
[ATTR_CODE_FUNCTION_NAME]: "getReleases",
[ATTR_RENOVATE_DATASOURCE]: datasource.id,
[ATTR_RENOVATE_REGISTRY_URL]: registryUrl,
[ATTR_RENOVATE_PACKAGE_NAME]: config.packageName
} });
if (res?.releases.length) res.registryUrl ??= registryUrl;
if (!res) return null;
let cache = false;
if (cacheForced) cache = true;
else if (cacheEnabled && !res.isPrivate) cache = true;
if (cache) {
logger.trace({ cacheKey }, "Caching datasource response");
await set$1(cacheNamespace, cacheKey, res, 15);
DatasourceCacheStats.set(datasource.id, registryUrl, config.packageName);
} else DatasourceCacheStats.skip(datasource.id, registryUrl, config.packageName);
return res;
}
function firstRegistry(config, datasource, registryUrls) {
if (registryUrls.length > 1) logger.warn({
datasource: datasource.id,
packageName: config.packageName,
registryUrls
}, "Excess registryUrls found for datasource lookup - using first configured only");
const registryUrl = registryUrls[0];
return getRegistryReleases(datasource, config, registryUrl);
}
async function huntRegistries(config, datasource, registryUrls) {
let res = null;
let caughtError;
for (const registryUrl of registryUrls) try {
res = await getRegistryReleases(datasource, config, registryUrl);
if (res) break;
} catch (err) {
if (err instanceof ExternalHostError) throw err;
caughtError = err;
logger.trace({ err }, "datasource hunt failure");
}
if (res) return res;
if (caughtError) throw caughtError;
return null;
}
async function mergeRegistries(config, datasource, registryUrls) {
let combinedRes;
let lastErr;
let singleRegistry = true;
const releaseVersioning = get$1(config.versioning);
for (const registryUrl of registryUrls) try {
const res = await getRegistryReleases(datasource, config, registryUrl);
if (!res) continue;
if (!combinedRes) {
combinedRes = res;
continue;
}
if (singleRegistry) {
for (const release of coerceArray(combinedRes.releases)) release.registryUrl ??= combinedRes.registryUrl;
singleRegistry = false;
}
const releases = coerceArray(res.releases);
for (const release of releases) release.registryUrl ??= res.registryUrl;
combinedRes.releases.push(...releases);
let tags = combinedRes.tags;
if (tags) {
if (res.tags) for (const tag of ["release", "latest"]) {
const existingTag = combinedRes?.tags?.[tag];
const newTag = res.tags?.[tag];
if (isString(newTag) && releaseVersioning.isVersion(newTag)) if (isString(existingTag) && releaseVersioning.isVersion(existingTag)) {
if (releaseVersioning.isGreaterThan(newTag, existingTag)) tags[tag] = newTag;
} else tags[tag] = newTag;
}
} else tags = res.tags;
combinedRes = {
...res,
...combinedRes
};
if (tags) combinedRes.tags = tags;
delete combinedRes.registryUrl;
} catch (err) {
if (err instanceof ExternalHostError) throw err;
lastErr = err;
logger.trace({ err }, "datasource merge failure");
}
if (!combinedRes) {
if (lastErr) throw lastErr;
return null;
}
const seenVersions = /* @__PURE__ */ new Set();
combinedRes.releases = filterMap(combinedRes.releases, (release) => {
if (seenVersions.has(release.version)) return null;
seenVersions.add(release.version);
return release;
});
return combinedRes;
}
function massageRegistryUrls(registryUrls) {
return registryUrls.filter(Boolean).map(trimTrailingSlash);
}
function resolveRegistryUrls(datasource, defaultRegistryUrls, registryUrls, additionalRegistryUrls) {
if (!datasource.customRegistrySupport) {
if (isNonEmptyArray(registryUrls) || isNonEmptyArray(defaultRegistryUrls) || isNonEmptyArray(additionalRegistryUrls)) logger.warn({
datasource: datasource.id,
registryUrls,
defaultRegistryUrls,
additionalRegistryUrls
}, "Custom registries are not allowed for this datasource and will be ignored");
return isFunction(datasource.defaultRegistryUrls) ? datasource.defaultRegistryUrls() : datasource.defaultRegistryUrls ?? [];
}
const customUrls = registryUrls?.filter(Boolean);
let resolvedUrls = [];
if (isNonEmptyArray(customUrls)) resolvedUrls = [...customUrls];
else if (isNonEmptyArray(defaultRegistryUrls)) {
resolvedUrls = [...defaultRegistryUrls];
resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
} else if (isFunction(datasource.defaultRegistryUrls)) {
resolvedUrls = [...datasource.defaultRegistryUrls()];
resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
} else if (isNonEmptyArray(datasource.defaultRegistryUrls)) {
resolvedUrls = [...datasource.defaultRegistryUrls];
resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
}
return massageRegistryUrls(resolvedUrls);
}
function applyReplacements(config) {
if (config.replacementName && config.replacementVersion) return {
replacementName: config.replacementName,
replacementVersion: config.replacementVersion
};
}
async function fetchReleases(config) {
const { datasource: datasourceName } = config;
let { registryUrls } = config;
// istanbul ignore if: need test
if (!datasourceName || getDatasourceFor(datasourceName) === void 0) {
logger.warn({ datasource: datasourceName }, "Unknown datasource");
return null;
}
if (datasourceName === "npm") {
if (isString(config.npmrc)) setNpmrc(config.npmrc);
if (!isNonEmptyArray(registryUrls)) registryUrls = [resolveRegistryUrl(config.packageName)];
}
const datasource = getDatasourceFor(datasourceName);
// istanbul ignore if: needs test
if (!datasource) {
logger.warn({ datasource: datasourceName }, "Unknown datasource");
return null;
}
registryUrls = resolveRegistryUrls(datasource, config.defaultRegistryUrls, registryUrls, config.additionalRegistryUrls);
let dep = null;
const registryStrategy = config.registryStrategy ?? datasource.registryStrategy ?? "hunt";
try {
if (isNonEmptyArray(registryUrls)) {
if (registryStrategy === "first") dep = await firstRegistry(config, datasource, registryUrls);
else if (registryStrategy === "hunt") dep = await huntRegistries(config, datasource, registryUrls);
else if (registryStrategy === "merge") dep = await mergeRegistries(config, datasource, registryUrls);
} else dep = await instrument("getReleases", () => datasource.getReleases(config), { attributes: {
[ATTR_CODE_FUNCTION_NAME]: "getReleases",
[ATTR_RENOVATE_DATASOURCE]: datasource.id,
[ATTR_RENOVATE_REGISTRY_URL]: config.registryUrl ?? "",
[ATTR_RENOVATE_PACKAGE_NAME]: config.packageName
} });
} catch (err) {
if (err.message === "host-disabled" || err.err?.message === "host-disabled") return null;
if (err instanceof ExternalHostError) throw err;
logError(datasource.id, config.packageName, err);
}
if (!dep || dequal(dep, { releases: [] })) return null;
addMetaData(dep, datasourceName, config.packageName);
dep = {
...dep,
...applyReplacements(config)
};
return dep;
}
function fetchCachedReleases(config) {
const { datasource, packageName, registryUrls } = config;
const cacheKey = `datasource-mem:releases:${datasource}:${packageName}:${config.registryStrategy}:${String(registryUrls)}`;
const cachedResult = get(cacheKey);
// istanbul ignore if
if (cachedResult !== void 0) return cachedResult;
const promisedRes = fetchReleases(config);
set(cacheKey, promisedRes);
return promisedRes;
}
function getRawPkgReleases(config) {
if (!config.datasource) {
logger.warn("No datasource found");
return AsyncResult.err("no-datasource");
}
const packageName = config.packageName;
if (!packageName) {
logger.error({ config }, "Datasource getReleases without packageName");
return AsyncResult.err("no-package-name");
}
return Result.wrapNullable(fetchCachedReleases(config), "no-result").catch((e) => {
if (e instanceof ExternalHostError) {
e.hostType = config.datasource;
e.packageName = packageName;
}
return Result.err(e);
}).transform(clone);
}
function applyDatasourceFilters(releaseResult, config) {
let res = releaseResult;
res = applyExtractVersion(res, config.extractVersion);
res = applyVersionCompatibility(res, config.versionCompatibility, config.currentCompatibility);
res = filterValidVersions(res, config);
res = sortAndRemoveDuplicates(res, config);
res = applyConstraintsFiltering(res, config);
return res;
}
async function getPkgReleases(config) {
const { val = null, err } = await getRawPkgReleases(config).transform((res) => applyDatasourceFilters(res, config)).unwrap();
if (err instanceof Error) throw err;
return val;
}
function supportsDigests(datasource) {
const ds = !!datasource && getDatasourceFor(datasource);
return !!ds && "getDigest" in ds;
}
function getDigestConfig(datasource, config) {
const { lookupName, currentValue, currentDigest } = config;
return {
lookupName,
packageName: config.replacementName ?? config.packageName,
registryUrl: config.registryUrl ?? resolveRegistryUrls(datasource, config.defaultRegistryUrls, config.registryUrls, config.additionalRegistryUrls)[0],
currentValue,
currentDigest
};
}
function getDigest(config, value) {
const datasource = getDatasourceFor(config.datasource);
// istanbul ignore if: need test
if (!datasource || !("getDigest" in datasource)) return Promise.resolve(null);
const digestConfig = getDigestConfig(datasource, config);
return datasource.getDigest(digestConfig, value);
}
function getDefaultConfig(datasource) {
const loadedDatasource = getDatasourceFor(datasource);
return Promise.resolve(loadedDatasource?.defaultConfig ?? Object.create({}));
}
//#endregion
export { applyDatasourceFilters, getDatasourceList, getDatasources, getDefaultConfig, getDigest, getPkgReleases, getRawPkgReleases, isGetPkgReleasesConfig, supportsDigests };
//# sourceMappingURL=index.js.map