renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
205 lines • 10.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NugetV3Api = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const extract_zip_1 = tslib_1.__importDefault(require("extract-zip"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const upath_1 = tslib_1.__importDefault(require("upath"));
const xmldoc_1 = require("xmldoc");
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 decorator_1 = require("../../../util/cache/package/decorator");
const env_1 = require("../../../util/env");
const fs = tslib_1.__importStar(require("../../../util/fs"));
const fs_1 = require("../../../util/fs");
const http_1 = require("../../../util/http");
const memory_http_cache_provider_1 = require("../../../util/http/cache/memory-http-cache-provider");
const p = tslib_1.__importStar(require("../../../util/promises"));
const regex_1 = require("../../../util/regex");
const timestamp_1 = require("../../../util/timestamp");
const url_1 = require("../../../util/url");
const nuget_1 = require("../../versioning/nuget");
const common_1 = require("./common");
class NugetV3Api {
static cacheNamespace = 'datasource-nuget-v3';
async getResourceUrl(http, url, resourceType = 'RegistrationsBaseUrl') {
// https://docs.microsoft.com/en-us/nuget/api/service-index
const resultCacheKey = `${url}:${resourceType}`;
const cachedResult = await packageCache.get(NugetV3Api.cacheNamespace, resultCacheKey);
/* v8 ignore next 3 -- TODO: add test */
if (cachedResult) {
return cachedResult;
}
let servicesIndexRaw;
try {
const responseCacheKey = url;
servicesIndexRaw = await packageCache.get(NugetV3Api.cacheNamespace, responseCacheKey);
if (!servicesIndexRaw) {
servicesIndexRaw = (await http.getJsonUnchecked(url, {
cacheProvider: memory_http_cache_provider_1.memCacheProvider,
})).body;
await packageCache.set(NugetV3Api.cacheNamespace, responseCacheKey, servicesIndexRaw, 3 * 24 * 60);
}
const services = servicesIndexRaw.resources
.map(({ '@id': serviceId, '@type': t }) => ({
serviceId,
type: t?.split('/')?.shift(),
version: t?.split('/')?.pop(),
}))
.filter(({ type, version }) => type === resourceType && semver_1.default.valid(version))
.sort((x, y) => x.version && y.version
? semver_1.default.compare(x.version, y.version)
: /* istanbul ignore next: hard to test */ 0);
if (services.length === 0) {
await packageCache.set(NugetV3Api.cacheNamespace, resultCacheKey, null, 60);
logger_1.logger.debug({ url, servicesIndexRaw }, `no ${resourceType} services found`);
return null;
}
const { serviceId, version } = services.pop();
// istanbul ignore if
if (resourceType === 'RegistrationsBaseUrl' &&
version &&
!version.startsWith('3.0.0-') &&
!semver_1.default.satisfies(version, '^3.0.0')) {
logger_1.logger.warn({ url, version }, `Nuget: Unknown version returned. Only v3 is supported`);
}
await packageCache.set(NugetV3Api.cacheNamespace, resultCacheKey, serviceId, 60);
return serviceId;
}
catch (err) {
// istanbul ignore if: not easy testable with nock
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
logger_1.logger.debug({ err, url, servicesIndexRaw }, `nuget registry failure: can't get ${resourceType}`);
return null;
}
}
async getCatalogEntry(http, catalogPage) {
let items = catalogPage.items;
if (!items) {
const url = catalogPage['@id'];
const catalogPageFull = await http.getJsonUnchecked(url);
items = catalogPageFull.body.items;
}
return items.map(({ catalogEntry }) => catalogEntry);
}
async getReleases(http, registryUrl, feedUrl, pkgName) {
const baseUrl = feedUrl.replace((0, regex_1.regEx)(/\/*$/), '');
const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`;
const packageRegistration = await http.getJsonUnchecked(url);
const catalogPages = packageRegistration.body.items || [];
const catalogPagesQueue = catalogPages.map((page) => () => this.getCatalogEntry(http, page));
const catalogEntries = (await p.all(catalogPagesQueue))
.flat()
.sort((a, b) => (0, common_1.sortNugetVersions)(a.version, b.version));
let homepage = null;
let latestStable = null;
let nupkgUrl = null;
const releases = catalogEntries.map(({ version, published, projectUrl, listed, packageContent }) => {
const release = { version: (0, common_1.removeBuildMeta)(version) };
const releaseTimestamp = (0, timestamp_1.asTimestamp)(published);
if (releaseTimestamp) {
release.releaseTimestamp = releaseTimestamp;
}
if (nuget_1.api.isValid(version) && nuget_1.api.isStable(version)) {
latestStable = (0, common_1.removeBuildMeta)(version);
homepage = projectUrl ? (0, common_1.massageUrl)(projectUrl) : homepage;
nupkgUrl = (0, common_1.massageUrl)(packageContent);
}
if (listed === false) {
release.isDeprecated = true;
}
return release;
});
if (!releases.length) {
return null;
}
// istanbul ignore next: only happens when no stable version exists
if (latestStable === null && catalogPages.length) {
const last = catalogEntries.pop();
latestStable = (0, common_1.removeBuildMeta)(last.version);
homepage ??= last.projectUrl ?? null;
nupkgUrl ??= (0, common_1.massageUrl)(last.packageContent);
}
const dep = {
releases,
};
try {
const packageBaseAddress = await this.getResourceUrl(http, registryUrl, 'PackageBaseAddress');
if (is_1.default.nonEmptyString(packageBaseAddress)) {
const nuspecUrl = `${(0, url_1.ensureTrailingSlash)(packageBaseAddress)}${pkgName.toLowerCase()}/${
// TODO: types (#22198)
latestStable}/${pkgName.toLowerCase()}.nuspec`;
const metaresult = await http.getText(nuspecUrl, {
cacheProvider: memory_http_cache_provider_1.memCacheProvider,
});
const nuspec = new xmldoc_1.XmlDocument(metaresult.body);
const sourceUrl = nuspec.valueWithPath('metadata.repository@url');
if (sourceUrl) {
dep.sourceUrl = (0, common_1.massageUrl)(sourceUrl);
}
}
else if (nupkgUrl) {
const sourceUrl = await this.getSourceUrlFromNupkg(http, registryUrl, pkgName, latestStable, nupkgUrl);
if (sourceUrl) {
dep.sourceUrl = (0, common_1.massageUrl)(sourceUrl);
logger_1.logger.debug(`Determined sourceUrl ${sourceUrl} from ${nupkgUrl}`);
}
}
}
catch (err) {
// istanbul ignore if: not easy testable with nock
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
// ignore / silence 404. Seen on proget, if remote connector is used and package is not yet cached
if (err instanceof http_1.HttpError && err.response?.statusCode === 404) {
logger_1.logger.debug({ registryUrl, pkgName, pkgVersion: latestStable }, `package manifest (.nuspec) not found`);
}
else {
logger_1.logger.debug({ err, registryUrl, pkgName, pkgVersion: latestStable }, `Cannot obtain sourceUrl`);
}
}
if (homepage) {
// only assign if not assigned
dep.sourceUrl ??= homepage;
dep.homepage ??= homepage;
}
return dep;
}
async getSourceUrlFromNupkg(http, _registryUrl, packageName, packageVersion, nupkgUrl) {
/* v8 ignore next 4 */
if (!(0, env_1.getEnv)().RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) {
logger_1.logger.once.debug('RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set');
return null;
}
const cacheDir = await (0, fs_1.ensureCacheDir)('nuget');
const nupkgFile = upath_1.default.join(cacheDir, `${packageName}.${packageVersion}.nupkg`);
const nupkgContentsDir = upath_1.default.join(cacheDir, `${packageName}.${packageVersion}`);
const readStream = http.stream(nupkgUrl);
try {
const writeStream = fs.createCacheWriteStream(nupkgFile);
await fs.pipeline(readStream, writeStream);
await (0, extract_zip_1.default)(nupkgFile, { dir: nupkgContentsDir });
const nuspecFile = upath_1.default.join(nupkgContentsDir, `${packageName}.nuspec`);
const nuspec = new xmldoc_1.XmlDocument(await fs.readCacheFile(nuspecFile, 'utf8'));
return nuspec.valueWithPath('metadata.repository@url') ?? null;
}
finally {
await fs.rmCache(nupkgFile);
await fs.rmCache(nupkgContentsDir);
}
}
}
exports.NugetV3Api = NugetV3Api;
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: NugetV3Api.cacheNamespace,
key: (_http, registryUrl, packageName, _packageVersion, _nupkgUrl) => `source-url:${registryUrl}:${packageName}`,
ttlMinutes: 10080, // 1 week
})
], NugetV3Api.prototype, "getSourceUrlFromNupkg", null);
//# sourceMappingURL=v3.js.map