UNPKG

renovate

Version:

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

205 lines • 10.3 kB
"use strict"; 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