renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
179 lines (178 loc) • 8.08 kB
JavaScript
import { getEnv } from "../../../util/env.js";
import { regEx } from "../../../util/regex.js";
import { logger } from "../../../logger/index.js";
import { ensureTrailingSlash } from "../../../util/url.js";
import { coerceArray } from "../../../util/array.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { createCacheWriteStream, ensureCacheDir, pipeline, readCacheFile, rmCache } from "../../../util/fs/index.js";
import api from "../../versioning/nuget/index.js";
import { get, set } from "../../../util/cache/package/index.js";
import { withCache } from "../../../util/cache/package/with-cache.js";
import { RequestError } from "../../../util/http/got.js";
import "../../../util/http/index.js";
import { asTimestamp } from "../../../util/timestamp.js";
import { all } from "../../../util/promises.js";
import { memCacheProvider } from "../../../util/http/cache/memory-http-cache-provider.js";
import { massageUrl, removeBuildMeta, sortNugetVersions } from "./common.js";
import { CatalogPage, PackageRegistration, ServicesIndexRaw } from "./schema.js";
import { isNonEmptyString } from "@sindresorhus/is";
import upath from "upath";
import semver from "semver";
import { XmlDocument } from "xmldoc";
import AdmZip from "adm-zip";
//#region lib/modules/datasource/nuget/v3.ts
var NugetV3Api = class NugetV3Api {
static cacheNamespace = "datasource-nuget-v3";
async getResourceUrl(http, url, resourceType = "RegistrationsBaseUrl") {
const resultCacheKey = `${url}:${resourceType}`;
const cachedResult = await get(NugetV3Api.cacheNamespace, resultCacheKey);
/* v8 ignore next 3 -- TODO: add test */
if (cachedResult) return cachedResult;
let servicesIndexRaw;
try {
const responseCacheKey = url;
servicesIndexRaw = await get(NugetV3Api.cacheNamespace, responseCacheKey);
if (!servicesIndexRaw) {
servicesIndexRaw = (await http.getJson(url, { cacheProvider: memCacheProvider }, ServicesIndexRaw)).body;
await set(NugetV3Api.cacheNamespace, responseCacheKey, servicesIndexRaw, 4320);
}
const services = servicesIndexRaw.resources.map(({ "@id": serviceId, "@type": t }) => ({
serviceId,
type: t?.split("/")?.shift(),
version: t?.split("/")?.pop()
})).filter(({ type, version }) => type === resourceType && semver.valid(version)).sort((x, y) => x.version && y.version ? semver.compare(x.version, y.version) : /* istanbul ignore next: hard to test */ 0);
if (services.length === 0) {
await set(NugetV3Api.cacheNamespace, resultCacheKey, null, 60);
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.satisfies(version, "^3.0.0")) logger.warn({
url,
version
}, `Nuget: Unknown version returned. Only v3 is supported`);
await set(NugetV3Api.cacheNamespace, resultCacheKey, serviceId, 60);
return serviceId;
} catch (err) {
// istanbul ignore if: not easy testable with nock
if (err instanceof ExternalHostError) throw err;
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"];
if (!url) return [];
items = (await http.getJson(url, CatalogPage)).body.items;
}
return coerceArray(items).map(({ catalogEntry }) => catalogEntry);
}
async getReleases(http, registryUrl, feedUrl, pkgName) {
const url = `${feedUrl.replace(regEx(/\/*$/), "")}/${pkgName.toLowerCase()}/index.json`;
const catalogPages = (await http.getJson(url, PackageRegistration)).body.items;
const catalogEntries = (await all(catalogPages.map((page) => () => this.getCatalogEntry(http, page)))).flat().sort((a, b) => sortNugetVersions(a.version, b.version));
let homepage = null;
let latestStable = null;
let nupkgUrl = null;
const releases = catalogEntries.map(({ version, published, projectUrl, listed, packageContent, deprecation }) => {
const release = { version: removeBuildMeta(version) };
const releaseTimestamp = asTimestamp(published);
if (releaseTimestamp) release.releaseTimestamp = releaseTimestamp;
if (api.isValid(version) && api.isStable(version) && listed) {
latestStable = removeBuildMeta(version);
homepage = projectUrl ? massageUrl(projectUrl) : homepage;
nupkgUrl = massageUrl(packageContent);
}
if (listed === false || deprecation) 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 = removeBuildMeta(last.version);
homepage ??= last.projectUrl ?? null;
nupkgUrl ??= massageUrl(last.packageContent);
}
const dep = { releases };
if (releases.every((release) => release.isDeprecated === true)) dep.deprecationMessage = this.getDeprecationMessage(pkgName);
try {
const packageBaseAddress = await this.getResourceUrl(http, registryUrl, "PackageBaseAddress");
if (isNonEmptyString(packageBaseAddress)) {
const nuspecUrl = `${ensureTrailingSlash(packageBaseAddress)}${pkgName.toLowerCase()}/${latestStable}/${pkgName.toLowerCase()}.nuspec`;
const nuspec = new XmlDocument((await http.getText(nuspecUrl, { cacheProvider: memCacheProvider })).body);
const releaseNotes = nuspec.valueWithPath("metadata.releaseNotes");
if (releaseNotes) dep.changelogContent = releaseNotes;
const sourceUrl = nuspec.valueWithPath("metadata.repository@url");
if (sourceUrl) dep.sourceUrl = massageUrl(sourceUrl);
} else if (nupkgUrl) {
const sourceUrl = await this.getSourceUrlFromNupkg(http, registryUrl, pkgName, latestStable, nupkgUrl);
if (sourceUrl) {
dep.sourceUrl = massageUrl(sourceUrl);
logger.debug(`Determined sourceUrl ${sourceUrl} from ${nupkgUrl}`);
}
}
} catch (err) {
// istanbul ignore if: not easy testable with nock
if (err instanceof ExternalHostError) throw err;
if (err instanceof RequestError && err.response?.statusCode === 404) logger.debug({
registryUrl,
pkgName,
pkgVersion: latestStable
}, `package manifest (.nuspec) not found`);
else logger.debug({
err,
registryUrl,
pkgName,
pkgVersion: latestStable
}, `Cannot obtain sourceUrl`);
}
if (homepage) {
dep.sourceUrl ??= homepage;
dep.homepage ??= homepage;
}
return dep;
}
async _getSourceUrlFromNupkg(http, _registryUrl, packageName, packageVersion, nupkgUrl) {
/* v8 ignore next 4 */
if (!getEnv().RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) {
logger.once.debug("RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set");
return null;
}
const cacheDir = await ensureCacheDir("nuget");
const nupkgFile = upath.join(cacheDir, `${packageName}.${packageVersion}.nupkg`);
const nupkgContentsDir = upath.join(cacheDir, `${packageName}.${packageVersion}`);
const readStream = http.stream(nupkgUrl);
try {
await pipeline(readStream, createCacheWriteStream(nupkgFile));
new AdmZip(nupkgFile).extractAllTo(nupkgContentsDir);
return new XmlDocument(await readCacheFile(upath.join(nupkgContentsDir, `${packageName}.nuspec`), "utf8")).valueWithPath("metadata.repository@url") ?? null;
} finally {
await rmCache(nupkgFile);
await rmCache(nupkgContentsDir);
}
}
getSourceUrlFromNupkg(http, registryUrl, packageName, packageVersion, nupkgUrl) {
return withCache({
namespace: NugetV3Api.cacheNamespace,
key: `source-url:${registryUrl}:${packageName}`,
ttlMinutes: 10080
}, () => this._getSourceUrlFromNupkg(http, registryUrl, packageName, packageVersion, nupkgUrl));
}
getDeprecationMessage(packageName) {
return `The package \`${packageName}\` is deprecated.`;
}
};
//#endregion
export { NugetV3Api };
//# sourceMappingURL=v3.js.map