diff --git a/services/npms-io/npms-io-score.service.js b/services/npms-io/npms-io-score.service.js new file mode 100644 index 0000000000..79adf48816 --- /dev/null +++ b/services/npms-io/npms-io-score.service.js @@ -0,0 +1,85 @@ +'use strict' + +const Joi = require('joi') +const { BaseJsonService } = require('..') +const { coveragePercentage } = require('../color-formatters') + +// https://api-docs.npms.io/#api-Package-GetPackageInfo +const numberSchema = Joi.number().min(0).max(1).required() +const responseSchema = Joi.object({ + score: Joi.object({ + final: numberSchema, + detail: Joi.object({ + maintenance: numberSchema, + popularity: numberSchema, + quality: numberSchema, + }), + }), +}).required() + +const keywords = ['node', 'npm score'] + +module.exports = class NpmsIOScore extends BaseJsonService { + static category = 'analysis' + + static route = { + base: 'npms-io', + pattern: + ':type(final|maintenance|popularity|quality)-score/:scope(@.+)?/:packageName', + } + + static examples = [ + { + title: 'npms.io (final)', + namedParams: { type: 'final', packageName: 'egg' }, + staticPreview: this.render({ score: 0.9711 }), + keywords, + }, + { + title: 'npms.io (popularity)', + pattern: ':type/:scope/:packageName', + namedParams: { type: 'popularity', scope: '@vue', packageName: 'cli' }, + staticPreview: this.render({ type: 'popularity', score: 0.89 }), + keywords, + }, + { + title: 'npms.io (quality)', + namedParams: { type: 'quality', packageName: 'egg' }, + staticPreview: this.render({ type: 'quality', score: 0.98 }), + keywords, + }, + { + title: 'npms.io (maintenance)', + namedParams: { type: 'maintenance', packageName: 'command' }, + staticPreview: this.render({ type: 'maintenance', score: 0.222 }), + keywords, + }, + ] + + static defaultBadgeData = { + label: 'score', + } + + static render({ type, score }) { + return { + label: type === 'final' ? 'score' : type, + message: `${(score * 100).toFixed(0)}%`, + color: coveragePercentage(score * 100), + } + } + + async handle({ type, scope, packageName }) { + const slug = scope ? `${scope}/${packageName}` : packageName + const url = `https://api.npms.io/v2/package/${encodeURIComponent(slug)}` + + const json = await this._requestJson({ + schema: responseSchema, + url, + errorMessages: { 404: 'package not found or too new' }, + }) + + const score = type === 'final' ? json.score.final : json.score.detail[type] + + return this.constructor.render({ type, score }) + } +} diff --git a/services/npms-io/npms-io-score.tester.js b/services/npms-io/npms-io-score.tester.js new file mode 100644 index 0000000000..1c1769ed21 --- /dev/null +++ b/services/npms-io/npms-io-score.tester.js @@ -0,0 +1,62 @@ +'use strict' + +const { isPercentage } = require('../test-validators') +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('should show final score').get('/final-score/vue.json').expectBadge({ + label: 'score', + message: isPercentage, +}) + +t.create('should show color') + .get('/final-score/mock-for-package-score.json') + .intercept(nock => { + nock.enableNetConnect() + + return nock('https://api.npms.io', { allowUnmocked: true }) + .get(`/v2/package/mock-for-package-score`) + .reply(200, { + score: { + final: 0.89, + }, + }) + }) + .expectBadge({ + label: 'score', + message: isPercentage, + color: 'yellowgreen', + }) + +t.create('should show final score with scope') + .get('/final-score/@vue/cli.json') + .expectBadge({ + label: 'score', + message: isPercentage, + }) + +t.create('should show maintenance') + .get('/maintenance-score/vue.json') + .expectBadge({ + label: 'maintenance', + message: isPercentage, + }) + +t.create('should show popularity') + .get('/popularity-score/vue.json') + .expectBadge({ + label: 'popularity', + message: isPercentage, + }) + +t.create('should show quality').get('/quality-score/vue.json').expectBadge({ + label: 'quality', + message: isPercentage, +}) + +t.create('unknown package') + .get('/final-score/npm-api-does-not-have-this-package.json') + .expectBadge({ + label: 'score', + message: 'package not found or too new', + color: 'red', + })