renovate/lib/datasource/nuget/v3.ts
2020-06-17 10:07:22 +02:00

135 lines
4.2 KiB
TypeScript

import * as semver from 'semver';
import { XmlDocument } from 'xmldoc';
import { logger } from '../../logger';
import * as globalCache from '../../util/cache/global';
import { Http } from '../../util/http';
import { ReleaseResult } from '../common';
import { id } from './common';
const http = new Http(id);
// https://api.nuget.org/v3/index.json is a default official nuget feed
const defaultNugetFeed = 'https://api.nuget.org/v3/index.json';
const cacheNamespace = 'datasource-nuget';
export function getDefaultFeed(): string {
return defaultNugetFeed;
}
export async function getQueryUrl(url: string): Promise<string | null> {
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
const resourceType = 'SearchQueryService';
const cacheKey = `${url}:${resourceType}`;
const cachedResult = await globalCache.get<string>(cacheNamespace, cacheKey);
// istanbul ignore if
if (cachedResult) {
return cachedResult;
}
try {
// TODO: fix types
const servicesIndexRaw = await http.getJson<any>(url);
const searchQueryService = servicesIndexRaw.body.resources.find(
(resource) =>
resource['@type'] && resource['@type'].startsWith(resourceType)
);
const searchQueryServiceId = searchQueryService['@id'];
const cacheMinutes = 60;
await globalCache.set(
cacheNamespace,
cacheKey,
searchQueryServiceId,
cacheMinutes
);
return searchQueryServiceId;
} catch (e) {
logger.debug(
{ e },
`nuget registry failure: can't get SearchQueryService form ${url}`
);
return null;
}
}
export async function getReleases(
registryUrl: string,
feedUrl: string,
pkgName: string
): Promise<ReleaseResult | null> {
let queryUrl = `${feedUrl}?q=${pkgName}`;
if (registryUrl.toLowerCase() === defaultNugetFeed.toLowerCase()) {
queryUrl = queryUrl.replace('q=', 'q=PackageId:');
queryUrl += '&semVerLevel=2.0.0&prerelease=true';
}
const dep: ReleaseResult = {
pkgName,
releases: [],
};
try {
// TODO: fix types
const pkgUrlListRaw = await http.getJson<any>(queryUrl);
const match = pkgUrlListRaw.body.data.find(
(item) => item.id.toLowerCase() === pkgName.toLowerCase()
);
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-result
if (!match) {
// There are no pkgName or releases in current feed
return null;
}
dep.releases = match.versions.map((item) => ({
version: item.version,
}));
try {
// For nuget.org we have a way to get nuspec file
const sanitizedVersions = dep.releases
.map((release) => semver.valid(release.version))
.filter(Boolean)
.filter((version) => !semver.prerelease(version));
let lastVersion: string;
// istanbul ignore else
if (sanitizedVersions.length) {
// Use the last stable version we found
lastVersion = sanitizedVersions.pop();
} else {
// Just use the last one from the list and hope for the best
lastVersion = [...dep.releases].pop().version;
}
if (registryUrl.toLowerCase() === defaultNugetFeed.toLowerCase()) {
const nugetOrgApi = `https://api.nuget.org/v3-flatcontainer/${pkgName.toLowerCase()}/${lastVersion}/${pkgName.toLowerCase()}.nuspec`;
let metaresult: { body: string };
try {
metaresult = await http.get(nugetOrgApi);
} catch (err) /* istanbul ignore next */ {
logger.debug(
`Cannot fetch metadata for ${pkgName} using popped version ${lastVersion}`
);
return dep;
}
const nuspec = new XmlDocument(metaresult.body);
const sourceUrl = nuspec.valueWithPath('metadata.repository@url');
if (sourceUrl) {
dep.sourceUrl = sourceUrl;
}
} else if (match.projectUrl) {
dep.sourceUrl = match.projectUrl;
}
} catch (err) /* istanbul ignore next */ {
logger.debug(
{ err, pkgName, feedUrl },
`nuget registry failure: can't parse pkg info for project url`
);
}
return dep;
} catch (err) {
logger.debug(
{ err, pkgName, feedUrl },
'nuget registry failure: Unknown error'
);
return null;
}
}