diff --git a/.circleci/config.yml b/.circleci/config.yml index 27ab88c387336..a3b85adf9c5cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - save_cache: paths: @@ -37,7 +37,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - save_cache: paths: @@ -88,7 +88,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Linter @@ -135,7 +135,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Integration tests @@ -164,7 +164,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Integration tests @@ -191,7 +191,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Danger @@ -215,7 +215,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Prepare frontend tests @@ -250,7 +250,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Identify services tagged in the PR title @@ -280,7 +280,7 @@ jobs: - run: name: Install dependencies - command: npm install + command: npm ci - run: name: Identify services tagged in the PR title diff --git a/.gitpod.yml b/.gitpod.yml index c426f7462cbfe..16f7d889fe53a 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,5 +1,5 @@ ports: - port: 8080 tasks: - - init: npm install && npm run build + - init: npm ci && npm run build command: node server 8080 0.0.0.0 diff --git a/.prettierignore b/.prettierignore index 3ffc29d698e3b..5201ef69ed9b5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,7 @@ package.json package-lock.json /__snapshots__ /.next +/.cache /build /public /coverage diff --git a/Dockerfile b/Dockerfile index 827d3ea1a29d6..e35dad59c3bc4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.9.4-alpine +FROM node:8-alpine RUN apk add --no-cache gettext imagemagick librsvg git @@ -10,9 +10,9 @@ ARG NODE_ENV ENV NODE_ENV $NODE_ENV COPY package.json package-lock.json /usr/src/app/ -# Without the gh-badges package.json and CLI script in place, `npm install` will fail. +# Without the gh-badges package.json and CLI script in place, `npm ci` will fail. COPY gh-badges /usr/src/app/gh-badges/ -RUN npm install +RUN npm ci COPY . /usr/src/app RUN npm run build diff --git a/README.md b/README.md index 220373c008750..c1ed2f6093131 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ You can read a [tutorial on how to add a badge][tutorial]. 1. Install Node 8 or later. You can use the [package manager][] of your choice. Tests need to pass in Node 8 and 9. 2. Clone this repository. -3. Run `npm install` to install the dependencies. +3. Run `npm ci` to install the dependencies. 4. Run `npm start` to start the server. 5. Open `http://localhost:3000/` to view the frontend. diff --git a/core/base-service/base.js b/core/base-service/base.js index 10fcb08cf616b..b08b1a3bcb130 100644 --- a/core/base-service/base.js +++ b/core/base-service/base.js @@ -128,7 +128,7 @@ module.exports = class BaseService { * this service. * * The preferred way to specify an example is with `namedParams` which are - * substitued into the service's compiled route pattern. The rendered badge + * substituted into the service's compiled route pattern. The rendered badge * is specified with `staticPreview`. * * For services which use a route `format`, the `pattern` can be specified as diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index a3e4b7fe26201..f4b27b7ad0fcb 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -43,18 +43,6 @@ class DummyService extends BaseService { staticPreview: this.render({ namedParamA: 'foo', queryParamA: 'bar' }), keywords: ['hello'], }, - { - namedParams: { namedParamA: 'World' }, - staticPreview: this.render({ namedParamA: 'foo', queryParamA: 'bar' }), - keywords: ['hello'], - }, - { - pattern: ':world', - namedParams: { world: 'World' }, - queryParams: { queryParamA: '!!!' }, - staticPreview: this.render({ namedParamA: 'foo', queryParamA: 'bar' }), - keywords: ['hello'], - }, ] } @@ -343,59 +331,8 @@ describe('BaseService', function() { queryParams: [], }, }) - - const [first, second, third] = examples - expect(first).to.deep.equal({ - title: 'DummyService', - example: { - pattern: '/foo/:world', - namedParams: { world: 'World' }, - queryParams: {}, - }, - preview: { - label: 'cat', - message: 'Hello namedParamA: foo with queryParamA: bar', - color: 'lightgrey', - namedLogo: undefined, - style: undefined, - }, - keywords: ['hello'], - documentation: undefined, - }) - expect(second).to.deep.equal({ - title: 'DummyService', - example: { - pattern: '/foo/:namedParamA', - namedParams: { namedParamA: 'World' }, - queryParams: {}, - }, - preview: { - label: 'cat', - message: 'Hello namedParamA: foo with queryParamA: bar', - color: 'lightgrey', - namedLogo: undefined, - style: undefined, - }, - keywords: ['hello'], - documentation: undefined, - }) - expect(third).to.deep.equal({ - title: 'DummyService', - example: { - pattern: '/foo/:world', - namedParams: { world: 'World' }, - queryParams: { queryParamA: '!!!' }, - }, - preview: { - color: 'lightgrey', - label: 'cat', - message: 'Hello namedParamA: foo with queryParamA: bar', - namedLogo: undefined, - style: undefined, - }, - keywords: ['hello'], - documentation: undefined, - }) + // The in-depth tests for examples reside in transform-example.spec.js + expect(examples).to.have.lengthOf(1) }) }) diff --git a/core/base-service/coalesce-badge.js b/core/base-service/coalesce-badge.js index 6a809cf785aea..30819fb81485f 100644 --- a/core/base-service/coalesce-badge.js +++ b/core/base-service/coalesce-badge.js @@ -41,19 +41,36 @@ module.exports = function coalesceBadge( defaultBadgeData, { category, _cacheLength: defaultCacheSeconds } = {} ) { + // The "overrideX" naming is based on services that provide badge + // parameters themselves, which can be overridden by a query string + // parameter. (For a couple services, the dynamic badge and the + // query-string-based static badge, the service never sets a value + // so the query string overrides are the _only_ way to configure + // these badge parameters. const { style: overrideStyle, label: overrideLabel, logoColor: overrideLogoColor, link: overrideLink, + colorB: legacyOverrideColor, + colorA: legacyOverrideLabelColor, } = overrides - // Scoutcamp converts numeric query params to numbers. Convert them back. let { - colorB: overrideColor, - colorA: overrideLabelColor, logoWidth: overrideLogoWidth, logoPosition: overrideLogoPosition, + color: overrideColor, + labelColor: overrideLabelColor, } = overrides + + // Only use the legacy properties of the new ones are not provided + if (typeof overrideColor === 'undefined') { + overrideColor = legacyOverrideColor + } + if (typeof overrideLabelColor === 'undefined') { + overrideLabelColor = legacyOverrideLabelColor + } + + // Scoutcamp converts numeric query params to numbers. Convert them back. if (typeof overrideColor === 'number') { overrideColor = `${overrideColor}` } diff --git a/core/base-service/coalesce-badge.spec.js b/core/base-service/coalesce-badge.spec.js index 90416ee9731cd..e543a5599d3a4 100644 --- a/core/base-service/coalesce-badge.spec.js +++ b/core/base-service/coalesce-badge.spec.js @@ -58,15 +58,27 @@ describe('coalesceBadge', function() { it('overrides the color', function() { expect( - coalesceBadge({ colorB: '10ADED' }, { color: 'red' }, {}).color + coalesceBadge({ color: '10ADED' }, { color: 'red' }, {}).color ).to.equal('10ADED') + // also expected for legacy name + expect( + coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {}).color + ).to.equal('B0ADED') }) context('In case of an error', function() { it('does not override the color', function() { expect( coalesceBadge( - { colorB: '10ADED' }, + { color: '10ADED' }, + { isError: true, color: 'lightgray' }, + {} + ).color + ).to.equal('lightgray') + // also expected for legacy name + expect( + coalesceBadge( + { colorB: 'B0ADED' }, { isError: true, color: 'lightgray' }, {} ).color @@ -92,11 +104,25 @@ describe('coalesceBadge', function() { it('overrides the label color', function() { expect( - coalesceBadge({ colorA: '42f483' }, { color: 'green' }, {}).labelColor + coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {}) + .labelColor ).to.equal('42f483') + // also expected for legacy name + expect( + coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {}).labelColor + ).to.equal('B2f483') }) it('converts a query-string numeric color to a string', function() { + expect( + coalesceBadge( + // Scoutcamp converts numeric query params to numbers. + { color: 123 }, + { color: 'green' }, + {} + ).color + ).to.equal('123') + // also expected for legacy name expect( coalesceBadge( // Scoutcamp converts numeric query params to numbers. diff --git a/core/base-service/deprecated-service.js b/core/base-service/deprecated-service.js index e934f63060a2b..b774f6dc519ac 100644 --- a/core/base-service/deprecated-service.js +++ b/core/base-service/deprecated-service.js @@ -1,10 +1,18 @@ 'use strict' +const Joi = require('joi') const BaseService = require('./base') const { Deprecated } = require('./errors') // Only `url` is required. -function deprecatedService({ route, label, category, examples = [], message }) { +function deprecatedService({ + route, + label, + category, + examples = [], + message, + dateAdded, +}) { return class DeprecatedService extends BaseService { static get category() { return category @@ -18,6 +26,17 @@ function deprecatedService({ route, label, category, examples = [], message }) { return true } + static validateDefinition() { + super.validateDefinition() + Joi.assert( + { dateAdded }, + Joi.object({ + dateAdded: Joi.date().required(), + }), + `Deprecated service for ${route.base}` + ) + } + static get defaultBadgeData() { return { label } } diff --git a/core/base-service/legacy-request-handler.js b/core/base-service/legacy-request-handler.js index 9795d77d542a5..f5e0903857c71 100644 --- a/core/base-service/legacy-request-handler.js +++ b/core/base-service/legacy-request-handler.js @@ -52,6 +52,8 @@ const globalQueryParams = new Set([ 'link', 'colorA', 'colorB', + 'color', + 'labelColor', ]) function flattenQueryParams(queryParams) { diff --git a/core/base-service/legacy-request-handler.spec.js b/core/base-service/legacy-request-handler.spec.js index f501a9eed5f9a..b1dbc5519515d 100644 --- a/core/base-service/legacy-request-handler.spec.js +++ b/core/base-service/legacy-request-handler.spec.js @@ -297,17 +297,17 @@ describe('The request handler', function() { beforeEach(function() { register({ cacheHeaderConfig: standardCacheHeaders }) }) - const expectedCacheKey = '/testing/123.json?colorB=123&label=foo' + const expectedCacheKey = '/testing/123.json?color=123&label=foo' it('should match expected and use canonical order - 1', async function() { const res = await fetch( - `${baseUrl}/testing/123.json?colorB=123&label=foo` + `${baseUrl}/testing/123.json?color=123&label=foo` ) expect(res.ok).to.be.true expect(_requestCache.cache).to.have.keys(expectedCacheKey) }) it('should match expected and use canonical order - 2', async function() { const res = await fetch( - `${baseUrl}/testing/123.json?label=foo&colorB=123` + `${baseUrl}/testing/123.json?label=foo&color=123` ) expect(res.ok).to.be.true expect(_requestCache.cache).to.have.keys(expectedCacheKey) diff --git a/core/base-service/transform-example.spec.js b/core/base-service/transform-example.spec.js index 558cd6e523045..52a767e6dba91 100644 --- a/core/base-service/transform-example.spec.js +++ b/core/base-service/transform-example.spec.js @@ -1,7 +1,8 @@ 'use strict' const { expect } = require('chai') -const { validateExample } = require('./transform-example') +const { test, given } = require('sazerac') +const { validateExample, transformExample } = require('./transform-example') describe('validateExample function', function() { it('passes valid examples', function() { @@ -72,3 +73,96 @@ describe('validateExample function', function() { }) }) }) + +test(transformExample, function() { + const ExampleService = { + name: 'ExampleService', + route: { + base: 'some-service', + pattern: ':interval/:packageName', + }, + defaultBadgeData: { + label: 'downloads', + }, + } + + given( + { + pattern: 'dt/:packageName', + namedParams: { packageName: 'express' }, + staticPreview: { message: '50k' }, + keywords: ['hello'], + }, + 0, + ExampleService + ).expect({ + title: 'ExampleService', + example: { + pattern: '/some-service/dt/:packageName', + namedParams: { packageName: 'express' }, + queryParams: {}, + }, + preview: { + label: 'downloads', + message: '50k', + color: 'lightgrey', + namedLogo: undefined, + style: undefined, + }, + keywords: ['hello'], + documentation: undefined, + }) + + given( + { + namedParams: { interval: 'dt', packageName: 'express' }, + staticPreview: { message: '50k' }, + keywords: ['hello'], + }, + 0, + ExampleService + ).expect({ + title: 'ExampleService', + example: { + pattern: '/some-service/:interval/:packageName', + namedParams: { interval: 'dt', packageName: 'express' }, + queryParams: {}, + }, + preview: { + label: 'downloads', + message: '50k', + color: 'lightgrey', + namedLogo: undefined, + style: undefined, + }, + keywords: ['hello'], + documentation: undefined, + }) + + given( + { + namedParams: { interval: 'dt', packageName: 'express' }, + queryParams: { registry_url: 'http://example.com/' }, + staticPreview: { message: '50k' }, + keywords: ['hello'], + }, + 0, + ExampleService + ).expect({ + title: 'ExampleService', + example: { + pattern: '/some-service/:interval/:packageName', + namedParams: { interval: 'dt', packageName: 'express' }, + queryParams: { registry_url: 'http://example.com/' }, + }, + preview: { + label: 'downloads', + message: '50k', + color: 'lightgrey', + namedLogo: undefined, + style: undefined, + }, + keywords: ['hello'], + documentation: undefined, + }) +}) diff --git a/doc/TUTORIAL.md b/doc/TUTORIAL.md index 4d58aea59c957..64694f1017cf5 100644 --- a/doc/TUTORIAL.md +++ b/doc/TUTORIAL.md @@ -37,7 +37,7 @@ install node and npm: https://nodejs.org/en/download/ `git clone git@github.com:YOURGITHUBUSERNAME/shields.git` 3. `cd shields` 4. Install project dependencies - `npm install` + `npm ci` 5. Run the server `npm start` 6. Visit the website to check the front-end is loaded: diff --git a/doc/deprecating-badges.md b/doc/deprecating-badges.md index 69aa5359bfd70..abc32775abbb6 100644 --- a/doc/deprecating-badges.md +++ b/doc/deprecating-badges.md @@ -2,17 +2,10 @@ When a service that Shields integrates with shuts down, those badges will no longer work and need to be deprecated within Shields. -Deprecating a badge involves 3 steps: +Deprecating a badge involves two steps: -1. Adding an entry for the service to the deprecated service list in `deprecated-services.js` -2. Updating the service code to use the `DeprecatedService` class -3. Updating the service tests to reflect the new behavior of the deprecated service - -## Update Deprecated Service List - -All deprecated services are enumerated in the `deprecated-services.js` [file](https://github.com/badges/shields/blob/master/lib/deprecated-services.js) which can be found in the `lib` directory (`./lib/deprecated-services.js`). - -Add a key for the service with the corresponding date for deprecation, for example: `nsp: new Date('2018-12-13')`, to the `deprecatedServices` object. +1. Updating the service code to use the `DeprecatedService` class +2. Updating the service tests to reflect the new behavior of the deprecated service ## Update Service Implementation @@ -25,7 +18,6 @@ Replace the existing service class implementation with the `DeprecatedService` c const { deprecatedService } = require('..') -// image layers integration - deprecated as of November 2018. module.exports = deprecatedService({ category: 'size', route: { @@ -33,6 +25,7 @@ module.exports = deprecatedService({ format: '(?:.+)', }, label: 'imagelayers', + dateAdded: new Date('2019-xx-xx'), // Be sure to update this with today's date! }) ``` diff --git a/doc/self-hosting.md b/doc/self-hosting.md index 5ba6262b760ab..06781c740f09f 100644 --- a/doc/self-hosting.md +++ b/doc/self-hosting.md @@ -14,7 +14,7 @@ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -; sudo apt-get ins ```sh git clone https://github.com/badges/shields.git cd shields -npm install # You may need sudo for this. +npm ci # You may need sudo for this. ``` [package manager]: https://nodejs.org/en/download/package-manager/ diff --git a/frontend/components/customizer/builder-common.js b/frontend/components/customizer/builder-common.js index 6972d55410292..7dbe6a7742d36 100644 --- a/frontend/components/customizer/builder-common.js +++ b/frontend/components/customizer/builder-common.js @@ -11,7 +11,7 @@ const BuilderOuterContainer = styled.div` const BuilderInnerContainer = styled.div` display: inline-block; - padding: 11px 14px 10px; + padding: 1px 14px 10px; border-radius: 4px; background: #eef; diff --git a/frontend/components/customizer/customizer.js b/frontend/components/customizer/customizer.js index 93b2d4d1f0ac4..83d4d3ea2cc06 100644 --- a/frontend/components/customizer/customizer.js +++ b/frontend/components/customizer/customizer.js @@ -19,7 +19,7 @@ export default class Customizer extends React.Component { pattern: PropTypes.string.isRequired, exampleNamedParams: PropTypes.object.isRequired, exampleQueryParams: PropTypes.object.isRequired, - defaultStyle: PropTypes.string, + initialStyle: PropTypes.string, } indicatorRef = React.createRef() @@ -137,7 +137,7 @@ export default class Customizer extends React.Component { pattern, exampleNamedParams, exampleQueryParams, - defaultStyle, + initialStyle, } = this.props return ( @@ -149,7 +149,7 @@ export default class Customizer extends React.Component { />
{this.renderMarkupAndLivePreview()}
diff --git a/frontend/components/customizer/path-builder.js b/frontend/components/customizer/path-builder.js index a8364e196ad71..7f9cafb3f7c0f 100644 --- a/frontend/components/customizer/path-builder.js +++ b/frontend/components/customizer/path-builder.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import styled, { css } from 'styled-components' import pathToRegexp from 'path-to-regexp' import humanizeString from 'humanize-string' +import { patternToOptions } from '../../lib/pattern-helpers' import { noAutocorrect, StyledInput } from '../common' import { BuilderContainer, @@ -11,13 +12,13 @@ import { } from './builder-common' const PathBuilderColumn = styled.span` - height: 58px; + height: 78px; float: left; display: flex; flex-direction: column; - margin: 5px 0; + margin: 0; ${({ withHorizPadding }) => withHorizPadding && @@ -27,7 +28,7 @@ const PathBuilderColumn = styled.span` ` const PathLiteral = styled.div` - margin-top: 20px; + margin-top: 39px; ${({ isFirstToken }) => isFirstToken && css` @@ -35,20 +36,32 @@ const PathLiteral = styled.div` `}; ` -const NamedParamLabel = styled(BuilderLabel)` - height: 20px; +const NamedParamLabelContainer = styled.span` + display: flex; + flex-direction: column; + height: 37px; width: 100%; - - text-align: center; + justify-content: center; ` -const NamedParamInput = styled(StyledInput)` +const inputStyling = ` width: 100%; text-align: center; +` +// 2px to align with input boxes alongside. +const NamedParamInput = styled(StyledInput)` + ${inputStyling} + margin-top: 2px; margin-bottom: 10px; ` +const NamedParamSelect = styled.select` + ${inputStyling} + margin-bottom: 9px; + font-size: 10px; +` + const NamedParamCaption = styled(BuilderCaption)` width: 100%; text-align: center; @@ -91,13 +104,16 @@ export default class PathBuilder extends React.Component { if (typeof token === 'string') { return token } else { - const { delimiter, name } = token - let value = namedParams[name] - if (!value) { + const { delimiter, name, optional } = token + const value = namedParams[name] + if (value) { + return `${delimiter}${value}` + } else if (optional) { + return '' + } else { isComplete = false - value = `:${name}` + return `${delimiter}:${name}` } - return `${delimiter}${value}` } }) .join('') @@ -142,29 +158,58 @@ export default class PathBuilder extends React.Component { ) } - renderNamedParam(token, tokenIndex, namedParamIndex) { - const { delimiter, name } = token - - const { exampleParams } = this.props - const exampleValue = exampleParams[name] + renderNamedParamInput(token) { + const { name, pattern } = token + const options = patternToOptions(pattern) const { namedParams } = this.state const value = namedParams[name] + if (options) { + return ( + + + {options.map(option => ( + + ))} + + ) + } else { + return ( + + ) + } + } + + renderNamedParam(token, tokenIndex, namedParamIndex) { + const { delimiter, name, optional } = token + + const { exampleParams } = this.props + const exampleValue = exampleParams[name] || '(not set)' + return ( {this.renderLiteral(delimiter, tokenIndex)} - - {humanizeString(name)} - - + + {humanizeString(name)} + {optional ? (optional) : null} + + {this.renderNamedParamInput(token)} {namedParamIndex === 0 ? `e.g. ${exampleValue}` : exampleValue} diff --git a/frontend/components/customizer/query-string-builder.js b/frontend/components/customizer/query-string-builder.js index 65a8cf02c36c4..9297c1193fb29 100644 --- a/frontend/components/customizer/query-string-builder.js +++ b/frontend/components/customizer/query-string-builder.js @@ -24,9 +24,9 @@ const QueryParamCaption = styled(BuilderCaption)` ` const supportedBadgeOptions = [ - { name: 'style' }, + { name: 'style', shieldsDefaultValue: 'flat' }, { name: 'label', label: 'override label' }, - { name: 'colorB', label: 'override color' }, + { name: 'color', label: 'override color' }, { name: 'logo', label: 'named logo' }, { name: 'logoColor', label: 'override logo color' }, ] @@ -35,22 +35,39 @@ function getBadgeOption(name) { return supportedBadgeOptions.find(opt => opt.name === name) } +// The UI for building the query string, which includes two kinds of settings: +// 1. Custom query params defined by the service, stored in +// `this.state.queryParams` +// 2. The standard badge options which apply to all badges, stored in +// `this.state.badgeOptions` export default class QueryStringBuilder extends React.Component { constructor(props) { super(props) - const { exampleParams, defaultStyle } = props + const { exampleParams, initialStyle } = props + // Create empty values in `this.state.queryParams` for each of the custom + // query params defined in `this.props.exampleParams`. const queryParams = {} Object.entries(exampleParams).forEach(([name, value]) => { + // Custom query params are either string or boolean. Inspect the example + // value to infer which one, and set empty values accordingly. + // Throughout the component, these two types are supported in the same + // manner: by inspecting this value type. const isStringParam = typeof value === 'string' queryParams[name] = isStringParam ? '' : true }) + // Create empty values in `this.state.badgeOptions` for each of the + // standard badge options. When `this.props.initialStyle` has been + // provided, use that as the initial style. const badgeOptions = {} - const defaults = { style: defaultStyle } supportedBadgeOptions.forEach(({ name }) => { - badgeOptions[name] = defaults[name] || '' + if (name === 'style') { + badgeOptions[name] = initialStyle + } else { + badgeOptions[name] = '' + } }) this.state = { queryParams, badgeOptions } @@ -61,17 +78,20 @@ export default class QueryStringBuilder extends React.Component { let isComplete = true Object.entries(queryParams).forEach(([name, value]) => { + // As above, there are two types of supported params: strings and + // booleans. const isStringParam = typeof value === 'string' if (isStringParam) { if (value) { outQuery[name] = value } else { - // Omit empty string params. + // Skip empty params. isComplete = false } } else { - // Translate `true` to `null`, which provides an empty query param - // like `?compact_message`. Omit `false`. Omit default values. + // Generate empty query params for boolean parameters by translating + // `{ compact_message: true }` to `?compact_message`. When values are + // false, skip the param. if (value) { outQuery[name] = null } @@ -79,8 +99,8 @@ export default class QueryStringBuilder extends React.Component { }) Object.entries(badgeOptions).forEach(([name, value]) => { - const { defaultValue } = getBadgeOption(name) - if (value && value !== defaultValue) { + const { shieldsDefaultValue } = getBadgeOption(name) + if (value && value !== shieldsDefaultValue) { outQuery[name] = value } }) @@ -204,7 +224,7 @@ export default class QueryStringBuilder extends React.Component { renderBadgeOption(name, value) { const { label = humanizeString(name), - defaultValue: hasDefaultValue, + shieldsDefaultValue: hasShieldsDefaultValue, } = getBadgeOption(name) return ( @@ -212,7 +232,9 @@ export default class QueryStringBuilder extends React.Component { {label} - {!hasDefaultValue && optional} + {!hasShieldsDefaultValue && ( + optional + )} {this.renderBadgeOptionInput(name, value)} @@ -259,9 +281,9 @@ export default class QueryStringBuilder extends React.Component { } QueryStringBuilder.propTypes = { exampleParams: PropTypes.object.isRequired, - defaultStyle: PropTypes.string, + initialStyle: PropTypes.string, onChange: PropTypes.func, } QueryStringBuilder.defaultProps = { - defaultStyle: 'flat', + initialStyle: 'flat', } diff --git a/frontend/components/markup-modal/markup-modal-content.js b/frontend/components/markup-modal/markup-modal-content.js index 5aabdfea6e8c4..aa1bae6567c53 100644 --- a/frontend/components/markup-modal/markup-modal-content.js +++ b/frontend/components/markup-modal/markup-modal-content.js @@ -33,7 +33,7 @@ export default class MarkupModalContent extends React.Component { example: { title, example: { pattern, namedParams, queryParams }, - preview: { style: defaultStyle }, + preview: { style: initialStyle }, }, baseUrl, } = this.props @@ -47,7 +47,7 @@ export default class MarkupModalContent extends React.Component { pattern={pattern} exampleNamedParams={namedParams} exampleQueryParams={queryParams} - defaultStyle={defaultStyle} + initialStyle={initialStyle} /> ) diff --git a/frontend/components/usage.js b/frontend/components/usage.js index 7a88c2fef0f23..427353dd6c6b4 100644 --- a/frontend/components/usage.js +++ b/frontend/components/usage.js @@ -243,7 +243,7 @@ export default class Usage extends React.PureComponent { > $.DATA.SUBDATA - >&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + >&color=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

@@ -257,7 +257,7 @@ export default class Usage extends React.PureComponent { > //data/subdata - >&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + >&color=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

@@ -271,7 +271,7 @@ export default class Usage extends React.PureComponent { > $.DATA.SUBDATA - >&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + >&color=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

@@ -314,6 +314,10 @@ export default class Usage extends React.PureComponent { simple-icons + . Simple-icons are referenced using names as they appear on + the simple-icons site. If the name includes spaces, replace + them with dashes (e.g:{' '} + ?logo=visual-studio-code) } /> @@ -352,22 +356,24 @@ export default class Usage extends React.PureComponent { } /> Set background of the left part (hex, rgb, rgba, hsl, hsla and - css named colors supported) + css named colors supported). The legacy name "colorA" is also + supported. } /> Set background of the right part (hex, rgb, rgba, hsl, hsla - and css named colors supported) + and css named colors supported). The legacy name "colorB" is + also supported. } /> diff --git a/frontend/lib/badge-url.js b/frontend/lib/badge-url.js index d6f5c6c5adc19..5ff3aca4c7272 100644 --- a/frontend/lib/badge-url.js +++ b/frontend/lib/badge-url.js @@ -38,7 +38,7 @@ export function dynamicBadgeUrl( }) if (color) { - queryParams.colorB = color + queryParams.color = color } if (prefix) { queryParams.prefix = prefix diff --git a/frontend/lib/badge-url.spec.js b/frontend/lib/badge-url.spec.js index 2265a80e06ffd..4817e4cb133c0 100644 --- a/frontend/lib/badge-url.spec.js +++ b/frontend/lib/badge-url.spec.js @@ -58,7 +58,7 @@ describe('Badge URL functions', function() { '?label=foo', `&url=${encodeURIComponent(dataUrl)}`, `&query=${encodeURIComponent(query)}`, - '&colorB=blue', + '&color=blue', `&suffix=${encodeURIComponent(suffix)}`, '&style=plastic', ].join('') diff --git a/frontend/lib/generate-image-markup.js b/frontend/lib/generate-image-markup.js index 438bea56c0709..6441caa178444 100644 --- a/frontend/lib/generate-image-markup.js +++ b/frontend/lib/generate-image-markup.js @@ -4,9 +4,10 @@ export function bareLink(badgeUrl, link, title = '') { export function html(badgeUrl, link, title) { // To be more robust, this should escape the title. - const img = `${title}` + const alt = title ? ` alt="${title}"` : '' + const img = `` if (link) { - return `${img}` + return `${img}` } else { return img } @@ -33,13 +34,11 @@ export function reStructuredText(badgeUrl, link, title) { } function quoteAsciiDocAttribute(attr) { - if (typeof attr === 'string') { - const withQuotesEscaped = attr.replace(/"/g, '\\"') - return `"${withQuotesEscaped}"` - } else if (attr == null) { + if (attr == null) { return 'None' } else { - return attr + const withQuotesEscaped = attr.replace(/"/g, '\\"') + return `"${withQuotesEscaped}"` } } @@ -55,7 +54,8 @@ function mapValues(obj, iteratee) { export function renderAsciiDocAttributes(positional, named) { // http://asciidoc.org/userguide.html#X21 const needsQuoting = - positional.some(attr => attr.includes(',')) || Object.keys(named).length > 0 + positional.some(attr => attr && attr.includes(',')) || + Object.keys(named).length > 0 if (needsQuoting) { positional = positional.map(attr => quoteAsciiDocAttribute(attr)) @@ -69,7 +69,7 @@ export function renderAsciiDocAttributes(positional, named) { if (items.length) { return `[${items.join(',')}]` } else { - return '' + return '[]' } } @@ -80,14 +80,6 @@ export function asciiDoc(badgeUrl, link, title) { return `image:${badgeUrl}${attrs}` } -export default function generateAllMarkup(badgeUrl, link, title) { - // This is a wee bit "clever". It runs each of the three functions on the - // parameters provided, and returns the result in an object. - return mapValues({ markdown, reStructuredText, asciiDoc }, fn => - fn(badgeUrl, link, title) - ) -} - export function generateMarkup({ badgeUrl, link, title, markupFormat }) { const generatorFn = { markdown, diff --git a/frontend/lib/generate-image-markup.spec.js b/frontend/lib/generate-image-markup.spec.js index f715eedb1e97d..36c595f70e25b 100644 --- a/frontend/lib/generate-image-markup.spec.js +++ b/frontend/lib/generate-image-markup.spec.js @@ -1,10 +1,35 @@ import { test, given } from 'sazerac' -import generateAllMarkup, { +import { + bareLink, + html, markdown, reStructuredText, + renderAsciiDocAttributes, asciiDoc, + generateMarkup, } from './generate-image-markup' +test(bareLink, () => { + given( + 'https://img.shields.io/badge.svg', + 'https://example.com/example', + 'Example' + ).expect('https://img.shields.io/badge.svg') +}) + +test(html, () => { + given( + 'https://img.shields.io/badge.svg', + 'https://example.com/example', + 'Example' + ).expect( + 'Example' + ) + given('https://img.shields.io/badge.svg', undefined, undefined).expect( + '' + ) +}) + test(markdown, () => { given('https://img.shields.io/badge.svg', undefined, 'Example').expect( '![Example](https://img.shields.io/badge.svg)' @@ -16,9 +41,15 @@ test(markdown, () => { ).expect( '[![Example](https://img.shields.io/badge.svg)](https://example.com/example)' ) + given('https://img.shields.io/badge.svg', undefined, undefined).expect( + '![](https://img.shields.io/badge.svg)' + ) }) test(reStructuredText, () => { + given('https://img.shields.io/badge.svg', undefined, undefined).expect( + '.. image:: https://img.shields.io/badge.svg' + ) given('https://img.shields.io/badge.svg', undefined, 'Example').expect( '.. image:: https://img.shields.io/badge.svg :alt: Example' ) @@ -31,7 +62,17 @@ test(reStructuredText, () => { ) }) +test(renderAsciiDocAttributes, () => { + given(['abc', '123'], {}).expect('[abc,123]') + given(['abc', '123', null], { foo: 'def', bar: 'hello, world!' }).expect( + '["abc","123",None,foo="def",bar="hello, world!"]' + ) +}) + test(asciiDoc, () => { + given('https://img.shields.io/badge.svg', undefined, undefined).expect( + 'image:https://img.shields.io/badge.svg[]' + ) given('https://img.shields.io/badge.svg', undefined, 'Example').expect( 'image:https://img.shields.io/badge.svg[Example]' ) @@ -49,17 +90,13 @@ test(asciiDoc, () => { ) }) -test(generateAllMarkup, () => { - given( - 'https://img.shields.io/badge.svg', - 'https://example.com/example', - 'Example' - ).expect({ - markdown: - '[![Example](https://img.shields.io/badge.svg)](https://example.com/example)', - reStructuredText: - '.. image:: https://img.shields.io/badge.svg :alt: Example :target: https://example.com/example', - asciiDoc: - 'image:https://img.shields.io/badge.svg["Example",link="https://example.com/example"]', - }) +test(generateMarkup, () => { + given({ + badgeUrl: 'https://img.shields.io/badge.svg', + link: 'https://example.com/example', + title: 'Example', + markupFormat: 'markdown', + }).expect( + '[![Example](https://img.shields.io/badge.svg)](https://example.com/example)' + ) }) diff --git a/frontend/lib/pattern-helpers.js b/frontend/lib/pattern-helpers.js new file mode 100644 index 0000000000000..b54e181e74e94 --- /dev/null +++ b/frontend/lib/pattern-helpers.js @@ -0,0 +1,14 @@ +// Given a patternToRegex `pattern` with multiple-choice options like +// `foo|bar|baz`, return an array with the options. If it can't be described +// as multiple-choice options, return `undefined`. +const basicChars = /^[A-za-z0-9-]+$/ +function patternToOptions(pattern) { + const split = pattern.split('|') + if (split.some(part => !part.match(basicChars))) { + return undefined + } else { + return split + } +} + +module.exports = { patternToOptions } diff --git a/frontend/lib/pattern-helpers.spec.js b/frontend/lib/pattern-helpers.spec.js new file mode 100644 index 0000000000000..15f1ab7ad629b --- /dev/null +++ b/frontend/lib/pattern-helpers.spec.js @@ -0,0 +1,10 @@ +import { test, given } from 'sazerac' +import { patternToOptions } from './pattern-helpers' + +describe('Badge URL functions', function() { + test(patternToOptions, () => { + given('[^\\/]+?').expect(undefined) + given('abc|[^\\/]+').expect(undefined) + given('abc|def|ghi').expect(['abc', 'def', 'ghi']) + }) +}) diff --git a/frontend/pages/endpoint.js b/frontend/pages/endpoint.js index f001242b3e702..71cc07282d5de 100644 --- a/frontend/pages/endpoint.js +++ b/frontend/pages/endpoint.js @@ -113,7 +113,7 @@ const EndpointPage = () => (

Using the endpoint badge, you can provide content for a badge through a JSON endpoint. The content can be prerendered, or generated on the fly. - To strike a balance between responsiveness and bandwith utilization on + To strike a balance between responsiveness and bandwidth utilization on one hand, and freshness on the other, cache behavior is configurable, subject to the Shields minimum. The endpoint URL is provided to Shields through the query string. Shields fetches it and formats the badge. @@ -173,11 +173,12 @@ const EndpointPage = () => (

Default: lightgrey. The right color. Supports the eight named colors above, as well as hex, rgb, rgba, hsl, hsla and css named - colors. + colors. This can be overridden by the query string.
labelColor
- Default: grey. The left color. + Default: grey. The left color. This can be overridden by + the query string.
isError
diff --git a/gh-badges/lib/color.js b/gh-badges/lib/color.js index 189bf71675692..fbfa8a13391c9 100644 --- a/gh-badges/lib/color.js +++ b/gh-badges/lib/color.js @@ -34,7 +34,7 @@ Object.entries(aliases).forEach(([alias, original]) => { // true. const hexColorRegex = /^([\da-f]{3}){1,2}$/i function isHexColor(s = '') { - return hexColorRegex.test(s.toLowerCase()) + return hexColorRegex.test(s) } function normalizeColor(color) { diff --git a/gh-badges/lib/color.spec.js b/gh-badges/lib/color.spec.js index 783b0bbe1fdbd..8b71cc0252372 100644 --- a/gh-badges/lib/color.spec.js +++ b/gh-badges/lib/color.spec.js @@ -5,7 +5,16 @@ const { isHexColor, normalizeColor, toSvgColor } = require('./color') test(isHexColor, () => { forCases([given('f00bae'), given('4c1'), given('ABC123')]).expect(true) - forCases([given('f00bar'), given(''), given(undefined)]).expect(false) + forCases([ + given('f00bar'), + given(''), + given(undefined), + given(null), + given(true), + given([]), + given({}), + given(() => {}), + ]).expect(false) }) test(normalizeColor, () => { @@ -17,9 +26,16 @@ test(normalizeColor, () => { given('#ABC123').expect('#abc123') given('papayawhip').expect('papayawhip') given('purple').expect('purple') - forCases([given(''), given(undefined), given('not-a-color')]).expect( - undefined - ) + forCases([ + given(''), + given('not-a-color'), + given(undefined), + given(null), + given(true), + given([]), + given({}), + given(() => {}), + ]).expect(undefined) given('lightgray').expect('lightgrey') given('informational').expect('blue') }) diff --git a/lib/deprecated-services.js b/lib/deprecated-services.js deleted file mode 100644 index 38f78e7b65ec2..0000000000000 --- a/lib/deprecated-services.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const deprecatedServices = { - gittip: new Date('2017-12-29'), - gratipay: new Date('2017-12-29'), - dockbit: new Date('2017-12-31'), - gemnasium: new Date('2018-05-15'), - snap: new Date('2018-01-23'), - 'snap-ci': new Date('2018-01-23'), - cauditor: new Date('2018-02-15'), - dotnetstatus: new Date('2018-04-01'), - magnumci: new Date('2018-07-08'), - bithound: new Date('2018-07-08'), - versioneye: new Date('2018-08-20'), - issuestats: new Date('2018-09-01'), - libscore: new Date('2018-09-22'), - imagelayers: new Date('2018-11-18'), - nsp: new Date('2018-12-13'), - 'coverity-on-demand': new Date('2018-12-18'), -} - -module.exports = { - deprecatedServices, -} diff --git a/mocha.opts b/mocha.opts deleted file mode 100644 index 5b22027ee39b3..0000000000000 --- a/mocha.opts +++ /dev/null @@ -1 +0,0 @@ ---reporter mocha-env-reporter diff --git a/mocharc-frontend.yml b/mocharc-frontend.yml new file mode 100644 index 0000000000000..8a85e65d938f1 --- /dev/null +++ b/mocharc-frontend.yml @@ -0,0 +1,6 @@ +reporter: mocha-env-reporter +require: + - '@babel/polyfill' + - '@babel/register' + - mocha-yaml-loader + - frontend/mocha-ignore-pngs diff --git a/mocharc.yml b/mocharc.yml new file mode 100644 index 0000000000000..41348198be1e5 --- /dev/null +++ b/mocharc.yml @@ -0,0 +1 @@ +reporter: mocha-env-reporter diff --git a/package-lock.json b/package-lock.json index 34dfd445d3e96..2e3c4e74eb83e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,44 +14,44 @@ } }, "@babel/core": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", - "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.3.tgz", + "integrity": "sha512-w445QGI2qd0E0GlSnq6huRZWPMmQGCp5gd5ZWS4hagn0EiwzxD5QMFkpchyusAyVC1n27OKXzQ0/88aVU9n4xQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.2.2", + "@babel/generator": "^7.3.3", "@babel/helpers": "^7.2.0", - "@babel/parser": "^7.2.2", + "@babel/parser": "^7.3.3", "@babel/template": "^7.2.2", "@babel/traverse": "^7.2.2", - "@babel/types": "^7.2.2", + "@babel/types": "^7.3.3", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { "@babel/generator": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", - "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.3.tgz", + "integrity": "sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A==", "dev": true, "requires": { - "@babel/types": "^7.2.2", + "@babel/types": "^7.3.3", "jsesc": "^2.5.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "source-map": "^0.5.0", "trim-right": "^1.0.1" } }, "@babel/parser": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", - "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.3.tgz", + "integrity": "sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg==", "dev": true }, "@babel/template": { @@ -83,20 +83,20 @@ } }, "@babel/types": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", - "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", + "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "to-fast-properties": "^2.0.0" } }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -181,9 +181,9 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.0.tgz", - "integrity": "sha512-DUsQNS2CGLZZ7I3W3fvh0YpPDd6BuWJlDl+qmZZpABZHza2ErE3LxtEzLJFHFC1ZwtlAXvHhbFYbtM5o5B0WBw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.2.tgz", + "integrity": "sha512-tdW8+V8ceh2US4GsYdNVNoohq5uVwOf9k6krjwW4E1lINcHgttnWcNqgdoessn12dAy8QkbezlbQh2nXISNY+A==", "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", @@ -478,33 +478,33 @@ } }, "@babel/helpers": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", - "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", "dev": true, "requires": { "@babel/template": "^7.1.2", "@babel/traverse": "^7.1.5", - "@babel/types": "^7.2.0" + "@babel/types": "^7.3.0" }, "dependencies": { "@babel/generator": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", - "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.3.tgz", + "integrity": "sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A==", "dev": true, "requires": { - "@babel/types": "^7.2.2", + "@babel/types": "^7.3.3", "jsesc": "^2.5.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "source-map": "^0.5.0", "trim-right": "^1.0.1" } }, "@babel/parser": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", - "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.3.tgz", + "integrity": "sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg==", "dev": true }, "@babel/template": { @@ -536,20 +536,20 @@ } }, "@babel/types": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", - "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", + "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "to-fast-properties": "^2.0.0" } }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -600,9 +600,9 @@ } }, "@babel/plugin-proposal-class-properties": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz", - "integrity": "sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.3.tgz", + "integrity": "sha512-XO9eeU1/UwGPM8L+TjnQCykuVcXqaO5J1bkRPIygqZ/A2L1xVMJ9aZXrY31c0U4H2/LHKL4lbFQLsxktSrc/Ng==", "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.3.0", @@ -1371,6 +1371,120 @@ "integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw==", "dev": true }, + "@gatsbyjs/relay-compiler": { + "version": "2.0.0-printer-fix.2", + "resolved": "https://registry.npmjs.org/@gatsbyjs/relay-compiler/-/relay-compiler-2.0.0-printer-fix.2.tgz", + "integrity": "sha512-7GeCCEQ7O15lMTT/SXy9HuRde4cv5vs465ZnLK2QCajSDLior+20yrMqHn1PGsJYK6nNZH7p3lw9qTCpqmuc7Q==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/polyfill": "^7.0.0", + "@babel/runtime": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "babel-preset-fbjs": "^3.1.2", + "chalk": "^2.4.1", + "fast-glob": "^2.2.2", + "fb-watchman": "^2.0.0", + "fbjs": "^1.0.0", + "immutable": "~3.7.6", + "nullthrows": "^1.1.0", + "relay-runtime": "2.0.0", + "signedsource": "^1.0.0", + "yargs": "^9.0.0" + }, + "dependencies": { + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "fbjs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz", + "integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==", + "dev": true, + "requires": { + "core-js": "^2.4.1", + "fbjs-css-vars": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "~2.0.3" + } + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "@iamstarkov/listr-update-renderer": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz", @@ -1450,12 +1564,12 @@ "dev": true }, "@octokit/endpoint": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-3.1.2.tgz", - "integrity": "sha512-iRx4kDYybAv9tOrHDBE6HwlgiFi8qmbZl8SHliZWtxbUFuXLZXh2yv8DxGIK9wzD9J0wLDMZneO8vNYJNUSJ9Q==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-3.1.3.tgz", + "integrity": "sha512-vAWzeoj9Lzpl3V3YkWKhGzmDUoMfKpyxJhpq74/ohMvmLXDoEuAGnApy/7TRi3OmnjyX2Lr+e9UGGAD0919ohA==", "dev": true, "requires": { - "deepmerge": "3.1.0", + "deepmerge": "3.2.0", "is-plain-object": "^2.0.4", "universal-user-agent": "^2.0.1", "url-template": "^2.0.8" @@ -1474,9 +1588,9 @@ } }, "@octokit/rest": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.15.0.tgz", - "integrity": "sha512-Un+e7rgh38RtPOTe453pT/KPM/p2KZICimBmuZCd2wEo8PacDa4h6RqTPZs+f2DPazTTqdM7QU4LKlUjgiBwWw==", + "version": "16.16.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.16.0.tgz", + "integrity": "sha512-Q6L5OwQJrdJ188gLVmUHLKNXBoeCU0DynKPYW8iZQQoGNGws2hkP/CePVNlzzDgmjuv7o8dCrJgecvDcIHccTA==", "dev": true, "requires": { "@octokit/request": "2.3.0", @@ -1574,16 +1688,22 @@ } }, "@sinonjs/samsam": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz", - "integrity": "sha512-m08g4CS3J6lwRQk1pj1EO+KEVWbrbXsmi9Pw0ySmrIbcVxVaedoFgLvFsV8wHLwh01EpROVz3KvVcD1Jmks9FQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.1.1.tgz", + "integrity": "sha512-ILlwvQUwAiaVBzr3qz8oT1moM7AIUHqUc2UmEjQcH9lLe+E+BZPwUMuc9FFojMswRK4r96x5zDTTrowMLw/vuA==", "dev": true, "requires": { "@sinonjs/commons": "^1.0.2", "array-from": "^2.1.1", - "lodash.get": "^4.4.2" + "lodash": "^4.17.11" } }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -1659,9 +1779,9 @@ "dev": true }, "@types/prop-types": { - "version": "15.5.8", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz", - "integrity": "sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.9.tgz", + "integrity": "sha512-Nha5b+jmBI271jdTMwrHiNXM+DvThjHOfyZtMX9kj/c/LUj2xiLHsG/1L3tJ8DjAoQN48cHwUwtqBotjyXaSdQ==", "dev": true }, "@types/reach__router": { @@ -1675,9 +1795,9 @@ } }, "@types/react": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.2.tgz", - "integrity": "sha512-6mcKsqlqkN9xADrwiUz2gm9Wg4iGnlVGciwBRYFQSMWG6MQjhOZ/AVnxn+6v8nslFgfYTV8fNdE6XwKu6va5PA==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.3.tgz", + "integrity": "sha512-PjPocAxL9SNLjYMP4dfOShW/rj9FDBJGu3JFRt0zEYf77xfihB6fq8zfDpMrV6s82KnAi7F1OEe5OsQX25Ybdw==", "dev": true, "requires": { "@types/prop-types": "*", @@ -2229,17 +2349,59 @@ }, "dependencies": { "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", "dev": true, "requires": { - "es-to-primitive": "^1.1.1", + "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "object-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "dev": true } } }, @@ -2659,9 +2821,9 @@ } }, "babel-plugin-istanbul": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz", - "integrity": "sha512-CLoXPRSUWiR8yao8bShqZUIC6qLfZVVY3X1wj+QPNXu0wfmrRRfarh1LYy+dYMVI+bDj0ghy3tuqFFRFZmL1Nw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", + "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", "dev": true, "requires": { "find-up": "^3.0.0", @@ -2689,9 +2851,9 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -2705,12 +2867,6 @@ "requires": { "p-limit": "^2.0.0" } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true } } }, @@ -3551,9 +3707,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000936", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz", - "integrity": "sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==", + "version": "1.0.30000938", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000938.tgz", + "integrity": "sha512-ekW8NQ3/FvokviDxhdKLZZAx7PptXNwxKgXtnR5y+PR3hckwuP3yJ1Ir+4/c97dsHNqtAyfKUGdw8P4EYzBNgw==", "dev": true }, "capture-stack-trace": { @@ -4389,12 +4545,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "cjson": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.2.1.tgz", @@ -5616,9 +5766,9 @@ "dev": true }, "cssnano": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.9.tgz", - "integrity": "sha512-osEbYy4kzaNY3nkd92Uf3hy5Jqb5Aztuv+Ze3Z6DjRhyntZDlb3YljiYDdJ05k167U86CZpSR+rbuJYN7N3oBQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", "dev": true, "requires": { "cosmiconfig": "^5.0.0", @@ -5754,9 +5904,9 @@ "dev": true }, "danger": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/danger/-/danger-7.0.11.tgz", - "integrity": "sha512-KwPD0qFEr5NqUH0RFynO6c2KMNa9n6ZgwsVw8WUuJct9THT5cMsSBzX3Gm/bLwSCVugc8H/9xhI06o6DycS8Ww==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/danger/-/danger-7.0.12.tgz", + "integrity": "sha512-pew8vsBs2bitXt5upLuvfEAqvVuvzLoo/BjX7ysGI3yXQ8X7vrESo1/gGRvkA4uaDEoDq7LaBR4xhxfqxk9fog==", "dev": true, "requires": { "@babel/polyfill": "^7.2.5", @@ -5934,9 +6084,9 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deepmerge": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.1.0.tgz", - "integrity": "sha512-/TnecbwXEdycfbsM2++O3eGiatEFHjjNciHEwJclM+T5Kd94qD1AP+2elP/Mq0L5b9VZJao5znR01Mz6eX8Seg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz", + "integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow==", "dev": true }, "default-gateway": { @@ -6037,6 +6187,12 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, "detect-indent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", @@ -6615,18 +6771,20 @@ "dev": true }, "enzyme": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.8.0.tgz", - "integrity": "sha512-bfsWo5nHyZm1O1vnIsbwdfhU989jk+squU9NKvB+Puwo5j6/Wg9pN5CO0YJelm98Dao3NPjkDZk+vvgwpMwYxw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.9.0.tgz", + "integrity": "sha512-JqxI2BRFHbmiP7/UFqvsjxTirWoM1HfeaJrmVSZ9a1EADKkZgdPcAuISPMpoUiHlac9J4dYt81MC5BBIrbJGMg==", "dev": true, "requires": { "array.prototype.flat": "^1.2.1", "cheerio": "^1.0.0-rc.2", "function.prototype.name": "^1.1.0", "has": "^1.0.3", + "html-element-map": "^1.0.0", "is-boolean-object": "^1.0.0", "is-callable": "^1.1.4", "is-number-object": "^1.0.3", + "is-regex": "^1.0.4", "is-string": "^1.0.4", "is-subset": "^0.1.1", "lodash.escape": "^4.0.1", @@ -6655,6 +6813,12 @@ "parse5": "^3.0.1" } }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6665,17 +6829,17 @@ } }, "htmlparser2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", - "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", "dev": true, "requires": { - "domelementtype": "^1.3.0", + "domelementtype": "^1.3.1", "domhandler": "^2.3.0", "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", - "readable-stream": "^3.0.6" + "readable-stream": "^3.1.1" } }, "is-callable": { @@ -6706,9 +6870,9 @@ } }, "readable-stream": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", - "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -6945,9 +7109,9 @@ }, "dependencies": { "es6-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", - "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", "dev": true } } @@ -6985,35 +7149,35 @@ } }, "eslint": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.13.0.tgz", - "integrity": "sha512-nqD5WQMisciZC5EHZowejLKQjWGuFS5c70fxqSKlnDME+oz9zmE8KTlX+lHSg+/5wsC/kf9Q9eMkC8qS3oM2fg==", + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.14.1.tgz", + "integrity": "sha512-CyUMbmsjxedx8B0mr79mNOqetvkbij/zrXnFeK2zc3pGRn3/tibjiNAv/3UxFEyfMDjh+ZqTrJrEGBFiGfD5Og==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "ajv": "^6.5.3", + "ajv": "^6.9.1", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", "debug": "^4.0.1", - "doctrine": "^2.1.0", + "doctrine": "^3.0.0", "eslint-scope": "^4.0.0", "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.0", + "espree": "^5.0.1", "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", "globals": "^11.7.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.1.0", + "inquirer": "^6.2.2", "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.5", + "lodash": "^4.17.11", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", @@ -7024,10 +7188,22 @@ "semver": "^5.5.1", "strip-ansi": "^4.0.0", "strip-json-comments": "^2.0.1", - "table": "^5.0.2", + "table": "^5.2.3", "text-table": "^0.2.0" }, "dependencies": { + "ajv": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -7057,14 +7233,20 @@ } }, "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" } }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "import-fresh": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", @@ -7075,6 +7257,12 @@ "resolve-from": "^4.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -7392,20 +7580,20 @@ "dev": true }, "espree": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", - "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", "dev": true, "requires": { - "acorn": "^6.0.2", + "acorn": "^6.0.7", "acorn-jsx": "^5.0.0", "eslint-visitor-keys": "^1.0.0" }, "dependencies": { "acorn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.6.tgz", - "integrity": "sha512-5M3G/A4uBSMIlfJ+h9W125vJvPFH/zirISsW5qfxF5YzEvXJCtolLoQvM5yZft0DvMcUrPGKPOlgEu55I6iUtA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", "dev": true } } @@ -7937,13 +8125,12 @@ } }, "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "^2.0.1" } }, "file-loader": { @@ -8053,6 +8240,29 @@ "locate-path": "^2.0.0" } }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, "flat": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", @@ -8071,17 +8281,22 @@ } }, "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" } }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, "fleau": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/fleau/-/fleau-16.2.0.tgz", @@ -8148,22 +8363,28 @@ "dev": true }, "follow-redirects": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.6.1.tgz", - "integrity": "sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", "dev": true, "requires": { - "debug": "=3.1.0" + "debug": "^3.2.6" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -8957,9 +9178,9 @@ } }, "gatsby": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.1.0.tgz", - "integrity": "sha512-SVjl2rV8pkKrsGR6RjKsrKP4Mo/y2VERk6PNILdPMOMSZiQvbhg0yq1bo8X+wvUNMeKosHH4mz52QMZcEKaoiQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.1.4.tgz", + "integrity": "sha512-WWbl7DkF6FSL/HE+hMEUxPPi8vGvEieGLTvXOxvS5S1C0FF3P2GOr/TP28TVqzaEQ4sPKtlPeV7s2v1P3MW/2A==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -8968,6 +9189,7 @@ "@babel/polyfill": "^7.0.0", "@babel/runtime": "^7.0.0", "@babel/traverse": "^7.0.0", + "@gatsbyjs/relay-compiler": "2.0.0-printer-fix.2", "@reach/router": "^1.1.1", "address": "1.0.3", "autoprefixer": "^9.4.3", @@ -9057,7 +9279,6 @@ "react-error-overlay": "^3.0.0", "react-hot-loader": "^4.6.2", "redux": "^4.0.0", - "relay-compiler": "2.0.0", "request": "^2.85.0", "semver": "^5.6.0", "shallow-compare": "^1.2.2", @@ -10046,35 +10267,6 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=" }, - "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", - "dev": true, - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -10231,9 +10423,9 @@ } }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "hex-color-regex": { @@ -10363,6 +10555,23 @@ "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", "dev": true }, + "html-element-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.0.0.tgz", + "integrity": "sha512-/SP6aOiM5Ai9zALvCxDubIeez0LvG3qP7R9GcRDnJEP/HBmv0A8A9K0o8+HFudcFt46+i921ANjzKsjPjb7Enw==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + }, + "dependencies": { + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + } + } + }, "html-entities": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", @@ -10707,9 +10916,9 @@ "dev": true }, "icedfrisby": { - "version": "2.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/icedfrisby/-/icedfrisby-2.0.0-alpha.2.tgz", - "integrity": "sha1-RT6LmgODCVmOfeOj+Lj90OwVHyA=", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/icedfrisby/-/icedfrisby-2.0.0-alpha.3.tgz", + "integrity": "sha512-hZmD7s+RWt/0nhkjJctuIZBg7xDl3Pw7WCc2I+hVSvVQIvvcURR1tAVY0R38Mr090pgdkuVI8twSO6gw3vh+xA==", "dev": true, "requires": { "chai": "^4.0.1", @@ -10721,44 +10930,8 @@ "lodash": "^4.16.6", "qs": "^6.3.0", "request": "^2.76.0", - "verror": "^1.10.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "verror": "^1.10.0", + "wait-promise": "^0.4.1" } }, "icedfrisby-nock": { @@ -11603,15 +11776,15 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", "dev": true }, "istanbul-lib-instrument": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz", - "integrity": "sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", "dev": true, "requires": { "@babel/generator": "^7.0.0", @@ -11619,7 +11792,7 @@ "@babel/template": "^7.0.0", "@babel/traverse": "^7.0.0", "@babel/types": "^7.0.0", - "istanbul-lib-coverage": "^2.0.1", + "istanbul-lib-coverage": "^2.0.3", "semver": "^5.5.0" } }, @@ -12077,9 +12250,9 @@ "integrity": "sha1-ZMTwJfF/1Tv7RXY/rrFvAVp0dVA=" }, "lint-staged": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.3.tgz", - "integrity": "sha512-6TGkikL1B+6mIOuSNq2TV6oP21IhPMnV8q0cf9oYZ296ArTVNcbFh1l1pfVOHHbBIYLlziWNsQ2q45/ffmJ4AA==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.4.tgz", + "integrity": "sha512-oFbbhB/VzN8B3i/sIdb9gMfngGArI6jIfxSn+WPdQb2Ni3GJeS6T4j5VriSbQfxfMuYoQlMHOoFt+lfcWV0HfA==", "dev": true, "requires": { "@iamstarkov/listr-update-renderer": "0.4.1", @@ -12095,7 +12268,7 @@ "is-glob": "^4.0.0", "is-windows": "^1.0.2", "listr": "^0.14.2", - "lodash": "^4.17.5", + "lodash": "^4.17.11", "log-symbols": "^2.2.0", "micromatch": "^3.1.8", "npm-which": "^3.0.1", @@ -12653,9 +12826,9 @@ "dev": true }, "lolex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", - "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", + "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", "dev": true }, "loose-envify": { @@ -13278,51 +13451,101 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.0.0.tgz", + "integrity": "sha512-A7g9k3yr8oJaXn2IItFnfgjyxFc/LTe6Wwv7FczP+e8G74o9xYNSbMYmCf1ouldRojLrFcOb+z75P6Ak0GX6ug==", "dev": true, "requires": { + "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", + "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", + "findup-sync": "2.0.0", + "glob": "7.1.3", "growl": "1.10.5", - "he": "1.1.1", + "he": "1.2.0", + "js-yaml": "3.12.0", + "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "ms": "2.1.1", + "node-environment-flags": "1.0.4", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "12.0.5", + "yargs-parser": "11.1.1", + "yargs-unparser": "1.5.0" }, "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" } }, "has-flag": { @@ -13331,55 +13554,187 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "ms": { + "invert-kv": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } - } - } - }, - "mocha-env-reporter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mocha-env-reporter/-/mocha-env-reporter-3.0.0.tgz", - "integrity": "sha1-FFvSwkS0LSu5r4UVhQwcD2PxZCk=", - "dev": true - }, - "mocha-junit-reporter": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", - "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", - "dev": true, - "requires": { - "debug": "^2.2.0", - "md5": "^2.1.0", - "mkdirp": "~0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "mocha-env-reporter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mocha-env-reporter/-/mocha-env-reporter-3.0.0.tgz", + "integrity": "sha1-FFvSwkS0LSu5r4UVhQwcD2PxZCk=", + "dev": true + }, + "mocha-junit-reporter": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", + "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", + "dev": true, + "requires": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" } } } @@ -13529,40 +13884,16 @@ "dev": true }, "nearley": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.15.1.tgz", - "integrity": "sha512-8IUY/rUrKz2mIynUGh8k+tul1awMKEjeHHC5G3FHvvyAW6oq4mQfNp2c0BMea+sYZJvYcrrM6GmZVIle/GRXGw==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.16.0.tgz", + "integrity": "sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==", "dev": true, "requires": { + "commander": "^2.19.0", "moo": "^0.4.3", - "nomnom": "~1.6.2", "railroad-diagrams": "^1.0.0", "randexp": "0.4.6", "semver": "^5.4.1" - }, - "dependencies": { - "colors": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", - "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", - "dev": true - }, - "nomnom": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", - "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", - "dev": true, - "requires": { - "colors": "0.5.x", - "underscore": "~1.4.4" - } - }, - "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", - "dev": true - } } }, "negotiator": { @@ -13607,16 +13938,16 @@ } }, "nise": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz", - "integrity": "sha512-kGASVhuL4tlAV0tvA34yJYZIVihrUt/5bDwpp4tTluigxUr2bBlJeDXmivb6NuEdFkqvdv/Ybb9dm16PSKUhtw==", + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", "dev": true, "requires": { "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "lolex": "^2.3.2", - "path-to-regexp": "^1.7.0", - "text-encoding": "^0.6.4" + "path-to-regexp": "^1.7.0" }, "dependencies": { "lolex": { @@ -13676,9 +14007,9 @@ "dev": true }, "node-emoji": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", - "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", "dev": true, "requires": { "lodash.toarray": "^4.4.0" @@ -13712,6 +14043,15 @@ } } }, + "node-environment-flags": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.4.tgz", + "integrity": "sha512-M9rwCnWVLW7PX+NUWe3ejEdiLYinRpsEre9hMkU/6NS4h+EEulYaDH1gCEZ2gyXsmw+RXYDaV2JkkTNcsPDJ0Q==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, "node-eta": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/node-eta/-/node-eta-0.9.0.tgz", @@ -14110,9 +14450,9 @@ "optional": true }, "nyc": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.2.0.tgz", - "integrity": "sha512-gQBlOqvfpYt9b2PZ7qElrHWt8x4y8ApNfbMBoDPdl3sY4/4RJwCxDGTSqhA9RnaguZjS5nW7taW8oToe86JLgQ==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", + "integrity": "sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==", "dev": true, "requires": { "archy": "^1.0.0", @@ -14125,10 +14465,10 @@ "glob": "^7.1.3", "istanbul-lib-coverage": "^2.0.3", "istanbul-lib-hook": "^2.0.3", - "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-instrument": "^3.1.0", "istanbul-lib-report": "^2.0.4", "istanbul-lib-source-maps": "^3.0.2", - "istanbul-reports": "^2.1.0", + "istanbul-reports": "^2.1.1", "make-dir": "^1.3.0", "merge-source-map": "^1.1.0", "resolve-from": "^4.0.0", @@ -14165,10 +14505,11 @@ "dev": true }, "async": { - "version": "2.6.1", + "version": "2.6.2", "bundled": true, + "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "balanced-match": { @@ -14185,11 +14526,6 @@ "concat-map": "0.0.1" } }, - "builtin-modules": { - "version": "1.1.1", - "bundled": true, - "dev": true - }, "caching-transform": { "version": "3.0.1", "bundled": true, @@ -14224,6 +14560,7 @@ "commander": { "version": "2.17.1", "bundled": true, + "dev": true, "optional": true }, "commondir": { @@ -14386,6 +14723,24 @@ "bundled": true, "dev": true }, + "handlebars": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, "has-flag": { "version": "3.0.0", "bundled": true, @@ -14433,14 +14788,6 @@ "bundled": true, "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "bundled": true, @@ -14524,11 +14871,11 @@ } }, "istanbul-reports": { - "version": "2.1.0", + "version": "2.1.1", "bundled": true, "dev": true, "requires": { - "handlebars": "^4.0.11" + "handlebars": "^4.1.0" } }, "json-parse-better-errors": { @@ -14564,6 +14911,11 @@ "path-exists": "^3.0.0" } }, + "lodash": { + "version": "4.17.11", + "bundled": true, + "dev": true + }, "lodash.flattendeep": { "version": "4.4.0", "bundled": true, @@ -14595,13 +14947,13 @@ } }, "mem": { - "version": "4.0.0", + "version": "4.1.0", "bundled": true, "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "p-is-promise": "^2.0.0" } }, "merge-source-map": { @@ -14634,7 +14986,8 @@ }, "minimist": { "version": "0.0.10", - "bundled": true + "bundled": true, + "dev": true }, "mkdirp": { "version": "0.5.1", @@ -14662,12 +15015,12 @@ "dev": true }, "normalize-package-data": { - "version": "2.4.0", + "version": "2.5.0", "bundled": true, "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } @@ -14696,6 +15049,7 @@ "optimist": { "version": "0.6.1", "bundled": true, + "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -14727,7 +15081,7 @@ "dev": true }, "p-is-promise": { - "version": "1.1.0", + "version": "2.0.0", "bundled": true, "dev": true }, @@ -14787,6 +15141,11 @@ "bundled": true, "dev": true }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, "path-type": { "version": "3.0.0", "bundled": true, @@ -14859,6 +15218,14 @@ "bundled": true, "dev": true }, + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-from": { "version": "4.0.0", "bundled": true, @@ -14987,6 +15354,8 @@ "uglify-js": { "version": "3.4.9", "bundled": true, + "dev": true, + "optional": true, "requires": { "commander": "~2.17.1", "source-map": "~0.6.1" @@ -14995,6 +15364,7 @@ "source-map": { "version": "0.6.1", "bundled": true, + "dev": true, "optional": true } } @@ -15028,7 +15398,8 @@ }, "wordwrap": { "version": "0.0.3", - "bundled": true + "bundled": true, + "dev": true }, "wrap-ansi": { "version": "2.1.0", @@ -15255,15 +15626,81 @@ } }, "object.entries": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", - "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "object-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "dev": true + } } }, "object.fromentries": { @@ -15455,30 +15892,6 @@ "temp-write": "^3.4.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, "optimize-css-assets-webpack-plugin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.1.tgz", @@ -15751,9 +16164,9 @@ } }, "parse-asn1": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.3.tgz", - "integrity": "sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "dev": true, "requires": { "asn1.js": "^4.0.0", @@ -17195,21 +17608,21 @@ } }, "react": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.1.tgz", - "integrity": "sha512-wLw5CFGPdo7p/AgteFz7GblI2JPOos0+biSoxf1FPsGxWQZdN/pj6oToJs1crn61DL3Ln7mN86uZ4j74p31ELQ==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.2.tgz", + "integrity": "sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==", "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.1" + "scheduler": "^0.13.2" }, "dependencies": { "scheduler": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.1.tgz", - "integrity": "sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", + "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -17393,21 +17806,21 @@ } }, "react-dom": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.1.tgz", - "integrity": "sha512-N74IZUrPt6UiDjXaO7UbDDFXeUXnVhZzeRLy/6iqqN1ipfjrhR60Bp5NuBK+rv3GMdqdIuwIl22u1SYwf330bg==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.2.tgz", + "integrity": "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==", "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.1" + "scheduler": "^0.13.2" }, "dependencies": { "scheduler": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.1.tgz", - "integrity": "sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", + "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -17417,9 +17830,9 @@ } }, "react-error-overlay": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.2.tgz", - "integrity": "sha512-7kEBKwU9R8fKnZJBRa5RSIfay4KJwnYvKB6gODGicUmDSAhQJ7Tdnll5S0RLtYrzRfMVXlqYw61rzrSpP4ThLQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.3.tgz", + "integrity": "sha512-GoqeM3Xadie7XUApXOjkY3Qhs8RkwB/Za4WMedBGrOKH1eTuKGyoAECff7jiVonJchOx6KZ9i8ILO5XIoHB+Tg==", "dev": true }, "react-helmet": { @@ -17516,18 +17929,26 @@ } }, "react-select": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-2.3.0.tgz", - "integrity": "sha512-CD3jyZs5lwy/CHW3SdYU1d1FtmJxgvVPdKMwEE8dD6MyNANFtvW95P/V20StPsPppFY7oePv/i2Mun2C2+WgTA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-2.4.1.tgz", + "integrity": "sha512-je1cVAFsyQrGxkruQnNCuwo3+P0+1dyq7M4/gTlSwhO4ptPeTjf0eNrvCJ9iurVyorrnI88zgx2/yxrr2/oLLQ==", "dev": true, "requires": { "classnames": "^2.2.5", "emotion": "^9.1.2", - "memoize-one": "^4.0.0", + "memoize-one": "^5.0.0", "prop-types": "^15.6.0", "raf": "^3.4.0", "react-input-autosize": "^2.2.1", "react-transition-group": "^2.2.1" + }, + "dependencies": { + "memoize-one": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", + "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==", + "dev": true + } } }, "react-side-effect": { @@ -17960,128 +18381,14 @@ "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", "dev": true, "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "relay-compiler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/relay-compiler/-/relay-compiler-2.0.0.tgz", - "integrity": "sha512-OcWHdFJAp9++8xhES5rZMDolB2USKn940bc/jDMYrMyHtVM+FnvlZaXDSvXpOjlzPeucpDYPiqFlx+/UQoGF2g==", - "dev": true, - "requires": { - "@babel/generator": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/polyfill": "^7.0.0", - "@babel/runtime": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "babel-preset-fbjs": "^3.1.2", - "chalk": "^2.4.1", - "fast-glob": "^2.2.2", - "fb-watchman": "^2.0.0", - "fbjs": "^1.0.0", - "immutable": "~3.7.6", - "nullthrows": "^1.1.0", - "relay-runtime": "2.0.0", - "signedsource": "^1.0.0", - "yargs": "^9.0.0" - }, - "dependencies": { - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "fbjs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz", - "integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==", - "dev": true, - "requires": { - "core-js": "^2.4.1", - "fbjs-css-vars": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "requires": { - "asap": "~2.0.3" - } - }, - "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true } } }, @@ -18779,9 +19086,9 @@ } }, "simple-icons": { - "version": "1.9.19", - "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-1.9.19.tgz", - "integrity": "sha512-kjnseNerSy17jILwyKSA9K2kpWPzhU7bHWTQXVi5PVTP8NRyl56Py7J5mzgq1I9JN7Tw+m6Na2J9N3ffHDmoPA==" + "version": "1.9.20", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-1.9.20.tgz", + "integrity": "sha512-2lTEAo9BcyyAtXctNhRE6TXJ7EvzjGKCz3QE2ccxVOLAFwQNsxkP48d0/ThURYJhFsSjsoIoame89sHXdUfK2Q==" }, "simple-swizzle": { "version": "0.2.2", @@ -18801,17 +19108,17 @@ } }, "sinon": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.3.tgz", - "integrity": "sha512-i6j7sqcLEqTYqUcMV327waI745VASvYuSuQMCjbAwlpAeuCgKZ3LtrjDxAbu+GjNQR0FEDpywtwGCIh8GicNyg==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.4.tgz", + "integrity": "sha512-FGlcfrkiBRfaEIKRw8s/9mk4nP4AMGswvKFixLo+AzsOhskjaBCHAHGLMd8pCJpQGS+9ZI71px6qoQUyvADeyA==", "dev": true, "requires": { "@sinonjs/commons": "^1.3.0", "@sinonjs/formatio": "^3.1.0", - "@sinonjs/samsam": "^3.0.2", + "@sinonjs/samsam": "^3.1.1", "diff": "^3.5.0", - "lolex": "^3.0.0", - "nise": "^1.4.8", + "lolex": "^3.1.0", + "nise": "^1.4.10", "supports-color": "^5.5.0" }, "dependencies": { @@ -18898,9 +19205,9 @@ } }, "snap-shot-core": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/snap-shot-core/-/snap-shot-core-7.1.12.tgz", - "integrity": "sha512-bRZfGP2MgPCpzRi/Zou9om7VoAMp3cwFj5Xp19NSRuRzOiLJpuoa62msyY7jcy/zr69Shg1OgHCDSoaH7B59wA==", + "version": "7.1.13", + "resolved": "https://registry.npmjs.org/snap-shot-core/-/snap-shot-core-7.1.13.tgz", + "integrity": "sha512-ylPfUBV1gjE5s/rlViEqRZGI4E4Cwn9ZeGss/0ujgGnXc73LsXpo7YY+f/LXfBlDP1HZbltVKoEbdqq8KWYtUA==", "dev": true, "requires": { "check-more-types": "2.24.0", @@ -18908,7 +19215,7 @@ "debug": "3.2.6", "escape-quotes": "1.0.2", "folktale": "2.3.2", - "is-ci": "1.2.1", + "is-ci": "2.0.0", "jsesc": "2.5.2", "lazy-ass": "1.6.0", "mkdirp": "0.5.1", @@ -18916,6 +19223,12 @@ "ramda": "0.26.1" }, "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -18931,6 +19244,15 @@ "integrity": "sha512-+8GbtQBwEqutP0v3uajDDoN64K2ehmHd0cjlghhxh0WpcfPzAIjPA03e1VvHlxL02FVGR0A6lwXsNQKn3H1RNQ==", "dev": true }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -18946,9 +19268,9 @@ } }, "snap-shot-it": { - "version": "6.2.9", - "resolved": "https://registry.npmjs.org/snap-shot-it/-/snap-shot-it-6.2.9.tgz", - "integrity": "sha512-bN51yrg6a14bqwjCzCUD2czyOu47J2ndYxc4fiSlYz3jT0HBCJL8rP5M6nDbaftH7DQYIkew3Vx7lShpzpJZjg==", + "version": "6.2.10", + "resolved": "https://registry.npmjs.org/snap-shot-it/-/snap-shot-it-6.2.10.tgz", + "integrity": "sha512-BfYhkbJda5orPz8NZWz7KWskCdujCImvCXDRH8wg07ALVf5EFMUtJLt14nZzWGBtmasnxK7koK2l2YSxIw9pNg==", "dev": true, "requires": { "@bahmutov/data-driven": "1.0.0", @@ -18958,7 +19280,7 @@ "pluralize": "7.0.0", "ramda": "0.26.1", "snap-shot-compare": "2.7.1", - "snap-shot-core": "7.1.12" + "snap-shot-core": "7.1.13" }, "dependencies": { "debug": { @@ -19833,9 +20155,9 @@ } }, "stylehacks": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.2.tgz", - "integrity": "sha512-AZwvn2b3aNKK1yp+VgNPOuC2jIJOvh9PAiCq2gjDBW1WkQxQUksR1RugOJRIOhMYTGHZeoMcMQKp3/qaS3evNg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -19857,9 +20179,9 @@ } }, "stylis": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.3.tgz", - "integrity": "sha512-TxU0aAscJghF9I3V9q601xcK3Uw1JbXvpsBGj/HULqexKOKlOEzzlIpLFRbKkCK990ccuxfXUqmPbIIo7Fq/cQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==", "dev": true }, "stylis-rule-sheet": { @@ -19955,21 +20277,21 @@ "dev": true }, "table": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/table/-/table-5.2.2.tgz", - "integrity": "sha512-f8mJmuu9beQEDkKHLzOv4VxVYlU68NpdzjbGPl69i4Hx0sTopJuNxuzJd17iV2h24dAfa93u794OnDA5jqXvfQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", "dev": true, "requires": { - "ajv": "^6.6.1", + "ajv": "^6.9.1", "lodash": "^4.17.11", - "slice-ansi": "^2.0.0", - "string-width": "^2.1.1" + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { "ajv": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.8.1.tgz", - "integrity": "sha512-eqxCp82P+JfqL683wwsL73XmFs1eG6qjw+RD3YHx+Jll1r0jNd4dh8QG9NYAeNGA/hnZjeEDgtTskgJULbxpWQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -19978,6 +20300,12 @@ "uri-js": "^4.2.2" } }, + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -19989,6 +20317,26 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true + }, + "string-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", + "integrity": "sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } } } }, @@ -20189,9 +20537,9 @@ } }, "test-exclude": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.0.0.tgz", - "integrity": "sha512-bO3Lj5+qFa9YLfYW2ZcXMOV1pmQvw+KS/DpjqhyX6Y6UZ8zstpZJ+mA2ERkXfpOqhxsJlQiLeVXD3Smsrs6oLw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", "dev": true, "requires": { "arrify": "^1.0.1", @@ -20232,9 +20580,9 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -20259,12 +20607,6 @@ "json-parse-better-errors": "^1.0.1" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -20303,12 +20645,6 @@ } } }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -20660,33 +20996,6 @@ "resolved": "https://registry.npmjs.org/ucfirst/-/ucfirst-1.0.0.tgz", "integrity": "sha1-ThBbZEjQXiZOzsQ14LkZNjxfLy8=" }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -21144,6 +21453,12 @@ "indexof": "0.0.1" } }, + "wait-promise": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/wait-promise/-/wait-promise-0.4.1.tgz", + "integrity": "sha1-Hq2PgtKRolAQwLHu+NRxEyPMTas=", + "dev": true + }, "walkdir": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.3.2.tgz", @@ -21695,6 +22010,15 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -21784,9 +22108,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { "mkdirp": "^0.5.1" @@ -21924,6 +22248,172 @@ "camelcase": "^4.1.0" } }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index d4de4df4ae570..ec70c40a901f1 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "redis": "~2.8.0", "request": "~2.88.0", "semver": "~5.6.0", - "simple-icons": "1.9.19", + "simple-icons": "1.9.20", "xml2js": "~0.4.16", "xmldom": "~0.1.27", "xpath": "~0.0.27" @@ -82,15 +82,15 @@ "test:js:server": "echo \"Deprecated; run `npm run test:core` instead.\" && npm run test:core", "test:js:frontend": "echo \"Deprecated; run `npm run test:frontend` instead.\" && npm run test:frontend", "test:js:package": "echo \"Deprecated; run `npm run test:package` instead.\" && npm run test:package", - "test:frontend": "cross-env NODE_ENV=test mocha --opts mocha.opts --require @babel/polyfill --require @babel/register --require mocha-yaml-loader --require frontend/mocha-ignore-pngs \"frontend/**/*.spec.js\"", - "test:core": "cross-env NODE_CONFIG_ENV=test mocha --opts mocha.opts \"core/**/*.spec.js\" \"lib/**/*.spec.js\" \"services/**/*.spec.js\"", - "test:package": "mocha --opts mocha.opts \"gh-badges/**/*.spec.js\"", - "test:entrypoint": "cross-env NODE_CONFIG_ENV=test mocha --opts mocha.opts entrypoint.spec.js", - "test:integration": "cross-env NODE_CONFIG_ENV=test mocha --opts mocha.opts \"core/**/*.integration.js\" \"services/**/*.integration.js\"", - "test:services": "cross-env NODE_CONFIG_ENV=test mocha --opts mocha.opts --delay core/service-test-runner/cli.js", + "test:frontend": "cross-env NODE_ENV=test mocha --config mocharc-frontend.yml \"frontend/**/*.spec.js\"", + "test:core": "cross-env NODE_CONFIG_ENV=test mocha \"core/**/*.spec.js\" \"lib/**/*.spec.js\" \"services/**/*.spec.js\"", + "test:package": "mocha \"gh-badges/**/*.spec.js\"", + "test:entrypoint": "cross-env NODE_CONFIG_ENV=test mocha entrypoint.spec.js", + "test:integration": "cross-env NODE_CONFIG_ENV=test mocha \"core/**/*.integration.js\" \"services/**/*.integration.js\"", + "test:services": "cross-env NODE_CONFIG_ENV=test mocha --delay core/service-test-runner/cli.js", "test:services:trace": "cross-env NODE_CONFIG_ENV=test TRACE_SERVICES=true npm run test:services -- $*", "test:services:pr:prepare": "node core/service-test-runner/pull-request-services-cli.js > pull-request-services.log", - "test:services:pr:run": "cross-env NODE_CONFIG_ENV=test mocha --opts mocha.opts --delay core/service-test-runner/cli.js --stdin < pull-request-services.log", + "test:services:pr:run": "cross-env NODE_CONFIG_ENV=test mocha --delay core/service-test-runner/cli.js --stdin < pull-request-services.log", "test:services:pr": "run-s --silent test:services:pr:prepare test:services:pr:run", "pretest": "run-s --silent defs features", "test": "run-s --silent --continue-on-error lint test:frontend test:package test:server test:entrypoint prettier-check", @@ -116,8 +116,8 @@ ] }, "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/plugin-proposal-class-properties": "^7.3.0", + "@babel/core": "^7.3.3", + "@babel/plugin-proposal-class-properties": "^7.3.3", "@babel/plugin-proposal-object-rest-spread": "^7.3.2", "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.3.1", @@ -125,7 +125,7 @@ "@mapbox/react-click-to-select": "^2.2.0", "babel-eslint": "^10.0.0", "babel-plugin-inline-react-svg": "^1.0.1", - "babel-plugin-istanbul": "^5.1.0", + "babel-plugin-istanbul": "^5.1.1", "babel-preset-gatsby": "^0.1.7", "caller": "^1.0.1", "chai": "^4.1.2", @@ -136,12 +136,12 @@ "child-process-promise": "^2.2.1", "clipboard-copy": "^2.0.1", "concurrently": "^4.1.0", - "danger": "^7.0.11", + "danger": "^7.0.12", "danger-plugin-no-test-shortcuts": "^2.0.0", - "enzyme": "^3.8.0", + "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.9.1", "eol": "^0.9.1", - "eslint": "^5.13.0", + "eslint": "^5.14.1", "eslint-config-prettier": "^4.0.0", "eslint-config-standard": "^12.0.0", "eslint-config-standard-jsx": "^6.0.2", @@ -156,7 +156,7 @@ "eslint-plugin-standard": "^4.0.0", "fetch-ponyfill": "^6.0.0", "fs-readfile-promise": "^3.0.1", - "gatsby": "2.1.0", + "gatsby": "2.1.4", "gatsby-plugin-catch-links": "^2.0.11", "gatsby-plugin-page-creator": "^2.0.6", "gatsby-plugin-react-helmet": "^3.0.5", @@ -166,18 +166,18 @@ "got": "^9.6.0", "humanize-string": "^1.0.2", "husky": "^1.3.1", - "icedfrisby": "2.0.0-alpha.2", + "icedfrisby": "2.0.0-alpha.3", "icedfrisby-nock": "^1.1.0", "is-png": "^1.1.0", "is-svg": "^3.0.0", "js-yaml-loader": "^1.0.1", - "lint-staged": "^8.1.3", + "lint-staged": "^8.1.4", "lodash.debounce": "^4.0.8", "lodash.difference": "^4.5.0", "lodash.groupby": "^4.6.0", "minimist": "^1.2.0", "mkdirp": "^0.5.1", - "mocha": "^5.0.0", + "mocha": "^6.0.0", "mocha-env-reporter": "^3.0.0", "mocha-junit-reporter": "^1.18.0", "mocha-yaml-loader": "^1.0.3", @@ -185,28 +185,28 @@ "node-fetch": "^2.3.0", "node-mocks-http": "^1.7.3", "npm-run-all": "^4.1.5", - "nyc": "^13.2.0", + "nyc": "^13.3.0", "opn-cli": "^4.0.0", "portfinder": "^1.0.20", "prettier": "1.16.4", "prettier-check": "^2.0.0", "pretty": "^2.0.0", "prop-types": "^15.7.2", - "react": "^16.8.1", - "react-dom": "^16.8.1", - "react-error-overlay": "^5.1.2", + "react": "^16.8.2", + "react-dom": "^16.8.2", + "react-error-overlay": "^5.1.3", "react-helmet": "^5.2.0", "react-modal": "^3.8.1", "react-pose": "^4.0.7", - "react-select": "^2.3.0", + "react-select": "^2.4.1", "read-all-stdin-sync": "^1.0.5", "redis-server": "^1.2.2", "require-hacker": "^3.0.1", "rimraf": "^2.6.3", "sazerac": "^0.4.2", - "sinon": "^7.2.3", + "sinon": "^7.2.4", "sinon-chai": "^3.3.0", - "snap-shot-it": "^6.2.9", + "snap-shot-it": "^6.2.10", "styled-components": "^4.1.3", "tmp": "0.0.33", "url": "^0.11.0", diff --git a/services/bithound/bithound.service.js b/services/bithound/bithound.service.js index 9655f8532f03c..ac7097a730874 100644 --- a/services/bithound/bithound.service.js +++ b/services/bithound/bithound.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// bitHound integration - deprecated as of July 2018 module.exports = deprecatedService({ category: 'dependencies', route: { @@ -10,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:code/|dependencies/|devDependencies/)?(?:.+?)', }, label: 'bithound', + dateAdded: new Date('2018-07-08'), }) diff --git a/services/bugzilla/bugzilla.service.js b/services/bugzilla/bugzilla.service.js index 06d602ba541a7..069dda299f0b3 100644 --- a/services/bugzilla/bugzilla.service.js +++ b/services/bugzilla/bugzilla.service.js @@ -1,8 +1,9 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') -const { checkErrorResponse } = require('../../lib/error-helper') +const Joi = require('joi') +const { BaseJsonService } = require('..') + +const schema = Joi.any() const documentation = `

@@ -10,13 +11,7 @@ const documentation = `

` -// This legacy service should be rewritten to use e.g. BaseJsonService. -// -// Tips for rewriting: -// https://github.com/badges/shields/blob/master/doc/rewriting-services.md -// -// Do not base new services on this code. -module.exports = class Bugzilla extends LegacyService { +module.exports = class Bugzilla extends BaseJsonService { static get category() { return 'issue-tracking' } @@ -33,82 +28,73 @@ module.exports = class Bugzilla extends LegacyService { { title: 'Bugzilla bug status', namedParams: { bugNumber: '996038' }, - staticPreview: { - label: 'bug 996038', - message: 'fixed', - color: 'brightgreen', - }, + staticPreview: this.render({ + bugNumber: 996038, + status: 'FIXED', + resolution: '', + }), documentation, }, ] } - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/bugzilla\/(\d+)\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const bugNumber = match[1] // eg, 1436739 - const format = match[2] - const options = { - method: 'GET', - json: true, - uri: `https://bugzilla.mozilla.org/rest/bug/${bugNumber}`, - } - const badgeData = getBadgeData(`bug ${bugNumber}`, data) - request(options, (err, res, json) => { - if (checkErrorResponse(badgeData, err, res)) { - sendBadge(format, badgeData) - return - } - try { - const bug = json.bugs[0] + static get defaultBadgeData() { + return { label: 'bugzilla' } + } + + async fetch({ bugNumber }) { + return this._requestJson({ + schema, + url: `https://bugzilla.mozilla.org/rest/bug/${bugNumber}`, + }) + } + + static getDisplayStatus({ status, resolution }) { + let displayStatus = + status === 'RESOLVED' ? resolution.toLowerCase() : status.toLowerCase() + if (displayStatus === 'worksforme') { + displayStatus = 'works for me' + } + if (displayStatus === 'wontfix') { + displayStatus = "won't fix" + } + return displayStatus + } + + static getColor({ displayStatus }) { + const colorMap = { + unconfirmed: 'blue', + new: 'blue', + assigned: 'green', + fixed: 'brightgreen', + invalid: 'yellow', + "won't fix": 'orange', + duplicate: 'lightgrey', + 'works for me': 'yellowgreen', + incomplete: 'red', + } + if (displayStatus in colorMap) { + return colorMap[displayStatus] + } + return 'lightgrey' + } + + static render({ bugNumber, status, resolution }) { + const displayStatus = this.getDisplayStatus({ status, resolution }) + const color = this.getColor({ displayStatus }) + return { + label: `bug ${bugNumber}`, + message: displayStatus, + color, + } + } - switch (bug.status) { - case 'UNCONFIRMED': - badgeData.text[1] = 'unconfirmed' - badgeData.colorscheme = 'blue' - break - case 'NEW': - badgeData.text[1] = 'new' - badgeData.colorscheme = 'blue' - break - case 'ASSIGNED': - badgeData.text[1] = 'assigned' - badgeData.colorscheme = 'green' - break - case 'RESOLVED': - if (bug.resolution === 'FIXED') { - badgeData.text[1] = 'fixed' - badgeData.colorscheme = 'brightgreen' - } else if (bug.resolution === 'INVALID') { - badgeData.text[1] = 'invalid' - badgeData.colorscheme = 'yellow' - } else if (bug.resolution === 'WONTFIX') { - badgeData.text[1] = "won't fix" - badgeData.colorscheme = 'orange' - } else if (bug.resolution === 'DUPLICATE') { - badgeData.text[1] = 'duplicate' - badgeData.colorscheme = 'lightgrey' - } else if (bug.resolution === 'WORKSFORME') { - badgeData.text[1] = 'works for me' - badgeData.colorscheme = 'yellowgreen' - } else if (bug.resolution === 'INCOMPLETE') { - badgeData.text[1] = 'incomplete' - badgeData.colorscheme = 'red' - } else { - badgeData.text[1] = 'unknown' - } - break - default: - badgeData.text[1] = 'unknown' - } - sendBadge(format, badgeData) - } catch (e) { - badgeData.text[1] = 'unknown' - sendBadge(format, badgeData) - } - }) - }) - ) + async handle({ bugNumber }) { + const data = await this.fetch({ bugNumber }) + return this.constructor.render({ + bugNumber, + status: data.bugs[0].status, + resolution: data.bugs[0].resolution, + }) } } diff --git a/services/bugzilla/bugzilla.spec.js b/services/bugzilla/bugzilla.spec.js new file mode 100644 index 0000000000000..89453c32ae5f0 --- /dev/null +++ b/services/bugzilla/bugzilla.spec.js @@ -0,0 +1,16 @@ +'use strict' + +const { test, given } = require('sazerac') +const Bugzilla = require('./bugzilla.service') + +describe('getDisplayStatus function', function() { + it('formats status correctly', async function() { + test(Bugzilla.getDisplayStatus, () => { + given({ status: 'RESOLVED', resolution: 'WORKSFORME' }).expect( + 'works for me' + ) + given({ status: 'RESOLVED', resolution: 'WONTFIX' }).expect("won't fix") + given({ status: 'ASSIGNED', resolution: '' }).expect('assigned') + }) + }) +}) diff --git a/services/bugzilla/bugzilla.tester.js b/services/bugzilla/bugzilla.tester.js index 24b0a41eefe35..9d2d4ebd377ed 100644 --- a/services/bugzilla/bugzilla.tester.js +++ b/services/bugzilla/bugzilla.tester.js @@ -1,7 +1,7 @@ 'use strict' const Joi = require('joi') -const { ServiceTester } = require('../tester') +const t = (module.exports = require('../tester').createServiceTester()) const bzBugStatus = Joi.equal( 'unconfirmed', @@ -15,11 +15,6 @@ const bzBugStatus = Joi.equal( 'incomplete' ) -const t = (module.exports = new ServiceTester({ - id: 'bugzilla', - title: 'Bugzilla', -})) - t.create('Bugzilla valid bug status') .get('/996038.json') .expectJSONTypes( @@ -31,9 +26,4 @@ t.create('Bugzilla valid bug status') t.create('Bugzilla invalid bug status') .get('/83548978974387943879.json') - .expectJSON({ name: 'bug 83548978974387943879', value: 'not found' }) - -t.create('Bugzilla failed request bug status') - .get('/996038.json') - .networkOff() - .expectJSON({ name: 'bug 996038', value: 'inaccessible' }) + .expectJSON({ name: 'bugzilla', value: 'not found' }) diff --git a/services/bundlephobia/bundlephobia.service.js b/services/bundlephobia/bundlephobia.service.js index 1bc78d49971fe..1bd56c0418528 100644 --- a/services/bundlephobia/bundlephobia.service.js +++ b/services/bundlephobia/bundlephobia.service.js @@ -4,8 +4,6 @@ const prettyBytes = require('pretty-bytes') const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') const LegacyService = require('../legacy-service') -const keywords = ['node'] - // This legacy service should be rewritten to use e.g. BaseJsonService. // // Tips for rewriting: @@ -27,26 +25,15 @@ module.exports = class Bundlephobia extends LegacyService { static get examples() { return [ { - title: 'npm bundle size (minified)', - pattern: 'min/:packageName', - namedParams: { packageName: 'react' }, + title: 'npm bundle size', + pattern: ':format(min|minzip)/:packageName', + namedParams: { format: 'minzip', packageName: 'react' }, staticPreview: { label: 'minified size', - message: '6.06 kB', - color: 'blue', - }, - keywords, - }, - { - title: 'npm bundle size (minified + gzip)', - pattern: 'minzip/:packageName', - namedParams: { packageName: 'react' }, - staticPreview: { - label: 'minzipped size', message: '2.57 kB', color: 'blue', }, - keywords, + keywords: ['node'], }, ] } diff --git a/services/cauditor/cauditor.service.js b/services/cauditor/cauditor.service.js index b6b3775c1439a..73466c910bbaf 100644 --- a/services/cauditor/cauditor.service.js +++ b/services/cauditor/cauditor.service.js @@ -9,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:mi|ccn|npath|hi|i|ca|ce|dit)/(?:[^/]+)/(?:[^/]+)/(?:.+)', }, label: 'cauditor', + dateAdded: new Date('2018-02-15'), }) diff --git a/services/cocoapods/cocoapods-apps.service.js b/services/cocoapods/cocoapods-apps.service.js index 1728e74afb9ae..9734e375f4b66 100644 --- a/services/cocoapods/cocoapods-apps.service.js +++ b/services/cocoapods/cocoapods-apps.service.js @@ -9,4 +9,5 @@ module.exports = deprecatedService({ pattern: ':interval(aw|at)/:spec', }, label: 'apps', + dateAdded: new Date('2018-01-06'), }) diff --git a/services/cocoapods/cocoapods-downloads.service.js b/services/cocoapods/cocoapods-downloads.service.js index 0974e136693bf..777cdd4047cd2 100644 --- a/services/cocoapods/cocoapods-downloads.service.js +++ b/services/cocoapods/cocoapods-downloads.service.js @@ -9,4 +9,5 @@ module.exports = deprecatedService({ pattern: ':interval(dm|dw|dt)/:spec', }, label: 'downloads', + dateAdded: new Date('2018-01-06'), }) diff --git a/services/coverity/coverity-on-demand.service.js b/services/coverity/coverity-on-demand.service.js index d36aca750f28d..42f67426d13d9 100644 --- a/services/coverity/coverity-on-demand.service.js +++ b/services/coverity/coverity-on-demand.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// coverity on demand integration - deprecated as of December 2018. module.exports = deprecatedService({ route: { base: 'coverity/ondemand', @@ -10,4 +9,5 @@ module.exports = deprecatedService({ }, label: 'coverity', category: 'analysis', + dateAdded: new Date('2018-12-18'), }) diff --git a/services/david/david.service.js b/services/david/david.service.js index 08819106fd87b..ca4fda64901ce 100644 --- a/services/david/david.service.js +++ b/services/david/david.service.js @@ -17,7 +17,7 @@ module.exports = class David extends LegacyService { static get route() { return { base: 'david', - pattern: '', + pattern: ':kind(dev|optional|peer)?/:user/:repo', } } @@ -25,33 +25,11 @@ module.exports = class David extends LegacyService { return [ { title: 'David', - pattern: ':user/:repo', namedParams: { user: 'expressjs', repo: 'express' }, staticPreview: this.renderStaticExample(), }, - { - title: 'David', - pattern: 'dev/:user/:repo', - namedParams: { user: 'expressjs', repo: 'express' }, - staticPreview: this.renderStaticExample({ label: 'dev dependencies' }), - }, - { - title: 'David', - pattern: 'optional/:user/:repo', - namedParams: { user: 'elnounch', repo: 'byebye' }, - staticPreview: this.renderStaticExample({ - label: 'optional dependencies', - }), - }, - { - title: 'David', - pattern: 'peer/:user/:repo', - namedParams: { user: 'webcomponents', repo: 'generator-element' }, - staticPreview: this.renderStaticExample({ label: 'peer dependencies' }), - }, { title: 'David (path)', - pattern: ':user/:repo', namedParams: { user: 'babel', repo: 'babel' }, queryParams: { path: 'packages/babel-core' }, staticPreview: this.renderStaticExample(), diff --git a/services/discourse/discourse.service.js b/services/discourse/discourse.service.js index 732adf16a7135..623e64faff324 100644 --- a/services/discourse/discourse.service.js +++ b/services/discourse/discourse.service.js @@ -1,176 +1,112 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') +const Joi = require('joi') +const { BaseJsonService } = require('..') const { metric } = require('../../lib/text-formatters') +const { nonNegativeInteger } = require('../validators') -// This legacy service should be rewritten to use e.g. BaseJsonService. -// -// Tips for rewriting: -// https://github.com/badges/shields/blob/master/doc/rewriting-services.md -// -// Do not base new services on this code. -module.exports = class Discourse extends LegacyService { +const schema = Joi.object({ + topic_count: nonNegativeInteger, + user_count: nonNegativeInteger, + post_count: nonNegativeInteger, + like_count: nonNegativeInteger, +}).required() + +class DiscourseBase extends BaseJsonService { static get category() { return 'chat' } - static get route() { + static buildRoute(metric) { return { base: 'discourse', - pattern: - ':scheme(http|https)/:host/:which(topics|posts|users|likes|status)', + pattern: `:scheme(http|https)/:host/${metric}`, } } + static get defaultBadgeData() { + return { label: 'discourse' } + } + + async fetch({ scheme, host }) { + return this._requestJson({ + schema, + url: `${scheme}://${host}/site/statistics.json`, + }) + } +} + +function DiscourseMetricIntegrationFactory({ metricName, property }) { + return class DiscourseMetric extends DiscourseBase { + static get route() { + return this.buildRoute(metricName) + } + + static get examples() { + return [ + { + title: `Discourse ${metricName}`, + namedParams: { + scheme: 'https', + host: 'meta.discourse.org', + }, + staticPreview: this.render({ stat: 100 }), + }, + ] + } + + static render({ stat }) { + return { + message: `${metric(stat)} ${metricName}`, + color: 'brightgreen', + } + } + + async handle({ scheme, host }) { + const data = await this.fetch({ scheme, host }) + return this.constructor.render({ stat: data[property] }) + } + } +} + +class DiscourseStatus extends DiscourseBase { + static get route() { + return this.buildRoute('status') + } + static get examples() { return [ { - title: 'Discourse topics', - pattern: ':scheme(http|https)/:host/topics', - namedParams: { - scheme: 'https', - host: 'meta.discourse.org', - }, - staticPreview: { - label: 'discourse', - message: '27k topics', - color: 'brightgreen', - }, - }, - { - title: 'Discourse posts', - pattern: ':scheme(http|https)/:host/posts', + title: `Discourse status`, namedParams: { scheme: 'https', host: 'meta.discourse.org', }, - staticPreview: { - label: 'discourse', - message: '490k posts', - color: 'brightgreen', - }, - }, - { - title: 'Discourse users', - pattern: ':scheme(http|https)/:host/users', - namedParams: { - scheme: 'https', - host: 'meta.discourse.org', - }, - staticPreview: { - label: 'discourse', - message: '42k users', - color: 'brightgreen', - }, - }, - { - title: 'Discourse likes', - pattern: ':scheme(http|https)/:host/likes', - namedParams: { - scheme: 'https', - host: 'meta.discourse.org', - }, - staticPreview: { - label: 'discourse', - message: '499k likes', - color: 'brightgreen', - }, - }, - { - title: 'Discourse status', - pattern: ':scheme(http|https)/:host/status', - namedParams: { - scheme: 'https', - host: 'meta.discourse.org', - }, - staticPreview: { - label: 'discourse', - message: 'online', - color: 'brightgreen', - }, + staticPreview: this.render(), }, ] } - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/discourse\/(http(?:s)?)\/(.*)\/(.*)\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const scheme = match[1] // eg, https - const host = match[2] // eg, meta.discourse.org - const stat = match[3] // eg, user_count - const format = match[4] - const url = `${scheme}://${host}/site/statistics.json` - - const options = { - method: 'GET', - uri: url, - headers: { - Accept: 'application/json', - }, - } - - const badgeData = getBadgeData('discourse', data) - request(options, (err, res) => { - if (err != null) { - if (res) { - console.error(`${res}`) - } - - badgeData.text[1] = 'inaccessible' - sendBadge(format, badgeData) - return - } - - if (res.statusCode !== 200) { - badgeData.text[1] = 'inaccessible' - badgeData.colorscheme = 'red' - sendBadge(format, badgeData) - return - } - - badgeData.colorscheme = 'brightgreen' - - try { - const data = JSON.parse(res['body']) - let statCount - - switch (stat) { - case 'topics': - statCount = data.topic_count - badgeData.text[1] = `${metric(statCount)} topics` - break - case 'posts': - statCount = data.post_count - badgeData.text[1] = `${metric(statCount)} posts` - break - case 'users': - statCount = data.user_count - badgeData.text[1] = `${metric(statCount)} users` - break - case 'likes': - statCount = data.like_count - badgeData.text[1] = `${metric(statCount)} likes` - break - case 'status': - badgeData.text[1] = 'online' - break - default: - badgeData.text[1] = 'invalid' - badgeData.colorscheme = 'yellow' - break - } - - sendBadge(format, badgeData) - } catch (e) { - console.error(`${e.stack}`) - badgeData.colorscheme = 'yellow' - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - } - }) - }) - ) + static render() { + return { + message: 'online', + color: 'brightgreen', + } + } + + async handle({ scheme, host }) { + await this.fetch({ scheme, host }) + // if fetch() worked, the server is up + // if it failed, we'll show an error e.g: 'inaccessible' + return this.constructor.render() } } + +const metricIntegrations = [ + { metricName: 'topics', property: 'topic_count' }, + { metricName: 'users', property: 'user_count' }, + { metricName: 'posts', property: 'post_count' }, + { metricName: 'likes', property: 'like_count' }, +].map(DiscourseMetricIntegrationFactory) + +module.exports = [...metricIntegrations, DiscourseStatus] diff --git a/services/discourse/discourse.tester.js b/services/discourse/discourse.tester.js index 74857a677827e..2840dc95adf26 100644 --- a/services/discourse/discourse.tester.js +++ b/services/discourse/discourse.tester.js @@ -79,6 +79,11 @@ t.create('Status with http (not https)') ) .expectJSON({ name: 'discourse', value: 'online' }) +t.create('Status (offline)') + .get('/https/meta.discourse.org/status.json') + .networkOff() + .expectJSON({ name: 'discourse', value: 'inaccessible' }) + t.create('Invalid Host') .get('/https/some.host/status.json') .intercept(nock => @@ -86,21 +91,7 @@ t.create('Invalid Host') .get('/site/statistics.json') .reply(404, '

Not Found

') ) - .expectJSON({ name: 'discourse', value: 'inaccessible' }) - -t.create('Invalid Stat') - .get('/https/meta.discourse.org/unknown.json') - .intercept(nock => - nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) - ) - .expectJSON({ name: 'discourse', value: 'invalid' }) - -t.create('Connection Error') - .get('/https/meta.discourse.org/status.json') - .networkOff() - .expectJSON({ name: 'discourse', value: 'inaccessible' }) + .expectJSON({ name: 'discourse', value: 'not found' }) t.create('Topics (live)') .get('/https/meta.discourse.org/topics.json') diff --git a/services/dockbit/dockbit.service.js b/services/dockbit/dockbit.service.js index 2d0b6834bda3c..973f481eb8f17 100644 --- a/services/dockbit/dockbit.service.js +++ b/services/dockbit/dockbit.service.js @@ -1,7 +1,7 @@ 'use strict' const { deprecatedService } = require('..') -// dockbit integration - deprecated as of December 2017. + module.exports = deprecatedService({ category: 'build', route: { @@ -9,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:.+)', }, label: 'dockbit', + dateAdded: new Date('2017-12-31'), }) diff --git a/services/dotnetstatus/dotnetstatus.service.js b/services/dotnetstatus/dotnetstatus.service.js index be508e6370d66..fad54aceb96e9 100644 --- a/services/dotnetstatus/dotnetstatus.service.js +++ b/services/dotnetstatus/dotnetstatus.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// dotnet-status integration - deprecated as of April 2018. module.exports = deprecatedService({ category: 'dependencies', route: { @@ -10,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:.+)', }, label: 'dotnet status', + dateAdded: new Date('2018-04-01'), }) diff --git a/services/endpoint/endpoint.tester.js b/services/endpoint/endpoint.tester.js index 2d8fd9a4c260e..371969bc136e4 100644 --- a/services/endpoint/endpoint.tester.js +++ b/services/endpoint/endpoint.tester.js @@ -170,6 +170,20 @@ t.create('Invalid schema (mocked)') }) t.create('User color overrides success color') + .get('.json?url=https://example.com/badge&color=101010&style=_shields_test') + .intercept(nock => + nock('https://example.com/') + .get('/badge') + .reply(200, { + schemaVersion: 1, + label: '', + message: 'yo', + color: 'blue', + }) + ) + .expectJSON({ name: '', value: 'yo', color: '#101010' }) + +t.create('User legacy color overrides success color') .get('.json?url=https://example.com/badge&colorB=101010&style=_shields_test') .intercept(nock => nock('https://example.com/') @@ -184,6 +198,21 @@ t.create('User color overrides success color') .expectJSON({ name: '', value: 'yo', color: '#101010' }) t.create('User color does not override error color') + .get('.json?url=https://example.com/badge&color=101010&style=_shields_test') + .intercept(nock => + nock('https://example.com/') + .get('/badge') + .reply(200, { + schemaVersion: 1, + isError: true, + label: 'something is', + message: 'not right', + color: 'red', + }) + ) + .expectJSON({ name: 'something is', value: 'not right', color: 'red' }) + +t.create('User legacy color does not override error color') .get('.json?url=https://example.com/badge&colorB=101010&style=_shields_test') .intercept(nock => nock('https://example.com/') diff --git a/services/gemnasium/gemnasium.service.js b/services/gemnasium/gemnasium.service.js index 636e3e997aae2..34d7aafa71448 100644 --- a/services/gemnasium/gemnasium.service.js +++ b/services/gemnasium/gemnasium.service.js @@ -9,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:.+)', }, label: 'gemnasium', + dateAdded: new Date('2018-05-15'), }) diff --git a/services/github/github-commit-activity.service.js b/services/github/github-commit-activity.service.js index e1a8522cd3560..a7940c0129561 100644 --- a/services/github/github-commit-activity.service.js +++ b/services/github/github-commit-activity.service.js @@ -23,43 +23,20 @@ module.exports = class GithubCommitActivity extends LegacyService { static get route() { return { base: 'github/commit-activity', - pattern: ':interval(y|4w|w)/:user/:repo', + pattern: ':interval(y|m|4w|w)/:user/:repo', } } static get examples() { return [ { - title: 'GitHub commit activity the past year', - pattern: 'y/:user/:repo', - namedParams: { user: 'eslint', repo: 'eslint' }, + title: 'GitHub commit activity', + // Override the pattern to omit the deprecated interval "4w". + pattern: ':interval(y|m|w)/:user/:repo', + namedParams: { interval: 'm', user: 'eslint', repo: 'eslint' }, staticPreview: { label: 'commit activity', - message: '457/year', - color: 'blue', - }, - keywords: ['commits'], - documentation, - }, - { - title: 'GitHub commit activity the past month', - pattern: 'm/:user/:repo', - namedParams: { user: 'eslint', repo: 'eslint' }, - staticPreview: { - label: 'commit activity', - message: '38/month', - color: 'blue', - }, - keywords: ['commits'], - documentation, - }, - { - title: 'GitHub commit activity the past week', - pattern: 'w/:user/:repo', - namedParams: { user: 'eslint', repo: 'eslint' }, - staticPreview: { - label: 'commit activity', - message: '9/week', + message: '457/month', color: 'blue', }, keywords: ['commits'], diff --git a/services/github/github-commits-since.service.js b/services/github/github-commits-since.service.js index 0b7687a2be831..80b4ff26db9c5 100644 --- a/services/github/github-commits-since.service.js +++ b/services/github/github-commits-since.service.js @@ -29,7 +29,7 @@ module.exports = class GithubCommitsSince extends LegacyService { static get examples() { return [ { - title: 'GitHub commits', + title: 'GitHub commits since tagged version', namedParams: { user: 'SubtitleEdit', repo: 'subtitleedit', @@ -43,7 +43,7 @@ module.exports = class GithubCommitsSince extends LegacyService { documentation, }, { - title: 'GitHub commits (since latest release)', + title: 'GitHub commits since latest release', namedParams: { user: 'SubtitleEdit', repo: 'subtitleedit', diff --git a/services/github/github-common-fetch.js b/services/github/github-common-fetch.js index d1991c30ba216..a14e48eca8e8a 100644 --- a/services/github/github-common-fetch.js +++ b/services/github/github-common-fetch.js @@ -28,31 +28,35 @@ async function fetchJsonFromRepo( serviceInstance, { schema, user, repo, branch = 'master', filename } ) { - let url, options + const errorMessages = errorMessagesFor( + `repo not found, branch not found, or ${filename} missing` + ) if (serviceInstance.staticAuthConfigured) { - url = `/repos/${user}/${repo}/contents/${filename}` - options = { qs: { ref: branch } } - } else { - url = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${filename}` - } + const url = `/repos/${user}/${repo}/contents/${filename}` + const options = { qs: { ref: branch } } + const { content } = await serviceInstance._requestJson({ + schema: contentSchema, + url, + options, + errorMessages, + }) - const { content } = await serviceInstance._requestJson({ - schema: contentSchema, - url, - options, - errorMessages: errorMessagesFor( - `repo not found, branch not found, or ${filename} missing` - ), - }) - - let decoded - try { - decoded = Buffer.from(content, 'base64').toString('utf-8') - } catch (e) { - throw InvalidResponse({ prettyMessage: 'undecodable content' }) + let decoded + try { + decoded = Buffer.from(content, 'base64').toString('utf-8') + } catch (e) { + throw InvalidResponse({ prettyMessage: 'undecodable content' }) + } + const json = serviceInstance._parseJson(decoded) + return serviceInstance.constructor._validate(json, schema) + } else { + const url = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${filename}` + return serviceInstance._requestJson({ + schema, + url, + errorMessages, + }) } - const json = serviceInstance._parseJson(decoded) - return serviceInstance.constructor._validate(json, schema) } module.exports = { diff --git a/services/github/github-issue-detail.service.js b/services/github/github-issue-detail.service.js index 2f6e19b0a34cf..c1ca7fe7f0ee2 100644 --- a/services/github/github-issue-detail.service.js +++ b/services/github/github-issue-detail.service.js @@ -15,11 +15,6 @@ const { checkErrorResponse: githubCheckErrorResponse, } = require('./github-helpers') -const commonExampleAttrs = { - keywords: ['pullrequest', 'detail'], - documentation, -} - // This legacy service should be rewritten to use e.g. BaseJsonService. // // Tips for rewriting: @@ -35,16 +30,18 @@ module.exports = class GithubIssueDetail extends LegacyService { return { base: 'github/issues/detail', pattern: - ':which(s|title|u|label|comments|age|last-update)/:user/:repo/:number(0-9+)', + ':which(s|state|title|u|author|label|comments|age|last-update)/:user/:repo/:number(0-9+)', } } static get examples() { return [ { - title: 'GitHub issue/pull request state', - pattern: 's/:user/:repo/:number', + title: 'GitHub issue/pull request detail', + pattern: + ':which(state|title|author|label|comments|age|last-update)/:user/:repo/:number', namedParams: { + which: 'state', user: 'badges', repo: 'shields', number: '979', @@ -54,97 +51,16 @@ module.exports = class GithubIssueDetail extends LegacyService { message: 'closed', color: 'red', }, - ...commonExampleAttrs, - }, - { - title: 'GitHub issue/pull request title', - pattern: 'title/:user/:repo/:number', - namedParams: { - user: 'badges', - repo: 'shields', - number: '1290', - }, - staticPreview: { - label: 'issue 1290', - message: 'Node 9 support', - color: 'lightgrey', - }, - ...commonExampleAttrs, - }, - { - title: 'GitHub issue/pull request author', - pattern: 'u/:user/:repo/:number', - namedParams: { - user: 'badges', - repo: 'shields', - number: '979', - }, - staticPreview: { - label: 'author', - message: 'paulmelnikow', - color: 'lightgrey', - }, - ...commonExampleAttrs, - }, - { - title: 'GitHub issue/pull request label', - pattern: 'label/:user/:repo/:number', - namedParams: { - user: 'badges', - repo: 'shields', - number: '979', - }, - staticPreview: { - label: 'label', - message: 'bug | developer-experience', - color: 'lightgrey', - }, - ...commonExampleAttrs, - }, - { - title: 'GitHub issue/pull request comments', - pattern: 'comments/:user/:repo/:number', - namedParams: { - user: 'badges', - repo: 'shields', - number: '979', - }, - staticPreview: { - label: 'comments', - message: '24', - color: 'yellow', - }, - ...commonExampleAttrs, - }, - { - title: 'GitHub issue/pull request age', - pattern: 'age/:user/:repo/:number', - namedParams: { - user: 'badges', - repo: 'shields', - number: '979', - }, - staticPreview: { - label: 'created', - message: 'april 2017', - color: 'orange', - }, - ...commonExampleAttrs, - }, - { - title: 'GitHub issue/pull request last update', - pattern: 'last-update/:user/:repo/:number', - namedParams: { - user: 'badges', - repo: 'shields', - number: '979', - }, - staticPreview: { - label: 'updated', - message: 'december 2017', - color: 'orange', - }, - ...commonExampleAttrs, + keywords: [ + 'state', + 'title', + 'author', + 'label', + 'comments', + 'age', + 'last update', + ], + documentation, }, ] } @@ -183,7 +99,8 @@ module.exports = class GithubIssueDetail extends LegacyService { queryParams ) switch (which) { - case 's': { + case 's': + case 'state': { const state = (badgeData.text[1] = parsedData.state) badgeData.colorscheme = undefined badgeData.colorB = queryParams.colorB || githubStateColor(state) @@ -193,6 +110,7 @@ module.exports = class GithubIssueDetail extends LegacyService { badgeData.text[1] = parsedData.title break case 'u': + case 'author': badgeData.text[0] = getLabel('author', queryParams) badgeData.text[1] = parsedData.user.login break diff --git a/services/gratipay/gratipay.service.js b/services/gratipay/gratipay.service.js index 2ae98d48630fa..3551dd039aae4 100644 --- a/services/gratipay/gratipay.service.js +++ b/services/gratipay/gratipay.service.js @@ -8,4 +8,5 @@ module.exports = deprecatedService({ format: '(?:gittip|gratipay(?:/user|/team|/project)?)/(?:.*)', }, label: 'gratipay', + dateAdded: new Date('2017-12-29'), }) diff --git a/services/homebrew/homebrew.service.js b/services/homebrew/homebrew.service.js index 37936c4407f4d..5c500fbd98912 100644 --- a/services/homebrew/homebrew.service.js +++ b/services/homebrew/homebrew.service.js @@ -1,18 +1,16 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') -const { checkErrorResponse } = require('../../lib/error-helper') -const { addv: versionText } = require('../../lib/text-formatters') -const { version: versionColor } = require('../../lib/color-formatters') +const Joi = require('joi') +const { renderVersionBadge } = require('../../lib/version') +const { BaseJsonService } = require('..') -// This legacy service should be rewritten to use e.g. BaseJsonService. -// -// Tips for rewriting: -// https://github.com/badges/shields/blob/master/doc/rewriting-services.md -// -// Do not base new services on this code. -module.exports = class Homebrew extends LegacyService { +const schema = Joi.object({ + versions: Joi.object({ + stable: Joi.string().required(), + }).required(), +}).required() + +module.exports = class Homebrew extends BaseJsonService { static get category() { return 'version' } @@ -29,47 +27,24 @@ module.exports = class Homebrew extends LegacyService { { title: 'homebrew', namedParams: { formula: 'cake' }, - staticPreview: { - label: 'homebrew', - message: 'v0.32.0', - color: 'orange', - }, + staticPreview: renderVersionBadge({ version: 'v0.32.0' }), }, ] } - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/homebrew\/v\/([^/]+)\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const pkg = match[1] // eg. cake - const format = match[2] - const apiUrl = `https://formulae.brew.sh/api/formula/${pkg}.json` - - const badgeData = getBadgeData('homebrew', data) - request( - apiUrl, - { headers: { Accept: 'application/json' } }, - (err, res, buffer) => { - if (checkErrorResponse(badgeData, err, res)) { - sendBadge(format, badgeData) - return - } - try { - const data = JSON.parse(buffer) - const version = data.versions.stable + static get defaultBadgeData() { + return { label: 'homebrew' } + } - badgeData.text[1] = versionText(version) - badgeData.colorscheme = versionColor(version) + async fetch({ formula }) { + return this._requestJson({ + schema, + url: `https://formulae.brew.sh/api/formula/${formula}.json`, + }) + } - sendBadge(format, badgeData) - } catch (e) { - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - } - } - ) - }) - ) + async handle({ formula }) { + const data = await this.fetch({ formula }) + return renderVersionBadge({ version: data.versions.stable }) } } diff --git a/services/homebrew/homebrew.tester.js b/services/homebrew/homebrew.tester.js index f8be425c32edb..11ae25d6bde6b 100644 --- a/services/homebrew/homebrew.tester.js +++ b/services/homebrew/homebrew.tester.js @@ -1,17 +1,11 @@ 'use strict' const Joi = require('joi') -const { ServiceTester } = require('../tester') const { isVPlusTripleDottedVersion } = require('../test-validators') -const { invalidJSON } = require('../response-fixtures') - -const t = (module.exports = new ServiceTester({ - id: 'homebrew', - title: 'homebrew', -})) +const t = (module.exports = require('../tester').createServiceTester()) t.create('homebrew (valid)') - .get('/v/cake.json') + .get('/cake.json') .expectJSONTypes( Joi.object().keys({ name: 'homebrew', @@ -20,7 +14,7 @@ t.create('homebrew (valid)') ) t.create('homebrew (valid, mocked response)') - .get('/v/cake.json') + .get('/cake.json') .intercept(nock => nock('https://formulae.brew.sh') .get('/api/formula/cake.json') @@ -28,20 +22,6 @@ t.create('homebrew (valid, mocked response)') ) .expectJSON({ name: 'homebrew', value: 'v0.23.0' }) -t.create('homebrew (invalid)') - .get('/v/not-a-package.json') +t.create('homebrew (not found)') + .get('/not-a-package.json') .expectJSON({ name: 'homebrew', value: 'not found' }) - -t.create('homebrew (connection error)') - .get('/v/cake.json') - .networkOff() - .expectJSON({ name: 'homebrew', value: 'inaccessible' }) - -t.create('homebrew (unexpected response)') - .get('/v/cake.json') - .intercept(nock => - nock('https://formulae.brew.sh') - .get('/api/formula/cake.json') - .reply(invalidJSON) - ) - .expectJSON({ name: 'homebrew', value: 'invalid' }) diff --git a/services/imagelayers/imagelayers.service.js b/services/imagelayers/imagelayers.service.js index d62c8754263a8..f6ad4de2e826a 100644 --- a/services/imagelayers/imagelayers.service.js +++ b/services/imagelayers/imagelayers.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// image layers integration - deprecated as of November 2018. module.exports = deprecatedService({ category: 'size', route: { @@ -10,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:.+)', }, label: 'imagelayers', + dateAdded: new Date('2018-11-18'), }) diff --git a/services/issuestats/issuestats.service.js b/services/issuestats/issuestats.service.js index f547f9998a23d..3edb9cc7b6a09 100644 --- a/services/issuestats/issuestats.service.js +++ b/services/issuestats/issuestats.service.js @@ -9,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:[^/]+)(?:/long)?/(?:[^/]+)/(?:.+)', }, label: 'issue stats', + dateAdded: new Date('2018-09-01'), }) diff --git a/services/jsdelivr/jsdelivr-base.js b/services/jsdelivr/jsdelivr-base.js index 9679b23365b27..fc3002abeae80 100644 --- a/services/jsdelivr/jsdelivr-base.js +++ b/services/jsdelivr/jsdelivr-base.js @@ -1,6 +1,7 @@ 'use strict' const Joi = require('joi') +const { downloadCount } = require('../../lib/color-formatters') const { metric } = require('../../lib/text-formatters') const { BaseJsonService } = require('..') @@ -18,19 +19,19 @@ const periodMap = { class BaseJsDelivrService extends BaseJsonService { static render({ period, hits }) { return { - message: `${metric(hits)} hits/${periodMap[period]}`, + message: `${metric(hits)}/${periodMap[period]}`, + color: downloadCount(hits), } } static get defaultBadgeData() { return { label: 'jsdelivr', - color: 'orange', } } static get category() { - return 'other' + return 'downloads' } } diff --git a/services/jsdelivr/jsdelivr-hits-github.service.js b/services/jsdelivr/jsdelivr-hits-github.service.js index f1f9930dc2d5d..e323969c28706 100644 --- a/services/jsdelivr/jsdelivr-hits-github.service.js +++ b/services/jsdelivr/jsdelivr-hits-github.service.js @@ -27,41 +27,14 @@ module.exports = class jsDelivrHitsGitHub extends BaseJsDelivrService { static get examples() { return [ { - title: 'jsDelivr Hits (GitHub)', - pattern: 'hd/:user/:repo', - namedParams: { - user: 'jquery', - repo: 'jquery', - }, - staticPreview: this.render({ period: 'hd', hits: 272042 }), - }, - { - title: 'jsDelivr Hits (GitHub)', - pattern: 'hw/:user/:repo', - namedParams: { - user: 'jquery', - repo: 'jquery', - }, - staticPreview: this.render({ period: 'hw', hits: 2156336 }), - }, - { - title: 'jsDelivr Hits (GitHub)', - pattern: 'hm/:user/:repo', + title: 'jsDelivr hits (GitHub)', namedParams: { + period: 'hm', user: 'jquery', repo: 'jquery', }, staticPreview: this.render({ period: 'hm', hits: 9809876 }), }, - { - title: 'jsDelivr Hits (GitHub)', - pattern: 'hy/:user/:repo', - namedParams: { - user: 'jquery', - repo: 'jquery', - }, - staticPreview: this.render({ period: 'hy', hits: 95317723 }), - }, ] } } diff --git a/services/jsdelivr/jsdelivr-hits-github.tester.js b/services/jsdelivr/jsdelivr-hits-github.tester.js index e2b7daa1ecdfe..1260d03a548ef 100644 --- a/services/jsdelivr/jsdelivr-hits-github.tester.js +++ b/services/jsdelivr/jsdelivr-hits-github.tester.js @@ -1,7 +1,7 @@ 'use strict' const Joi = require('joi') -const { withRegex } = require('../test-validators') +const { isMetricOverTimePeriod } = require('../test-validators') const t = (module.exports = require('../tester').createServiceTester()) @@ -10,7 +10,7 @@ t.create('(live) jquery/jquery hits/day') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(day)$/), + value: isMetricOverTimePeriod, }) ) @@ -19,7 +19,7 @@ t.create('(live) jquery/jquery hits/week') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(week)$/), + value: isMetricOverTimePeriod, }) ) @@ -28,7 +28,7 @@ t.create('(live) jquery/jquery hits/month') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(month)$/), + value: isMetricOverTimePeriod, }) ) @@ -37,7 +37,7 @@ t.create('(live) jquery/jquery hits/year') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(year)$/), + value: isMetricOverTimePeriod, }) ) @@ -46,5 +46,5 @@ t.create('(live) fake package') .expectJSON({ name: 'jsdelivr', // Will return 0 hits/day as the endpoint can't send 404s at present. - value: '0 hits/day', + value: '0/day', }) diff --git a/services/jsdelivr/jsdelivr-hits-npm.service.js b/services/jsdelivr/jsdelivr-hits-npm.service.js index 9a1b67a8456f3..5809579ba0bd5 100644 --- a/services/jsdelivr/jsdelivr-hits-npm.service.js +++ b/services/jsdelivr/jsdelivr-hits-npm.service.js @@ -6,19 +6,19 @@ module.exports = class jsDelivrHitsNPM extends BaseJsDelivrService { static get route() { return { base: 'jsdelivr/npm', - pattern: ':period(hd|hw|hm|hy)/:pkg', + pattern: ':period(hd|hw|hm|hy)/:packageName', } } - async handle({ period, pkg }) { - const { total } = await this.fetch({ period, pkg }) + async handle({ period, packageName }) { + const { total } = await this.fetch({ period, packageName }) return this.constructor.render({ period, hits: total }) } - async fetch({ period, pkg }) { + async fetch({ period, packageName }) { return this._requestJson({ schema, - url: `https://data.jsdelivr.com/v1/package/npm/${pkg}/stats/date/${ + url: `https://data.jsdelivr.com/v1/package/npm/${packageName}/stats/date/${ periodMap[period] }`, }) @@ -27,37 +27,13 @@ module.exports = class jsDelivrHitsNPM extends BaseJsDelivrService { static get examples() { return [ { - title: 'jsDelivr Hits (npm)', - pattern: 'hd/:packageName', - namedParams: { - packageName: 'jquery', - }, - staticPreview: this.render({ period: 'hd', hits: 31471644 }), - }, - { - title: 'jsDelivr Hits (npm)', - pattern: 'hw/:packageName', - namedParams: { - packageName: 'jquery', - }, - staticPreview: this.render({ period: 'hw', hits: 209922436 }), - }, - { - title: 'jsDelivr Hits (npm)', - pattern: 'hm/:packageName', + title: 'jsDelivr hits (npm)', namedParams: { + period: 'hm', packageName: 'jquery', }, staticPreview: this.render({ period: 'hm', hits: 920101789 }), }, - { - title: 'jsDelivr Hits (npm)', - pattern: 'hy/:packageName', - namedParams: { - packageName: 'jquery', - }, - staticPreview: this.render({ period: 'hy', hits: 10576760414 }), - }, ] } } diff --git a/services/jsdelivr/jsdelivr-hits-npm.tester.js b/services/jsdelivr/jsdelivr-hits-npm.tester.js index 96538894ff5df..f9e8809b6dc82 100644 --- a/services/jsdelivr/jsdelivr-hits-npm.tester.js +++ b/services/jsdelivr/jsdelivr-hits-npm.tester.js @@ -1,7 +1,7 @@ 'use strict' const Joi = require('joi') -const { withRegex } = require('../test-validators') +const { isMetricOverTimePeriod } = require('../test-validators') const t = (module.exports = require('../tester').createServiceTester()) @@ -10,7 +10,7 @@ t.create('(live) jquery hits/day') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(day)$/), + value: isMetricOverTimePeriod, }) ) @@ -19,7 +19,7 @@ t.create('(live) jquery hits/week') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(week)$/), + value: isMetricOverTimePeriod, }) ) @@ -28,7 +28,7 @@ t.create('(live) jquery hits/month') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(month)$/), + value: isMetricOverTimePeriod, }) ) @@ -37,7 +37,7 @@ t.create('(live) jquery hits/year') .expectJSONTypes( Joi.object().keys({ name: 'jsdelivr', - value: withRegex(/^(\d)+([kMG])*( hits\/)+(year)$/), + value: isMetricOverTimePeriod, }) ) @@ -46,5 +46,5 @@ t.create('(live) fake package') .expectJSON({ name: 'jsdelivr', // Will return 0 hits/day as the endpoint can't send 404s at present. - value: '0 hits/day', + value: '0/day', }) diff --git a/services/keybase/keybase-btc.service.js b/services/keybase/keybase-btc.service.js new file mode 100644 index 0000000000000..efdf4235d37bb --- /dev/null +++ b/services/keybase/keybase-btc.service.js @@ -0,0 +1,94 @@ +'use strict' + +const KeybaseProfile = require('./keybase-profile') +const Joi = require('joi') +const { nonNegativeInteger } = require('../validators') + +const bitcoinAddressSchema = Joi.object({ + status: Joi.object({ + code: nonNegativeInteger.required(), + }).required(), + them: Joi.array() + .items( + Joi.object({ + cryptocurrency_addresses: Joi.object({ + bitcoin: Joi.array().items( + Joi.object({ + address: Joi.string().required(), + }).required() + ), + }) + .required() + .allow(null), + }) + .required() + .allow(null) + ) + .min(0) + .max(1), +}).required() + +module.exports = class KeybaseBTC extends KeybaseProfile { + static get apiVersion() { + return '1.0' + } + + static get route() { + return { + base: 'keybase/btc', + pattern: ':username', + } + } + + static get defaultBadgeData() { + return { + label: 'btc', + color: 'informational', + } + } + + async handle({ username }) { + const options = { + form: { + usernames: username, + fields: 'cryptocurrency_addresses', + }, + } + + const data = await this.fetch({ + schema: bitcoinAddressSchema, + options, + }) + + const { user } = this.transform({ data }) + const bitcoinAddresses = user.cryptocurrency_addresses.bitcoin + + if (bitcoinAddresses == null || bitcoinAddresses.length === 0) { + return { + message: 'no bitcoin addresses found', + color: 'inactive', + } + } + + return this.constructor.render({ address: bitcoinAddresses[0].address }) + } + + static render({ address }) { + return { + message: address, + } + } + + static get examples() { + return [ + { + title: 'Keybase BTC', + namedParams: { username: 'skyplabs' }, + staticPreview: this.render({ + address: '12ufRLmbEmgjsdGzhUUFY4pcfiQZyRPV9J', + }), + keywords: ['bitcoin'], + }, + ] + } +} diff --git a/services/keybase/keybase-btc.tester.js b/services/keybase/keybase-btc.tester.js new file mode 100644 index 0000000000000..a0ad4efa78516 --- /dev/null +++ b/services/keybase/keybase-btc.tester.js @@ -0,0 +1,42 @@ +'use strict' + +const Joi = require('joi') +const { withRegex } = require('../test-validators') + +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('existing bitcoin address') + .get('/skyplabs.json') + .expectJSONTypes( + Joi.object({ + name: 'btc', + value: withRegex(/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/), + }) + ) + +t.create('unknown username') + .get('/skyplabsssssss.json') + .expectJSONTypes( + Joi.object({ + name: 'btc', + value: 'profile not found', + }) + ) + +t.create('invalid username') + .get('/s.json') + .expectJSONTypes( + Joi.object({ + name: 'btc', + value: 'invalid username', + }) + ) + +t.create('missing bitcoin address') + .get('/test.json') + .expectJSONTypes( + Joi.object({ + name: 'btc', + value: 'no bitcoin addresses found', + }) + ) diff --git a/services/keybase/keybase-pgp.service.js b/services/keybase/keybase-pgp.service.js new file mode 100644 index 0000000000000..42ef69b103f3d --- /dev/null +++ b/services/keybase/keybase-pgp.service.js @@ -0,0 +1,89 @@ +'use strict' + +const KeybaseProfile = require('./keybase-profile') +const Joi = require('joi') +const { nonNegativeInteger } = require('../validators') + +const keyFingerprintSchema = Joi.object({ + status: Joi.object({ + code: nonNegativeInteger.required(), + }).required(), + them: Joi.array() + .items( + Joi.object({ + public_keys: { + primary: { + key_fingerprint: Joi.string() + .hex() + .required(), + }, + }, + }) + .required() + .allow(null) + ) + .min(0) + .max(1), +}).required() + +module.exports = class KeybasePGP extends KeybaseProfile { + static get apiVersion() { + return '1.0' + } + + static get route() { + return { + base: 'keybase/pgp', + pattern: ':username', + } + } + + static get defaultBadgeData() { + return { + label: 'pgp', + color: 'informational', + } + } + + async handle({ username }) { + const options = { + form: { + usernames: username, + fields: 'public_keys', + }, + } + + const data = await this.fetch({ + schema: keyFingerprintSchema, + options, + }) + + const { user } = this.transform({ data }) + const primaryKey = user.public_keys.primary + + if (primaryKey == null) { + return { + message: 'no key fingerprint found', + color: 'inactive', + } + } + + return this.constructor.render({ fingerprint: primaryKey.key_fingerprint }) + } + + static render({ fingerprint }) { + return { + message: fingerprint.slice(-16).toUpperCase(), + } + } + + static get examples() { + return [ + { + title: 'Keybase PGP', + namedParams: { username: 'skyplabs' }, + staticPreview: this.render({ fingerprint: '1863145FD39EE07E' }), + }, + ] + } +} diff --git a/services/keybase/keybase-pgp.tester.js b/services/keybase/keybase-pgp.tester.js new file mode 100644 index 0000000000000..bea3fbabb7016 --- /dev/null +++ b/services/keybase/keybase-pgp.tester.js @@ -0,0 +1,43 @@ +'use strict' + +const Joi = require('joi') + +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('existing key fingerprint') + .get('/skyplabs.json') + .expectJSONTypes( + Joi.object({ + name: 'pgp', + value: Joi.string() + .hex() + .length(16), + }) + ) + +t.create('unknown username') + .get('/skyplabsssssss.json') + .expectJSONTypes( + Joi.object({ + name: 'pgp', + value: 'profile not found', + }) + ) + +t.create('invalid username') + .get('/s.json') + .expectJSONTypes( + Joi.object({ + name: 'pgp', + value: 'invalid username', + }) + ) + +t.create('missing key fingerprint') + .get('/skyp.json') + .expectJSONTypes( + Joi.object({ + name: 'pgp', + value: 'no key fingerprint found', + }) + ) diff --git a/services/keybase/keybase-profile.js b/services/keybase/keybase-profile.js new file mode 100644 index 0000000000000..5e784a53caaf3 --- /dev/null +++ b/services/keybase/keybase-profile.js @@ -0,0 +1,38 @@ +'use strict' + +const { BaseJsonService } = require('..') +const { NotFound } = require('..') + +module.exports = class KeybaseProfile extends BaseJsonService { + static get apiVersion() { + throw new Error(`apiVersion() is not implemented for ${this.name}`) + } + + static get category() { + return 'social' + } + + async fetch({ schema, options }) { + const apiVersion = this.constructor.apiVersion + // See https://keybase.io/docs/api/1.0/call/user/lookup. + const url = `https://keybase.io/_/api/${apiVersion}/user/lookup.json` + + return this._requestJson({ + url, + schema, + options, + }) + } + + transform({ data }) { + if (data.status.code !== 0) { + throw new NotFound({ prettyMessage: 'invalid username' }) + } + + if (data.them.length === 0 || !data.them[0]) { + throw new NotFound({ prettyMessage: 'profile not found' }) + } + + return { user: data.them[0] } + } +} diff --git a/services/keybase/keybase-xlm.service.js b/services/keybase/keybase-xlm.service.js new file mode 100644 index 0000000000000..3a148aacbc10a --- /dev/null +++ b/services/keybase/keybase-xlm.service.js @@ -0,0 +1,92 @@ +'use strict' + +const KeybaseProfile = require('./keybase-profile') +const Joi = require('joi') +const { nonNegativeInteger } = require('../validators') + +const stellarAddressSchema = Joi.object({ + status: Joi.object({ + code: nonNegativeInteger.required(), + }).required(), + them: Joi.array() + .items( + Joi.object({ + stellar: Joi.object({ + primary: Joi.object({ + account_id: Joi.string(), + }) + .required() + .allow(null), + }).required(), + }) + .required() + .allow(null) + ) + .min(0) + .max(1), +}).required() + +module.exports = class KeybaseXLM extends KeybaseProfile { + static get apiVersion() { + return '1.0' + } + + static get route() { + return { + base: 'keybase/xlm', + pattern: ':username', + } + } + + static get defaultBadgeData() { + return { + label: 'xlm', + color: 'informational', + } + } + + async handle({ username }) { + const options = { + form: { + usernames: username, + fields: 'stellar', + }, + } + + const data = await this.fetch({ + schema: stellarAddressSchema, + options, + }) + + const { user } = this.transform({ data }) + const accountId = user.stellar.primary.account_id + + if (accountId == null) { + return { + message: 'no stellar address found', + color: 'inactive', + } + } + + return this.constructor.render({ address: accountId }) + } + + static render({ address }) { + return { + message: address, + } + } + + static get examples() { + return [ + { + title: 'Keybase XLM', + namedParams: { username: 'skyplabs' }, + staticPreview: this.render({ + address: 'GCGH37DYONEBPGAZGCHJEZZF3J2Q3EFYZBQBE6UJL5QKTULCMEA6MXLA', + }), + keywords: ['stellar'], + }, + ] + } +} diff --git a/services/keybase/keybase-xlm.tester.js b/services/keybase/keybase-xlm.tester.js new file mode 100644 index 0000000000000..8cec16fc7fd56 --- /dev/null +++ b/services/keybase/keybase-xlm.tester.js @@ -0,0 +1,42 @@ +'use strict' + +const Joi = require('joi') +const { withRegex } = require('../test-validators') + +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('existing stellar address') + .get('/skyplabs.json') + .expectJSONTypes( + Joi.object({ + name: 'xlm', + value: withRegex(/^(?!not found$)/), + }) + ) + +t.create('unknown username') + .get('/skyplabsssssss.json') + .expectJSONTypes( + Joi.object({ + name: 'xlm', + value: 'profile not found', + }) + ) + +t.create('invalid username') + .get('/s.json') + .expectJSONTypes( + Joi.object({ + name: 'xlm', + value: 'invalid username', + }) + ) + +t.create('missing stellar address') + .get('/test.json') + .expectJSONTypes( + Joi.object({ + name: 'xlm', + value: 'no stellar address found', + }) + ) diff --git a/services/keybase/keybase-zec.service.js b/services/keybase/keybase-zec.service.js new file mode 100644 index 0000000000000..76be61f4d04b0 --- /dev/null +++ b/services/keybase/keybase-zec.service.js @@ -0,0 +1,94 @@ +'use strict' + +const KeybaseProfile = require('./keybase-profile') +const Joi = require('joi') +const { nonNegativeInteger } = require('../validators') + +const zcachAddressSchema = Joi.object({ + status: Joi.object({ + code: nonNegativeInteger.required(), + }).required(), + them: Joi.array() + .items( + Joi.object({ + cryptocurrency_addresses: Joi.object({ + zcash: Joi.array().items( + Joi.object({ + address: Joi.string().required(), + }).required() + ), + }) + .required() + .allow(null), + }) + .required() + .allow(null) + ) + .min(0) + .max(1), +}).required() + +module.exports = class KeybaseZEC extends KeybaseProfile { + static get apiVersion() { + return '1.0' + } + + static get route() { + return { + base: 'keybase/zec', + pattern: ':username', + } + } + + static get defaultBadgeData() { + return { + label: 'zec', + color: 'informational', + } + } + + async handle({ username }) { + const options = { + form: { + usernames: username, + fields: 'cryptocurrency_addresses', + }, + } + + const data = await this.fetch({ + schema: zcachAddressSchema, + options, + }) + + const { user } = this.transform({ data }) + const zcashAddresses = user.cryptocurrency_addresses.zcash + + if (zcashAddresses == null || zcashAddresses.length === 0) { + return { + message: 'no zcash addresses found', + color: 'inactive', + } + } + + return this.constructor.render({ address: zcashAddresses[0].address }) + } + + static render({ address }) { + return { + message: address, + } + } + + static get examples() { + return [ + { + title: 'Keybase ZEC', + namedParams: { username: 'skyplabs' }, + staticPreview: this.render({ + address: 't1RJDxpBcsgqAotqhepkhLFMv2XpMfvnf1y', + }), + keywords: ['zcash'], + }, + ] + } +} diff --git a/services/keybase/keybase-zec.tester.js b/services/keybase/keybase-zec.tester.js new file mode 100644 index 0000000000000..b63e2c808d0af --- /dev/null +++ b/services/keybase/keybase-zec.tester.js @@ -0,0 +1,42 @@ +'use strict' + +const Joi = require('joi') +const { withRegex } = require('../test-validators') + +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('existing zcash address') + .get('/skyplabs.json') + .expectJSONTypes( + Joi.object({ + name: 'zec', + value: withRegex(/^(?!not found$)/), + }) + ) + +t.create('unknown username') + .get('/skyplabsssssss.json') + .expectJSONTypes( + Joi.object({ + name: 'zec', + value: 'profile not found', + }) + ) + +t.create('invalid username') + .get('/s.json') + .expectJSONTypes( + Joi.object({ + name: 'zec', + value: 'invalid username', + }) + ) + +t.create('missing zcash address') + .get('/test.json') + .expectJSONTypes( + Joi.object({ + name: 'zec', + value: 'no zcash addresses found', + }) + ) diff --git a/services/libscore/libscore.service.js b/services/libscore/libscore.service.js index 4146b70a1cea9..b325c17b18d03 100644 --- a/services/libscore/libscore.service.js +++ b/services/libscore/libscore.service.js @@ -9,4 +9,5 @@ module.exports = deprecatedService({ format: 's/(?:.+)', }, label: 'libscore', + dateAdded: new Date('2018-09-22'), }) diff --git a/services/magnumci/magnumci.service.js b/services/magnumci/magnumci.service.js index a4c9658e6e5ac..070f817dee5af 100644 --- a/services/magnumci/magnumci.service.js +++ b/services/magnumci/magnumci.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// Magnum CI integration - deprecated as of July 2018 module.exports = deprecatedService({ category: 'build', route: { @@ -10,4 +9,5 @@ module.exports = deprecatedService({ format: '(?:[^/]+)(?:/(?:.+))?', }, label: 'magnum ci', + dateAdded: new Date('2018-07-08'), }) diff --git a/services/maintenance/maintenance.service.js b/services/maintenance/maintenance.service.js index c86fe9d22538d..a48aced05d4ab 100644 --- a/services/maintenance/maintenance.service.js +++ b/services/maintenance/maintenance.service.js @@ -1,77 +1,69 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') -const log = require('../../core/server/log') - -// This legacy service should be rewritten to use e.g. BaseJsonService. -// -// Tips for rewriting: -// https://github.com/badges/shields/blob/master/doc/rewriting-services.md -// -// Do not base new services on this code. -module.exports = class Maintenance extends LegacyService { - static get category() { - return 'other' - } +const { NonMemoryCachingBaseService } = require('..') +module.exports = class Maintenance extends NonMemoryCachingBaseService { static get route() { return { base: 'maintenance', - pattern: ':maintained(yes|no)/:year(\\d{4})', + pattern: ':maintained/:year(\\d{4})', + } + } + + static get defaultBadgeData() { + return { + label: 'maintained', + } + } + + async handle({ maintained, year }) { + const now = new Date() + const cy = now.getUTCFullYear() // current year. + const m = now.getUTCMonth() // month. + + if (maintained === 'no') { + return this.constructor.render({ message: `no! (as of ${year})` }) + } else if (cy <= year) { + return this.constructor.render({ message: maintained }) + } else if (parseInt(cy) === parseInt(year) + 1 && parseInt(m) < 3) { + return this.constructor.render({ message: `stale (as of ${cy})` }) + } else { + return this.constructor.render({ message: `no! (as of ${year})` }) + } + } + + static render({ message }) { + if (message.startsWith('yes')) { + return { + message, + color: 'brightgreen', + } + } else if (message.startsWith('no')) { + return { + message, + color: 'red', + } + } else { + return { message } } } + static get category() { + return 'other' + } + static get examples() { return [ { title: 'Maintenance', - pattern: ':maintained/:year', + pattern: ':maintained(yes|no)/:year', namedParams: { maintained: 'yes', year: '2019', }, - staticPreview: { - label: 'yes', - message: '2019', - color: 'brightgreen', - }, + staticPreview: this.render({ message: 'yes' }), keywords: ['maintained'], }, ] } - - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/maintenance\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const status = match[1] // eg, yes - const year = +match[2] // eg, 2016 - const format = match[3] - const badgeData = getBadgeData('maintained', data) - try { - const now = new Date() - const cy = now.getUTCFullYear() // current year. - const m = now.getUTCMonth() // month. - if (status === 'no') { - badgeData.text[1] = `no! (as of ${year})` - badgeData.colorscheme = 'red' - } else if (cy <= year) { - badgeData.text[1] = status - badgeData.colorscheme = 'brightgreen' - } else if (cy === year + 1 && m < 3) { - badgeData.text[1] = `stale (as of ${cy})` - } else { - badgeData.text[1] = `no! (as of ${year})` - badgeData.colorscheme = 'red' - } - sendBadge(format, badgeData) - } catch (e) { - log.error(e.stack) - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - } - }) - ) - } } diff --git a/services/maintenance/maintenance.tester.js b/services/maintenance/maintenance.tester.js index 9c879520ac079..28131acd281a6 100644 --- a/services/maintenance/maintenance.tester.js +++ b/services/maintenance/maintenance.tester.js @@ -1,9 +1,6 @@ 'use strict' -const { ServiceTester } = require('../tester') - -const t = new ServiceTester({ id: 'maintenance', title: 'Maintenance' }) -module.exports = t +const t = (module.exports = require('../tester').createServiceTester()) const currentYear = new Date().getUTCFullYear() @@ -22,3 +19,7 @@ t.create('yes this year (yes)') t.create(`until end of ${currentYear} (yes)`) .get(`/until end of ${currentYear}/${currentYear}.json`) .expectJSON({ name: 'maintained', value: `until end of ${currentYear}` }) + +t.create(`stale last maintained ${currentYear - 1} (yes)`) + .get(`/yes/${currentYear - 1}.json`) + .expectJSON({ name: 'maintained', value: `stale (as of ${currentYear})` }) diff --git a/services/mozilla-observatory/mozilla-observatory.service.js b/services/mozilla-observatory/mozilla-observatory.service.js new file mode 100644 index 0000000000000..54da067470761 --- /dev/null +++ b/services/mozilla-observatory/mozilla-observatory.service.js @@ -0,0 +1,123 @@ +'use strict' + +const { BaseJsonService } = require('..') + +const Joi = require('joi') +const schema = Joi.object({ + state: Joi.string() + .valid('ABORTED', 'FAILED', 'FINISHED', 'PENDING', 'STARTING', 'RUNNING') + .required(), + grade: Joi.alternatives() + .when('state', { + is: 'FINISHED', + then: Joi.string().regex(/^[ABCDEF][+-]?$/), + otherwise: Joi.only(null), + }) + .required(), + score: Joi.alternatives() + .when('state', { + is: 'FINISHED', + then: Joi.number() + .integer() + .min(0) + .max(200), + otherwise: Joi.only(null), + }) + .required(), +}).required() + +const documentation = ` +

+ The Mozilla HTTP Observatory + is a set of tools to analyze your website + and inform you if you are utilizing the many available methods to secure it. +

+

+ By default the scan result is hidden from the public result list. + You can activate the publication of the scan result + by setting publish parameter to true +

+

+ The badge returns a cached site result if the site has been scanned anytime in the previous 24 hours. + If you need to force invalidating the cache, + you can to do it manually through the Mozilla Observatory Website +

+` + +module.exports = class MozillaObservatory extends BaseJsonService { + static get category() { + // TODO: Once created, change to a more appropriate category, + // see https://github.com/badges/shields/pull/2926#issuecomment-460777017 + return 'monitoring' + } + + static get route() { + return { + base: 'mozilla-observatory', + pattern: ':which(grade|grade-score)/:host', + queryParams: ['publish'], + } + } + + static get examples() { + return [ + { + title: 'Mozilla HTTP Observatory Grade', + namedParams: { which: 'grade', host: 'github.com' }, + staticPreview: this.render({ + which: 'grade', + state: 'FINISHED', + grade: 'A+', + score: 115, + }), + keywords: ['scanner', 'security'], + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { + label: 'observatory', + } + } + + async fetch({ host, publish }) { + return this._requestJson({ + schema, + url: `https://http-observatory.security.mozilla.org/api/v1/analyze`, + options: { + method: 'POST', + qs: { host }, + form: { hidden: !publish }, + }, + }) + } + + async handle({ which, host }, { publish }) { + const { state, grade, score } = await this.fetch({ host, publish }) + return this.constructor.render({ which, state, grade, score }) + } + + static render({ which, state, grade, score }) { + if (state !== 'FINISHED') { + return { + message: state.toLowerCase(), + color: 'lightgrey', + } + } + const letter = grade[0].toLowerCase() + const colorMap = { + a: 'brightgreen', + b: 'green', + c: 'yellow', + d: 'orange', + e: 'orange', // Handles legacy grade + f: 'red', + } + return { + message: which === 'grade' ? grade : `${grade} (${score}/100)`, + color: colorMap[letter], + } + } +} diff --git a/services/mozilla-observatory/mozilla-observatory.tester.js b/services/mozilla-observatory/mozilla-observatory.tester.js new file mode 100644 index 0000000000000..81cce57f6e8d2 --- /dev/null +++ b/services/mozilla-observatory/mozilla-observatory.tester.js @@ -0,0 +1,331 @@ +'use strict' + +const Joi = require('joi') +const t = (module.exports = require('../tester').createServiceTester()) + +const validColors = ['brightgreen', 'green', 'yellow', 'orange', 'red'] + +t.create('request on observatory.mozilla.org') + .get('/grade-score/observatory.mozilla.org.json?style=_shields_test') + .expectJSONTypes( + Joi.object().keys({ + name: 'observatory', + value: Joi.string().regex(/^[ABCDEF][+-]? \([0-9]{1,3}\/100\)$/), + color: Joi.string() + .valid(validColors) + .required(), + }) + ) + +t.create('request on observatory.mozilla.org with inclusion in public results') + .get( + '/grade-score/observatory.mozilla.org.json?publish=true&style=_shields_test' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'observatory', + value: Joi.string().regex(/^[ABCDEF][+-]? \([0-9]{1,3}\/100\)$/), + color: Joi.string() + .valid(validColors) + .required(), + }) + ) + +t.create('grade without score (mock)') + .get('/grade/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'A', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'A', + color: 'brightgreen', + }) + +t.create('grade A with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'A', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'A (115/100)', + color: 'brightgreen', + }) + +t.create('grade A+ with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'A+', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'A+ (115/100)', + color: 'brightgreen', + }) + +t.create('grade A- with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'A-', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'A- (115/100)', + color: 'brightgreen', + }) + +t.create('grade B with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'B', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'B (115/100)', + color: 'green', + }) + +t.create('grade B+ with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'B+', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'B+ (115/100)', + color: 'green', + }) + +t.create('grade B- with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'B-', score: 115 }) + ) + .expectJSON({ + name: 'observatory', + value: 'B- (115/100)', + color: 'green', + }) + +t.create('grade C with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'C', score: 80 }) + ) + .expectJSON({ + name: 'observatory', + value: 'C (80/100)', + color: 'yellow', + }) + +t.create('grade C+ with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'C+', score: 80 }) + ) + .expectJSON({ + name: 'observatory', + value: 'C+ (80/100)', + color: 'yellow', + }) + +t.create('grade C- with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'C-', score: 80 }) + ) + .expectJSON({ + name: 'observatory', + value: 'C- (80/100)', + color: 'yellow', + }) + +t.create('grade D with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'D', score: 15 }) + ) + .expectJSON({ + name: 'observatory', + value: 'D (15/100)', + color: 'orange', + }) + +t.create('grade D+ with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'D+', score: 15 }) + ) + .expectJSON({ + name: 'observatory', + value: 'D+ (15/100)', + color: 'orange', + }) + +t.create('grade D- with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'D-', score: 15 }) + ) + .expectJSON({ + name: 'observatory', + value: 'D- (15/100)', + color: 'orange', + }) + +t.create('grade E with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'E', score: 15 }) + ) + .expectJSON({ + name: 'observatory', + value: 'E (15/100)', + color: 'orange', + }) + +t.create('grade E+ with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'E+', score: 15 }) + ) + .expectJSON({ + name: 'observatory', + value: 'E+ (15/100)', + color: 'orange', + }) + +t.create('grade E- with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'E-', score: 15 }) + ) + .expectJSON({ + name: 'observatory', + value: 'E- (15/100)', + color: 'orange', + }) + +t.create('grade F with score (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FINISHED', grade: 'F', score: 0 }) + ) + .expectJSON({ + name: 'observatory', + value: 'F (0/100)', + color: 'red', + }) + +t.create('aborted (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'ABORTED', grade: null, score: null }) + ) + .expectJSON({ + name: 'observatory', + value: 'aborted', + color: 'lightgrey', + }) + +t.create('failed (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'FAILED', grade: null, score: null }) + ) + .expectJSON({ + name: 'observatory', + value: 'failed', + color: 'lightgrey', + }) + +t.create('pending (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'PENDING', grade: null, score: null }) + ) + .expectJSON({ + name: 'observatory', + value: 'pending', + color: 'lightgrey', + }) + +t.create('starting (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'STARTING', grade: null, score: null }) + ) + .expectJSON({ + name: 'observatory', + value: 'starting', + color: 'lightgrey', + }) + +t.create('running (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'RUNNING', grade: null, score: null }) + ) + .expectJSON({ + name: 'observatory', + value: 'running', + color: 'lightgrey', + }) + +t.create('invalid response with grade and score but not finished (mock)') + .get('/grade-score/foo.bar.json?style=_shields_test') + .intercept(nock => + nock('https://http-observatory.security.mozilla.org') + .post('/api/v1/analyze?host=foo.bar') + .reply(200, { state: 'RUNNING', grade: 'A+', score: 135 }) + ) + .expectJSON({ + name: 'observatory', + value: 'invalid response data', + color: 'lightgrey', + }) diff --git a/services/nsp/nsp.service.js b/services/nsp/nsp.service.js index c69eb6d33b8b5..75316c8fcc79b 100644 --- a/services/nsp/nsp.service.js +++ b/services/nsp/nsp.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// nsp integration - deprecated as of December 2018. module.exports = deprecatedService({ route: { base: 'nsp/npm', @@ -10,4 +9,5 @@ module.exports = deprecatedService({ }, label: 'nsp', category: 'other', + dateAdded: new Date('2018-12-13'), }) diff --git a/services/packagist/packagist-downloads.service.js b/services/packagist/packagist-downloads.service.js index a67cf09c1fd19..6c61a763e50b0 100644 --- a/services/packagist/packagist-downloads.service.js +++ b/services/packagist/packagist-downloads.service.js @@ -7,8 +7,6 @@ const { downloadCount: downloadCountColor, } = require('../../lib/color-formatters') -const keywords = ['PHP'] - // This legacy service should be rewritten to use e.g. BaseJsonService. // // Tips for rewriting: @@ -31,8 +29,8 @@ module.exports = class PackagistDownloads extends LegacyService { return [ { title: 'Packagist', - pattern: 'dm/:user/:repo', namedParams: { + interval: 'dm', user: 'doctrine', repo: 'orm', }, @@ -41,35 +39,7 @@ module.exports = class PackagistDownloads extends LegacyService { message: '1M/month', color: 'brightgreen', }, - keywords, - }, - { - title: 'Packagist', - pattern: 'dd/:user/:repo', - namedParams: { - user: 'doctrine', - repo: 'orm', - }, - staticPreview: { - label: 'downloads', - message: '49k/day', - color: 'brightgreen', - }, - keywords, - }, - { - title: 'Packagist', - pattern: 'dt/:user/:repo', - namedParams: { - user: 'doctrine', - repo: 'orm', - }, - staticPreview: { - label: 'downloads', - message: '45M', - color: 'brightgreen', - }, - keywords, + keywords: ['PHP'], }, ] } diff --git a/services/pypi/pypi-downloads.service.js b/services/pypi/pypi-downloads.service.js index 0bae85d40ba93..1ff9f69fd6f34 100644 --- a/services/pypi/pypi-downloads.service.js +++ b/services/pypi/pypi-downloads.service.js @@ -8,7 +8,7 @@ const { nonNegativeInteger } = require('../validators') const keywords = ['python'] -const pypiStatsSchema = Joi.object({ +const schema = Joi.object({ data: Joi.object({ last_day: nonNegativeInteger, last_week: nonNegativeInteger, @@ -34,11 +34,10 @@ const periodMap = { // this badge uses PyPI Stats instead of the PyPI API // so it doesn't extend PypiBase module.exports = class PypiDownloads extends BaseJsonService { - async fetch({ pkg }) { - const url = `https://pypistats.org/api/packages/${pkg.toLowerCase()}/recent` + async fetch({ packageName }) { return this._requestJson({ - url, - schema: pypiStatsSchema, + url: `https://pypistats.org/api/packages/${packageName.toLowerCase()}/recent`, + schema, errorMessages: { 404: 'package not found' }, }) } @@ -50,8 +49,8 @@ module.exports = class PypiDownloads extends BaseJsonService { } } - async handle({ period, pkg }) { - const json = await this.fetch({ pkg }) + async handle({ period, packageName }) { + const json = await this.fetch({ packageName }) return this.constructor.render({ period, downloads: json.data[periodMap[period].api_field], @@ -69,7 +68,7 @@ module.exports = class PypiDownloads extends BaseJsonService { static get route() { return { base: 'pypi', - pattern: ':period(dd|dw|dm)/:pkg', + pattern: ':period(dd|dw|dm)/:packageName', } } @@ -77,31 +76,13 @@ module.exports = class PypiDownloads extends BaseJsonService { return [ { title: 'PyPI - Downloads', - pattern: 'dd/:packageName', namedParams: { + period: 'dd', packageName: 'Django', }, staticPreview: this.render({ period: 'dd', downloads: 14000 }), keywords, }, - { - title: 'PyPI - Downloads', - pattern: 'dw/:packageName', - namedParams: { - packageName: 'Django', - }, - staticPreview: this.render({ period: 'dw', downloads: 250000 }), - keywords, - }, - { - title: 'PyPI - Downloads', - pattern: 'dm/:packageName', - namedParams: { - packageName: 'Django', - }, - staticPreview: this.render({ period: 'dm', downloads: 1070100 }), - keywords, - }, ] } } diff --git a/services/pypi/pypi-pyversions.service.js b/services/pypi/pypi-pyversions.service.js index 611cbcbc152bd..ff559c4b70015 100644 --- a/services/pypi/pypi-pyversions.service.js +++ b/services/pypi/pypi-pyversions.service.js @@ -58,6 +58,15 @@ module.exports = class PypiPythonVersions extends PypiBase { packageData, /^Programming Language :: Python :: ([\d.]+)$/ ) + // If no versions are found yet, check "X :: Only" as a fallback. + if (versions.length === 0) { + versions.push( + ...parseClassifiers( + packageData, + /^Programming Language :: Python :: (\d+) :: Only$/ + ) + ) + } return this.constructor.render({ versions }) } diff --git a/services/pypi/pypi.tester.js b/services/pypi/pypi.tester.js index 00d1400069822..93b1d2a09e515 100644 --- a/services/pypi/pypi.tester.js +++ b/services/pypi/pypi.tester.js @@ -215,6 +215,14 @@ t.create('python versions (valid, no package version specified)') }) ) +t.create('python versions ("Only" and others)') + .get('/pyversions/uvloop/0.12.1.json') + .expectJSON({ name: 'python', value: '3.5 | 3.6 | 3.7' }) + +t.create('python versions ("Only" only)') + .get('/pyversions/hashpipe/0.9.1.json') + .expectJSON({ name: 'python', value: '3' }) + t.create('python versions (no versions specified)') .get('/pyversions/pyshp/1.2.12.json') .expectJSON({ name: 'python', value: 'missing' }) diff --git a/services/snap-ci/snap-ci.service.js b/services/snap-ci/snap-ci.service.js index e5c2efb429cba..6d6fdf1a8c339 100644 --- a/services/snap-ci/snap-ci.service.js +++ b/services/snap-ci/snap-ci.service.js @@ -8,4 +8,5 @@ module.exports = deprecatedService({ format: 'snap(?:-ci?)/(?:[^/]+/[^/]+)(?:/(?:.+))', }, label: 'snap ci', + dateAdded: new Date('2018-01-23'), }) diff --git a/services/sourceforge/sourceforge.service.js b/services/sourceforge/sourceforge.service.js index 5c86eb932a543..f9359c0b1cb0a 100644 --- a/services/sourceforge/sourceforge.service.js +++ b/services/sourceforge/sourceforge.service.js @@ -33,8 +33,9 @@ module.exports = class Sourceforge extends LegacyService { return [ { title: 'SourceForge', - pattern: 'dm/:project', + pattern: ':interval(dt|dm|dw|dd)/:project', namedParams: { + interval: 'dm', project: 'sevenzip', }, staticPreview: { @@ -45,50 +46,15 @@ module.exports = class Sourceforge extends LegacyService { }, { title: 'SourceForge', - pattern: 'dw/:project', - namedParams: { - project: 'sevenzip', - }, - staticPreview: { - label: 'downloads', - message: '52k/week', - color: 'brightgreen', - }, - }, - { - title: 'SourceForge', - pattern: 'dd/:project', - namedParams: { - project: 'sevenzip', - }, - staticPreview: { - label: 'downloads', - message: '6k/day', - color: 'brightgreen', - }, - }, - { - title: 'SourceForge', - pattern: 'dt/:project', - namedParams: { - project: 'sevenzip', - }, - staticPreview: { - label: 'downloads', - message: '416M', - color: 'brightgreen', - }, - }, - { - title: 'SourceForge', - pattern: 'dt/:project/:folder', + pattern: ':interval(dt|dm|dw|dd)/:project/:folder', namedParams: { + interval: 'dm', project: 'arianne', folder: 'stendhal', }, staticPreview: { label: 'downloads', - message: '177k', + message: '550/month', color: 'brightgreen', }, }, diff --git a/services/versioneye/versioneye.service.js b/services/versioneye/versioneye.service.js index 63bb55b9dded7..dd6d3aa7743e1 100644 --- a/services/versioneye/versioneye.service.js +++ b/services/versioneye/versioneye.service.js @@ -2,7 +2,6 @@ const { deprecatedService } = require('..') -// VersionEye integration - deprecated as of August 2018. module.exports = deprecatedService({ category: 'downloads', route: { @@ -10,4 +9,5 @@ module.exports = deprecatedService({ format: 'd/(?:.+)', }, label: 'versioneye', + dateAdded: new Date('2018-08-20'), }) diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js index adaa5a4eb0451..dae36e02df072 100644 --- a/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js +++ b/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js @@ -4,6 +4,13 @@ const VisualStudioMarketplaceBase = require('./visual-studio-marketplace-base') const { metric } = require('../../lib/text-formatters') const { downloadCount } = require('../../lib/color-formatters') +const documentation = ` +

+ This badge can show total installs, installs for Azure DevOps Services, + or on-premises installs for Azure DevOps Server. +

+` + // This service exists separately from the other Marketplace downloads badges (in ./visual-studio-marketplace-downloads.js) // due differences in how the Marketplace tracks metrics for Azure DevOps extensions vs. other extension types. // See https://github.com/badges/shields/pull/2748 for more information on the discussion and decision. @@ -35,29 +42,14 @@ module.exports = class VisualStudioMarketplaceAzureDevOpsInstalls extends Visual static get examples() { return [ { - title: - 'Visual Studio Marketplace - Azure DevOps Extension (Total Installs)', - pattern: 'total/:extensionId', - namedParams: { extensionId: 'swellaby.mirror-git-repository' }, + title: 'Visual Studio Marketplace Installs - Azure DevOps Extension', + namedParams: { + measure: 'total', + extensionId: 'swellaby.mirror-git-repository', + }, staticPreview: this.render({ count: 651 }), keywords: this.keywords, - }, - { - title: - 'Visual Studio Marketplace - Azure DevOps Extension (Services Installs)', - pattern: 'services/:extensionId', - namedParams: { extensionId: 'swellaby.mirror-git-repository' }, - staticPreview: this.render({ count: 496 }), - keywords: this.keywords, - }, - - { - title: - 'Visual Studio Marketplace - Azure DevOps Extension (OnPrem Installs)', - pattern: 'onprem/:extensionId', - namedParams: { extensionId: 'swellaby.mirror-git-repository' }, - staticPreview: this.render({ count: 155 }), - keywords: this.keywords, + documentation, }, ] } diff --git a/services/wordpress/wordpress-downloads.service.js b/services/wordpress/wordpress-downloads.service.js index 2cfb340635a2f..20063f356fa6b 100644 --- a/services/wordpress/wordpress-downloads.service.js +++ b/services/wordpress/wordpress-downloads.service.js @@ -176,8 +176,8 @@ function DownloadsForInterval(interval) { const downloads = Object.values(json).reduce( (a, b) => parseInt(a) + parseInt(b) ) - // This check is for non-existant and brand-new plugins both having new stats. - // Non-Existant plugins results are the same as a brandspanking new plugin with no downloads. + // This check is for non-existent and brand-new plugins both having new stats. + // Non-Existent plugins results are the same as a brandspanking new plugin with no downloads. if (downloads <= 0 && size <= 1) { throw new NotFound({ prettyMessage: 'plugin not found or too new' }) }