diff --git a/services/base-json.js b/services/base-json.js index 8425a2d192..5584531b50 100644 --- a/services/base-json.js +++ b/services/base-json.js @@ -15,7 +15,7 @@ class BaseJsonService extends BaseService { }) if (error) { this.logTrace( - 'error', + 'validate', emojic.womanShrugging, 'Response did not match schema', error.message diff --git a/services/librariesio/librariesio-base.js b/services/librariesio/librariesio-base.js new file mode 100644 index 0000000000..bb45901725 --- /dev/null +++ b/services/librariesio/librariesio-base.js @@ -0,0 +1,49 @@ +'use strict' + +const Joi = require('joi') +const BaseJsonService = require('../base-json') +const { InvalidResponse } = require('../errors') +const { nonNegativeInteger } = require('../validators') + +// API doc: https://libraries.io/api#project +// We distinguishb between packages and repos based on the presence of the +// `platform` key. +const packageSchema = Joi.object({ + platform: Joi.string().required(), + dependents_count: nonNegativeInteger, + dependent_repos_count: nonNegativeInteger, +}).required() + +const repoSchema = Joi.object({ + platform: Joi.any().forbidden(), +}).required() + +const packageOrRepoSchema = Joi.alternatives(repoSchema, packageSchema) + +class LibrariesIoBase extends BaseJsonService { + static buildUrl(base) { + return { + base, + format: '(\\w+)/(.+)', + capture: ['platform', 'packageName'], + } + } + + async fetch({ platform, packageName }, { allowPackages, allowRepos } = {}) { + const json = await this._requestJson({ + schema: packageOrRepoSchema, + url: `https://libraries.io/api/${platform}/${packageName}`, + notFoundMessage: 'package not found', + }) + const isPackage = Boolean(json.platform) + if (isPackage && !allowPackages) { + throw new InvalidResponse({ prettyMessage: 'not supported for packages' }) + } + if (!isPackage && !allowRepos) { + throw new InvalidResponse({ prettyMessage: 'not supported for repos' }) + } + return json + } +} + +module.exports = LibrariesIoBase diff --git a/services/librariesio/librariesio-dependent-repos.service.js b/services/librariesio/librariesio-dependent-repos.service.js new file mode 100644 index 0000000000..39f531b8b8 --- /dev/null +++ b/services/librariesio/librariesio-dependent-repos.service.js @@ -0,0 +1,50 @@ +'use strict' + +const { metric } = require('../../lib/text-formatters') +const LibrariesIoBase = require('./librariesio-base') + +// https://libraries.io/api#project-dependent-repositories +class LibrariesIoDependentRepos extends LibrariesIoBase { + static get category() { + return 'other' + } + + static get defaultBadgeData() { + return { + label: 'dependent repos', + } + } + + static get url() { + return this.buildUrl('librariesio/dependent-repos') + } + + static get examples() { + return [ + { + title: 'Dependent repos (via libraries.io)', + previewUrl: 'npm/got', + }, + ] + } + + static render({ dependentReposCount }) { + return { + message: metric(dependentReposCount), + color: dependentReposCount === 0 ? 'orange' : 'brightgreen', + } + } + + async handle({ platform, packageName }) { + const { dependent_repos_count: dependentReposCount } = await this.fetch( + { + platform, + packageName, + }, + { allowPackages: true } + ) + return this.constructor.render({ dependentReposCount }) + } +} + +module.exports = LibrariesIoDependentRepos diff --git a/services/librariesio/librariesio-dependent-repos.tester.js b/services/librariesio/librariesio-dependent-repos.tester.js new file mode 100644 index 0000000000..e159090c6a --- /dev/null +++ b/services/librariesio/librariesio-dependent-repos.tester.js @@ -0,0 +1,29 @@ +'use strict' + +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric } = require('../test-validators') + +const t = new ServiceTester({ + id: 'librariesio-dependent-repos', + title: 'Libraries.io dependent repos', + pathPrefix: '/librariesio/dependent-repos', +}) +module.exports = t + +t.create('dependent repo count') + .get('/npm/got.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'dependent repos', + value: isMetric, + }) + ) + +t.create('dependent repo count (not a package)') + .get('/npm/foobar-is-not-package.json') + .timeout(10000) + .expectJSON({ + name: 'dependent repos', + value: 'package not found', + }) diff --git a/services/librariesio/librariesio-dependents.service.js b/services/librariesio/librariesio-dependents.service.js new file mode 100644 index 0000000000..7f9e6544fb --- /dev/null +++ b/services/librariesio/librariesio-dependents.service.js @@ -0,0 +1,50 @@ +'use strict' + +const { metric } = require('../../lib/text-formatters') +const LibrariesIoBase = require('./librariesio-base') + +// https://libraries.io/api#project-dependents +class LibrariesIoDependents extends LibrariesIoBase { + static get category() { + return 'other' + } + + static get defaultBadgeData() { + return { + label: 'dependents', + } + } + + static get url() { + return this.buildUrl('librariesio/dependents') + } + + static get examples() { + return [ + { + title: 'Dependents (via libraries.io)', + previewUrl: 'npm/got', + }, + ] + } + + static render({ dependentCount }) { + return { + message: metric(dependentCount), + color: dependentCount === 0 ? 'orange' : 'brightgreen', + } + } + + async handle({ platform, packageName }) { + const { dependents_count: dependentCount } = await this.fetch( + { + platform, + packageName, + }, + { allowPackages: true } + ) + return this.constructor.render({ dependentCount }) + } +} + +module.exports = LibrariesIoDependents diff --git a/services/librariesio/librariesio-dependents.tester.js b/services/librariesio/librariesio-dependents.tester.js new file mode 100644 index 0000000000..8a0bf1dd54 --- /dev/null +++ b/services/librariesio/librariesio-dependents.tester.js @@ -0,0 +1,36 @@ +'use strict' + +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric } = require('../test-validators') + +const t = new ServiceTester({ + id: 'librariesio-dependents', + title: 'Libraries.io dependents', + pathPrefix: '/librariesio/dependents', +}) +module.exports = t + +t.create('dependent count') + .get('/npm/got.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'dependents', + value: isMetric, + }) + ) + +t.create('dependent count (nonexistent package)') + .get('/npm/foobar-is-not-package.json') + .timeout(10000) + .expectJSON({ + name: 'dependents', + value: 'package not found', + }) + +t.create('dependent count (repo)') + .get('/github/sindresorhus/got.json') + .expectJSON({ + name: 'dependents', + value: 'not supported for repos', + }) diff --git a/services/npm/npm-base.js b/services/npm/npm-base.js index 692d0e272a..3aa86a7986 100644 --- a/services/npm/npm-base.js +++ b/services/npm/npm-base.js @@ -22,7 +22,7 @@ const schema = Joi.object({ // Abstract class for NPM badges which display data about the latest version // of a package. module.exports = class NpmBase extends BaseJsonService { - static buildUrl(base, { withTag }) { + static buildUrl(base, { withTag } = {}) { if (withTag) { return { base,