Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NuGet BaGet]: Added new BaGet service (Self hosted NuGet server) #9805

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions services/baget/baget.service.js
Original file line number Diff line number Diff line change
@@ -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 }
104 changes: 72 additions & 32 deletions services/nuget/nuget-v3-service-family.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -72,23 +74,29 @@ 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',
},
},
})
}

const queryParamSchema = Joi.object({
source: optionalUrl,
}).required()

/*
* Create a version and download service for a NuGet v2 API. Return an object
* containing both services.
Expand All @@ -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 = []

Expand All @@ -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 })
}
Expand All @@ -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 = []

Expand All @@ -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 })
}
Expand Down
Loading