mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 23:16:26 +00:00
refactor(datasource/nuget): move v2/v3 API logic to classes (#28117)
This commit is contained in:
parent
fde2dff36d
commit
87bba9d31a
5 changed files with 288 additions and 262 deletions
|
@ -1,6 +1,6 @@
|
|||
import { sortNugetVersions } from './v3';
|
||||
import { sortNugetVersions } from './common';
|
||||
|
||||
describe('modules/datasource/nuget/v3', () => {
|
||||
describe('modules/datasource/nuget/common', () => {
|
||||
it.each<{ version: string; other: string; result: number }>`
|
||||
version | other | result
|
||||
${'invalid1'} | ${'invalid2'} | ${0}
|
|
@ -1,6 +1,7 @@
|
|||
import { logger } from '../../../logger';
|
||||
import { regEx } from '../../../util/regex';
|
||||
import { parseUrl } from '../../../util/url';
|
||||
import { api as versioning } from '../../versioning/nuget';
|
||||
import type { ParsedRegistryUrl } from './types';
|
||||
|
||||
const buildMetaRe = regEx(/\+.+$/g);
|
||||
|
@ -47,3 +48,23 @@ export function parseRegistryUrl(registryUrl: string): ParsedRegistryUrl {
|
|||
const feedUrl = parsedUrl.href;
|
||||
return { feedUrl, protocolVersion };
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two versions. Return:
|
||||
* - `1` if `a > b` or `b` is invalid
|
||||
* - `-1` if `a < b` or `a` is invalid
|
||||
* - `0` if `a == b` or both `a` and `b` are invalid
|
||||
*/
|
||||
export function sortNugetVersions(a: string, b: string): number {
|
||||
if (versioning.isValid(a)) {
|
||||
if (versioning.isValid(b)) {
|
||||
return versioning.sortVersions(a, b);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else if (versioning.isValid(b)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import * as nugetVersioning from '../../versioning/nuget';
|
|||
import { Datasource } from '../datasource';
|
||||
import type { GetReleasesConfig, ReleaseResult } from '../types';
|
||||
import { parseRegistryUrl } from './common';
|
||||
import * as v2 from './v2';
|
||||
import * as v3 from './v3';
|
||||
import { NugetV2Api } from './v2';
|
||||
import { NugetV3Api } from './v3';
|
||||
|
||||
// https://api.nuget.org/v3/index.json is a default official nuget feed
|
||||
export const nugetOrg = 'https://api.nuget.org/v3/index.json';
|
||||
|
@ -18,6 +18,10 @@ export class NugetDatasource extends Datasource {
|
|||
|
||||
override readonly registryStrategy = 'merge';
|
||||
|
||||
readonly v2Api = new NugetV2Api();
|
||||
|
||||
readonly v3Api = new NugetV3Api();
|
||||
|
||||
constructor() {
|
||||
super(NugetDatasource.id);
|
||||
}
|
||||
|
@ -33,12 +37,17 @@ export class NugetDatasource extends Datasource {
|
|||
}
|
||||
const { feedUrl, protocolVersion } = parseRegistryUrl(registryUrl);
|
||||
if (protocolVersion === 2) {
|
||||
return v2.getReleases(this.http, feedUrl, packageName);
|
||||
return this.v2Api.getReleases(this.http, feedUrl, packageName);
|
||||
}
|
||||
if (protocolVersion === 3) {
|
||||
const queryUrl = await v3.getResourceUrl(this.http, feedUrl);
|
||||
const queryUrl = await this.v3Api.getResourceUrl(this.http, feedUrl);
|
||||
if (queryUrl) {
|
||||
return v3.getReleases(this.http, feedUrl, queryUrl, packageName);
|
||||
return this.v3Api.getReleases(
|
||||
this.http,
|
||||
feedUrl,
|
||||
queryUrl,
|
||||
packageName,
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { XmlDocument, XmlElement } from 'xmldoc';
|
||||
import { logger } from '../../../logger';
|
||||
import type { Http } from '../../../util/http';
|
||||
import type { HttpResponse } from '../../../util/http/types';
|
||||
import { regEx } from '../../../util/regex';
|
||||
import type { ReleaseResult } from '../types';
|
||||
import { massageUrl, removeBuildMeta } from './common';
|
||||
|
||||
function getPkgProp(pkgInfo: XmlElement, propName: string): string | undefined {
|
||||
export class NugetV2Api {
|
||||
getPkgProp(pkgInfo: XmlElement, propName: string): string | undefined {
|
||||
return pkgInfo.childNamed('m:properties')?.childNamed(`d:${propName}`)?.val;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getReleases(
|
||||
async getReleases(
|
||||
http: Http,
|
||||
feedUrl: string,
|
||||
pkgName: string,
|
||||
): Promise<ReleaseResult | null> {
|
||||
): Promise<ReleaseResult | null> {
|
||||
const dep: ReleaseResult = {
|
||||
releases: [],
|
||||
};
|
||||
|
@ -24,24 +24,27 @@ export async function getReleases(
|
|||
)}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl,Published`;
|
||||
while (pkgUrlList !== null) {
|
||||
// typescript issue
|
||||
const pkgVersionsListRaw: HttpResponse<string> = await http.get(pkgUrlList);
|
||||
const pkgVersionsListRaw = await http.get(pkgUrlList);
|
||||
const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body);
|
||||
|
||||
const pkgInfoList = pkgVersionsListDoc.childrenNamed('entry');
|
||||
|
||||
for (const pkgInfo of pkgInfoList) {
|
||||
const version = getPkgProp(pkgInfo, 'Version');
|
||||
const releaseTimestamp = getPkgProp(pkgInfo, 'Published');
|
||||
const version = this.getPkgProp(pkgInfo, 'Version');
|
||||
const releaseTimestamp = this.getPkgProp(pkgInfo, 'Published');
|
||||
dep.releases.push({
|
||||
// TODO: types (#22198)
|
||||
version: removeBuildMeta(`${version}`),
|
||||
releaseTimestamp,
|
||||
});
|
||||
try {
|
||||
const pkgIsLatestVersion = getPkgProp(pkgInfo, 'IsLatestVersion');
|
||||
const pkgIsLatestVersion = this.getPkgProp(
|
||||
pkgInfo,
|
||||
'IsLatestVersion',
|
||||
);
|
||||
if (pkgIsLatestVersion === 'true') {
|
||||
dep['tags'] = { latest: removeBuildMeta(`${version}`) };
|
||||
const projectUrl = getPkgProp(pkgInfo, 'ProjectUrl');
|
||||
const projectUrl = this.getPkgProp(pkgInfo, 'ProjectUrl');
|
||||
if (projectUrl) {
|
||||
dep.sourceUrl = massageUrl(projectUrl);
|
||||
}
|
||||
|
@ -67,4 +70,5 @@ export async function getReleases(
|
|||
}
|
||||
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { regEx } from '../../../util/regex';
|
|||
import { ensureTrailingSlash } from '../../../util/url';
|
||||
import { api as versioning } from '../../versioning/nuget';
|
||||
import type { Release, ReleaseResult } from '../types';
|
||||
import { massageUrl, removeBuildMeta } from './common';
|
||||
import { massageUrl, removeBuildMeta, sortNugetVersions } from './common';
|
||||
import type {
|
||||
CatalogEntry,
|
||||
CatalogPage,
|
||||
|
@ -18,17 +18,18 @@ import type {
|
|||
ServicesIndexRaw,
|
||||
} from './types';
|
||||
|
||||
const cacheNamespace = 'datasource-nuget';
|
||||
export class NugetV3Api {
|
||||
static readonly cacheNamespace = 'datasource-nuget';
|
||||
|
||||
export async function getResourceUrl(
|
||||
async getResourceUrl(
|
||||
http: Http,
|
||||
url: string,
|
||||
resourceType = 'RegistrationsBaseUrl',
|
||||
): Promise<string | null> {
|
||||
): Promise<string | null> {
|
||||
// https://docs.microsoft.com/en-us/nuget/api/service-index
|
||||
const resultCacheKey = `${url}:${resourceType}`;
|
||||
const cachedResult = await packageCache.get<string>(
|
||||
cacheNamespace,
|
||||
NugetV3Api.cacheNamespace,
|
||||
resultCacheKey,
|
||||
);
|
||||
|
||||
|
@ -40,14 +41,14 @@ export async function getResourceUrl(
|
|||
try {
|
||||
const responseCacheKey = url;
|
||||
servicesIndexRaw = await packageCache.get<ServicesIndexRaw>(
|
||||
cacheNamespace,
|
||||
NugetV3Api.cacheNamespace,
|
||||
responseCacheKey,
|
||||
);
|
||||
// istanbul ignore else: currently not testable
|
||||
if (!servicesIndexRaw) {
|
||||
servicesIndexRaw = (await http.getJson<ServicesIndexRaw>(url)).body;
|
||||
await packageCache.set(
|
||||
cacheNamespace,
|
||||
NugetV3Api.cacheNamespace,
|
||||
responseCacheKey,
|
||||
servicesIndexRaw,
|
||||
3 * 24 * 60,
|
||||
|
@ -70,7 +71,12 @@ export async function getResourceUrl(
|
|||
);
|
||||
|
||||
if (services.length === 0) {
|
||||
await packageCache.set(cacheNamespace, resultCacheKey, null, 60);
|
||||
await packageCache.set(
|
||||
NugetV3Api.cacheNamespace,
|
||||
resultCacheKey,
|
||||
null,
|
||||
60,
|
||||
);
|
||||
logger.debug(
|
||||
{ url, servicesIndexRaw },
|
||||
`no ${resourceType} services found`,
|
||||
|
@ -93,7 +99,12 @@ export async function getResourceUrl(
|
|||
);
|
||||
}
|
||||
|
||||
await packageCache.set(cacheNamespace, resultCacheKey, serviceId, 60);
|
||||
await packageCache.set(
|
||||
NugetV3Api.cacheNamespace,
|
||||
resultCacheKey,
|
||||
serviceId,
|
||||
60,
|
||||
);
|
||||
return serviceId;
|
||||
} catch (err) {
|
||||
// istanbul ignore if: not easy testable with nock
|
||||
|
@ -106,12 +117,12 @@ export async function getResourceUrl(
|
|||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getCatalogEntry(
|
||||
async getCatalogEntry(
|
||||
http: Http,
|
||||
catalogPage: CatalogPage,
|
||||
): Promise<CatalogEntry[]> {
|
||||
): Promise<CatalogEntry[]> {
|
||||
let items = catalogPage.items;
|
||||
if (!items) {
|
||||
const url = catalogPage['@id'];
|
||||
|
@ -119,40 +130,20 @@ async function getCatalogEntry(
|
|||
items = catalogPageFull.body.items;
|
||||
}
|
||||
return items.map(({ catalogEntry }) => catalogEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two versions. Return:
|
||||
* - `1` if `a > b` or `b` is invalid
|
||||
* - `-1` if `a < b` or `a` is invalid
|
||||
* - `0` if `a == b` or both `a` and `b` are invalid
|
||||
*/
|
||||
export function sortNugetVersions(a: string, b: string): number {
|
||||
if (versioning.isValid(a)) {
|
||||
if (versioning.isValid(b)) {
|
||||
return versioning.sortVersions(a, b);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else if (versioning.isValid(b)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getReleases(
|
||||
async getReleases(
|
||||
http: Http,
|
||||
registryUrl: string,
|
||||
feedUrl: string,
|
||||
pkgName: string,
|
||||
): Promise<ReleaseResult | null> {
|
||||
): Promise<ReleaseResult | null> {
|
||||
const baseUrl = feedUrl.replace(regEx(/\/*$/), '');
|
||||
const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`;
|
||||
const packageRegistration = await http.getJson<PackageRegistration>(url);
|
||||
const catalogPages = packageRegistration.body.items || [];
|
||||
const catalogPagesQueue = catalogPages.map(
|
||||
(page) => (): Promise<CatalogEntry[]> => getCatalogEntry(http, page),
|
||||
(page) => (): Promise<CatalogEntry[]> => this.getCatalogEntry(http, page),
|
||||
);
|
||||
const catalogEntries = (await p.all(catalogPagesQueue))
|
||||
.flat()
|
||||
|
@ -193,7 +184,7 @@ export async function getReleases(
|
|||
};
|
||||
|
||||
try {
|
||||
const packageBaseAddress = await getResourceUrl(
|
||||
const packageBaseAddress = await this.getResourceUrl(
|
||||
http,
|
||||
registryUrl,
|
||||
'PackageBaseAddress',
|
||||
|
@ -241,4 +232,5 @@ export async function getReleases(
|
|||
}
|
||||
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue