renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
396 lines • 16.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDatasourceList = exports.getDatasources = exports.isGetPkgReleasesConfig = void 0;
exports.getRawPkgReleases = getRawPkgReleases;
exports.applyDatasourceFilters = applyDatasourceFilters;
exports.getPkgReleases = getPkgReleases;
exports.supportsDigests = supportsDigests;
exports.getDigest = getDigest;
exports.getDefaultConfig = getDefaultConfig;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const dequal_1 = require("dequal");
const global_1 = require("../../config/global");
const error_messages_1 = require("../../constants/error-messages");
const logger_1 = require("../../logger");
const external_host_error_1 = require("../../types/errors/external-host-error");
const array_1 = require("../../util/array");
const memCache = tslib_1.__importStar(require("../../util/cache/memory"));
const packageCache = tslib_1.__importStar(require("../../util/cache/package"));
const clone_1 = require("../../util/clone");
const filter_map_1 = require("../../util/filter-map");
const result_1 = require("../../util/result");
const stats_1 = require("../../util/stats");
const url_1 = require("../../util/url");
const versioning = tslib_1.__importStar(require("../versioning"));
const api_1 = tslib_1.__importDefault(require("./api"));
const common_1 = require("./common");
const metadata_1 = require("./metadata");
const npm_1 = require("./npm");
const npmrc_1 = require("./npm/npmrc");
tslib_1.__exportStar(require("./types"), exports);
var common_2 = require("./common");
Object.defineProperty(exports, "isGetPkgReleasesConfig", { enumerable: true, get: function () { return common_2.isGetPkgReleasesConfig; } });
const getDatasources = () => api_1.default;
exports.getDatasources = getDatasources;
const getDatasourceList = () => Array.from(api_1.default.keys());
exports.getDatasourceList = getDatasourceList;
const cacheNamespace = 'datasource-releases';
// TODO: fix error Type
function logError(datasource, packageName, err) {
const { statusCode, code: errCode, url } = err;
if (statusCode === 404) {
logger_1.logger.debug({ datasource, packageName, url }, 'Datasource 404');
}
else if (statusCode === 401 || statusCode === 403) {
logger_1.logger.debug({ datasource, packageName, url }, 'Datasource unauthorized');
}
else if (errCode) {
logger_1.logger.debug({ datasource, packageName, url, errCode }, 'Datasource connection error');
}
else {
logger_1.logger.debug({ datasource, packageName, err }, 'Datasource unknown error');
}
}
async function getRegistryReleases(datasource, config, registryUrl) {
const cacheKey = `${datasource.id} ${registryUrl} ${config.packageName}`;
if (datasource.caching) {
const cachedResult = await packageCache.get(cacheNamespace, cacheKey);
// istanbul ignore if
if (cachedResult) {
logger_1.logger.trace({ cacheKey }, 'Returning cached datasource response');
stats_1.DatasourceCacheStats.hit(datasource.id, registryUrl, config.packageName);
return cachedResult;
}
stats_1.DatasourceCacheStats.miss(datasource.id, registryUrl, config.packageName);
}
const res = await datasource.getReleases({ ...config, registryUrl });
if (res?.releases.length) {
res.registryUrl ??= registryUrl;
}
// cache non-null responses unless marked as private
if (datasource.caching && res) {
const cachePrivatePackages = global_1.GlobalConfig.get('cachePrivatePackages', false);
if (cachePrivatePackages || !res.isPrivate) {
logger_1.logger.trace({ cacheKey }, 'Caching datasource response');
const cacheMinutes = 15;
await packageCache.set(cacheNamespace, cacheKey, res, cacheMinutes);
stats_1.DatasourceCacheStats.set(datasource.id, registryUrl, config.packageName);
}
else {
stats_1.DatasourceCacheStats.skip(datasource.id, registryUrl, config.packageName);
}
}
return res;
}
function firstRegistry(config, datasource, registryUrls) {
if (registryUrls.length > 1) {
logger_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 external_host_error_1.ExternalHostError) {
throw err;
}
// We'll always save the last-thrown error
caughtError = err;
logger_1.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 = versioning.get(config.versioning);
for (const registryUrl of registryUrls) {
try {
const res = await getRegistryReleases(datasource, config, registryUrl);
if (!res) {
continue;
}
if (!combinedRes) {
// This is the first registry, so we can just use it and continue
combinedRes = res;
continue;
}
if (singleRegistry) {
// This is the second registry
// We need to move the registryUrl from the package level to the release level
for (const release of (0, array_1.coerceArray)(combinedRes.releases)) {
release.registryUrl ??= combinedRes.registryUrl;
}
singleRegistry = false;
}
const releases = (0, array_1.coerceArray)(res.releases);
for (const release of releases) {
// We have more than one registry, so we need to move the registryUrl
// from the package level to the release level before merging
release.registryUrl ??= res.registryUrl;
}
combinedRes.releases.push(...releases);
// Merge the tags from the two results
let tags = combinedRes.tags;
if (tags) {
if (res.tags) {
// Both results had tags, so we need to compare them
for (const tag of ['release', 'latest']) {
const existingTag = combinedRes?.tags?.[tag];
const newTag = res.tags?.[tag];
if (is_1.default.string(newTag) && releaseVersioning.isVersion(newTag)) {
if (is_1.default.string(existingTag) &&
releaseVersioning.isVersion(existingTag)) {
// We need to compare them
if (releaseVersioning.isGreaterThan(newTag, existingTag)) {
// New tag is greater than the existing one
tags[tag] = newTag;
}
}
else {
// Existing tag was not present or not a version
// so we can just use the new one
tags[tag] = newTag;
}
}
}
}
}
else {
// Existing results had no tags, so we can just use the new ones
tags = res.tags;
}
combinedRes = { ...res, ...combinedRes };
if (tags) {
combinedRes.tags = tags;
}
// Remove the registryUrl from the package level when more than one registry
delete combinedRes.registryUrl;
}
catch (err) {
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
lastErr = err;
logger_1.logger.trace({ err }, 'datasource merge failure');
}
}
if (!combinedRes) {
if (lastErr) {
throw lastErr;
}
return null;
}
const seenVersions = new Set();
combinedRes.releases = (0, filter_map_1.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(url_1.trimTrailingSlash);
}
function resolveRegistryUrls(datasource, defaultRegistryUrls, registryUrls, additionalRegistryUrls) {
if (!datasource.customRegistrySupport) {
if (is_1.default.nonEmptyArray(registryUrls) ||
is_1.default.nonEmptyArray(defaultRegistryUrls) ||
is_1.default.nonEmptyArray(additionalRegistryUrls)) {
logger_1.logger.warn({
datasource: datasource.id,
registryUrls,
defaultRegistryUrls,
additionalRegistryUrls,
}, 'Custom registries are not allowed for this datasource and will be ignored');
}
return is_1.default.function(datasource.defaultRegistryUrls)
? datasource.defaultRegistryUrls()
: (datasource.defaultRegistryUrls ?? []);
}
const customUrls = registryUrls?.filter(Boolean);
let resolvedUrls = [];
if (is_1.default.nonEmptyArray(customUrls)) {
resolvedUrls = [...customUrls];
}
else if (is_1.default.nonEmptyArray(defaultRegistryUrls)) {
resolvedUrls = [...defaultRegistryUrls];
resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
}
else if (is_1.default.function(datasource.defaultRegistryUrls)) {
resolvedUrls = [...datasource.defaultRegistryUrls()];
resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
}
else if (is_1.default.nonEmptyArray(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,
};
}
return undefined;
}
async function fetchReleases(config) {
const { datasource: datasourceName } = config;
let { registryUrls } = config;
// istanbul ignore if: need test
if (!datasourceName || (0, common_1.getDatasourceFor)(datasourceName) === undefined) {
logger_1.logger.warn({ datasource: datasourceName }, 'Unknown datasource');
return null;
}
if (datasourceName === 'npm') {
if (is_1.default.string(config.npmrc)) {
(0, npm_1.setNpmrc)(config.npmrc);
}
if (!is_1.default.nonEmptyArray(registryUrls)) {
registryUrls = [(0, npmrc_1.resolveRegistryUrl)(config.packageName)];
}
}
const datasource = (0, common_1.getDatasourceFor)(datasourceName);
// istanbul ignore if: needs test
if (!datasource) {
logger_1.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 (is_1.default.nonEmptyArray(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 datasource.getReleases(config);
}
}
catch (err) {
if (err.message === error_messages_1.HOST_DISABLED || err.err?.message === error_messages_1.HOST_DISABLED) {
return null;
}
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
logError(datasource.id, config.packageName, err);
}
if (!dep || (0, dequal_1.dequal)(dep, { releases: [] })) {
return null;
}
(0, metadata_1.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)}`;
// By returning a Promise and reusing it, we should only fetch each package at most once
const cachedResult = memCache.get(cacheKey);
// istanbul ignore if
if (cachedResult !== undefined) {
return cachedResult;
}
const promisedRes = fetchReleases(config);
memCache.set(cacheKey, promisedRes);
return promisedRes;
}
function getRawPkgReleases(config) {
if (!config.datasource) {
logger_1.logger.warn('No datasource found');
return result_1.AsyncResult.err('no-datasource');
}
const packageName = config.packageName;
if (!packageName) {
logger_1.logger.error({ config }, 'Datasource getReleases without packageName');
return result_1.AsyncResult.err('no-package-name');
}
return result_1.Result.wrapNullable(fetchCachedReleases(config), 'no-result')
.catch((e) => {
if (e instanceof external_host_error_1.ExternalHostError) {
e.hostType = config.datasource;
e.packageName = packageName;
}
return result_1.Result.err(e);
})
.transform(clone_1.clone);
}
function applyDatasourceFilters(releaseResult, config) {
let res = releaseResult;
res = (0, common_1.applyExtractVersion)(res, config.extractVersion);
res = (0, common_1.applyVersionCompatibility)(res, config.versionCompatibility, config.currentCompatibility);
res = (0, common_1.filterValidVersions)(res, config);
res = (0, common_1.sortAndRemoveDuplicates)(res, config);
res = (0, common_1.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 && (0, common_1.getDatasourceFor)(datasource);
return !!ds && 'getDigest' in ds;
}
function getDigestConfig(datasource, config) {
const { lookupName, currentValue, currentDigest } = config;
const packageName = config.replacementName ?? config.packageName;
// Prefer registryUrl from getReleases() lookup if it has been passed
const registryUrl = config.registryUrl ??
resolveRegistryUrls(datasource, config.defaultRegistryUrls, config.registryUrls, config.additionalRegistryUrls)[0];
return { lookupName, packageName, registryUrl, currentValue, currentDigest };
}
function getDigest(config, value) {
const datasource = (0, common_1.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 = (0, common_1.getDatasourceFor)(datasource);
return Promise.resolve(loadedDatasource?.defaultConfig ?? Object.create({}));
}
//# sourceMappingURL=index.js.map