UNPKG

renovate

Version:

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

217 lines • 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CACHE_REVISION = void 0; exports.getDependency = getDependency; const tslib_1 = require("tslib"); const node_url_1 = tslib_1.__importDefault(require("node:url")); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const luxon_1 = require("luxon"); const zod_1 = require("zod"); 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 packageCache = tslib_1.__importStar(require("../../../util/cache/package")); const hostRules = tslib_1.__importStar(require("../../../util/host-rules")); const regex_1 = require("../../../util/regex"); const stats_1 = require("../../../util/stats"); const timestamp_1 = require("../../../util/timestamp"); const url_1 = require("../../../util/url"); exports.CACHE_REVISION = 1; const SHORT_REPO_REGEX = (0, regex_1.regEx)(/^((?<platform>bitbucket|github|gitlab):)?(?<shortRepo>[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/); const platformMapping = { bitbucket: 'https://bitbucket.org/', github: 'https://github.com/', gitlab: 'https://gitlab.com/', }; const PackageSource = zod_1.z .union([ zod_1.z .string() .nonempty() .transform((repository) => { let sourceUrl = null; const sourceDirectory = null; const shortMatch = repository.match(SHORT_REPO_REGEX); if (shortMatch?.groups) { const { platform = 'github', shortRepo } = shortMatch.groups; sourceUrl = platformMapping[platform] + shortRepo; } else { sourceUrl = repository; } return { sourceUrl, sourceDirectory }; }), zod_1.z .object({ url: zod_1.z.string().nonempty().nullish(), directory: zod_1.z.string().nonempty().nullish(), }) .transform(({ url, directory }) => { const res = { sourceUrl: null, sourceDirectory: null }; if (url) { res.sourceUrl = url; } if (directory) { res.sourceDirectory = directory; } return res; }), ]) .catch({ sourceUrl: null, sourceDirectory: null }); async function getDependency(http, registryUrl, packageName) { logger_1.logger.trace(`npm.getDependency(${packageName})`); const packageUrl = (0, url_1.joinUrlParts)(registryUrl, packageName.replace('/', '%2F')); // Now check the persistent cache const cacheNamespace = 'datasource-npm:data'; const cachedResult = await packageCache.get(cacheNamespace, packageUrl); if (cachedResult?.cacheData) { if (cachedResult.cacheData.revision === exports.CACHE_REVISION) { const softExpireAt = luxon_1.DateTime.fromISO(cachedResult.cacheData.softExpireAt); if (softExpireAt.isValid && softExpireAt > luxon_1.DateTime.local()) { logger_1.logger.trace('Cached result is not expired - reusing'); stats_1.HttpCacheStats.incLocalHits(packageUrl); delete cachedResult.cacheData; return cachedResult; } logger_1.logger.trace('Cached result is soft expired'); stats_1.HttpCacheStats.incLocalMisses(packageUrl); } else { logger_1.logger.trace(`Package cache for npm package "${packageName}" is from an old revision - discarding`); delete cachedResult.cacheData; } } const cacheMinutes = 15; const softExpireAt = luxon_1.DateTime.local().plus({ minutes: cacheMinutes }).toISO(); let cacheHardTtlMinutes = global_1.GlobalConfig.get('cacheHardTtlMinutes'); if (!(is_1.default.number(cacheHardTtlMinutes) && /* istanbul ignore next: needs test */ cacheHardTtlMinutes > cacheMinutes)) { cacheHardTtlMinutes = cacheMinutes; } const uri = node_url_1.default.parse(packageUrl); try { const options = {}; if (cachedResult?.cacheData?.etag) { logger_1.logger.trace({ packageName }, 'Using cached etag'); options.headers = { 'If-None-Match': cachedResult.cacheData.etag }; } // set abortOnError for registry.npmjs.org if no hostRule with explicit abortOnError exists if (registryUrl === 'https://registry.npmjs.org' && hostRules.find({ url: 'https://registry.npmjs.org' })?.abortOnError === undefined) { logger_1.logger.trace({ packageName, registry: 'https://registry.npmjs.org' }, 'setting abortOnError hostRule for well known host'); hostRules.add({ matchHost: 'https://registry.npmjs.org', abortOnError: true, }); } const raw = await http.getJsonUnchecked(packageUrl, options); if (cachedResult?.cacheData && raw.statusCode === 304) { logger_1.logger.trace(`Cached npm result for ${packageName} is revalidated`); stats_1.HttpCacheStats.incRemoteHits(packageUrl); cachedResult.cacheData.softExpireAt = softExpireAt; await packageCache.set(cacheNamespace, packageUrl, cachedResult, cacheHardTtlMinutes); delete cachedResult.cacheData; return cachedResult; } stats_1.HttpCacheStats.incRemoteMisses(packageUrl); const etag = raw.headers.etag; const res = raw.body; if (!res.versions || !Object.keys(res.versions).length) { // Registry returned a 200 OK but with no versions logger_1.logger.debug(`No versions returned for npm dependency ${packageName}`); return null; } const latestVersion = res.versions[res['dist-tags']?.latest ?? '']; res.repository ??= latestVersion?.repository; res.homepage ??= latestVersion?.homepage; const { sourceUrl, sourceDirectory } = PackageSource.parse(res.repository); // Simplify response before caching and returning const dep = { homepage: res.homepage, releases: [], tags: res['dist-tags'], registryUrl, }; if (sourceUrl) { dep.sourceUrl = sourceUrl; } if (sourceDirectory) { dep.sourceDirectory = sourceDirectory; } if (latestVersion?.deprecated) { dep.deprecationMessage = `On registry \`${registryUrl}\`, the "latest" version of dependency \`${packageName}\` has the following deprecation notice:\n\n\`${latestVersion.deprecated}\`\n\nMarking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake.`; } dep.releases = Object.keys(res.versions).map((version) => { const release = { version, gitRef: res.versions?.[version].gitHead, dependencies: res.versions?.[version].dependencies, devDependencies: res.versions?.[version].devDependencies, }; const releaseTimestamp = (0, timestamp_1.asTimestamp)(res.time?.[version]); if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } if (res.versions?.[version].deprecated) { release.isDeprecated = true; } const nodeConstraint = res.versions?.[version].engines?.node; if (is_1.default.nonEmptyString(nodeConstraint)) { release.constraints = { node: [nodeConstraint] }; } const source = PackageSource.parse(res.versions?.[version].repository); if (source.sourceUrl && source.sourceUrl !== dep.sourceUrl) { release.sourceUrl = source.sourceUrl; } if (source.sourceDirectory && source.sourceDirectory !== dep.sourceDirectory) { release.sourceDirectory = source.sourceDirectory; } if (dep.deprecationMessage) { release.isDeprecated = true; } return release; }); logger_1.logger.trace({ dep }, 'dep'); const cacheControl = raw.headers?.['cache-control']; if (is_1.default.nonEmptyString(cacheControl) && (0, regex_1.regEx)(/(^|,)\s*public\s*(,|$)/).test(cacheControl)) { dep.isPrivate = false; const cacheData = { revision: exports.CACHE_REVISION, softExpireAt, etag }; await packageCache.set(cacheNamespace, packageUrl, { ...dep, cacheData }, etag ? /* istanbul ignore next: needs test */ cacheHardTtlMinutes : cacheMinutes); } else { dep.isPrivate = true; } return dep; } catch (err) { const actualError = err instanceof external_host_error_1.ExternalHostError ? err.err : err; const ignoredStatusCodes = [401, 402, 403, 404]; const ignoredResponseCodes = ['ENOTFOUND']; if (actualError.message === error_messages_1.HOST_DISABLED || ignoredStatusCodes.includes(actualError.statusCode) || ignoredResponseCodes.includes(actualError.code)) { return null; } if (err instanceof external_host_error_1.ExternalHostError) { if (cachedResult) { logger_1.logger.warn({ err, host: uri.host }, `npm host error, reusing expired cached result instead`); delete cachedResult.cacheData; return cachedResult; } if (actualError.name === 'ParseError' && actualError.body) { actualError.body = 'err.body deleted by Renovate'; err.err = actualError; } throw err; } logger_1.logger.debug({ err }, 'Unknown npm lookup error'); return null; } } //# sourceMappingURL=get.js.map