[GithubCommitsSince GithubDownloads GithubRelease] GithubDownloads supports latest release by SemVer (#5756)

* Latest release by SemVer

* Tests covering altered behavior

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
This commit is contained in:
Hubert Jagodziński
2020-10-31 05:59:31 +01:00
committed by GitHub
parent 79e7d2f7f2
commit c6d97de22e
3 changed files with 221 additions and 39 deletions

View File

@@ -1,11 +1,18 @@
'use strict'
const Joi = require('joi')
const { nonNegativeInteger } = require('../validators')
const { latest } = require('../version')
const { NotFound } = require('..')
const { errorMessagesFor } = require('./github-helpers')
const releaseInfoSchema = Joi.object({
assets: Joi.array()
.items({
name: Joi.string().required(),
download_count: nonNegativeInteger,
})
.required(),
tag_name: Joi.string().required(),
prerelease: Joi.boolean().required(),
}).required()
@@ -42,16 +49,11 @@ async function fetchReleases(serviceInstance, { user, repo }) {
function getLatestRelease({ releases, sort, includePrereleases }) {
if (sort === 'semver') {
const latestRelease = latest(
const latestTagName = latest(
releases.map(release => release.tag_name),
{
pre: includePrereleases,
}
{ pre: includePrereleases }
)
const kvpairs = Object.assign(
...releases.map(release => ({ [release.tag_name]: release.prerelease }))
)
return { tag_name: latestRelease, prerelease: kvpairs[latestRelease] }
return releases.find(({ tag_name }) => tag_name === latestTagName)
}
if (!includePrereleases) {

View File

@@ -6,8 +6,13 @@ const { nonNegativeInteger } = require('../validators')
const { downloadCount: downloadCountColor } = require('../color-formatters')
const { NotFound } = require('..')
const { GithubAuthV3Service } = require('./github-auth-service')
const { fetchLatestRelease } = require('./github-common-release')
const { documentation, errorMessagesFor } = require('./github-helpers')
const queryParamSchema = Joi.object({
sort: Joi.string().valid('date', 'semver').default('date'),
}).required()
const releaseSchema = Joi.object({
assets: Joi.array()
.items({
@@ -27,11 +32,12 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
static route = {
base: 'github',
pattern: ':kind(downloads|downloads-pre)/:user/:repo/:tag*/:assetName',
queryParamSchema,
}
static examples = [
{
title: 'GitHub All Releases',
title: 'GitHub all releases',
pattern: 'downloads/:user/:repo/total',
namedParams: {
user: 'atom',
@@ -44,7 +50,7 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
documentation,
},
{
title: 'GitHub Releases',
title: 'GitHub release (latest by date)',
pattern: 'downloads/:user/:repo/:tag/total',
namedParams: {
user: 'atom',
@@ -59,7 +65,23 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
documentation,
},
{
title: 'GitHub Pre-Releases',
title: 'GitHub release (latest by SemVer)',
pattern: 'downloads/:user/:repo/:tag/total',
namedParams: {
user: 'atom',
repo: 'atom',
tag: 'latest',
},
queryParams: { sort: 'semver' },
staticPreview: this.render({
tag: 'latest',
assetName: 'total',
downloadCount: 27000,
}),
documentation,
},
{
title: 'GitHub release (latest by date including pre-releases)',
pattern: 'downloads-pre/:user/:repo/:tag/total',
namedParams: {
user: 'atom',
@@ -74,7 +96,23 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
documentation,
},
{
title: 'GitHub Releases (by Release)',
title: 'GitHub release (latest by SemVer including pre-releases)',
pattern: 'downloads-pre/:user/:repo/:tag/total',
namedParams: {
user: 'atom',
repo: 'atom',
tag: 'latest',
},
queryParams: { sort: 'semver' },
staticPreview: this.render({
tag: 'latest',
assetName: 'total',
downloadCount: 2000,
}),
documentation,
},
{
title: 'GitHub release (by tag)',
pattern: 'downloads/:user/:repo/:tag/total',
namedParams: {
user: 'atom',
@@ -89,13 +127,13 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
documentation,
},
{
title: 'GitHub Releases (by Asset)',
pattern: 'downloads/:user/:repo/:tag/:path',
title: 'GitHub release (latest by date and asset)',
pattern: 'downloads/:user/:repo/:tag/:assetName',
namedParams: {
user: 'atom',
repo: 'atom',
tag: 'latest',
path: 'atom-amd64.deb',
assetName: 'atom-amd64.deb',
},
staticPreview: this.render({
tag: 'latest',
@@ -105,14 +143,49 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
documentation,
},
{
title: 'GitHub Pre-Releases (by Asset)',
pattern: 'downloads-pre/:user/:repo/:tag/:path',
title: 'GitHub release (latest by SemVer and asset)',
pattern: 'downloads/:user/:repo/:tag/:assetName',
namedParams: {
user: 'atom',
repo: 'atom',
tag: 'latest',
path: 'atom-amd64.deb',
assetName: 'atom-amd64.deb',
},
queryParams: { sort: 'semver' },
staticPreview: this.render({
tag: 'latest',
assetName: 'atom-amd64.deb',
downloadCount: 3000,
}),
documentation,
},
{
title: 'GitHub release (latest by date and asset including pre-releases)',
pattern: 'downloads-pre/:user/:repo/:tag/:assetName',
namedParams: {
user: 'atom',
repo: 'atom',
tag: 'latest',
assetName: 'atom-amd64.deb',
},
staticPreview: this.render({
tag: 'latest',
assetName: 'atom-amd64.deb',
downloadCount: 237,
}),
documentation,
},
{
title:
'GitHub release (latest by SemVer and asset including pre-releases)',
pattern: 'downloads-pre/:user/:repo/:tag/:assetName',
namedParams: {
user: 'atom',
repo: 'atom',
tag: 'latest',
assetName: 'atom-amd64.deb',
},
queryParams: { sort: 'semver' },
staticPreview: this.render({
tag: 'latest',
assetName: 'atom-amd64.deb',
@@ -154,29 +227,16 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
return { downloadCount }
}
async handle({ kind, user, repo, tag, assetName }) {
async handle({ kind, user, repo, tag, assetName }, { sort }) {
let releases
if (tag === 'latest' && kind === 'downloads') {
const latestRelease = await this._requestJson({
schema: releaseSchema,
url: `/repos/${user}/${repo}/releases/latest`,
errorMessages: errorMessagesFor('repo not found'),
})
if (tag === 'latest') {
const include_prereleases = kind === 'downloads-pre' || undefined
const latestRelease = await fetchLatestRelease(
this,
{ user, repo },
{ sort, include_prereleases }
)
releases = [latestRelease]
} else if (tag === 'latest') {
// Keep only the latest release.
const [latestReleaseIncludingPrereleases] = await this._requestJson({
schema: releaseArraySchema,
url: `/repos/${user}/${repo}/releases`,
options: { qs: { per_page: 1 } },
errorMessages: errorMessagesFor('repo not found'),
})
// Note that the API will return an empty array if there are no releases
// https://github.com/badges/shields/issues/3786
if (!latestReleaseIncludingPrereleases) {
throw new NotFound({ prettyMessage: 'no releases' })
}
releases = [latestReleaseIncludingPrereleases]
} else if (tag) {
const wantedRelease = await this._requestJson({
schema: releaseSchema,

View File

@@ -4,6 +4,16 @@ const Joi = require('joi')
const { isMetric } = require('../test-validators')
const t = (module.exports = require('../tester').createServiceTester())
const mockLatestRelease = release => nock =>
nock('https://api.github.com')
.get('/repos/photonstorm/phaser/releases/latest')
.reply(200, release)
const mockReleases = releases => nock =>
nock('https://api.github.com')
.get('/repos/photonstorm/phaser/releases')
.reply(200, releases)
t.create('Downloads all releases')
.get('/downloads/photonstorm/phaser/total.json')
.expectBadge({ label: 'downloads', message: isMetric })
@@ -28,6 +38,116 @@ t.create('downloads for latest release')
.get('/downloads/photonstorm/phaser/latest/total.json')
.expectBadge({ label: 'downloads@latest', message: isMetric })
t.create('downloads for latest release (sort by date)')
.get('/downloads/photonstorm/phaser/latest/total.json')
.intercept(
mockLatestRelease({
assets: [
{ name: 'phaser.js', download_count: 5 },
{ name: 'phaser.min.js', download_count: 7 },
],
tag_name: 'v3.15.1',
prerelease: false,
})
)
.expectBadge({ label: 'downloads@latest', message: '12' })
t.create('downloads for latest release (sort by SemVer)')
.get('/downloads/photonstorm/phaser/latest/total.json?sort=semver')
.intercept(
mockReleases([
{
assets: [
{ name: 'phaser.js', download_count: 1 },
{ name: 'phaser.min.js', download_count: 3 },
],
tag_name: 'v3.16.0-rc1',
prerelease: true,
},
{
assets: [
{ name: 'phaser.js', download_count: 5 },
{ name: 'phaser.min.js', download_count: 7 },
],
tag_name: 'v3.15.0',
prerelease: false,
},
{
assets: [
{ name: 'phaser.js', download_count: 9 },
{ name: 'phaser.min.js', download_count: 11 },
],
tag_name: 'v3.15.1',
prerelease: false,
},
])
)
.expectBadge({ label: 'downloads@latest', message: '20' })
t.create('downloads for latest release (sort by date including pre-releases)')
.get('/downloads-pre/photonstorm/phaser/latest/total.json')
.intercept(
mockReleases([
{
assets: [
{ name: 'phaser.js', download_count: 1 },
{ name: 'phaser.min.js', download_count: 3 },
],
tag_name: 'v3.16.0-rc1',
prerelease: true,
},
{
assets: [
{ name: 'phaser.js', download_count: 5 },
{ name: 'phaser.min.js', download_count: 7 },
],
tag_name: 'v3.15.0',
prerelease: false,
},
{
assets: [
{ name: 'phaser.js', download_count: 9 },
{ name: 'phaser.min.js', download_count: 11 },
],
tag_name: 'v3.15.1',
prerelease: false,
},
])
)
.expectBadge({ label: 'downloads@latest', message: '4' })
t.create('downloads for latest release (sort by SemVer including pre-releases)')
.get('/downloads-pre/photonstorm/phaser/latest/total.json?sort=semver')
.intercept(
mockReleases([
{
assets: [
{ name: 'phaser.js', download_count: 9 },
{ name: 'phaser.min.js', download_count: 11 },
],
tag_name: 'v3.15.1',
prerelease: false,
},
{
assets: [
{ name: 'phaser.js', download_count: 1 },
{ name: 'phaser.min.js', download_count: 3 },
],
tag_name: 'v3.16.0-rc1',
prerelease: true,
},
{
assets: [
{ name: 'phaser.js', download_count: 5 },
{ name: 'phaser.min.js', download_count: 7 },
],
tag_name: 'v3.15.0',
prerelease: false,
},
])
)
.expectBadge({ label: 'downloads@latest', message: '4' })
t.create('downloads-pre for latest release')
.get('/downloads-pre/photonstorm/phaser/latest/total.json')
.expectBadge({ label: 'downloads@latest', message: isMetric })