UNPKG

renovate

Version:

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

583 lines • 29.6 kB
"use strict"; 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