diff --git a/services/scoop/scoop-version.service.js b/services/scoop/scoop-version.service.js new file mode 100644 index 0000000000..fed239c639 --- /dev/null +++ b/services/scoop/scoop-version.service.js @@ -0,0 +1,93 @@ +'use strict' + +const Joi = require('@hapi/joi') +const { NotFound } = require('..') +const { + ConditionalGithubAuthV3Service, +} = require('../github/github-auth-service') +const { fetchJsonFromRepo } = require('../github/github-common-fetch') +const { renderVersionBadge } = require('../version') + +const gitHubRepoRegExp = new RegExp( + 'https://github.com/(?.*?)/(?.*?)(/|$)' +) +const bucketsSchema = Joi.object() + .pattern(/.+/, Joi.string().pattern(gitHubRepoRegExp).required()) + .required() +const scoopSchema = Joi.object({ + version: Joi.string().required(), +}).required() +const queryParamSchema = Joi.object({ + bucket: Joi.string(), +}) + +module.exports = class ScoopVersion extends ConditionalGithubAuthV3Service { + // The buckets file (https://github.com/lukesampson/scoop/blob/master/buckets.json) changes very rarely. + // Cache it for the lifetime of the current Node.js process. + buckets = null + + static category = 'version' + + static route = { + base: 'scoop/v', + pattern: ':app', + queryParamSchema, + } + + static examples = [ + { + title: 'Scoop Version', + namedParams: { app: 'ngrok' }, + staticPreview: this.render({ version: '2.3.35' }), + }, + { + title: 'Scoop Version (extras bucket)', + namedParams: { app: 'dnspy' }, + queryParams: { bucket: 'extras' }, + staticPreview: this.render({ version: '6.1.4' }), + }, + ] + + static defaultBadgeData = { label: 'scoop' } + + static render({ version }) { + return renderVersionBadge({ version }) + } + + async handle({ app }, queryParams) { + if (!this.buckets) { + this.buckets = await fetchJsonFromRepo(this, { + schema: bucketsSchema, + user: 'lukesampson', + repo: 'scoop', + branch: 'master', + filename: 'buckets.json', + }) + } + const bucket = queryParams.bucket || 'main' + const bucketUrl = this.buckets[bucket] + if (!bucketUrl) { + throw new NotFound({ prettyMessage: `bucket "${bucket}" not found` }) + } + const { + groups: { user, repo }, + } = gitHubRepoRegExp.exec(bucketUrl) + try { + const { version } = await fetchJsonFromRepo(this, { + schema: scoopSchema, + user, + repo, + branch: 'master', + filename: `bucket/${app}.json`, + }) + return this.constructor.render({ version }) + } catch (error) { + if (error instanceof NotFound) { + throw new NotFound({ + prettyMessage: `${app} not found in bucket "${bucket}"`, + }) + } + throw error + } + } +} diff --git a/services/scoop/scoop-version.tester.js b/services/scoop/scoop-version.tester.js new file mode 100644 index 0000000000..42b89bdeb5 --- /dev/null +++ b/services/scoop/scoop-version.tester.js @@ -0,0 +1,44 @@ +'use strict' + +const { ServiceTester } = require('../tester') +const { isVPlusDottedVersionNClauses } = require('../test-validators') + +const t = (module.exports = new ServiceTester({ + id: 'scoop', + title: 'Scoop', +})) + +// version + +t.create('version (valid)').get('/v/apache.json').expectBadge({ + label: 'scoop', + message: isVPlusDottedVersionNClauses, +}) + +t.create('version (not found)').get('/v/not-a-real-app.json').expectBadge({ + label: 'scoop', + message: 'not-a-real-app not found in bucket "main"', +}) + +// version (custom bucket) + +t.create('version (valid custom bucket)') + .get('/v/dnspy.json?bucket=extras') + .expectBadge({ + label: 'scoop', + message: isVPlusDottedVersionNClauses, + }) + +t.create('version (not found in custom bucket)') + .get('/v/not-a-real-app.json?bucket=extras') + .expectBadge({ + label: 'scoop', + message: 'not-a-real-app not found in bucket "extras"', + }) + +t.create('version (wrong bucket)') + .get('/v/not-a-real-app.json?bucket=not-a-real-bucket') + .expectBadge({ + label: 'scoop', + message: 'bucket "not-a-real-bucket" not found', + })