diff --git a/services/github/github-package-registry-version.service.js b/services/github/github-package-registry-version.service.js new file mode 100644 index 0000000000000000000000000000000000000000..d8f095afe8282d1c8479fed0d38596574652190b --- /dev/null +++ b/services/github/github-package-registry-version.service.js @@ -0,0 +1,185 @@ +'use strict' + +const gql = require('graphql-tag') +const Joi = require('@hapi/joi') +const { renderVersionBadge } = require('../version') +const { GithubAuthV4Service } = require('./github-auth-service') +const { documentation, transformErrors } = require('./github-helpers') +const { NotFound } = require('..') + +// https://developer.github.com/v4/object/registrypackageversion/ +const packageVersion = Joi.object({ + preRelease: Joi.bool().required(), + version: Joi.string().required(), +}) + +const latestVersionSchema = Joi.object({ + data: Joi.object({ + repository: Joi.object({ + registryPackages: Joi.object({ + nodes: Joi.array() + .items( + Joi.object({ + latestVersion: packageVersion, + }) + ) + .min(0) + .required(), + }).required(), + }).required(), + }).required(), +}).required() + +const versionSchema = Joi.object({ + data: Joi.object({ + repository: Joi.object({ + registryPackages: Joi.object({ + nodes: Joi.array() + .items( + Joi.object({ + versions: Joi.object({ + nodes: Joi.array() + .items(packageVersion) + .min(0) + .required(), + }), + }) + ) + .min(0) + .required(), + }).required(), + }).required(), + }).required(), +}).required() + +module.exports = class GithubPackageRegistryVersion extends GithubAuthV4Service { + static get category() { + return 'version' + } + + static get route() { + return { + base: 'github/packages', + pattern: ':type(v|vpre)/:user/:repo/:packageName', + } + } + + static get examples() { + return [ + { + title: 'GitHub Package Registry Version (with prereleases)', + pattern: 'vpre/:user/:repo/:packageName', + namedParams: { + user: 'github', + repo: 'semantic', + packageName: 'semantic', + }, + staticPreview: renderVersionBadge({ version: '0.8.0.0' }), + documentation, + }, + { + title: 'GitHub Package Registry Version', + pattern: 'v/:user/:repo/:packageName', + namedParams: { + user: 'github', + repo: 'auto-complete-element', + packageName: 'auto-complete-element', + }, + staticPreview: renderVersionBadge({ version: '0.8.0.0' }), + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { label: 'version' } + } + + async fetch({ user, repo, packageName, includePrereleases }) { + if (includePrereleases) { + return await this._requestGraphql({ + query: gql` + query($user: String!, $repo: String!, $packageName: String!) { + repository(owner: $user, name: $repo) { + registryPackages(name: $packageName, first: 1) { + nodes { + latestVersion { + preRelease + version + } + } + } + } + } + `, + variables: { user, repo, packageName }, + schema: latestVersionSchema, + transformErrors, + }) + } else { + return await this._requestGraphql({ + query: gql` + query($user: String!, $repo: String!, $packageName: String!) { + repository(owner: $user, name: $repo) { + registryPackages(name: $packageName, first: 1) { + nodes { + versions(first: 1) { + nodes { + preRelease + version + } + } + } + } + } + } + `, + variables: { user, repo, packageName }, + schema: versionSchema, + transformErrors, + }) + } + } + + transform({ json, includePrereleases }) { + const { + data: { + repository: { + registryPackages: { + nodes: [ghPackage], + }, + }, + }, + } = json + + if (!ghPackage) { + throw new NotFound({ prettyMessage: 'package not found' }) + } + + if (includePrereleases) { + const { + latestVersion: { version }, + } = ghPackage + return { version } + } else { + const { + versions: { + nodes: [{ version }], + }, + } = ghPackage + return { version } + } + } + + async handle({ type, user, repo, packageName }) { + const includePrereleases = type === 'vpre' + const json = await this.fetch({ + user, + repo, + packageName, + includePrereleases, + }) + const { version } = this.transform({ json, includePrereleases }) + return renderVersionBadge({ version }) + } +} diff --git a/services/github/github-package-registry-version.tester.js b/services/github/github-package-registry-version.tester.js new file mode 100644 index 0000000000000000000000000000000000000000..0a855766561f92f3854b05cad22da9cfce6d4c12 --- /dev/null +++ b/services/github/github-package-registry-version.tester.js @@ -0,0 +1,40 @@ +'use strict' + +const Joi = require('@hapi/joi') +const { + isVPlusDottedVersionNClausesWithOptionalSuffix, +} = require('../test-validators') +const t = (module.exports = require('../tester').createServiceTester()) + +const isPackageVersion = Joi.alternatives().try( + isVPlusDottedVersionNClausesWithOptionalSuffix, + Joi.string().required() +) + +t.create('Package Registry version (non-existent repository)') + .get('/v/badges/not-a-real-repo/super-fake-package.json') + .expectBadge({ + label: 'version', + message: 'repo not found', + }) + +t.create('Package Registry version (non-existent package)') + .get('/v/badges/shields/super-fake-package.json') + .expectBadge({ + label: 'version', + message: 'package not found', + }) + +t.create('Package Registry version (pre-release)') + .get('/vpre/UiPath/angular-components/angular.json') + .expectBadge({ + label: 'version', + message: isPackageVersion, + }) + +t.create('Package Registry version (release)') + .get('/v/github/semantic/semantic.json') + .expectBadge({ + label: 'version', + message: isPackageVersion, + })