diff --git a/services/baget/baget.service.js b/services/baget/baget.service.js new file mode 100644 index 0000000000000..b4ed482cb386a --- /dev/null +++ b/services/baget/baget.service.js @@ -0,0 +1,41 @@ +import { createServiceFamily } from '../nuget/nuget-v3-service-family.js' + +const { NugetVersionService: Version, NugetDownloadService: Downloads } = + createServiceFamily({ + defaultLabel: 'baget', + serviceBaseUrl: 'baget', + withTenant: false, + withFeed: false, + withQueryNamedParams: false, + packageDataIncludesVersion: true, + }) + +class BagetVersionService extends Version { + static examples = [ + { + title: 'Baget', + pattern: 'v/:packageName', + namedParams: { packageName: 'Microsoft.AspNet.Mvc' }, + staticPreview: this.render({ version: '5.2.4' }), + }, + { + title: 'Baget (with prereleases)', + pattern: 'vpre/:packageName', + namedParams: { packageName: 'Microsoft.AspNet.Mvc' }, + staticPreview: this.render({ version: '5.2.5-preview1' }), + }, + ] +} + +class BagetDownloadService extends Downloads { + static examples = [ + { + title: 'Baget', + pattern: 'dt/:packageName', + namedParams: { packageName: 'Microsoft.AspNet.Mvc' }, + staticPreview: this.render({ downloads: 49e6 }), + }, + ] +} + +export { BagetVersionService, BagetDownloadService } diff --git a/services/nuget/nuget-v3-service-family.js b/services/nuget/nuget-v3-service-family.js index 3a82e7b41d1d7..5258f9b8a2b8f 100644 --- a/services/nuget/nuget-v3-service-family.js +++ b/services/nuget/nuget-v3-service-family.js @@ -1,6 +1,7 @@ import Joi from 'joi' import RouteBuilder from '../route-builder.js' import { BaseJsonService, NotFound } from '../index.js' +import { optionalUrl } from '../validators.js' import { renderVersionBadge, renderDownloadBadge, @@ -61,6 +62,7 @@ const schema = Joi.object({ .default([]), totalDownloads: Joi.number().integer(), totaldownloads: Joi.number().integer(), + version: Joi.string().optional(), // Available in BaGet server }), ) .max(1) @@ -72,16 +74,18 @@ const schema = Joi.object({ */ async function fetch( serviceInstance, - { baseUrl, packageName, includePrereleases = false }, + { baseUrl, packageName, includePrereleases = true, queryNamedParams }, ) { return serviceInstance._requestJson({ schema, url: await searchServiceUrl(baseUrl, 'SearchQueryService'), options: { searchParams: { - q: `packageid:${encodeURIComponent(packageName.toLowerCase())}`, + q: queryNamedParams + ? `packageid:${encodeURIComponent(packageName.toLowerCase())}` + : encodeURIComponent(packageName.toLowerCase()), // Include prerelease versions. - prerelease: 'true', + prerelease: includePrereleases, // Include packages with SemVer 2 version numbers. semVerLevel: '2', }, @@ -89,6 +93,10 @@ async function fetch( }) } +const queryParamSchema = Joi.object({ + source: optionalUrl, +}).required() + /* * Create a version and download service for a NuGet v2 API. Return an object * containing both services. @@ -112,14 +120,28 @@ function createServiceFamily({ apiDomain, apiBaseUrl, withFeed = true, + withQueryNamedParams = true, + packageDataIncludesVersion = false, }) { + /** + * Extract source parameters + */ + + function unpackParams({ source = apiBaseUrl }) { + if (source.includes('v3')) return { source } + return { source: `${source}/v3` } + } + class NugetVersionService extends BaseJsonService { static category = 'version' - static route = buildRoute({ serviceBaseUrl, withTenant, withFeed }) - .push('(v|vpre)', 'which') - .push('(.+?)', 'packageName') - .toObject() + static route = { + ...buildRoute({ serviceBaseUrl, withTenant, withFeed }) + .push('(v|vpre)', 'which') + .push('(.+?)', 'packageName') + .toObject(), + queryParamSchema, + } static examples = [] @@ -135,28 +157,38 @@ function createServiceFamily({ * Extract version information from the raw package info. */ transform({ json, includePrereleases }) { - if (json.data.length === 1 && json.data[0].versions.length > 0) { - const { versions: packageVersions } = json.data[0] - const versions = packageVersions.map(item => - stripBuildMetadata(item.version), - ) - return selectVersion(versions, includePrereleases) - } else { + if (json.data.length !== 1 || json.data[0].versions.length <= 0) throw new NotFound({ prettyMessage: 'package not found' }) - } + + // Baget server includes latest package version in the data response + if (packageDataIncludesVersion) return json.data[0].version + + const { versions: packageVersions } = json.data[0] + const versions = packageVersions.map(item => + stripBuildMetadata(item.version), + ) + return selectVersion(versions, includePrereleases) } - async handle({ tenant, feed, which, packageName }) { + async handle({ tenant, feed, which, packageName }, queryParams) { const includePrereleases = which === 'vpre' + + const { source } = unpackParams(queryParams) + const baseUrl = apiUrl({ withTenant, - apiBaseUrl, + apiBaseUrl: source, apiDomain, tenant, withFeed, feed, }) - const json = await fetch(this, { baseUrl, packageName }) + const json = await fetch(this, { + baseUrl, + packageName, + includePrereleases, + queryNamedParams: withQueryNamedParams, + }) const version = this.transform({ json, includePrereleases }) return this.constructor.render({ version, feed }) } @@ -165,10 +197,13 @@ function createServiceFamily({ class NugetDownloadService extends BaseJsonService { static category = 'downloads' - static route = buildRoute({ serviceBaseUrl, withTenant, withFeed }) - .push('dt') - .push('(.+?)', 'packageName') - .toObject() + static route = { + ...buildRoute({ serviceBaseUrl, withTenant, withFeed }) + .push('dt') + .push('(.+?)', 'packageName') + .toObject(), + queryParamSchema, + } static examples = [] @@ -180,26 +215,31 @@ function createServiceFamily({ * Extract download count from the raw package. */ transform({ json }) { - if (json.data.length === 1) { - const packageInfo = json.data[0] - // Official NuGet server uses "totalDownloads" whereas MyGet uses - // "totaldownloads" (lowercase D). Ugh. - return packageInfo.totalDownloads || packageInfo.totaldownloads || 0 - } else { + if (json.data.length !== 1) throw new NotFound({ prettyMessage: 'package not found' }) - } + + const packageInfo = json.data[0] + // Official NuGet server uses "totalDownloads" whereas MyGet uses + // "totaldownloads" (lowercase D). Ugh. + return packageInfo.totalDownloads || packageInfo.totaldownloads || 0 } - async handle({ tenant, feed, which, packageName }) { + async handle({ tenant, feed, which, packageName }, queryParams) { + const { source } = unpackParams(queryParams) + const baseUrl = apiUrl({ withTenant, - apiBaseUrl, + apiBaseUrl: source, apiDomain, tenant, withFeed, feed, }) - const json = await fetch(this, { baseUrl, packageName }) + const json = await fetch(this, { + baseUrl, + packageName, + queryNamedParams: withQueryNamedParams, + }) const downloads = this.transform({ json }) return this.constructor.render({ downloads }) }