From a04e7c92297945635f45dd2f916343dd82c12dc3 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Tue, 8 Jan 2019 17:08:46 -0500 Subject: [PATCH 1/5] Add [GithubPackageJsonDependencyVersion] badge; affects [githubpackagejson githubmanifest npm] Close #2259 which is mostly about a `package.json` dependency badge. --- services/github/github-common-fetch.js | 4 +- services/github/github-manifest.tester.js | 2 +- .../github/github-package-json.service.js | 96 +++++++++++++++++++ services/github/github-package-json.tester.js | 57 ++++++++++- services/npm/npm-base.js | 23 ++--- .../npm/npm-dependency-version.service.js | 28 +----- services/package-json-helpers.js | 57 +++++++++++ 7 files changed, 221 insertions(+), 46 deletions(-) create mode 100644 services/package-json-helpers.js diff --git a/services/github/github-common-fetch.js b/services/github/github-common-fetch.js index 6eb60e43af4cb..aff6d6ec1dc46 100644 --- a/services/github/github-common-fetch.js +++ b/services/github/github-common-fetch.js @@ -40,7 +40,9 @@ async function fetchJsonFromRepo( schema: contentSchema, url, options, - errorMessages: errorMessagesFor(`${filename} missing or repo not found`), + errorMessages: errorMessagesFor( + `repo not found, branch not found, or ${filename} missing` + ), }) let decoded diff --git a/services/github/github-manifest.tester.js b/services/github/github-manifest.tester.js index 75cebbe57d6dc..55a77a5043b01 100644 --- a/services/github/github-manifest.tester.js +++ b/services/github/github-manifest.tester.js @@ -40,5 +40,5 @@ t.create('Manifest invalid json response') .get('/v/RedSparr0w/not-a-real-project.json') .expectJSON({ name: 'version', - value: 'manifest.json missing or repo not found', + value: 'repo not found, branch not found, or manifest.json missing', }) diff --git a/services/github/github-package-json.service.js b/services/github/github-package-json.service.js index a91e9b2e7f708..eb17bdf3fa31c 100644 --- a/services/github/github-package-json.service.js +++ b/services/github/github-package-json.service.js @@ -6,6 +6,10 @@ const { transformAndValidate, renderDynamicBadge, } = require('../dynamic-common') +const { + isPackageJsonWithDependencies, + getDependencyVersion, +} = require('../package-json-helpers') const { semver } = require('../validators') const { ConditionalGithubAuthService } = require('./github-auth-service') const { fetchJsonFromRepo } = require('./github-common-fetch') @@ -71,6 +75,97 @@ class GithubPackageJsonVersion extends ConditionalGithubAuthService { } } +class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthService { + static get category() { + return 'platform-support' + } + + static get route() { + return { + base: 'github/package-json/dependency-version', + pattern: + ':user/:repo/:kind(dev|peer)?/:scope(@[^/]+)?/:packageName/:branch*', + } + } + + static get examples() { + return [ + { + title: 'GitHub package.json npm dependency version', + pattern: ':user/:repo/:packageName', + namedParams: { + user: 'developit', + repo: 'microbundle', + packageName: 'rollup', + }, + staticPreview: this.render({ + dependency: 'rollup', + range: '^0.67.3', + }), + documentation, + }, + { + title: 'GitHub package.json npm dependency version (dev dep on branch)', + pattern: ':user/:repo/dev/:scope?/:packageName/:branch*', + namedParams: { + user: 'zeit', + repo: 'next.js', + branch: 'canary', + scope: '@babel', + packageName: 'preset-react', + }, + staticPreview: this.render({ + dependency: '@babel/preset-react', + range: '7.0.0', + }), + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { + label: 'dependency', + } + } + + static render({ dependency, range }) { + return { + label: dependency, + message: range, + color: 'blue', + } + } + + async handle({ user, repo, kind, branch = 'master', scope, packageName }) { + const { + dependencies, + devDependencies, + peerDependencies, + } = await fetchJsonFromRepo(this, { + schema: isPackageJsonWithDependencies, + user, + repo, + branch, + filename: 'package.json', + }) + + const wantedDependency = scope ? `${scope}/${packageName}` : packageName + const { range } = getDependencyVersion({ + kind, + wantedDependency, + dependencies, + devDependencies, + peerDependencies, + }) + + return this.constructor.render({ + dependency: wantedDependency, + range, + }) + } +} + class DynamicGithubPackageJson extends ConditionalGithubAuthService { static get category() { return 'other' @@ -152,5 +247,6 @@ class DynamicGithubPackageJson extends ConditionalGithubAuthService { module.exports = { GithubPackageJsonVersion, + GithubPackageJsonDependencyVersion, DynamicGithubPackageJson, } diff --git a/services/github/github-package-json.tester.js b/services/github/github-package-json.tester.js index 83f69266e76a4..27446e00ff91c 100644 --- a/services/github/github-package-json.tester.js +++ b/services/github/github-package-json.tester.js @@ -3,6 +3,7 @@ const Joi = require('joi') const ServiceTester = require('../service-tester') const { isSemver } = require('../test-validators') +const { semverRange } = require('../validators') const t = (module.exports = new ServiceTester({ id: 'GithubPackageJson', @@ -23,7 +24,7 @@ t.create('Package version (repo not found)') .get('/v/badges/helmets.json') .expectJSON({ name: 'version', - value: 'package.json missing or repo not found', + value: 'repo not found, branch not found, or package.json missing', }) t.create('Package name') @@ -46,3 +47,57 @@ t.create('Package array') t.create('Package object') .get('/dependencies/badges/shields.json') .expectJSON({ name: 'package.json', value: 'invalid key value' }) + +t.create('Peer dependency version') + .get('/dependency-version/paulmelnikow/react-boxplot/peer/react.json') + .expectJSONTypes( + Joi.object({ + name: 'react', + value: semverRange, + }) + ) + +t.create('Dev dependency version') + .get( + '/dependency-version/paulmelnikow/react-boxplot/dev/react.json?label=react%20tested' + ) + .expectJSONTypes( + Joi.object({ + name: 'react tested', + value: semverRange, + }) + ) + +t.create('Prod prod dependency version') + .get('/dependency-version/paulmelnikow/react-boxplot/simple-statistics.json') + .expectJSONTypes( + Joi.object({ + name: 'simple-statistics', + value: semverRange, + }) + ) + +t.create('Scoped dependency') + .get('/dependency-version/zeit/next.js/dev/babel-eslint.json') + .expectJSONTypes( + Joi.object({ + name: 'babel-eslint', + value: semverRange, + }) + ) + +t.create('Scoped dependency on branch') + .get('/dependency-version/zeit/next.js/dev/babel-eslint/alpha.json') + .expectJSONTypes( + Joi.object({ + name: 'babel-eslint', + value: semverRange, + }) + ) + +t.create('Unknown dependency') + .get('/dependency-version/paulmelnikow/react-boxplot/dev/i-made-this-up.json') + .expectJSON({ + name: 'dependency', + value: 'dependency not found', + }) diff --git a/services/npm/npm-base.js b/services/npm/npm-base.js index ac19ad658342c..2df4144579202 100644 --- a/services/npm/npm-base.js +++ b/services/npm/npm-base.js @@ -4,26 +4,15 @@ const Joi = require('joi') const serverSecrets = require('../../lib/server-secrets') const BaseJsonService = require('../base-json') const { InvalidResponse, NotFound } = require('../errors') -const { semverRange } = require('../validators') +const { isDependencyMap } = require('../package-json-helpers') const deprecatedLicenseObjectSchema = Joi.object({ type: Joi.string().required(), }) -const dependencyMap = Joi.object() - .pattern( - /./, - Joi.alternatives().try( - semverRange, - Joi.string() - .uri() - .required() - ) - ) - .default({}) -const schema = Joi.object({ - dependencies: dependencyMap, - devDependencies: dependencyMap, - peerDependencies: dependencyMap, +const packageDataSchema = Joi.object({ + dependencies: isDependencyMap, + devDependencies: isDependencyMap, + peerDependencies: isDependencyMap, engines: Joi.object().pattern(/./, Joi.string()), license: Joi.alternatives().try( Joi.string(), @@ -137,6 +126,6 @@ module.exports = class NpmBase extends BaseJsonService { } } - return this.constructor._validate(packageData, schema) + return this.constructor._validate(packageData, packageDataSchema) } } diff --git a/services/npm/npm-dependency-version.service.js b/services/npm/npm-dependency-version.service.js index a741aa7af814f..82c3fa4a488e6 100644 --- a/services/npm/npm-dependency-version.service.js +++ b/services/npm/npm-dependency-version.service.js @@ -1,6 +1,6 @@ 'use strict' -const { InvalidParameter } = require('../errors') +const { getDependencyVersion } = require('../package-json-helpers') const NpmBase = require('./npm-base') const keywords = ['node'] @@ -78,30 +78,6 @@ module.exports = class NpmDependencyVersion extends NpmBase { } } - transform({ - kind, - wantedDependency, - dependencies, - devDependencies, - peerDependencies, - }) { - let dependenciesOfKind - if (kind === 'peer') { - dependenciesOfKind = peerDependencies - } else if (kind === 'dev') { - dependenciesOfKind = devDependencies - } else { - dependenciesOfKind = dependencies - } - - const range = dependenciesOfKind[wantedDependency] - if (range === undefined) { - throw new InvalidParameter({ prettyMessage: 'not found' }) - } - - return { range } - } - async handle(namedParams, queryParams) { const { scope, packageName, registryUrl } = this.constructor.unpackParams( namedParams, @@ -119,7 +95,7 @@ module.exports = class NpmDependencyVersion extends NpmBase { registryUrl, }) - const { range } = this.transform({ + const { range } = getDependencyVersion({ kind, wantedDependency, dependencies, diff --git a/services/package-json-helpers.js b/services/package-json-helpers.js new file mode 100644 index 0000000000000..2809489b1cc46 --- /dev/null +++ b/services/package-json-helpers.js @@ -0,0 +1,57 @@ +'use strict' + +const Joi = require('joi') +const { InvalidParameter } = require('./errors') +const { semverRange } = require('./validators') + +const isDependencyMap = Joi.object() + .pattern( + /./, + Joi.alternatives().try( + semverRange, + Joi.string() + .uri() + .required() + ) + ) + .default({}) + +const isPackageJsonWithDependencies = Joi.object({ + dependencies: isDependencyMap, + devDependencies: isDependencyMap, + peerDependencies: isDependencyMap, +}).required() + +function getDependencyVersion({ + kind = 'prod', + wantedDependency, + dependencies, + devDependencies, + peerDependencies, +}) { + let dependenciesOfKind + if (kind === 'peer') { + dependenciesOfKind = peerDependencies + } else if (kind === 'dev') { + dependenciesOfKind = devDependencies + } else if (kind === 'prod') { + dependenciesOfKind = dependencies + } else { + throw Error(`Not very kind: ${kind}`) + } + + const range = dependenciesOfKind[wantedDependency] + if (range === undefined) { + throw new InvalidParameter({ + prettyMessage: `${kind} dependency not found`, + }) + } + + return { range } +} + +module.exports = { + isDependencyMap, + isPackageJsonWithDependencies, + getDependencyVersion, +} From 7c91b2eda88a8baa02aa8cd3e64330ff87390ad1 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Tue, 8 Jan 2019 17:11:14 -0500 Subject: [PATCH 2/5] Better visuals --- services/github/github-package-json.service.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/services/github/github-package-json.service.js b/services/github/github-package-json.service.js index eb17bdf3fa31c..83086c46f63d2 100644 --- a/services/github/github-package-json.service.js +++ b/services/github/github-package-json.service.js @@ -15,6 +15,8 @@ const { ConditionalGithubAuthService } = require('./github-auth-service') const { fetchJsonFromRepo } = require('./github-common-fetch') const { documentation } = require('./github-helpers') +const keywords = ['npm', 'node'] + const versionSchema = Joi.object({ version: semver, }).required() @@ -40,6 +42,7 @@ class GithubPackageJsonVersion extends ConditionalGithubAuthService { namedParams: { user: 'IcedFrisby', repo: 'IcedFrisby' }, staticPreview: this.render({ version: '2.0.0-alpha.2' }), documentation, + keywords, }, { title: 'GitHub package.json version', @@ -51,6 +54,7 @@ class GithubPackageJsonVersion extends ConditionalGithubAuthService { }, staticPreview: this.render({ version: '2.0.0-alpha.2' }), documentation, + keywords, }, ] } @@ -91,7 +95,7 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthService { static get examples() { return [ { - title: 'GitHub package.json npm dependency version', + title: 'GitHub package.json dependency version', pattern: ':user/:repo/:packageName', namedParams: { user: 'developit', @@ -103,9 +107,10 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthService { range: '^0.67.3', }), documentation, + keywords, }, { - title: 'GitHub package.json npm dependency version (dev dep on branch)', + title: 'GitHub package.json dependency version (dev dep on branch)', pattern: ':user/:repo/dev/:scope?/:packageName/:branch*', namedParams: { user: 'zeit', @@ -119,6 +124,7 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthService { range: '7.0.0', }), documentation, + keywords, }, ] } @@ -194,6 +200,7 @@ class DynamicGithubPackageJson extends ConditionalGithubAuthService { value: ['bundle', 'rollup', 'micro library'], }), documentation, + keywords, }, { title: 'GitHub package.json dynamic', @@ -210,6 +217,7 @@ class DynamicGithubPackageJson extends ConditionalGithubAuthService { branch: 'master', }), documentation, + keywords, }, ] } From f80880aee469eecfaedac74a3591a7360b62233f Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Wed, 9 Jan 2019 16:35:39 -0500 Subject: [PATCH 3/5] Fix tests --- services/github/github-package-json.tester.js | 2 +- services/npm/npm-dependency-version.tester.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/github/github-package-json.tester.js b/services/github/github-package-json.tester.js index 27446e00ff91c..194ed63e5a118 100644 --- a/services/github/github-package-json.tester.js +++ b/services/github/github-package-json.tester.js @@ -99,5 +99,5 @@ t.create('Unknown dependency') .get('/dependency-version/paulmelnikow/react-boxplot/dev/i-made-this-up.json') .expectJSON({ name: 'dependency', - value: 'dependency not found', + value: 'dev dependency not found', }) diff --git a/services/npm/npm-dependency-version.tester.js b/services/npm/npm-dependency-version.tester.js index 5232feda50312..5a998e8a25bb4 100644 --- a/services/npm/npm-dependency-version.tester.js +++ b/services/npm/npm-dependency-version.tester.js @@ -44,5 +44,5 @@ t.create('unknown dependency') .get('/react-boxplot/dev/i-made-this-up.json') .expectJSON({ name: 'dependency', - value: 'not found', + value: 'dev dependency not found', }) From abf4a34ecf75819df14ffac10d0a3b99a2495a0e Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Fri, 11 Jan 2019 12:26:09 -0500 Subject: [PATCH 4/5] Add "prod" --- services/github/github-package-json.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/github/github-package-json.service.js b/services/github/github-package-json.service.js index b40203e483108..870289905d4f6 100644 --- a/services/github/github-package-json.service.js +++ b/services/github/github-package-json.service.js @@ -94,7 +94,7 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthService { static get examples() { return [ { - title: 'GitHub package.json dependency version', + title: 'GitHub package.json dependency version (prod)', pattern: ':user/:repo/:packageName', namedParams: { user: 'developit', From 930a566f30ca16c6b3ede0abc183c06755b53d01 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Fri, 11 Jan 2019 14:33:47 -0500 Subject: [PATCH 5/5] Update test and make dependency validation more lenient --- services/github/github-package-json.tester.js | 4 ++-- services/package-json-helpers.js | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/services/github/github-package-json.tester.js b/services/github/github-package-json.tester.js index 194ed63e5a118..d376bd1b02a03 100644 --- a/services/github/github-package-json.tester.js +++ b/services/github/github-package-json.tester.js @@ -78,10 +78,10 @@ t.create('Prod prod dependency version') ) t.create('Scoped dependency') - .get('/dependency-version/zeit/next.js/dev/babel-eslint.json') + .get('/dependency-version/badges/shields/dev/@babel/core.json') .expectJSONTypes( Joi.object({ - name: 'babel-eslint', + name: '@babel/core', value: semverRange, }) ) diff --git a/services/package-json-helpers.js b/services/package-json-helpers.js index 2809489b1cc46..9ad97dc03cee3 100644 --- a/services/package-json-helpers.js +++ b/services/package-json-helpers.js @@ -2,17 +2,14 @@ const Joi = require('joi') const { InvalidParameter } = require('./errors') -const { semverRange } = require('./validators') const isDependencyMap = Joi.object() .pattern( /./, - Joi.alternatives().try( - semverRange, - Joi.string() - .uri() - .required() - ) + // This accepts a semver range, a URL, and many other possible values. + Joi.string() + .min(1) + .required() ) .default({})