Rewrite [NuGet] badges including [myget chocolatey resharper powershellgallery] (#2257)
The NuGet badge examples are straggling in all-badge-examples. Rather than move them as is, I thought it made more sense to refactor the services and see if they could be generated. I didn't take that on here; this is a straight rewrite of the badges. The old implementations were fairly difficult to follow. The new implementations are complicated too, though I hope much more readable. Though the NuGet behaviors could be consolidated into a single flag, I split `withTenant` and `withFeed` into separate flags, thinking naming the behaviors makes the implementations easier to understand. I defaulted these to true, thinking that really this is really a MyGet implementation which is generalized to NuGet. Though maybe it makes more sense to have the MyGet style as the default. Probably it doesn't matter much either way. I added a helper class ServiceUrlBuilder to construct the Shields service URL. It's useful in this complex case where the URL must be built up conditionally. This might be useful in a couple other places. I also wrote a new service to handle the Powershell badges. They've diverged a little bit from the Nuget v2. There's a bit of shared code which I factored out. If the XML Nuget APIs are more reliable, we could consider switching everything else over to them, though for now I would like to get this merged and get #2078 fixed. Fix #2078
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const { Inaccessible, InvalidResponse } = require('../services/errors')
|
||||
|
||||
// Map from URL to { timestamp: last fetch time, data: data }.
|
||||
let regularUpdateCache = Object.create(null)
|
||||
|
||||
@@ -42,16 +44,32 @@ function regularUpdate(
|
||||
}
|
||||
request(url, options, (err, res, buffer) => {
|
||||
if (err != null) {
|
||||
cb(err)
|
||||
cb(
|
||||
new Inaccessible({
|
||||
prettyMessage: 'intermediate resource inaccessible',
|
||||
underlyingError: err,
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'intermediate resource inaccessible',
|
||||
})
|
||||
}
|
||||
|
||||
let reqData
|
||||
if (json) {
|
||||
try {
|
||||
reqData = JSON.parse(buffer)
|
||||
} catch (e) {
|
||||
cb(e)
|
||||
cb(
|
||||
new InvalidResponse({
|
||||
prettyMessage: 'unparseable intermediate json response',
|
||||
underlyingError: e,
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
9
services/chocolatey/chocolatey.service.js
Normal file
9
services/chocolatey/chocolatey.service.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const { createServiceFamily } = require('../nuget/nuget-v2-service-family')
|
||||
|
||||
module.exports = createServiceFamily({
|
||||
defaultLabel: 'chocolatey',
|
||||
serviceBaseUrl: 'chocolatey',
|
||||
apiBaseUrl: 'https://www.chocolatey.org/api/v2',
|
||||
})
|
||||
@@ -43,11 +43,11 @@ t.create('total downloads (unexpected response)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'unparseable json response' })
|
||||
|
||||
// version
|
||||
|
||||
@@ -65,7 +65,7 @@ t.create('version (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonWithDash)
|
||||
)
|
||||
@@ -80,7 +80,7 @@ t.create('version (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -95,7 +95,7 @@ t.create('version (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -119,11 +119,11 @@ t.create('version (unexpected response)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'chocolatey', value: 'invalid' })
|
||||
.expectJSON({ name: 'chocolatey', value: 'unparseable json response' })
|
||||
|
||||
// version (pre)
|
||||
|
||||
@@ -141,7 +141,7 @@ t.create('version (pre) (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonWithDash)
|
||||
)
|
||||
@@ -156,7 +156,7 @@ t.create('version (pre) (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -171,7 +171,7 @@ t.create('version (pre) (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -195,8 +195,8 @@ t.create('version (pre) (unexpected response)')
|
||||
.intercept(nock =>
|
||||
nock('https://www.chocolatey.org')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'chocolatey', value: 'invalid' })
|
||||
.expectJSON({ name: 'chocolatey', value: 'unparseable json response' })
|
||||
|
||||
9
services/myget/myget.service.js
Normal file
9
services/myget/myget.service.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const { createServiceFamily } = require('../nuget/nuget-v3-service-family')
|
||||
|
||||
module.exports = createServiceFamily({
|
||||
defaultLabel: 'myget',
|
||||
serviceBaseUrl: 'myget',
|
||||
apiDomain: 'myget.org',
|
||||
})
|
||||
@@ -4,7 +4,6 @@ const Joi = require('joi')
|
||||
const ServiceTester = require('../service-tester')
|
||||
const {
|
||||
isMetric,
|
||||
isVPlusDottedVersionNClauses,
|
||||
isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
} = require('../test-validators')
|
||||
const colorscheme = require('../../lib/colorscheme.json')
|
||||
@@ -16,13 +15,22 @@ const {
|
||||
} = require('../nuget-fixtures')
|
||||
const { invalidJSON } = require('../response-fixtures')
|
||||
|
||||
const t = new ServiceTester({ id: 'myget', title: 'MyGet' })
|
||||
const t = new ServiceTester({ id: 'myget', title: 'MyGet', pathPrefix: '' })
|
||||
module.exports = t
|
||||
|
||||
// downloads
|
||||
|
||||
t.create('total downloads (valid)')
|
||||
.get('/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'downloads',
|
||||
value: isMetric,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('total downloads (tenant)')
|
||||
.get('/dotnet.myget/dotnet-coreclr/dt/Microsoft.DotNet.CoreCLR.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'downloads',
|
||||
@@ -31,25 +39,33 @@ t.create('total downloads (valid)')
|
||||
)
|
||||
|
||||
t.create('total downloads (not found)')
|
||||
.get('/mongodb/dt/not-a-real-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'not found' })
|
||||
.get('/myget/mongodb/dt/not-a-real-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'package not found' })
|
||||
|
||||
// This tests the erroring behavior in regular-update.
|
||||
t.create('total downloads (connection error)')
|
||||
.get('/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'downloads', value: 'inaccessible' })
|
||||
.expectJSON({
|
||||
name: 'downloads',
|
||||
value: 'intermediate resource inaccessible',
|
||||
})
|
||||
|
||||
// This tests the erroring behavior in regular-update.
|
||||
t.create('total downloads (unexpected first response)')
|
||||
.get('/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
.expectJSON({
|
||||
name: 'downloads',
|
||||
value: 'unparseable intermediate json response',
|
||||
})
|
||||
|
||||
t.create('total downloads (unexpected second response)')
|
||||
.get('/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/dt/MongoDB.Driver.Core.json')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -58,25 +74,34 @@ t.create('total downloads (unexpected second response)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'unparseable json response' })
|
||||
|
||||
// version
|
||||
|
||||
t.create('version (valid)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/v/MongoDB.Driver.Core.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'mongodb',
|
||||
value: isVPlusDottedVersionNClauses,
|
||||
value: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('total downloads (tenant)')
|
||||
.get('/dotnet.myget/dotnet-coreclr/v/Microsoft.DotNet.CoreCLR.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'dotnet-coreclr',
|
||||
value: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('version (mocked, yellow badge)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.get('/myget/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -85,7 +110,7 @@ t.create('version (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonWithDash)
|
||||
)
|
||||
@@ -96,7 +121,7 @@ t.create('version (mocked, yellow badge)')
|
||||
})
|
||||
|
||||
t.create('version (mocked, orange badge)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.get('/myget/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -105,7 +130,7 @@ t.create('version (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -116,7 +141,7 @@ t.create('version (mocked, orange badge)')
|
||||
})
|
||||
|
||||
t.create('version (mocked, blue badge)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.get('/myget/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -125,7 +150,7 @@ t.create('version (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -136,25 +161,11 @@ t.create('version (mocked, blue badge)')
|
||||
})
|
||||
|
||||
t.create('version (not found)')
|
||||
.get('/foo/v/not-a-real-package.json')
|
||||
.expectJSON({ name: 'foo', value: 'not found' })
|
||||
|
||||
t.create('version (connection error)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'mongodb', value: 'inaccessible' })
|
||||
|
||||
t.create('version (unexpected first response)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'mongodb', value: 'invalid' })
|
||||
.get('/myget/foo/v/not-a-real-package.json')
|
||||
.expectJSON({ name: 'myget', value: 'package not found' })
|
||||
|
||||
t.create('version (unexpected second response)')
|
||||
.get('/mongodb/v/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/v/MongoDB.Driver.Core.json')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -163,16 +174,16 @@ t.create('version (unexpected second response)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'mongodb', value: 'invalid' })
|
||||
.expectJSON({ name: 'myget', value: 'unparseable json response' })
|
||||
|
||||
// version (pre)
|
||||
|
||||
t.create('version (pre) (valid)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'mongodb',
|
||||
@@ -181,7 +192,7 @@ t.create('version (pre) (valid)')
|
||||
)
|
||||
|
||||
t.create('version (pre) (mocked, yellow badge)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -190,7 +201,7 @@ t.create('version (pre) (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonWithDash)
|
||||
)
|
||||
@@ -201,7 +212,7 @@ t.create('version (pre) (mocked, yellow badge)')
|
||||
})
|
||||
|
||||
t.create('version (pre) (mocked, orange badge)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -210,7 +221,7 @@ t.create('version (pre) (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -221,7 +232,7 @@ t.create('version (pre) (mocked, orange badge)')
|
||||
})
|
||||
|
||||
t.create('version (pre) (mocked, blue badge)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -230,7 +241,7 @@ t.create('version (pre) (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -241,25 +252,11 @@ t.create('version (pre) (mocked, blue badge)')
|
||||
})
|
||||
|
||||
t.create('version (pre) (not found)')
|
||||
.get('/foo/vpre/not-a-real-package.json')
|
||||
.expectJSON({ name: 'foo', value: 'not found' })
|
||||
|
||||
t.create('version (pre) (connection error)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'mongodb', value: 'inaccessible' })
|
||||
|
||||
t.create('version (pre) (unexpected first response)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'mongodb', value: 'invalid' })
|
||||
.get('/myget/foo/vpre/not-a-real-package.json')
|
||||
.expectJSON({ name: 'myget', value: 'package not found' })
|
||||
|
||||
t.create('version (pre) (unexpected second response)')
|
||||
.get('/mongodb/vpre/MongoDB.Driver.Core.json')
|
||||
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json')
|
||||
.intercept(nock =>
|
||||
nock('https://www.myget.org')
|
||||
.get('/F/mongodb/api/v3/index.json')
|
||||
@@ -268,8 +265,8 @@ t.create('version (pre) (unexpected second response)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:mongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'mongodb', value: 'invalid' })
|
||||
.expectJSON({ name: 'myget', value: 'unparseable json response' })
|
||||
|
||||
@@ -11,23 +11,26 @@ const queryIndex = JSON.stringify({
|
||||
|
||||
const nuGetV2VersionJsonWithDash = JSON.stringify({
|
||||
d: {
|
||||
results: [{ NormalizedVersion: '1.2-beta' }],
|
||||
results: [
|
||||
{ NormalizedVersion: '1.2-beta', Version: 'xxx', DownloadCount: 0 },
|
||||
],
|
||||
},
|
||||
})
|
||||
const nuGetV2VersionJsonFirstCharZero = JSON.stringify({
|
||||
d: {
|
||||
results: [{ NormalizedVersion: '0.35' }],
|
||||
results: [{ NormalizedVersion: '0.35', Version: 'xxx', DownloadCount: 0 }],
|
||||
},
|
||||
})
|
||||
const nuGetV2VersionJsonFirstCharNotZero = JSON.stringify({
|
||||
d: {
|
||||
results: [{ NormalizedVersion: '1.2.7' }],
|
||||
results: [{ NormalizedVersion: '1.2.7', Version: 'xxx', DownloadCount: 0 }],
|
||||
},
|
||||
})
|
||||
|
||||
const nuGetV3VersionJsonWithDash = JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
totalDownloads: 0,
|
||||
versions: [{ version: '1.2-beta' }],
|
||||
},
|
||||
],
|
||||
@@ -35,6 +38,7 @@ const nuGetV3VersionJsonWithDash = JSON.stringify({
|
||||
const nuGetV3VersionJsonFirstCharZero = JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
totalDownloads: 0,
|
||||
versions: [{ version: '0.35' }],
|
||||
},
|
||||
],
|
||||
@@ -42,6 +46,7 @@ const nuGetV3VersionJsonFirstCharZero = JSON.stringify({
|
||||
const nuGetV3VersionJsonFirstCharNotZero = JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
totalDownloads: 0,
|
||||
versions: [{ version: '1.2.7' }],
|
||||
},
|
||||
],
|
||||
|
||||
35
services/nuget/nuget-helpers.js
Normal file
35
services/nuget/nuget-helpers.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
|
||||
const { metric, addv } = require('../../lib/text-formatters')
|
||||
const {
|
||||
downloadCount: downloadCountColor,
|
||||
} = require('../../lib/color-formatters')
|
||||
|
||||
function renderVersionBadge({ version, feed }) {
|
||||
let color
|
||||
if (version.includes('-')) {
|
||||
color = 'yellow'
|
||||
} else if (version.startsWith('0')) {
|
||||
color = 'orange'
|
||||
} else {
|
||||
color = 'blue'
|
||||
}
|
||||
|
||||
return {
|
||||
message: addv(version),
|
||||
color,
|
||||
label: feed,
|
||||
}
|
||||
}
|
||||
|
||||
function renderDownloadBadge({ downloads }) {
|
||||
return {
|
||||
message: metric(downloads),
|
||||
color: downloadCountColor(downloads),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
renderVersionBadge,
|
||||
renderDownloadBadge,
|
||||
}
|
||||
142
services/nuget/nuget-v2-service-family.js
Normal file
142
services/nuget/nuget-v2-service-family.js
Normal file
@@ -0,0 +1,142 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const BaseJsonService = require('../base-json')
|
||||
const { NotFound } = require('../errors')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { renderVersionBadge, renderDownloadBadge } = require('./nuget-helpers')
|
||||
|
||||
function createFilter({ packageName, includePrereleases }) {
|
||||
const releaseTypeFilter = includePrereleases
|
||||
? 'IsAbsoluteLatestVersion eq true'
|
||||
: 'IsLatestVersion eq true'
|
||||
return `Id eq '${packageName}' and ${releaseTypeFilter}`
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
d: Joi.object({
|
||||
results: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
Version: Joi.string(),
|
||||
NormalizedVersion: Joi.string(),
|
||||
DownloadCount: nonNegativeInteger,
|
||||
})
|
||||
)
|
||||
.max(1)
|
||||
.default([]),
|
||||
}).required(),
|
||||
}).required()
|
||||
|
||||
async function fetch(
|
||||
serviceInstance,
|
||||
{ baseUrl, packageName, includePrereleases = false }
|
||||
) {
|
||||
const data = await serviceInstance._requestJson({
|
||||
schema,
|
||||
url: `${baseUrl}/Packages()`,
|
||||
options: {
|
||||
headers: { Accept: 'application/atom+json,application/json' },
|
||||
qs: { $filter: createFilter({ packageName, includePrereleases }) },
|
||||
},
|
||||
})
|
||||
|
||||
const packageData = data.d.results[0]
|
||||
|
||||
if (packageData) {
|
||||
return packageData
|
||||
} else if (!includePrereleases) {
|
||||
return fetch(serviceInstance, {
|
||||
baseUrl,
|
||||
packageName,
|
||||
includePrereleases: true,
|
||||
})
|
||||
} else {
|
||||
throw new NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a version and download service for a NuGet v2 API. Return an object
|
||||
* containing both services.
|
||||
*
|
||||
* defaultLabel: The label for the left hand side of the badge.
|
||||
* serviceBaseUrl: The base URL for the Shields service, e.g. chocolatey, resharper
|
||||
* apiBaseUrl: The complete base URL of the API, e.g. https://api.example.com/api/v2
|
||||
*/
|
||||
function createServiceFamily({ defaultLabel, serviceBaseUrl, apiBaseUrl }) {
|
||||
class NugetVersionService extends BaseJsonService {
|
||||
static get category() {
|
||||
return 'version'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: serviceBaseUrl,
|
||||
pattern: ':which(v|vpre)/:packageName',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return []
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: defaultLabel,
|
||||
}
|
||||
}
|
||||
|
||||
static render(props) {
|
||||
return renderVersionBadge(props)
|
||||
}
|
||||
|
||||
async handle({ which, packageName }) {
|
||||
const packageData = await fetch(this, {
|
||||
baseUrl: apiBaseUrl,
|
||||
packageName,
|
||||
includePrereleases: which === 'vpre',
|
||||
})
|
||||
const version = packageData.NormalizedVersion || packageData.Version
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
}
|
||||
|
||||
class NugetDownloadService extends BaseJsonService {
|
||||
static get category() {
|
||||
return 'downloads'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: serviceBaseUrl,
|
||||
pattern: 'dt/:packageName',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return []
|
||||
}
|
||||
|
||||
static render(props) {
|
||||
return renderDownloadBadge(props)
|
||||
}
|
||||
|
||||
async handle({ packageName }) {
|
||||
const packageData = await fetch(this, {
|
||||
baseUrl: apiBaseUrl,
|
||||
packageName,
|
||||
})
|
||||
const { DownloadCount: downloads } = packageData
|
||||
return this.constructor.render({ downloads })
|
||||
}
|
||||
}
|
||||
|
||||
return { NugetVersionService, NugetDownloadService }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createFilter,
|
||||
createServiceFamily,
|
||||
}
|
||||
251
services/nuget/nuget-v3-service-family.js
Normal file
251
services/nuget/nuget-v3-service-family.js
Normal file
@@ -0,0 +1,251 @@
|
||||
'use strict'
|
||||
|
||||
const { promisify } = require('util')
|
||||
const Joi = require('joi')
|
||||
const { regularUpdate } = require('../../lib/regular-update')
|
||||
const RouteBuilder = require('../route-builder')
|
||||
const BaseJsonService = require('../base-json')
|
||||
const { NotFound } = require('../errors')
|
||||
const { renderVersionBadge, renderDownloadBadge } = require('./nuget-helpers')
|
||||
|
||||
/*
|
||||
* Build the Shields service URL object for the given service configuration. Return
|
||||
* the RouteBuilder instance to which the service can add the route.
|
||||
*/
|
||||
function buildRoute({ serviceBaseUrl, withTenant, withFeed }) {
|
||||
let result
|
||||
if (withTenant) {
|
||||
result = new RouteBuilder().push(`(?:(.+)\\.)?${serviceBaseUrl}`, 'tenant')
|
||||
} else {
|
||||
result = new RouteBuilder({ base: serviceBaseUrl })
|
||||
}
|
||||
if (withFeed) {
|
||||
result.push('([^/]+)', 'feed')
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the URL for an individual request.
|
||||
*
|
||||
* `apiBaseUrl`, `apiDomain`, `withTenant` and `withFeed` come from the service
|
||||
* configuration. When `withTenant` and `withFeed` are false, return
|
||||
* `apiBaseUrl` for every request.
|
||||
*
|
||||
* When `withTenant` and/or `withFeed` are true, `tenant` and `feed` come from the
|
||||
* request, and this returns a different URL for each request.
|
||||
*
|
||||
* In practice, `withTenant` and `withFeed` are used together, for MyGet.
|
||||
*/
|
||||
function apiUrl({ withTenant, apiBaseUrl, apiDomain, tenant, withFeed, feed }) {
|
||||
let result = withTenant
|
||||
? `https://${tenant || 'www'}.${apiDomain}`
|
||||
: apiBaseUrl
|
||||
if (withFeed) {
|
||||
result += `/F/${feed}/api/v3`
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function randomElementFrom(items) {
|
||||
const index = Math.floor(Math.random() * items.length)
|
||||
return items[index]
|
||||
}
|
||||
|
||||
/*
|
||||
* Hit the service index endpoint and return a SearchQueryService URL, chosen
|
||||
* at random. Cache the responses, but return a different random URL each time.
|
||||
*/
|
||||
async function searchQueryServiceUrl(baseUrl) {
|
||||
// Should we really be caching all these NuGet feeds in memory?
|
||||
const searchQueryServices = await promisify(regularUpdate)({
|
||||
url: `${baseUrl}/index.json`,
|
||||
// The endpoint changes once per year (ie, a period of n = 1 year).
|
||||
// We minimize the users' waiting time for information.
|
||||
// With l = latency to fetch the endpoint and x = endpoint update period
|
||||
// both in years, the yearly number of queries for the endpoint are 1/x,
|
||||
// and when the endpoint changes, we wait for up to x years to get the
|
||||
// right endpoint.
|
||||
// So the waiting time within n years is n*l/x + x years, for which a
|
||||
// derivation yields an optimum at x = sqrt(n*l), roughly 42 minutes.
|
||||
intervalMillis: 42 * 60 * 1000,
|
||||
json: true,
|
||||
scraper: json =>
|
||||
json.resources.filter(
|
||||
resource => resource['@type'] === 'SearchQueryService'
|
||||
),
|
||||
})
|
||||
return randomElementFrom(searchQueryServices)['@id']
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
data: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
versions: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
version: Joi.string().required(),
|
||||
})
|
||||
)
|
||||
.default([]),
|
||||
totalDownloads: Joi.number().integer(),
|
||||
totaldownloads: Joi.number().integer(),
|
||||
})
|
||||
)
|
||||
.max(1)
|
||||
.default([]),
|
||||
}).required()
|
||||
|
||||
/*
|
||||
* Get information about a single package.
|
||||
*/
|
||||
async function fetch(
|
||||
serviceInstance,
|
||||
{ baseUrl, packageName, includePrereleases = false }
|
||||
) {
|
||||
const json = await serviceInstance._requestJson({
|
||||
schema,
|
||||
url: await searchQueryServiceUrl(baseUrl),
|
||||
options: {
|
||||
qs: {
|
||||
q: `packageid:${encodeURIComponent(packageName.toLowerCase())}`,
|
||||
// Include prerelease versions.
|
||||
prerelease: 'true',
|
||||
// Include packages with SemVer 2 version numbers.
|
||||
semVerLevel: '2',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (json.data.length === 1) {
|
||||
return json.data[0]
|
||||
} else {
|
||||
throw new NotFound({ prettyMessage: 'package not found' })
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a version and download service for a NuGet v2 API. Return an object
|
||||
* containing both services.
|
||||
*
|
||||
* defaultLabel: The label for the left hand side of the badge.
|
||||
* serviceBaseUrl: The base URL for the Shields service, e.g. nuget
|
||||
* withTenant: When true, an optional `tenant` is extracted from the badge
|
||||
* URL, which represents the subdomain of the API. When no tenant is
|
||||
* provided, defaults to `www`.
|
||||
* apiDomain: When `withTenant` is true, this is the rest of the domain,
|
||||
* e.g. `myget.org`.
|
||||
* apiBaseUrl: When `withTenant` is false, this is the base URL of the API,
|
||||
* e.g. https://api.nuget.org/v3
|
||||
* withFeed: When true, the badge URL includes a required feed name, which is
|
||||
* added to the request API.
|
||||
*/
|
||||
function createServiceFamily({
|
||||
defaultLabel,
|
||||
serviceBaseUrl,
|
||||
withTenant = true,
|
||||
apiDomain,
|
||||
apiBaseUrl,
|
||||
withFeed = true,
|
||||
}) {
|
||||
class NugetVersionService extends BaseJsonService {
|
||||
static get category() {
|
||||
return 'version'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return buildRoute({ serviceBaseUrl, withTenant, withFeed })
|
||||
.push('(v|vpre)', 'which')
|
||||
.push('(.*)', 'packageName')
|
||||
.toObject()
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return []
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: defaultLabel,
|
||||
}
|
||||
}
|
||||
|
||||
static render(props) {
|
||||
return renderVersionBadge(props)
|
||||
}
|
||||
|
||||
async handle({ tenant, feed, which, packageName }) {
|
||||
const baseUrl = apiUrl({
|
||||
withTenant,
|
||||
apiBaseUrl,
|
||||
apiDomain,
|
||||
tenant,
|
||||
withFeed,
|
||||
feed,
|
||||
})
|
||||
const { versions } = await fetch(this, { baseUrl, packageName })
|
||||
|
||||
let latest = versions.slice(-1).pop()
|
||||
const includePrereleases = which === 'vpre'
|
||||
if (!includePrereleases) {
|
||||
const filtered = versions.filter(item => !item.version.includes('-'))
|
||||
if (filtered.length) {
|
||||
latest = filtered.slice(-1).pop()
|
||||
}
|
||||
}
|
||||
|
||||
const { version } = latest
|
||||
return this.constructor.render({ version, feed })
|
||||
}
|
||||
}
|
||||
|
||||
class NugetDownloadService extends BaseJsonService {
|
||||
static get category() {
|
||||
return 'downloads'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return buildRoute({ serviceBaseUrl, withTenant, withFeed })
|
||||
.push('dt')
|
||||
.push('(.*)', 'packageName')
|
||||
.toObject()
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return []
|
||||
}
|
||||
|
||||
static render(props) {
|
||||
return renderDownloadBadge(props)
|
||||
}
|
||||
|
||||
async handle({ tenant, feed, which, packageName }) {
|
||||
const baseUrl = apiUrl({
|
||||
withTenant,
|
||||
apiBaseUrl,
|
||||
apiDomain,
|
||||
tenant,
|
||||
withFeed,
|
||||
feed,
|
||||
})
|
||||
const packageInfo = await fetch(this, { baseUrl, packageName })
|
||||
|
||||
// Official NuGet server uses "totalDownloads" whereas MyGet uses
|
||||
// "totaldownloads" (lowercase D). Ugh.
|
||||
const downloads =
|
||||
packageInfo.totalDownloads || packageInfo.totaldownloads || 0
|
||||
|
||||
return this.constructor.render({ downloads })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
NugetVersionService,
|
||||
NugetDownloadService,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createServiceFamily,
|
||||
}
|
||||
@@ -1,364 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const {
|
||||
downloadCount: downloadCountColor,
|
||||
} = require('../../lib/color-formatters')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
const { metric } = require('../../lib/text-formatters')
|
||||
const { regularUpdate } = require('../../lib/regular-update')
|
||||
const { createServiceFamily } = require('./nuget-v3-service-family')
|
||||
|
||||
function mapNugetFeedv2({ camp, cache }, pattern, offset, getInfo) {
|
||||
const vRegex = new RegExp(
|
||||
`^\\/${pattern}\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$`
|
||||
)
|
||||
const vPreRegex = new RegExp(
|
||||
`^\\/${pattern}\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$`
|
||||
)
|
||||
const dtRegex = new RegExp(
|
||||
`^\\/${pattern}\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$`
|
||||
)
|
||||
|
||||
function getNugetPackage(apiUrl, id, includePre, request, done) {
|
||||
const filter = includePre
|
||||
? `Id eq '${id}' and IsAbsoluteLatestVersion eq true`
|
||||
: `Id eq '${id}' and IsLatestVersion eq true`
|
||||
const reqUrl = `${apiUrl}/Packages()?$filter=${encodeURIComponent(filter)}`
|
||||
request(
|
||||
reqUrl,
|
||||
{ headers: { Accept: 'application/atom+json,application/json' } },
|
||||
(err, res, buffer) => {
|
||||
if (err != null) {
|
||||
done(new Error('inaccessible'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(buffer)
|
||||
const result = data.d.results[0]
|
||||
if (result == null) {
|
||||
if (includePre === null) {
|
||||
getNugetPackage(apiUrl, id, true, request, done)
|
||||
} else {
|
||||
done(new Error('not found'))
|
||||
}
|
||||
} else {
|
||||
done(null, result)
|
||||
}
|
||||
} catch (e) {
|
||||
done(new Error('invalid'))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
camp.route(
|
||||
vRegex,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const info = getInfo(match)
|
||||
const site = info.site // eg, `Chocolatey`, or `YoloDev`
|
||||
const repo = match[offset + 1] // eg, `Nuget.Core`.
|
||||
const format = match[offset + 2]
|
||||
const apiUrl = info.feed
|
||||
const badgeData = getBadgeData(site, data)
|
||||
getNugetPackage(apiUrl, repo, null, request, (err, data) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = err.message
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
const version = data.NormalizedVersion || data.Version
|
||||
badgeData.text[1] = `v${version}`
|
||||
if (version.indexOf('-') !== -1) {
|
||||
badgeData.colorscheme = 'yellow'
|
||||
} else if (version[0] === '0') {
|
||||
badgeData.colorscheme = 'orange'
|
||||
} else {
|
||||
badgeData.colorscheme = 'blue'
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
camp.route(
|
||||
vPreRegex,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const info = getInfo(match)
|
||||
const site = info.site // eg, `Chocolatey`, or `YoloDev`
|
||||
const repo = match[offset + 1] // eg, `Nuget.Core`.
|
||||
const format = match[offset + 2]
|
||||
const apiUrl = info.feed
|
||||
const badgeData = getBadgeData(site, data)
|
||||
getNugetPackage(apiUrl, repo, true, request, (err, data) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = err.message
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
const version = data.NormalizedVersion || data.Version
|
||||
badgeData.text[1] = `v${version}`
|
||||
if (version.indexOf('-') !== -1) {
|
||||
badgeData.colorscheme = 'yellow'
|
||||
} else if (version[0] === '0') {
|
||||
badgeData.colorscheme = 'orange'
|
||||
} else {
|
||||
badgeData.colorscheme = 'blue'
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
camp.route(
|
||||
dtRegex,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const info = getInfo(match)
|
||||
const repo = match[offset + 1] // eg, `Nuget.Core`.
|
||||
const format = match[offset + 2]
|
||||
const apiUrl = info.feed
|
||||
const badgeData = getBadgeData('downloads', data)
|
||||
getNugetPackage(apiUrl, repo, null, request, (err, data) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = err.message
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
const downloads = data.DownloadCount
|
||||
badgeData.text[1] = metric(downloads)
|
||||
badgeData.colorscheme = downloadCountColor(downloads)
|
||||
sendBadge(format, badgeData)
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) {
|
||||
const vRegex = new RegExp(
|
||||
`^\\/${pattern}\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$`
|
||||
)
|
||||
const vPreRegex = new RegExp(
|
||||
`^\\/${pattern}\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$`
|
||||
)
|
||||
const dtRegex = new RegExp(
|
||||
`^\\/${pattern}\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$`
|
||||
)
|
||||
|
||||
function getNugetData(apiUrl, id, request, done) {
|
||||
// get service index document
|
||||
|
||||
regularUpdate(
|
||||
{
|
||||
url: `${apiUrl}/index.json`,
|
||||
// The endpoint changes once per year (ie, a period of n = 1 year).
|
||||
// We minimize the users' waiting time for information.
|
||||
// With l = latency to fetch the endpoint and x = endpoint update period
|
||||
// both in years, the yearly number of queries for the endpoint are 1/x,
|
||||
// and when the endpoint changes, we wait for up to x years to get the
|
||||
// right endpoint.
|
||||
// So the waiting time within n years is n*l/x + x years, for which a
|
||||
// derivation yields an optimum at x = sqrt(n*l), roughly 42 minutes.
|
||||
intervalMillis: 42 * 60 * 1000,
|
||||
json: false,
|
||||
scraper: function(data) {
|
||||
return data
|
||||
},
|
||||
},
|
||||
(err, buf) => {
|
||||
if (err != null) {
|
||||
done(new Error('inaccessible'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const searchQueryResources = JSON.parse(buf).resources.filter(
|
||||
resource => resource['@type'] === 'SearchQueryService'
|
||||
)
|
||||
// query autocomplete service
|
||||
const randomEndpointIdx = Math.floor(
|
||||
Math.random() * searchQueryResources.length
|
||||
)
|
||||
const reqUrl =
|
||||
`${searchQueryResources[randomEndpointIdx]['@id']}?q=packageid:${
|
||||
encodeURIComponent(id.toLowerCase()) // NuGet package id (lowercase)
|
||||
}&prerelease=true` + `&semVerLevel=2` // Include prerelease versions? // Include packages with SemVer 2 version numbers
|
||||
|
||||
request(reqUrl, (err, res, buffer) => {
|
||||
if (err != null) {
|
||||
done(new Error('inaccessible'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(buffer)
|
||||
if (!Array.isArray(data.data) || data.data.length !== 1) {
|
||||
done(new Error('not found'))
|
||||
return
|
||||
}
|
||||
done(null, data.data[0])
|
||||
} catch (e) {
|
||||
done(new Error('invalid'))
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
done(new Error('invalid'))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function getNugetVersion(apiUrl, id, includePre, request, done) {
|
||||
getNugetData(apiUrl, id, request, (err, data) => {
|
||||
if (err) {
|
||||
done(err)
|
||||
return
|
||||
}
|
||||
let versions = data.versions || []
|
||||
if (!includePre) {
|
||||
// Remove prerelease versions.
|
||||
const filteredVersions = versions.filter(
|
||||
version => !/-/.test(version.version)
|
||||
)
|
||||
if (filteredVersions.length > 0) {
|
||||
versions = filteredVersions
|
||||
}
|
||||
}
|
||||
const lastVersion = versions[versions.length - 1]
|
||||
done(null, lastVersion.version)
|
||||
})
|
||||
}
|
||||
|
||||
camp.route(
|
||||
vRegex,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const info = getInfo(match)
|
||||
const site = info.site // eg, `Chocolatey`, or `YoloDev`
|
||||
const repo = match[offset + 1] // eg, `Nuget.Core`.
|
||||
const format = match[offset + 2]
|
||||
const apiUrl = info.feed
|
||||
const badgeData = getBadgeData(site, data)
|
||||
getNugetVersion(apiUrl, repo, false, request, (err, version) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = err.message
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
badgeData.text[1] = `v${version}`
|
||||
if (version.indexOf('-') !== -1) {
|
||||
badgeData.colorscheme = 'yellow'
|
||||
} else if (version[0] === '0') {
|
||||
badgeData.colorscheme = 'orange'
|
||||
} else {
|
||||
badgeData.colorscheme = 'blue'
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
camp.route(
|
||||
vPreRegex,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const info = getInfo(match)
|
||||
const site = info.site // eg, `Chocolatey`, or `YoloDev`
|
||||
const repo = match[offset + 1] // eg, `Nuget.Core`.
|
||||
const format = match[offset + 2]
|
||||
const apiUrl = info.feed
|
||||
const badgeData = getBadgeData(site, data)
|
||||
getNugetVersion(apiUrl, repo, true, request, (err, version) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = err.message
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
badgeData.text[1] = `v${version}`
|
||||
if (version.indexOf('-') !== -1) {
|
||||
badgeData.colorscheme = 'yellow'
|
||||
} else if (version[0] === '0') {
|
||||
badgeData.colorscheme = 'orange'
|
||||
} else {
|
||||
badgeData.colorscheme = 'blue'
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
camp.route(
|
||||
dtRegex,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const info = getInfo(match)
|
||||
const repo = match[offset + 1] // eg, `Nuget.Core`.
|
||||
const format = match[offset + 2]
|
||||
const apiUrl = info.feed
|
||||
const badgeData = getBadgeData('downloads', data)
|
||||
getNugetData(apiUrl, repo, request, (err, nugetData) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = err.message
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
// Official NuGet server uses "totalDownloads" whereas MyGet uses
|
||||
// "totaldownloads" (lowercase D). Ugh.
|
||||
const downloads =
|
||||
nugetData.totalDownloads || nugetData.totaldownloads || 0
|
||||
badgeData.text[1] = metric(downloads)
|
||||
badgeData.colorscheme = downloadCountColor(downloads)
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = class Nuget extends LegacyService {
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
// ReSharper
|
||||
mapNugetFeedv2({ camp, cache }, 'resharper', 0, match => ({
|
||||
site: 'resharper',
|
||||
feed: 'https://resharper-plugins.jetbrains.com/api/v2',
|
||||
}))
|
||||
|
||||
// Chocolatey
|
||||
mapNugetFeedv2({ camp, cache }, 'chocolatey', 0, match => ({
|
||||
site: 'chocolatey',
|
||||
feed: 'https://www.chocolatey.org/api/v2',
|
||||
}))
|
||||
|
||||
// PowerShell Gallery
|
||||
mapNugetFeedv2({ camp, cache }, 'powershellgallery', 0, match => ({
|
||||
site: 'powershellgallery',
|
||||
feed: 'https://msconfiggallery.cloudapp.net/api/v2',
|
||||
}))
|
||||
|
||||
// NuGet
|
||||
mapNugetFeed({ camp, cache }, 'nuget', 0, match => ({
|
||||
site: 'nuget',
|
||||
feed: 'https://api.nuget.org/v3',
|
||||
}))
|
||||
|
||||
// MyGet
|
||||
mapNugetFeed({ camp, cache }, '(.+\\.)?myget\\/(.*)', 2, match => {
|
||||
const tenant = match[1] || 'www.' // eg. dotnet
|
||||
const feed = match[2]
|
||||
return {
|
||||
site: feed,
|
||||
feed: `https://${tenant}myget.org/F/${feed}/api/v3`,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = createServiceFamily({
|
||||
defaultLabel: 'nuget',
|
||||
serviceBaseUrl: 'nuget',
|
||||
apiBaseUrl: 'https://api.nuget.org/v3',
|
||||
withTenant: false,
|
||||
withFeed: false,
|
||||
})
|
||||
|
||||
@@ -32,21 +32,7 @@ t.create('total downloads (valid)')
|
||||
|
||||
t.create('total downloads (not found)')
|
||||
.get('/dt/not-a-real-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'not found' })
|
||||
|
||||
t.create('total downloads (connection error)')
|
||||
.get('/dt/Microsoft.AspNetCore.Mvc.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'downloads', value: 'inaccessible' })
|
||||
|
||||
t.create('total downloads (unexpected first response)')
|
||||
.get('/dt/Microsoft.AspNetCore.Mvc.json')
|
||||
.intercept(nock =>
|
||||
nock('https://api.nuget.org')
|
||||
.get('/v3/index.json')
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'package not found' })
|
||||
|
||||
t.create('total downloads (unexpected second response)')
|
||||
.get('/dt/Microsoft.AspNetCore.Mvc.json')
|
||||
@@ -58,11 +44,11 @@ t.create('total downloads (unexpected second response)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'unparseable json response' })
|
||||
|
||||
// version
|
||||
|
||||
@@ -85,7 +71,7 @@ t.create('version (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonWithDash)
|
||||
)
|
||||
@@ -105,7 +91,7 @@ t.create('version (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -125,7 +111,7 @@ t.create('version (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -137,21 +123,7 @@ t.create('version (mocked, blue badge)')
|
||||
|
||||
t.create('version (not found)')
|
||||
.get('/v/not-a-real-package.json')
|
||||
.expectJSON({ name: 'nuget', value: 'not found' })
|
||||
|
||||
t.create('version (connection error)')
|
||||
.get('/v/Microsoft.AspNetCore.Mvc.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'nuget', value: 'inaccessible' })
|
||||
|
||||
t.create('version (unexpected first response)')
|
||||
.get('/v/Microsoft.AspNetCore.Mvc.json')
|
||||
.intercept(nock =>
|
||||
nock('https://api.nuget.org')
|
||||
.get('/v3/index.json')
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'nuget', value: 'invalid' })
|
||||
.expectJSON({ name: 'nuget', value: 'package not found' })
|
||||
|
||||
t.create('version (unexpected second response)')
|
||||
.get('/v/Microsoft.AspNetCore.Mvc.json')
|
||||
@@ -163,11 +135,11 @@ t.create('version (unexpected second response)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'nuget', value: 'invalid' })
|
||||
.expectJSON({ name: 'nuget', value: 'unparseable json response' })
|
||||
|
||||
// version (pre)
|
||||
|
||||
@@ -190,7 +162,7 @@ t.create('version (pre) (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonWithDash)
|
||||
)
|
||||
@@ -210,7 +182,7 @@ t.create('version (pre) (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -230,7 +202,7 @@ t.create('version (pre) (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(200, nuGetV3VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -242,21 +214,7 @@ t.create('version (pre) (mocked, blue badge)')
|
||||
|
||||
t.create('version (pre) (not found)')
|
||||
.get('/vpre/not-a-real-package.json')
|
||||
.expectJSON({ name: 'nuget', value: 'not found' })
|
||||
|
||||
t.create('version (pre) (connection error)')
|
||||
.get('/vpre/Microsoft.AspNetCore.Mvc.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'nuget', value: 'inaccessible' })
|
||||
|
||||
t.create('version (pre) (unexpected first response)')
|
||||
.get('/vpre/Microsoft.AspNetCore.Mvc.json')
|
||||
.intercept(nock =>
|
||||
nock('https://api.nuget.org')
|
||||
.get('/v3/index.json')
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'nuget', value: 'invalid' })
|
||||
.expectJSON({ name: 'nuget', value: 'package not found' })
|
||||
|
||||
t.create('version (pre) (unexpected second response)')
|
||||
.get('/vpre/Microsoft.AspNetCore.Mvc.json')
|
||||
@@ -268,8 +226,8 @@ t.create('version (pre) (unexpected second response)')
|
||||
.intercept(nock =>
|
||||
nock('https://api-v2v3search-0.nuget.org')
|
||||
.get(
|
||||
'/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
'/query?q=packageid%3Amicrosoft.aspnetcore.mvc&prerelease=true&semVerLevel=2'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'nuget', value: 'invalid' })
|
||||
.expectJSON({ name: 'nuget', value: 'unparseable json response' })
|
||||
|
||||
119
services/powershellgallery/powershellgallery.service.js
Normal file
119
services/powershellgallery/powershellgallery.service.js
Normal file
@@ -0,0 +1,119 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const BaseXmlService = require('../base-xml')
|
||||
const { NotFound } = require('../errors')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { createFilter } = require('../nuget/nuget-v2-service-family')
|
||||
const {
|
||||
renderVersionBadge,
|
||||
renderDownloadBadge,
|
||||
} = require('../nuget/nuget-helpers')
|
||||
|
||||
const schema = Joi.object({
|
||||
feed: Joi.object({
|
||||
entry: Joi.object({
|
||||
'm:properties': Joi.object({
|
||||
'd:Version': Joi.string(),
|
||||
'd:NormalizedVersion': Joi.string(),
|
||||
'd:DownloadCount': nonNegativeInteger,
|
||||
}),
|
||||
}),
|
||||
}).required(),
|
||||
}).required()
|
||||
|
||||
async function fetch(
|
||||
serviceInstance,
|
||||
{ packageName, includePrereleases = false }
|
||||
) {
|
||||
const data = await serviceInstance._requestXml({
|
||||
schema,
|
||||
url: `https://www.powershellgallery.com/api/v2/Search()`,
|
||||
options: {
|
||||
qs: { $filter: createFilter({ packageName, includePrereleases }) },
|
||||
},
|
||||
})
|
||||
|
||||
const packageData =
|
||||
'entry' in data.feed ? data.feed.entry['m:properties'] : undefined
|
||||
|
||||
if (packageData) {
|
||||
return packageData
|
||||
} else if (!includePrereleases) {
|
||||
return fetch(serviceInstance, {
|
||||
packageName,
|
||||
includePrereleases: true,
|
||||
})
|
||||
} else {
|
||||
throw new NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
class PowershellGalleryVersion extends BaseXmlService {
|
||||
static get category() {
|
||||
return 'version'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'powershellgallery',
|
||||
pattern: ':which(v|vpre)/:packageName',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return []
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'powershell gallery',
|
||||
}
|
||||
}
|
||||
|
||||
static render(props) {
|
||||
return renderVersionBadge(props)
|
||||
}
|
||||
|
||||
async handle({ which, packageName }) {
|
||||
const packageData = await fetch(this, {
|
||||
packageName,
|
||||
includePrereleases: which === 'vpre',
|
||||
})
|
||||
const version =
|
||||
packageData['d:NormalizedVersion'] || packageData['d:Version']
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
}
|
||||
|
||||
class PowershellGalleryDownloads extends BaseXmlService {
|
||||
static get category() {
|
||||
return 'downloads'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'powershellgallery',
|
||||
pattern: 'dt/:packageName',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return []
|
||||
}
|
||||
|
||||
static render(props) {
|
||||
return renderDownloadBadge(props)
|
||||
}
|
||||
|
||||
async handle({ packageName }) {
|
||||
const packageData = await fetch(this, {
|
||||
packageName,
|
||||
})
|
||||
const { 'd:DownloadCount': downloads } = packageData
|
||||
return this.constructor.render({ downloads })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { PowershellGalleryVersion, PowershellGalleryDownloads }
|
||||
@@ -7,13 +7,6 @@ const {
|
||||
isVPlusDottedVersionNClauses,
|
||||
isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
} = require('../test-validators')
|
||||
const colorscheme = require('../../lib/colorscheme.json')
|
||||
const {
|
||||
nuGetV2VersionJsonWithDash,
|
||||
nuGetV2VersionJsonFirstCharZero,
|
||||
nuGetV2VersionJsonFirstCharNotZero,
|
||||
} = require('../nuget-fixtures')
|
||||
const { invalidJSON } = require('../response-fixtures')
|
||||
|
||||
const t = new ServiceTester({
|
||||
id: 'powershellgallery',
|
||||
@@ -21,8 +14,6 @@ const t = new ServiceTester({
|
||||
})
|
||||
module.exports = t
|
||||
|
||||
// downloads
|
||||
|
||||
t.create('total downloads (valid)')
|
||||
.get('/dt/ACMESharp.json')
|
||||
.expectJSONTypes(
|
||||
@@ -36,170 +27,28 @@ t.create('total downloads (not found)')
|
||||
.get('/dt/not-a-real-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'not found' })
|
||||
|
||||
t.create('total downloads (connection error)')
|
||||
.get('/dt/ACMESharp.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'downloads', value: 'inaccessible' })
|
||||
|
||||
t.create('total downloads (unexpected response)')
|
||||
.get('/dt/ACMESharp.json')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
|
||||
// version
|
||||
|
||||
t.create('version (valid)')
|
||||
.get('/v/ACMESharp.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'powershellgallery',
|
||||
name: 'powershell gallery',
|
||||
value: isVPlusDottedVersionNClauses,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('version (mocked, yellow badge)')
|
||||
.get('/v/ACMESharp.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonWithDash)
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'powershellgallery',
|
||||
value: 'v1.2-beta',
|
||||
colorB: colorscheme.yellow.colorB,
|
||||
})
|
||||
|
||||
t.create('version (mocked, orange badge)')
|
||||
.get('/v/ACMESharp.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharZero)
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'powershellgallery',
|
||||
value: 'v0.35',
|
||||
colorB: colorscheme.orange.colorB,
|
||||
})
|
||||
|
||||
t.create('version (mocked, blue badge)')
|
||||
.get('/v/ACMESharp.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharNotZero)
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'powershellgallery',
|
||||
value: 'v1.2.7',
|
||||
colorB: colorscheme.blue.colorB,
|
||||
})
|
||||
|
||||
t.create('version (not found)')
|
||||
.get('/v/not-a-real-package.json')
|
||||
.expectJSON({ name: 'powershellgallery', value: 'not found' })
|
||||
|
||||
t.create('version (connection error)')
|
||||
.get('/v/ACMESharp.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'powershellgallery', value: 'inaccessible' })
|
||||
|
||||
t.create('version (unexpected response)')
|
||||
.get('/v/ACMESharp.json')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'powershellgallery', value: 'invalid' })
|
||||
|
||||
// version (pre)
|
||||
.expectJSON({ name: 'powershell gallery', value: 'not found' })
|
||||
|
||||
t.create('version (pre) (valid)')
|
||||
.get('/vpre/ACMESharp.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'powershellgallery',
|
||||
name: 'powershell gallery',
|
||||
value: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('version (pre) (mocked, yellow badge)')
|
||||
.get('/vpre/ACMESharp.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonWithDash)
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'powershellgallery',
|
||||
value: 'v1.2-beta',
|
||||
colorB: colorscheme.yellow.colorB,
|
||||
})
|
||||
|
||||
t.create('version (pre) (mocked, orange badge)')
|
||||
.get('/vpre/ACMESharp.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharZero)
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'powershellgallery',
|
||||
value: 'v0.35',
|
||||
colorB: colorscheme.orange.colorB,
|
||||
})
|
||||
|
||||
t.create('version (pre) (mocked, blue badge)')
|
||||
.get('/vpre/ACMESharp.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharNotZero)
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'powershellgallery',
|
||||
value: 'v1.2.7',
|
||||
colorB: colorscheme.blue.colorB,
|
||||
})
|
||||
|
||||
t.create('version (pre) (not found)')
|
||||
.get('/vpre/not-a-real-package.json')
|
||||
.expectJSON({ name: 'powershellgallery', value: 'not found' })
|
||||
|
||||
t.create('version (pre) (connection error)')
|
||||
.get('/vpre/ACMESharp.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'powershellgallery', value: 'inaccessible' })
|
||||
|
||||
t.create('version (pre) (unexpected response)')
|
||||
.get('/vpre/ACMESharp.json')
|
||||
.intercept(nock =>
|
||||
nock('https://msconfiggallery.cloudapp.net')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'powershellgallery', value: 'invalid' })
|
||||
.expectJSON({ name: 'powershell gallery', value: 'not found' })
|
||||
|
||||
9
services/resharper/resharper.service.js
Normal file
9
services/resharper/resharper.service.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const { createServiceFamily } = require('../nuget/nuget-v2-service-family')
|
||||
|
||||
module.exports = createServiceFamily({
|
||||
defaultLabel: 'resharper',
|
||||
serviceBaseUrl: 'resharper',
|
||||
apiBaseUrl: 'https://resharper-plugins.jetbrains.com/api/v2',
|
||||
})
|
||||
@@ -43,11 +43,11 @@ t.create('total downloads (unexpected response)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'downloads', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'unparseable json response' })
|
||||
|
||||
// version
|
||||
|
||||
@@ -65,7 +65,7 @@ t.create('version (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonWithDash)
|
||||
)
|
||||
@@ -80,7 +80,7 @@ t.create('version (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -95,7 +95,7 @@ t.create('version (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -119,11 +119,11 @@ t.create('version (unexpected response)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'resharper', value: 'invalid' })
|
||||
.expectJSON({ name: 'resharper', value: 'unparseable json response' })
|
||||
|
||||
// version (pre)
|
||||
|
||||
@@ -141,7 +141,7 @@ t.create('version (pre) (mocked, yellow badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonWithDash)
|
||||
)
|
||||
@@ -156,7 +156,7 @@ t.create('version (pre) (mocked, orange badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharZero)
|
||||
)
|
||||
@@ -171,7 +171,7 @@ t.create('version (pre) (mocked, blue badge)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(200, nuGetV2VersionJsonFirstCharNotZero)
|
||||
)
|
||||
@@ -195,8 +195,8 @@ t.create('version (pre) (unexpected response)')
|
||||
.intercept(nock =>
|
||||
nock('https://resharper-plugins.jetbrains.com')
|
||||
.get(
|
||||
'/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
'/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
)
|
||||
.reply(invalidJSON)
|
||||
)
|
||||
.expectJSON({ name: 'resharper', value: 'invalid' })
|
||||
.expectJSON({ name: 'resharper', value: 'unparseable json response' })
|
||||
|
||||
36
services/route-builder.js
Normal file
36
services/route-builder.js
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
const { toArray } = require('../lib/badge-data')
|
||||
|
||||
/*
|
||||
* Factory class for building a BaseService `route` object. This class is useful
|
||||
* in complex collections of service classes, when the URL is built
|
||||
* conditionally.
|
||||
*
|
||||
* Patterns based on path-to-regex may obviate the need for this, though they
|
||||
* haven't done so yet.
|
||||
*/
|
||||
module.exports = class RouteBuilder {
|
||||
constructor({ base = '' } = {}) {
|
||||
this.base = base
|
||||
|
||||
this._formatComponents = []
|
||||
this.capture = []
|
||||
}
|
||||
|
||||
get format() {
|
||||
return this._formatComponents.join('/')
|
||||
}
|
||||
|
||||
push(format, capture) {
|
||||
this._formatComponents = this._formatComponents.concat(toArray(format))
|
||||
this.capture = this.capture.concat(toArray(capture))
|
||||
// Return `this` for chaining.
|
||||
return this
|
||||
}
|
||||
|
||||
toObject() {
|
||||
const { base, format, capture } = this
|
||||
return { base, format, capture }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user