Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Github package.json dependency version badge; affects [githubpackagejson githubmanifest npm] #2709

Merged
merged 12 commits into from
Jan 12, 2019
Merged
4 changes: 3 additions & 1 deletion services/github/github-common-fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion services/github/github-manifest.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})
104 changes: 104 additions & 0 deletions services/github/github-package-json.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ 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')
const { documentation } = require('./github-helpers')

const keywords = ['npm', 'node']

const versionSchema = Joi.object({
version: semver,
}).required()
Expand All @@ -35,6 +41,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 (branch)',
Expand All @@ -46,6 +53,7 @@ class GithubPackageJsonVersion extends ConditionalGithubAuthService {
},
staticPreview: this.render({ version: '2.0.0-alpha.2' }),
documentation,
keywords,
},
]
}
Expand All @@ -70,6 +78,99 @@ 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 dependency version (prod)',
pattern: ':user/:repo/:packageName',
namedParams: {
user: 'developit',
repo: 'microbundle',
packageName: 'rollup',
},
staticPreview: this.render({
dependency: 'rollup',
range: '^0.67.3',
}),
documentation,
keywords,
},
{
title: 'GitHub package.json 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,
keywords,
},
]
}

static get defaultBadgeData() {
return {
label: 'dependency',
}
}

static render({ dependency, range }) {
return {
label: dependency,
message: range,
color: 'blue',
calebcartwright marked this conversation as resolved.
Show resolved Hide resolved
}
}

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'
Expand Down Expand Up @@ -98,6 +199,7 @@ class DynamicGithubPackageJson extends ConditionalGithubAuthService {
value: ['bundle', 'rollup', 'micro library'],
}),
documentation,
keywords,
},
{
title: 'GitHub package.json dynamic',
Expand All @@ -114,6 +216,7 @@ class DynamicGithubPackageJson extends ConditionalGithubAuthService {
branch: 'master',
}),
documentation,
keywords,
},
]
}
Expand Down Expand Up @@ -151,5 +254,6 @@ class DynamicGithubPackageJson extends ConditionalGithubAuthService {

module.exports = {
GithubPackageJsonVersion,
GithubPackageJsonDependencyVersion,
DynamicGithubPackageJson,
}
57 changes: 56 additions & 1 deletion services/github/github-package-json.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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')
Expand All @@ -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/badges/shields/dev/@babel/core.json')
.expectJSONTypes(
Joi.object({
name: '@babel/core',
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: 'dev dependency not found',
})
23 changes: 6 additions & 17 deletions services/npm/npm-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -135,6 +124,6 @@ module.exports = class NpmBase extends BaseJsonService {
}
}

return this.constructor._validate(packageData, schema)
return this.constructor._validate(packageData, packageDataSchema)
}
}
28 changes: 2 additions & 26 deletions services/npm/npm-dependency-version.service.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const { InvalidParameter } = require('../errors')
const { getDependencyVersion } = require('../package-json-helpers')
const NpmBase = require('./npm-base')

const keywords = ['node']
Expand Down Expand Up @@ -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,
Expand All @@ -119,7 +95,7 @@ module.exports = class NpmDependencyVersion extends NpmBase {
registryUrl,
})

const { range } = this.transform({
const { range } = getDependencyVersion({
kind,
wantedDependency,
dependencies,
Expand Down
2 changes: 1 addition & 1 deletion services/npm/npm-dependency-version.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})
54 changes: 54 additions & 0 deletions services/package-json-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict'

const Joi = require('joi')
const { InvalidParameter } = require('./errors')

const isDependencyMap = Joi.object()
.pattern(
/./,
// This accepts a semver range, a URL, and many other possible values.
Joi.string()
.min(1)
.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}`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆 The only time this would get hit is if a service accepts and/or passes along a bad dependency kind here right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought I responded to this before. Yes! That would be a programmer error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay cool. Just wanted to make sure we weren't planning on surfacing this message up to end users
👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it got thrown in production, they'd see shields internal error.

}

const range = dependenciesOfKind[wantedDependency]
if (range === undefined) {
throw new InvalidParameter({
prettyMessage: `${kind} dependency not found`,
})
}

return { range }
}

module.exports = {
isDependencyMap,
isPackageJsonWithDependencies,
getDependencyVersion,
}