renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
217 lines • 10.1 kB
JavaScript
"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