renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
583 lines • 29.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.lookupUpdates = lookupUpdates;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const config_1 = require("../../../../config");
const error_messages_1 = require("../../../../constants/error-messages");
const logger_1 = require("../../../../logger");
const datasource_1 = require("../../../../modules/datasource");
const common_1 = require("../../../../modules/datasource/common");
const postprocess_release_1 = require("../../../../modules/datasource/postprocess-release");
const manager_1 = require("../../../../modules/manager");
const allVersioning = tslib_1.__importStar(require("../../../../modules/versioning"));
const docker_1 = require("../../../../modules/versioning/docker");
const external_host_error_1 = require("../../../../types/errors/external-host-error");
const assign_keys_1 = require("../../../../util/assign-keys");
const date_1 = require("../../../../util/date");
const package_rules_1 = require("../../../../util/package-rules");
const regex_1 = require("../../../../util/regex");
const result_1 = require("../../../../util/result");
const abandonment_1 = require("./abandonment");
const bucket_1 = require("./bucket");
const current_1 = require("./current");
const filter_1 = require("./filter");
const filter_checks_1 = require("./filter-checks");
const generate_1 = require("./generate");
const rollback_1 = require("./rollback");
const timestamps_1 = require("./timestamps");
const utils_1 = require("./utils");
async function getTimestamp(config, versions, version, versioningApi) {
const currentRelease = versions.find((v) => versioningApi.isValid(v.version) &&
versioningApi.equals(v.version, version));
if (!currentRelease) {
return null;
}
if (currentRelease.releaseTimestamp) {
return currentRelease.releaseTimestamp;
}
const remoteRelease = await (0, postprocess_release_1.postprocessRelease)(config, currentRelease);
return remoteRelease?.releaseTimestamp;
}
async function lookupUpdates(inconfig) {
let config = { ...inconfig };
config.versioning ??= (0, common_1.getDefaultVersioning)(config.datasource);
const versioningApi = allVersioning.get(config.versioning);
let dependency = null;
const res = {
versioning: config.versioning,
updates: [],
warnings: [],
};
try {
logger_1.logger.trace({
dependency: config.packageName,
currentValue: config.currentValue,
}, 'lookupUpdates');
if (config.currentValue && !is_1.default.string(config.currentValue)) {
// If currentValue is not a string, then it's invalid
if (config.currentValue) {
logger_1.logger.debug(`Invalid currentValue for ${config.packageName}: ${JSON.stringify(config.currentValue)} (${typeof config.currentValue})`);
}
res.skipReason = 'invalid-value';
return result_1.Result.ok(res);
}
if (!(0, datasource_1.isGetPkgReleasesConfig)(config) ||
!(0, common_1.getDatasourceFor)(config.datasource)) {
res.skipReason = 'invalid-config';
return result_1.Result.ok(res);
}
let compareValue = config.currentValue;
if (is_1.default.string(config.currentValue) &&
is_1.default.string(config.versionCompatibility)) {
const versionCompatbilityRegEx = (0, regex_1.regEx)(config.versionCompatibility);
const regexMatch = versionCompatbilityRegEx.exec(config.currentValue);
if (regexMatch?.groups) {
logger_1.logger.debug({
versionCompatibility: config.versionCompatibility,
currentValue: config.currentValue,
packageName: config.packageName,
groups: regexMatch.groups,
}, 'version compatibility regex match');
config.currentCompatibility = regexMatch.groups.compatibility;
compareValue = regexMatch.groups.version;
}
else {
logger_1.logger.debug({
versionCompatibility: config.versionCompatibility,
currentValue: config.currentValue,
packageName: config.packageName,
}, 'version compatibility regex mismatch');
}
}
const isValid = is_1.default.string(compareValue) && versioningApi.isValid(compareValue);
const unconstrainedValue = !!config.lockedVersion && is_1.default.undefined(config.currentValue);
if (isValid || unconstrainedValue) {
if (!config.updatePinnedDependencies &&
// TODO #22198
versioningApi.isSingleVersion(compareValue)) {
res.skipReason = 'is-pinned';
return result_1.Result.ok(res);
}
const { val: releaseResult, err: lookupError } = await (0, datasource_1.getRawPkgReleases)(config)
.transform((res) => (0, timestamps_1.calculateMostRecentTimestamp)(versioningApi, res))
.transform((res) => (0, abandonment_1.calculateAbandonment)(res, config))
.transform((res) => (0, datasource_1.applyDatasourceFilters)(res, config))
.unwrap();
if (lookupError instanceof Error) {
throw lookupError;
}
if (lookupError) {
// If dependency lookup fails then warn and return
const warning = {
topic: config.packageName,
message: `Failed to look up ${config.datasource} package ${config.packageName}`,
};
logger_1.logger.debug({
dependency: config.packageName,
packageFile: config.packageFile,
}, warning.message);
// TODO: return warnings in own field
res.warnings.push(warning);
return result_1.Result.ok(res);
}
dependency = releaseResult;
if (dependency.deprecationMessage) {
logger_1.logger.debug(`Found deprecationMessage for ${config.datasource} package ${config.packageName}`);
}
(0, assign_keys_1.assignKeys)(res, dependency, [
'deprecationMessage',
'sourceUrl',
'registryUrl',
'sourceDirectory',
'homepage',
'changelogUrl',
'dependencyUrl',
'lookupName',
'packageScope',
'mostRecentTimestamp',
'isAbandoned',
'respectLatest',
]);
const latestVersion = dependency.tags?.latest;
// Filter out any results from datasource that don't comply with our versioning
let allVersions = dependency.releases.filter((release) => versioningApi.isVersion(release.version));
// istanbul ignore if
if (allVersions.length === 0) {
const message = `Found no results from datasource that look like a version`;
logger_1.logger.info({
dependency: config.packageName,
result: dependency,
}, message);
if (!config.currentDigest) {
return result_1.Result.ok(res);
}
}
// Reapply package rules in case we missed something from sourceUrl
config = await (0, package_rules_1.applyPackageRules)({ ...config, sourceUrl: res.sourceUrl }, 'source-url');
if (config.followTag) {
const taggedVersion = dependency.tags?.[config.followTag];
if (!taggedVersion) {
res.warnings.push({
topic: config.packageName,
message: `Can't find version with tag ${config.followTag} for ${config.datasource} package ${config.packageName}`,
});
return result_1.Result.ok(res);
}
allVersions = allVersions.filter((v) => v.version === taggedVersion ||
(v.version === compareValue &&
versioningApi.isGreaterThan(taggedVersion, compareValue)));
}
// Check that existing constraint can be satisfied
const allSatisfyingVersions = allVersions.filter((v) =>
// TODO #22198
unconstrainedValue || versioningApi.matches(v.version, compareValue));
if (!allSatisfyingVersions.length) {
logger_1.logger.debug(`Found no satisfying versions with '${config.versioning}' versioning`);
}
if (config.rollbackPrs && !allSatisfyingVersions.length) {
const rollback = (0, rollback_1.getRollbackUpdate)(config, allVersions, versioningApi);
// istanbul ignore if
if (!rollback) {
res.warnings.push({
topic: config.packageName,
// TODO: types (#22198)
message: `Can't find version matching ${compareValue} for ${config.datasource} package ${config.packageName}`,
});
return result_1.Result.ok(res);
}
res.updates.push(rollback);
}
let rangeStrategy = (0, manager_1.getRangeStrategy)(config);
// istanbul ignore next
if (config.isVulnerabilityAlert &&
rangeStrategy === 'update-lockfile' &&
!config.lockedVersion) {
rangeStrategy = 'bump';
}
// unconstrained deps with lockedVersion
if (config.isVulnerabilityAlert &&
!config.currentValue &&
config.lockedVersion) {
rangeStrategy = 'update-lockfile';
}
const nonDeprecatedVersions = dependency.releases
.filter((release) => !release.isDeprecated)
.map((release) => release.version);
let currentVersion;
if (rangeStrategy === 'update-lockfile') {
currentVersion = config.lockedVersion;
}
else if (allVersions.find((v) => v.version === compareValue)) {
currentVersion = compareValue;
}
// TODO #22198
currentVersion ??=
(0, current_1.getCurrentVersion)(compareValue, config.lockedVersion, versioningApi, rangeStrategy, latestVersion, nonDeprecatedVersions) ??
(0, current_1.getCurrentVersion)(compareValue, config.lockedVersion, versioningApi, rangeStrategy, latestVersion, allVersions.map((v) => v.version));
if (!currentVersion) {
if (!config.lockedVersion) {
logger_1.logger.debug(`No currentVersion or lockedVersion found for ${config.packageName}`);
res.skipReason = 'invalid-value';
}
return result_1.Result.ok(res);
}
res.currentVersion = currentVersion;
const currentVersionTimestamp = await getTimestamp(config, allVersions, currentVersion, versioningApi);
if (is_1.default.nonEmptyString(currentVersionTimestamp)) {
res.currentVersionTimestamp = currentVersionTimestamp;
res.currentVersionAgeInDays = (0, date_1.getElapsedDays)(currentVersionTimestamp);
if (config.packageRules?.some((rule) => is_1.default.nonEmptyString(rule.matchCurrentAge))) {
// Reapply package rules to check matches for matchCurrentAge
config = await (0, package_rules_1.applyPackageRules)({ ...config, currentVersionTimestamp }, 'current-timestamp');
}
}
if (compareValue &&
currentVersion &&
rangeStrategy === 'pin' &&
!versioningApi.isSingleVersion(compareValue)) {
res.updates.push({
updateType: 'pin',
isPin: true,
// TODO: newValue can be null! (#22198)
newValue: versioningApi.getNewValue({
currentValue: compareValue,
rangeStrategy,
currentVersion,
newVersion: currentVersion,
}),
newVersion: currentVersion,
newMajor: versioningApi.getMajor(currentVersion),
});
}
if (rangeStrategy === 'pin') {
// Fall back to replace once pinning logic is done
rangeStrategy = 'replace';
}
// istanbul ignore if
if (!versioningApi.isVersion(currentVersion)) {
res.skipReason = 'invalid-version';
return result_1.Result.ok(res);
}
// Filter latest, unstable, etc
// TODO #22198
let filteredReleases = (0, filter_1.filterVersions)(config, currentVersion, latestVersion, config.rangeStrategy === 'in-range-only'
? allSatisfyingVersions
: allVersions, versioningApi).filter((v) =>
// Leave only compatible versions
unconstrainedValue ||
versioningApi.isCompatible(v.version, compareValue));
let shrinkedViaVulnerability = false;
if (config.isVulnerabilityAlert) {
if (config.vulnerabilityFixVersion) {
res.vulnerabilityFixVersion = config.vulnerabilityFixVersion;
res.vulnerabilityFixStrategy = config.vulnerabilityFixStrategy;
if (versioningApi.isValid(config.vulnerabilityFixVersion)) {
let fixedFilteredReleases;
if (versioningApi.isVersion(config.vulnerabilityFixVersion)) {
// Retain only releases greater than or equal to the fix version
fixedFilteredReleases = filteredReleases.filter((release) => !versioningApi.isGreaterThan(config.vulnerabilityFixVersion, release.version));
}
else {
// Retain only releases which max the fix constraint
fixedFilteredReleases = filteredReleases.filter((release) => versioningApi.matches(release.version, config.vulnerabilityFixVersion));
}
// Warn if this filtering results caused zero releases
if (fixedFilteredReleases.length === 0 && filteredReleases.length) {
logger_1.logger.warn({
releases: filteredReleases,
vulnerabilityFixVersion: config.vulnerabilityFixVersion,
packageName: config.packageName,
}, 'No releases satisfy vulnerabilityFixVersion');
}
// Use the additionally filtered releases
filteredReleases = fixedFilteredReleases;
}
else {
logger_1.logger.warn({
vulnerabilityFixVersion: config.vulnerabilityFixVersion,
packageName: config.packageName,
}, 'vulnerabilityFixVersion is not valid');
}
}
if (config.vulnerabilityFixStrategy === 'highest') {
// Don't shrink the list of releases - let Renovate use its normal logic
logger_1.logger.once.debug(`Using vulnerabilityFixStrategy=highest for ${config.packageName}`);
}
else {
// Shrink the list of releases to the lowest fixed version
logger_1.logger.once.debug(`Using vulnerabilityFixStrategy=lowest for ${config.packageName}`);
filteredReleases = filteredReleases.slice(0, 1);
shrinkedViaVulnerability = true;
}
}
const buckets = {};
for (const release of filteredReleases) {
const bucket = (0, bucket_1.getBucket)(config,
// TODO #22198
currentVersion, release.version, versioningApi);
if (is_1.default.string(bucket)) {
if (buckets[bucket]) {
buckets[bucket].push(release);
}
else {
buckets[bucket] = [release];
}
}
}
const depResultConfig = (0, config_1.mergeChildConfig)(config, res);
for (const [bucket, releases] of Object.entries(buckets)) {
const sortedReleases = releases.sort((r1, r2) => versioningApi.sortVersions(r1.version, r2.version));
const { release, pendingChecks, pendingReleases } = await (0, filter_checks_1.filterInternalChecks)(depResultConfig, versioningApi, bucket, sortedReleases);
// istanbul ignore next
if (!release) {
return result_1.Result.ok(res);
}
const newVersion = release.version;
const update = await (0, generate_1.generateUpdate)(config, compareValue, versioningApi,
// TODO #22198
rangeStrategy, config.lockedVersion ?? currentVersion, bucket, release);
// #29034
if (config.manager === 'gomod' &&
compareValue?.startsWith('v0.0.0-') &&
update.newValue?.startsWith('v0.0.0-') &&
config.currentDigest !== update.newDigest) {
update.updateType = 'digest';
}
if (pendingChecks) {
update.pendingChecks = pendingChecks;
}
if (pendingReleases.length) {
update.pendingVersions = pendingReleases.map((r) => r.version);
}
if (!update.newValue || update.newValue === compareValue) {
if (!config.lockedVersion) {
continue;
}
// istanbul ignore if
if (rangeStrategy === 'bump') {
logger_1.logger.trace({
packageName: config.packageName,
currentValue: config.currentValue,
lockedVersion: config.lockedVersion,
newVersion,
}, 'Skipping bump because newValue is the same');
continue;
}
res.isSingleVersion = true;
}
res.isSingleVersion ??=
is_1.default.string(update.newValue) &&
versioningApi.isSingleVersion(update.newValue);
// istanbul ignore if
if (config.versioning === docker_1.id &&
update.updateType !== 'rollback' &&
update.newValue &&
versioningApi.isVersion(update.newValue) &&
compareValue &&
versioningApi.isVersion(compareValue) &&
versioningApi.isGreaterThan(compareValue, update.newValue)) {
logger_1.logger.warn({
packageName: config.packageName,
currentValue: config.currentValue,
compareValue,
currentVersion: config.currentVersion,
update,
allVersionsLength: allVersions.length,
filteredReleaseVersions: filteredReleases.map((r) => r.version),
shrinkedViaVulnerability,
}, 'Unexpected downgrade detected: skipping');
}
else {
res.updates.push(update);
}
}
}
else if (compareValue) {
logger_1.logger.debug(`Dependency ${config.packageName} has unsupported/unversioned value ${compareValue} (versioning=${config.versioning})`);
if (!config.pinDigests && !config.currentDigest) {
logger_1.logger.debug(`Skipping ${config.packageName} because no currentDigest or pinDigests`);
res.skipReason = 'invalid-value';
}
else {
delete res.skipReason;
}
}
else {
res.skipReason = 'invalid-value';
}
if ((0, utils_1.isReplacementRulesConfigured)(config)) {
(0, utils_1.addReplacementUpdateIfValid)(res.updates, config);
}
else if (dependency?.replacementName && dependency.replacementVersion) {
res.updates.push({
updateType: 'replacement',
newName: dependency.replacementName,
newValue: dependency.replacementVersion,
});
}
// Record if the dep is fixed to a version
if (config.lockedVersion) {
res.currentVersion = config.lockedVersion;
res.fixedVersion = config.lockedVersion;
}
else if (compareValue && versioningApi.isSingleVersion(compareValue)) {
res.fixedVersion = compareValue.replace((0, regex_1.regEx)(/^=+/), '');
}
// massage versionCompatibility
if (is_1.default.string(config.currentValue) &&
is_1.default.string(compareValue) &&
is_1.default.string(config.versionCompatibility)) {
for (const update of res.updates) {
logger_1.logger.debug({ update });
if (is_1.default.string(config.currentValue) && is_1.default.string(update.newValue)) {
update.newValue = config.currentValue.replace(compareValue, update.newValue);
}
}
}
// Add digests if necessary
if ((0, datasource_1.supportsDigests)(config.datasource)) {
if (config.currentDigest) {
if (!config.digestOneAndOnly || !res.updates.length) {
// digest update
res.updates.push({
updateType: 'digest',
newValue: config.currentValue,
});
}
}
else if (config.pinDigests) {
// Create a pin only if one doesn't already exists
if (!res.updates.some((update) => update.updateType === 'pin')) {
// pin digest
res.updates.push({
isPinDigest: true,
updateType: 'pinDigest',
newValue: config.currentValue,
});
}
}
if (versioningApi.valueToVersion) {
// TODO #22198
res.currentVersion = versioningApi.valueToVersion(res.currentVersion);
for (const update of res.updates || /* istanbul ignore next*/ []) {
// TODO #22198
update.newVersion = versioningApi.valueToVersion(update.newVersion);
}
}
if (res.registryUrl) {
config.registryUrls = [res.registryUrl];
}
// update digest for all
for (const update of res.updates) {
if (config.pinDigests === true || config.currentDigest) {
const getDigestConfig = {
...config,
registryUrl: update.registryUrl ?? res.registryUrl,
lookupName: res.lookupName,
};
// #20304 only pass it for replacement updates, otherwise we get wrong or invalid digest
if (update.updateType !== 'replacement') {
delete getDigestConfig.replacementName;
}
// #20304 don't use lookupName and currentDigest when we replace image name
if (update.updateType === 'replacement' &&
update.newName !== config.packageName) {
delete getDigestConfig.lookupName;
delete getDigestConfig.currentDigest;
}
// TODO #22198
update.newDigest ??=
dependency?.releases.find((r) => r.version === update.newValue)
?.newDigest ??
(await (0, datasource_1.getDigest)(getDigestConfig, update.newValue));
// If the digest could not be determined, report this as otherwise the
// update will be omitted later on without notice.
if (update.newDigest === null) {
logger_1.logger.debug({
packageName: config.packageName,
currentValue: config.currentValue,
datasource: config.datasource,
newValue: update.newValue,
bucket: update.bucket,
}, 'Could not determine new digest for update.');
// Only report a warning if there is a current digest.
// Context: https://github.com/renovatebot/renovate/pull/20175#discussion_r1102615059.
if (config.currentDigest) {
res.warnings.push({
message: `Could not determine new digest for update (${config.datasource} package ${config.packageName})`,
topic: config.packageName,
});
}
}
}
else {
delete update.newDigest;
}
if (update.newVersion) {
const registryUrl = dependency?.releases?.find((release) => release.version === update.newVersion)?.registryUrl;
if (registryUrl && registryUrl !== res.registryUrl) {
update.registryUrl = registryUrl;
}
}
}
}
if (res.updates.length) {
delete res.skipReason;
}
// Strip out any non-changed ones
res.updates = res.updates
.filter((update) => update.newValue !== null || config.currentValue === null)
.filter((update) => update.newDigest !== null)
.filter((update) => (is_1.default.string(update.newName) &&
update.newName !== config.packageName) ||
update.isReplacement === true ||
update.newValue !== config.currentValue ||
update.isLockfileUpdate === true ||
// TODO #22198
(update.newDigest &&
!update.newDigest.startsWith(config.currentDigest)));
// If range strategy specified in config is 'in-range-only', also strip out updates where currentValue !== newValue
if (config.rangeStrategy === 'in-range-only') {
res.updates = res.updates.filter((update) => update.newValue === config.currentValue);
}
// Handle a weird edge case involving followTag and fallbacks
if (config.rollbackPrs && config.followTag) {
res.updates = res.updates.filter((update) => res.updates.length === 1 ||
/* istanbul ignore next */ update.updateType !== 'rollback');
}
const release = res.updates.length > 0
? dependency?.releases.find((r) => r.version === res.updates[0].newValue)
: null;
if (release?.changelogContent) {
res.changelogContent = release.changelogContent;
res.changelogUrl = release.changelogUrl;
}
}
catch (err) /* istanbul ignore next */ {
if (err instanceof external_host_error_1.ExternalHostError) {
return result_1.Result.err(err);
}
if (err instanceof Error && err.message === error_messages_1.CONFIG_VALIDATION) {
return result_1.Result.err(err);
}
logger_1.logger.error({
currentDigest: config.currentDigest,
currentValue: config.currentValue,
datasource: config.datasource,
packageName: config.packageName,
digestOneAndOnly: config.digestOneAndOnly,
followTag: config.followTag,
lockedVersion: config.lockedVersion,
packageFile: config.packageFile,
pinDigests: config.pinDigests,
rollbackPrs: config.rollbackPrs,
isVulnerabilityAlert: config.isVulnerabilityAlert,
updatePinnedDependencies: config.updatePinnedDependencies,
err,
}, 'lookupUpdates error');
res.skipReason = 'internal-error';
}
return result_1.Result.ok(res);
}
//# sourceMappingURL=index.js.map