Compare commits
65 Commits
server-202
...
server-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a662e36bf4 | ||
|
|
4c52b0192a | ||
|
|
bab3e15c31 | ||
|
|
ee83590942 | ||
|
|
4347a1ade0 | ||
|
|
df6d1d2c37 | ||
|
|
27951e920b | ||
|
|
4f1fb4d8c6 | ||
|
|
9c0d1e86e6 | ||
|
|
8d8343641d | ||
|
|
3f821ac31a | ||
|
|
dcffa9edfa | ||
|
|
b9b3d40740 | ||
|
|
244aa214a8 | ||
|
|
3716391224 | ||
|
|
bb84d4115d | ||
|
|
5ffe050f15 | ||
|
|
59e997dcbd | ||
|
|
3d2b3e5482 | ||
|
|
0db77ba029 | ||
|
|
5e11168b5a | ||
|
|
5aa37e1a1b | ||
|
|
b77a91bf5b | ||
|
|
5f14c54833 | ||
|
|
06a22f8220 | ||
|
|
ce88681620 | ||
|
|
50af3d1343 | ||
|
|
0776c433f9 | ||
|
|
8399d881f5 | ||
|
|
46dfbb7d03 | ||
|
|
4098e37e5c | ||
|
|
7bc3bd4667 | ||
|
|
552ceb2acd | ||
|
|
9351738c05 | ||
|
|
570ab89529 | ||
|
|
f1dc5b27f1 | ||
|
|
e0510ca761 | ||
|
|
99fc7c1a86 | ||
|
|
9a91499d37 | ||
|
|
2094e25fff | ||
|
|
2d3b845adc | ||
|
|
b824b2fb86 | ||
|
|
2b5d4f6286 | ||
|
|
d14ec7cc9a | ||
|
|
f47a87a038 | ||
|
|
faca9b49d8 | ||
|
|
946ff38450 | ||
|
|
d1ca703a03 | ||
|
|
50a962d79f | ||
|
|
9d404bc2b0 | ||
|
|
fd7eddc7bb | ||
|
|
2359eb278b | ||
|
|
b2f224c3a5 | ||
|
|
abb049efec | ||
|
|
da8afd720c | ||
|
|
f59f7ec33b | ||
|
|
0c0505cc32 | ||
|
|
abc463e35f | ||
|
|
503c040e6e | ||
|
|
3dfbdcefc2 | ||
|
|
e10ea2222d | ||
|
|
4082d46810 | ||
|
|
2a29a1c881 | ||
|
|
0148a30fe5 | ||
|
|
ddc9ee5394 |
@@ -4,11 +4,11 @@ extends:
|
||||
- standard-react
|
||||
- plugin:@typescript-eslint/recommended
|
||||
- prettier
|
||||
- prettier/@typescript-eslint
|
||||
- prettier/standard
|
||||
- prettier/react
|
||||
- eslint:recommended
|
||||
|
||||
globals:
|
||||
JSX: 'readonly'
|
||||
|
||||
parserOptions:
|
||||
# Override eslint-config-standard, which incorrectly sets this to "module",
|
||||
# though that setting is only for ES6 modules, not CommonJS modules.
|
||||
@@ -43,6 +43,7 @@ overrides:
|
||||
es6: true
|
||||
rules:
|
||||
no-console: 'off'
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off'
|
||||
|
||||
- files:
|
||||
- '**/*.@(ts|tsx)'
|
||||
@@ -58,6 +59,7 @@ overrides:
|
||||
'@typescript-eslint/no-object-literal-type-assertion': 'off'
|
||||
'@typescript-eslint/no-explicit-any': 'error'
|
||||
'@typescript-eslint/ban-ts-ignore': 'off'
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off'
|
||||
|
||||
- files:
|
||||
- core/**/*.ts
|
||||
@@ -125,6 +127,9 @@ rules:
|
||||
|
||||
'@typescript-eslint/no-var-requires': 'off'
|
||||
|
||||
'@typescript-eslint/no-use-before-define': 'error'
|
||||
no-use-before-define: 'off'
|
||||
|
||||
# These should be disabled by eslint-config-prettier, but are not.
|
||||
no-extra-semi: 'off'
|
||||
|
||||
@@ -141,6 +146,20 @@ rules:
|
||||
new-cap: ['error', { 'capIsNew': true }]
|
||||
import/order: ['error', { 'newlines-between': 'never' }]
|
||||
|
||||
# Account for destructuring responses from upstream services,
|
||||
# many of which do not follow camelcase
|
||||
# Based on original rule configuration from eslint-config-standard
|
||||
camelcase:
|
||||
[
|
||||
'error',
|
||||
{
|
||||
ignoreDestructuring: true,
|
||||
properties: 'never',
|
||||
ignoreGlobals: true,
|
||||
allow: ['^UNSAFE_'],
|
||||
},
|
||||
]
|
||||
|
||||
# Chai friendly.
|
||||
no-unused-expressions: 'off'
|
||||
chai-friendly/no-unused-expressions: 'error'
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -4,6 +4,16 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2021-04-01
|
||||
|
||||
- Use NPM packages to provide fonts instead of Google Fonts [#6274](https://github.com/badges/shields/issues/6274)
|
||||
- Prevent duplication of parameters in badge examples [#6272](https://github.com/badges/shields/issues/6272)
|
||||
- Add docs for all types of releases [#6210](https://github.com/badges/shields/issues/6210)
|
||||
- refresh self-hosting docs [#6273](https://github.com/badges/shields/issues/6273)
|
||||
- allow missing 'goal' key in [liberapay] badges [#6258](https://github.com/badges/shields/issues/6258)
|
||||
- use got to push influx metrics [#6257](https://github.com/badges/shields/issues/6257)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-03-01
|
||||
|
||||
- ensure redirect target path is correctly encoded [#6229](https://github.com/badges/shields/issues/6229)
|
||||
|
||||
4
Procfile
4
Procfile
@@ -1,5 +1 @@
|
||||
web: npm run start:server:prod
|
||||
scale4: npm run heroku:scale 4
|
||||
scale5: npm run heroku:scale 5
|
||||
scale6: npm run heroku:scale 6
|
||||
scale7: npm run heroku:scale 7
|
||||
|
||||
@@ -35,7 +35,7 @@ and legible badges in SVG and raster format, which can easily be included in
|
||||
GitHub readmes or any other web page. The service supports dozens of
|
||||
continuous integration services, package registries, distributions, app
|
||||
stores, social networks, code coverage services, and code analysis services.
|
||||
Every month it serves over 470 million images.
|
||||
Every month it serves over 770 million images.
|
||||
|
||||
This repo hosts:
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
'use strict'
|
||||
const os = require('os')
|
||||
const { promisify } = require('util')
|
||||
const { post } = require('request')
|
||||
const postAsync = promisify(post)
|
||||
const got = require('got')
|
||||
const generateInstanceId = require('./instance-id-generator')
|
||||
const { promClientJsonToInfluxV2 } = require('./metrics/format-converters')
|
||||
const log = require('./log')
|
||||
@@ -15,21 +13,19 @@ module.exports = class InfluxMetrics {
|
||||
}
|
||||
|
||||
async sendMetrics() {
|
||||
const auth = {
|
||||
user: this._config.username,
|
||||
pass: this._config.password,
|
||||
}
|
||||
const request = {
|
||||
uri: this._config.url,
|
||||
url: this._config.url,
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: await this.metrics(),
|
||||
timeout: this._config.timeoutMillseconds,
|
||||
auth,
|
||||
username: this._config.username,
|
||||
password: this._config.password,
|
||||
throwHttpErrors: false,
|
||||
}
|
||||
|
||||
let response
|
||||
try {
|
||||
response = await postAsync(request)
|
||||
response = await got.post(request)
|
||||
} catch (error) {
|
||||
log.error(
|
||||
new Error(`Cannot push metrics. Cause: ${error.name}: ${error.message}`)
|
||||
@@ -38,7 +34,7 @@ module.exports = class InfluxMetrics {
|
||||
if (response && response.statusCode >= 300) {
|
||||
log.error(
|
||||
new Error(
|
||||
`Cannot push metrics. ${response.request.href} responded with status code ${response.statusCode}`
|
||||
`Cannot push metrics. ${request.url} responded with status code ${response.statusCode}`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ describe('Influx metrics', function () {
|
||||
.and(
|
||||
sinon.match.has(
|
||||
'message',
|
||||
'Cannot push metrics. Cause: NetConnectNotAllowedError: Nock: Disallowed net connect for "shields-metrics.io:80/metrics"'
|
||||
'Cannot push metrics. Cause: RequestError: Nock: Disallowed net connect for "shields-metrics.io:80/metrics"'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -62,4 +62,13 @@ describe('Main page', function () {
|
||||
|
||||
cy.get(`img[src='${backendUrl}/github/issues/badges/shields?color=orange']`)
|
||||
})
|
||||
|
||||
it('Do not duplicate example parameters', function () {
|
||||
cy.visit('/category/social')
|
||||
|
||||
cy.contains('GitHub Sponsors').click()
|
||||
cy.get('[name="style"]').should($style => {
|
||||
expect($style).to.have.length(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ and learn about the [Github workflow](http://try.github.io/).
|
||||
|
||||
#### Node, NPM
|
||||
|
||||
Node 12 or later is required. If you don't already have them,
|
||||
Node >=12 and NPM >=7 is required. If you don't already have them,
|
||||
install node and npm: https://nodejs.org/en/download/
|
||||
|
||||
### Setup a dev install
|
||||
|
||||
57
doc/releases.md
Normal file
57
doc/releases.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Releases
|
||||
|
||||
Shields is a community project that is stewarded by a handful of core maintainers who contribute on a volunteer basis. We do our best to maintain the availability and reliability of the service, and enhance and improve the project overall. However, if you've spotted something wrong or would like to see a specific feature implemented, please consider helping us resolve it by submitting a pull request. All community contributions, even documentation improvements, are welcome!
|
||||
|
||||
https://github.com/badges/shields is a monorepo and hosts the Shields frontend and server code as well as the [badge-maker][npm package] NPM library (and the [badge design specification](https://github.com/badges/shields/tree/master/spec)). The packaging and release processes for these items is described in the respective sections below.
|
||||
|
||||
## badge-maker package
|
||||
|
||||
We follow [Semantic Versioning](https://semver.org/) for the badge-maker package, and publish git Tags using the form `X.Y.Z`, and all such tags of this form are badge-maker releases (e.g. [3.3.0](https://github.com/badges/shields/releases/tag/3.3.0))
|
||||
|
||||
The [badge-maker][npm package] is also published to the npm.js registry.
|
||||
|
||||
Releases of badge-maker are done as and when needed, and not on any predetermined interval.
|
||||
|
||||
## Shields.io service
|
||||
|
||||
The [Shields.io Service][shields.io] consists of the frontend [https://shields.io][shields.io] website which allows users to browse and discover available badges, as well as the badge server backend that serves up requested badges (e.g. https://img.shields.io/badge/badges-rock-blue).
|
||||
|
||||
This is the core, free, anonymous service available for anyone to use and which serves up hundreds of millions of badges every month!
|
||||
|
||||
We do not have a fixed schedule for deploying updates to the Shields.io production environment, but we typically deploy the latest version at least once per week.
|
||||
|
||||
We do not have any guaranteed SLA for the Shields.io service, but we do have monitoring and observability capabilities in place and our [Operations team](https://github.com/badges/shields#project-leaders) will review and address any availability, performance, etc. issues on a best-effort basis.
|
||||
|
||||
More information about the production environment can be found [here][production hosting]
|
||||
|
||||
## Shields server
|
||||
|
||||
Some users may wish to host their own instance of Shields so we also make it possible for users to do so. This is particularly useful if you want to serve badges for resources that require authentication or are not exposed to the internet (e.g: inside a corporate network). A variety of options are available either by installing from source or using a docker image.
|
||||
|
||||
### Important information
|
||||
|
||||
This Shields server is the exact same codebase that powers the main Shields.io service; we do not have a separate self-hosted version of Shields. This means that the server codebase is geared towards the development, maintenance, and operation of the Shields.io service so there are a few things to note:
|
||||
|
||||
- We often have to reject or de-prioritize feature requests that are only applicable for self-hosting and/or which may be problematic for the core Shields.io offering (e.g. requiring authentication on the badge server)
|
||||
- We do not accept new badges that cannot be utilized with Shields.io (e.g. those that would always require user-specific authorization)
|
||||
- We do not do any additional testing nor validation of self-hosted instances (e.g. we test Shields.io with Jira Cloud badges, but we don't test a self-hosted Shields server against a self-hosted Jira server instance with auth)
|
||||
- We're happy to try to offer some tips and guidance to self-hosters when and where we can, but we don't have the ability to troubleshoot/debug/support/etc. self-hosted Shields servers
|
||||
- Note that Paul, a core team member, offers some paid support options for those interested in self-hosting support. Contact him at  for more information.
|
||||
|
||||
We are happy to document and collate any self-hosting patterns/approaches that others have found to be helpful to be able to share with the broader community. If you have any self-hosting material you'd like to share, please feel free to get in contact with us (or even open a PR) and we'll work with you to get that incorporated for the benefit of other self-hosters!
|
||||
|
||||
### Server Releases
|
||||
|
||||
We try to make it as easy as possible for users to self-host a Shields server so we publish a few releases of the server. Please be sure to refer to the [self hosting guide][self hosting] for a detailed walk through on how to spin up a server.
|
||||
|
||||
- The server uses [Calendar Versioning](https://calver.org/). Tags of the form `server-YYYY-MM-DD` are server releases (these are the tags that are relevant to self-hosting users, e.g. [server-2021-02-01](https://github.com/badges/shields/releases/tag/server-2021-02-01)).
|
||||
- As well as [tags on GitHub](https://github.com/badges/shields/tags), server releases are also pushed to [DockerHub](https://registry.hub.docker.com/r/shieldsio/shields/tags). See the self-hosting section on [Docker](https://github.com/badges/shields/blob/master/doc/self-hosting.md#Docker) for more details.
|
||||
- We publish release notes for server releases in the [CHANGELOG](https://github.com/badges/shields/blob/master/CHANGELOG.md). There may occasionally be non-backwards compatible changes to be aware of.
|
||||
- We will normally put out one release per month. If there is a security patch or major bugfix affecting self-hosting users, we may put out an out-of-sequence release.
|
||||
- Releases are just a snapshot in time. We advise always tracking the latest release to ensure you are up-to-date with the latest bug fixes and security updates. There are no 'patch' releases - we don't backport fixes to old releases. Tagged versions just provide a convenient way to apply upgrades in a controlled way or roll back to an older version if necessary and communicate about versions.
|
||||
- You can stay on the bleeding edge by tracking the `master` branch for source installs or the `next` tag on DockerHub.
|
||||
|
||||
[shields.io]: https://shields.io
|
||||
[npm package]: https://www.npmjs.com/package/badge-maker
|
||||
[production hosting]: https://github.com/badges/shields/blob/master/doc/production-hosting.md
|
||||
[self hosting]: https://github.com/badges/shields/blob/master/doc/self-hosting.md
|
||||
@@ -1,6 +1,8 @@
|
||||
# Hosting your own Shields server
|
||||
|
||||
## Installation
|
||||
This document describes how to host your own shields server either from source or using a docker image. See the docs on [releases](https://github.com/badges/shields/blob/master/doc/releases.md#shields-server) for info on how we version the server and how to choose a release.
|
||||
|
||||
## Installing from Source
|
||||
|
||||
You will need Node 12 or later, which you can install using a
|
||||
[package manager][].
|
||||
@@ -14,18 +16,19 @@ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -; sudo apt-get in
|
||||
```sh
|
||||
git clone https://github.com/badges/shields.git
|
||||
cd shields
|
||||
git checkout $(git tag | grep server | tail -n 1) # checkout the latest tag
|
||||
npm ci # You may need sudo for this.
|
||||
```
|
||||
|
||||
[package manager]: https://nodejs.org/en/download/package-manager/
|
||||
|
||||
## Build the frontend
|
||||
### Build the frontend
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Start the server
|
||||
### Start the server
|
||||
|
||||
```sh
|
||||
sudo node server
|
||||
@@ -44,7 +47,7 @@ The root gets redirected to https://shields.io.
|
||||
|
||||
For testing purposes, you can go to `http://localhost/`.
|
||||
|
||||
## Heroku
|
||||
### Deploying to Heroku
|
||||
|
||||
Once you have installed the [Heroku CLI][]
|
||||
|
||||
@@ -57,9 +60,32 @@ heroku open
|
||||
|
||||
[heroku cli]: https://devcenter.heroku.com/articles/heroku-cli
|
||||
|
||||
### Deploying to Zeit Vercel
|
||||
|
||||
To deploy using Zeit Vercel:
|
||||
|
||||
```console
|
||||
npm run build # Not sure why, but this needs to be run before deploying.
|
||||
vercel
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
You can build and run the server locally using Docker. First build an image:
|
||||
### DockerHub
|
||||
|
||||
We publish images to DockerHub at https://registry.hub.docker.com/r/shieldsio/shields
|
||||
|
||||
The `next` tag is the latest build from `master`, or tagged releases are available
|
||||
https://registry.hub.docker.com/r/shieldsio/shields/tags
|
||||
|
||||
```console
|
||||
$ docker pull shieldsio/shields:next
|
||||
$ docker run shieldsio/shields:next
|
||||
```
|
||||
|
||||
### Building Docker Image Locally
|
||||
|
||||
Alternatively, you can build and run the server locally using Docker. First build an image:
|
||||
|
||||
```console
|
||||
$ docker build -t shields .
|
||||
@@ -114,15 +140,6 @@ preconfigured raster server.
|
||||
[raster server]: https://github.com/badges/svg-to-image-proxy
|
||||
[micro]: https://github.com/zeit/micro
|
||||
|
||||
## Zeit Now
|
||||
|
||||
To deploy using Zeit Now:
|
||||
|
||||
```console
|
||||
npm run build # Not sure why, but this needs to be run before deploying.
|
||||
now
|
||||
```
|
||||
|
||||
## Persistence
|
||||
|
||||
To enable Redis-backed GitHub token persistence, point `REDIS_URL` to your
|
||||
@@ -195,7 +212,7 @@ private:
|
||||
sudo node server
|
||||
```
|
||||
|
||||
### Prometheus
|
||||
## Prometheus
|
||||
|
||||
Shields uses [prom-client](https://github.com/siimon/prom-client) to provide [default metrics](https://prometheus.io/docs/instrumenting/writing_clientlibs/#standard-and-runtime-collectors). These metrics are disabled by default.
|
||||
You can enable them by `METRICS_PROMETHEUS_ENABLED` and `METRICS_PROMETHEUS_ENDPOINT_ENABLED` environment variables.
|
||||
@@ -206,8 +223,8 @@ METRICS_PROMETHEUS_ENABLED=true METRICS_PROMETHEUS_ENDPOINT_ENABLED=true npm sta
|
||||
|
||||
Metrics are available at `/metrics` resource.
|
||||
|
||||
### Cloudflare
|
||||
## Cloudflare
|
||||
|
||||
Shields uses Cloudflare as a downstream CDN. If your installation does the same,
|
||||
Shields.io uses Cloudflare as a downstream CDN. If your installation does the same,
|
||||
you can configure your server to only accept requests coming from Cloudflare's IPs.
|
||||
Set `public.requireCloudflare: true`.
|
||||
|
||||
@@ -241,15 +241,21 @@ export default function QueryStringBuilder({
|
||||
const [queryParams, setQueryParams] = useState(() =>
|
||||
// For each of the custom query params defined in `exampleParams`,
|
||||
// create empty values in `queryParams`.
|
||||
Object.entries(exampleParams).reduce((accum, [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'
|
||||
accum[name] = isStringParam ? '' : true
|
||||
return accum
|
||||
}, {} as { [k: string]: string | boolean })
|
||||
Object.entries(exampleParams)
|
||||
.filter(
|
||||
// If the example defines a value for one of the standard supported
|
||||
// options, do not duplicate the corresponding parameter.
|
||||
([name]) => !supportedBadgeOptions.some(option => name === option.name)
|
||||
)
|
||||
.reduce((accum, [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'
|
||||
accum[name] = isStringParam ? '' : true
|
||||
return accum
|
||||
}, {} as { [k: string]: string | boolean })
|
||||
)
|
||||
// For each of the standard badge options, create empty values in
|
||||
// `badgeOptions`. When `initialStyle` has been provided, use it.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import styled from 'styled-components'
|
||||
// FIXME: is this needed?
|
||||
// @ts-ingnore
|
||||
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
|
||||
import { getBaseUrl } from '../../constants'
|
||||
import Meta from '../meta'
|
||||
// @ts-ignore
|
||||
// ts-expect-error: because reasons?
|
||||
import Header from '../header'
|
||||
import { H3, Badge } from '../common'
|
||||
|
||||
@@ -62,11 +63,6 @@ function Badges({
|
||||
)
|
||||
}
|
||||
|
||||
interface StyleExamples {
|
||||
title: string
|
||||
badges: BadgeData[]
|
||||
}
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Basic examples',
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
import favicon from '../images/favicon.png'
|
||||
import '@fontsource/lato'
|
||||
import '@fontsource/lekton'
|
||||
|
||||
const description = `We serve fast and scalable informational images as badges
|
||||
for GitHub, Travis CI, Jenkins, WordPress and many more services. Use them to
|
||||
@@ -17,10 +20,6 @@ export default function Meta(): JSX.Element {
|
||||
<meta content="width=device-width,initial-scale=1" name="viewport" />
|
||||
<meta content={description} name="description" />
|
||||
<link href={favicon} rel="icon" type="image/png" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Lato|Lekton"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Helmet>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Link } from 'gatsby'
|
||||
import styled from 'styled-components'
|
||||
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
|
||||
import { advertisedStyles, shieldsLogos } from '../lib/supported-features'
|
||||
// @ts-ignore
|
||||
// ts-expect-error: because reasons?
|
||||
import StaticBadgeMaker from './static-badge-maker'
|
||||
import DynamicBadgeMaker from './dynamic-badge-maker'
|
||||
import { H2, H3, Badge, VerticalSpace } from './common'
|
||||
@@ -434,8 +434,8 @@ export default function Usage({ baseUrl }: { baseUrl: string }): JSX.Element {
|
||||
for use cases where SVG will not work. These requests should be made to
|
||||
our raster server <code>https://raster.shields.io</code>. For example,
|
||||
the raster equivalent of{' '}
|
||||
<code>https://img.shields.io/v/npm/express</code> is{' '}
|
||||
<code>https://raster.shields.io/v/npm/express</code>. For backward
|
||||
<code>https://img.shields.io/npm/v/express</code> is{' '}
|
||||
<code>https://raster.shields.io/npm/v/express</code>. For backward
|
||||
compatibility, the badge server will redirect <code>.png</code> badges
|
||||
to the raster server.
|
||||
</p>
|
||||
|
||||
56117
package-lock.json
generated
56117
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
94
package.json
94
package.json
@@ -22,7 +22,9 @@
|
||||
"url": "https://github.com/badges/shields"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "^6.2.0",
|
||||
"@fontsource/lato": "^4.2.2",
|
||||
"@fontsource/lekton": "^4.2.2",
|
||||
"@sentry/node": "^6.2.3",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.0",
|
||||
@@ -30,17 +32,17 @@
|
||||
"chalk": "^4.1.0",
|
||||
"check-node-version": "^4.1.0",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.3",
|
||||
"config": "^3.3.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"decamelize": "^5.0.0",
|
||||
"emojic": "^1.1.16",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^3.17.6",
|
||||
"fast-xml-parser": "^3.19.0",
|
||||
"glob": "^7.1.6",
|
||||
"graphql": "^14.7.0",
|
||||
"got": "11.8.2",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"heroku-client": "^3.1.0",
|
||||
"ioredis": "4.23.0",
|
||||
"ioredis": "4.24.4",
|
||||
"joi": "17.4.0",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.0.0",
|
||||
@@ -52,15 +54,15 @@
|
||||
"node-env-flag": "^0.1.0",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pretty-bytes": "^5.5.0",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^13.1.0",
|
||||
"query-string": "^6.14.1",
|
||||
"query-string": "^7.0.0",
|
||||
"request": "~2.88.2",
|
||||
"semver": "~7.3.4",
|
||||
"simple-icons": "4.12.0",
|
||||
"semver": "~7.3.5",
|
||||
"simple-icons": "4.16.0",
|
||||
"webextension-store-meta": "^1.0.3",
|
||||
"xmldom": "~0.4.0",
|
||||
"xmldom": "~0.5.0",
|
||||
"xpath": "~0.0.32"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -101,7 +103,6 @@
|
||||
"defs": "node scripts/export-service-definitions-cli.js > service-definitions.yml",
|
||||
"build": "run-s defs features && gatsby build",
|
||||
"heroku-postbuild": "run-s --silent build",
|
||||
"heroku:scale": "node scripts/heroku-scale.js",
|
||||
"start:server:prod": "node server",
|
||||
"now-start": "npm run start:server:prod",
|
||||
"start:server:e2e-on-build": "node server 8080",
|
||||
@@ -140,78 +141,77 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.17",
|
||||
"@babel/core": "^7.13.10",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.13.0",
|
||||
"@babel/register": "7.13.8",
|
||||
"@mapbox/react-click-to-select": "^2.2.0",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.groupby": "^4.6.6",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@types/node": "^14.14.30",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "^14.14.36",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-modal": "^3.12.0",
|
||||
"@types/react-select": "^4.0.13",
|
||||
"@types/styled-components": "5.1.7",
|
||||
"@typescript-eslint/eslint-plugin": "^2.34.0",
|
||||
"@typescript-eslint/parser": "^2.34.0",
|
||||
"babel-plugin-inline-react-svg": "^1.1.2",
|
||||
"@types/styled-components": "5.1.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.19.0",
|
||||
"@typescript-eslint/parser": "^4.19.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-preset-gatsby": "^0.5.1",
|
||||
"caller": "^1.0.1",
|
||||
"chai": "^4.3.0",
|
||||
"chai": "^4.3.4",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-datetime": "^1.8.0",
|
||||
"chai-string": "^1.4.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^6.0.0",
|
||||
"cypress": "^6.5.0",
|
||||
"danger": "^10.6.3",
|
||||
"cypress": "^6.8.0",
|
||||
"danger": "^10.6.4",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint": "^7.22.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-config-standard-jsx": "^10.0.0",
|
||||
"eslint-config-standard-react": "^11.0.1",
|
||||
"eslint-plugin-chai-friendly": "^0.6.0",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsdoc": "^32.2.0",
|
||||
"eslint-plugin-mocha": "^8.0.0",
|
||||
"eslint-plugin-jsdoc": "^32.3.0",
|
||||
"eslint-plugin-mocha": "^8.1.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-react-hooks": "^2.5.1",
|
||||
"eslint-plugin-react": "^7.23.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-sort-class-members": "^1.9.0",
|
||||
"fetch-ponyfill": "^7.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"gatsby": "2.32.8",
|
||||
"gatsby-plugin-catch-links": "^2.3.10",
|
||||
"gatsby-plugin-page-creator": "^2.10.0",
|
||||
"gatsby-plugin-react-helmet": "^3.3.9",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^2.3.10",
|
||||
"gatsby-plugin-styled-components": "^3.10.0",
|
||||
"gatsby": "3.1.2",
|
||||
"gatsby-plugin-catch-links": "^3.1.0",
|
||||
"gatsby-plugin-page-creator": "^3.1.0",
|
||||
"gatsby-plugin-react-helmet": "^4.1.0",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^3.1.0",
|
||||
"gatsby-plugin-styled-components": "^4.1.0",
|
||||
"gatsby-plugin-typescript": "^2.12.0",
|
||||
"got": "11.8.2",
|
||||
"humanize-string": "^2.1.0",
|
||||
"husky": "^4.3.8",
|
||||
"icedfrisby": "4.0.0",
|
||||
"icedfrisby-nock": "^2.0.0",
|
||||
"is-svg": "^4.2.1",
|
||||
"is-svg": "^4.3.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdoc": "^3.6.6",
|
||||
"lint-staged": "^10.5.4",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.5",
|
||||
"mocha": "^8.3.0",
|
||||
"mocha": "^8.3.2",
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.0.0",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.0.7",
|
||||
"nock": "13.0.11",
|
||||
"node-mocks-http": "^1.10.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@@ -219,30 +219,30 @@
|
||||
"opn-cli": "^5.0.0",
|
||||
"portfinder": "^1.0.28",
|
||||
"prettier": "2.2.1",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-overlay": "^6.0.9",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-modal": "^3.12.1",
|
||||
"react-pose": "^4.0.10",
|
||||
"react-select": "^4.1.0",
|
||||
"react-select": "^4.3.0",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"redis-server": "^1.2.2",
|
||||
"require-hacker": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"sazerac": "^2.0.0",
|
||||
"sinon": "^9.2.4",
|
||||
"sinon-chai": "^3.5.0",
|
||||
"sinon": "^10.0.0",
|
||||
"sinon-chai": "^3.6.0",
|
||||
"snap-shot-it": "^7.9.6",
|
||||
"start-server-and-test": "1.12.0",
|
||||
"start-server-and-test": "1.12.1",
|
||||
"styled-components": "^5.2.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"tsd": "^0.14.0",
|
||||
"typescript": "^4.1.5"
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.18.3",
|
||||
"npm": "^6.14.7"
|
||||
"npm": ">=7.0.0"
|
||||
},
|
||||
"babel": {
|
||||
"plugins": [
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
if (process.argv.length < 3 || !/^\d+$/.test(process.argv[2])) {
|
||||
console.log('Usage: npm run heroku:scale [num-dynos]')
|
||||
process.exit(0)
|
||||
}
|
||||
if (!('HEROKU_API_TOKEN' in process.env)) {
|
||||
throw new Error("'HEROKU_API_TOKEN' env var must be set")
|
||||
}
|
||||
if (!('HEROKU_APP_ID' in process.env)) {
|
||||
throw new Error("'HEROKU_APP_ID' env var must be set")
|
||||
}
|
||||
|
||||
const Heroku = require('heroku-client')
|
||||
const HEROKU_API_TOKEN = process.env.HEROKU_API_TOKEN
|
||||
const HEROKU_APP_ID = process.env.HEROKU_APP_ID
|
||||
const numDynos = parseInt(process.argv[2])
|
||||
|
||||
const heroku = new Heroku({ token: HEROKU_API_TOKEN })
|
||||
|
||||
;(async () => {
|
||||
const currentConfig = await heroku.get(`/apps/${HEROKU_APP_ID}/formation/web`)
|
||||
if (currentConfig.quantity === numDynos) {
|
||||
console.log(
|
||||
`Already running the desired number of dynos (${numDynos}). No changes necessary.`
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
console.log(`Scaling to ${numDynos} dynos...`)
|
||||
const newConfig = await heroku.patch(`/apps/${HEROKU_APP_ID}/formation/web`, {
|
||||
body: {
|
||||
quantity: numDynos,
|
||||
},
|
||||
})
|
||||
console.log(`..done!`)
|
||||
console.log(newConfig)
|
||||
})()
|
||||
@@ -76,9 +76,9 @@ module.exports = class FDroid extends BaseJsonService {
|
||||
return { version }
|
||||
}
|
||||
|
||||
async handle({ appId }, { include_prereleases }) {
|
||||
async handle({ appId }, { include_prereleases: includePre }) {
|
||||
const json = await this.fetch({ appId })
|
||||
const suggested = include_prereleases === undefined
|
||||
const suggested = includePre === undefined
|
||||
const { version } = this.transform({ json, suggested })
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function getLatestRelease({ releases, sort, includePrereleases }) {
|
||||
releases.map(release => release.tag_name),
|
||||
{ pre: includePrereleases }
|
||||
)
|
||||
return releases.find(({ tag_name }) => tag_name === latestTagName)
|
||||
return releases.find(({ tag_name: tagName }) => tagName === latestTagName)
|
||||
}
|
||||
|
||||
if (!includePrereleases) {
|
||||
|
||||
@@ -230,11 +230,11 @@ module.exports = class GithubDownloads extends GithubAuthV3Service {
|
||||
async handle({ kind, user, repo, tag, assetName }, { sort }) {
|
||||
let releases
|
||||
if (tag === 'latest') {
|
||||
const include_prereleases = kind === 'downloads-pre' || undefined
|
||||
const includePre = kind === 'downloads-pre' || undefined
|
||||
const latestRelease = await fetchLatestRelease(
|
||||
this,
|
||||
{ user, repo },
|
||||
{ sort, include_prereleases }
|
||||
{ sort, include_prereleases: includePre }
|
||||
)
|
||||
releases = [latestRelease]
|
||||
} else if (tag) {
|
||||
|
||||
@@ -102,12 +102,12 @@ module.exports = class GitlabCoverage extends BaseSvgScrapingService {
|
||||
repo,
|
||||
branch,
|
||||
gitlab_url: baseUrl = 'https://gitlab.com',
|
||||
job_name,
|
||||
job_name: jobName,
|
||||
}) {
|
||||
// Since the URL doesn't return a usable value when an invalid job name is specified,
|
||||
// it is recommended to not use the query param at all if not required
|
||||
job_name = job_name ? `?job=${job_name}` : ''
|
||||
const url = `${baseUrl}/${user}/${repo}/badges/${branch}/coverage.svg${job_name}`
|
||||
jobName = jobName ? `?job=${jobName}` : ''
|
||||
const url = `${baseUrl}/${user}/${repo}/badges/${branch}/coverage.svg${jobName}`
|
||||
const errorMessages = {
|
||||
401: 'repo not found',
|
||||
404: 'repo not found',
|
||||
|
||||
@@ -26,8 +26,7 @@ const schema = Joi.object({
|
||||
.keys({
|
||||
amount: Joi.string().required(),
|
||||
})
|
||||
.allow(null)
|
||||
.required(),
|
||||
.allow(null),
|
||||
}).required()
|
||||
|
||||
const isCurrencyOverTime = Joi.string().regex(
|
||||
|
||||
@@ -12,6 +12,19 @@ t.create('Giving (not found)')
|
||||
.get('/does-not-exist.json')
|
||||
.expectBadge({ label: 'liberapay', message: 'not found' })
|
||||
|
||||
t.create('Giving (missing goal key)')
|
||||
.get('/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com')
|
||||
.get('/Liberapay/public.json')
|
||||
.reply(200, {
|
||||
npatrons: 0,
|
||||
giving: { amount: '3.71', currency: 'EUR' },
|
||||
receiving: null,
|
||||
})
|
||||
)
|
||||
.expectBadge({ label: 'gives', message: isCurrencyOverTime })
|
||||
|
||||
t.create('Giving (null)')
|
||||
.get('/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
|
||||
@@ -8,6 +8,17 @@ t.create('Goal Progress (valid)').get('/Liberapay.json').expectBadge({
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('Goal (missing goal key)')
|
||||
.get('/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com').get('/Liberapay/public.json').reply(200, {
|
||||
npatrons: 0,
|
||||
giving: null,
|
||||
receiving: null,
|
||||
})
|
||||
)
|
||||
.expectBadge({ label: 'liberapay', message: 'no public goals' })
|
||||
|
||||
t.create('Goal Progress (not found)')
|
||||
.get('/does-not-exist.json')
|
||||
.expectBadge({ label: 'liberapay', message: 'not found' })
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = class OreSpongeVersions extends BaseOreService {
|
||||
versions: promoted_versions
|
||||
.reduce((acc, { tags }) => acc.concat(tags), [])
|
||||
.filter(({ name }) => name.toLowerCase() === 'sponge')
|
||||
.map(({ display_data }) => display_data)
|
||||
.map(({ display_data: displayData }) => displayData)
|
||||
// display_data is not mandatory in the schema, filter null values
|
||||
.filter(x => !!x),
|
||||
}
|
||||
|
||||
@@ -121,8 +121,11 @@ class PackagistVersion extends BasePackagistService {
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ user, repo }, { include_prereleases, server }) {
|
||||
const includePrereleases = include_prereleases !== undefined
|
||||
async handle(
|
||||
{ user, repo },
|
||||
{ include_prereleases: includePrereleases, server }
|
||||
) {
|
||||
includePrereleases = includePrereleases !== undefined
|
||||
const json = await this.fetch({
|
||||
user,
|
||||
repo,
|
||||
|
||||
@@ -8,5 +8,5 @@ const t = (module.exports = new ServiceTester({
|
||||
}))
|
||||
|
||||
t.create('sensiolabs insight')
|
||||
.get('/i/45afb680-d4e6-4e66-93ea-bcfa79eb8a87.svg')
|
||||
.expectRedirect('/symfony/i/grade/45afb680-d4e6-4e66-93ea-bcfa79eb8a87.svg')
|
||||
.get('/i/15c5c748-f8d8-4b56-b536-a29a151aac6c.svg')
|
||||
.expectRedirect('/symfony/i/grade/15c5c748-f8d8-4b56-b536-a29a151aac6c.svg')
|
||||
|
||||
@@ -16,7 +16,7 @@ module.exports = class SymfonyInsightGrade extends SymfonyInsightBase {
|
||||
{
|
||||
title: 'SymfonyInsight Grade',
|
||||
namedParams: {
|
||||
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
|
||||
projectUuid: '15c5c748-f8d8-4b56-b536-a29a151aac6c',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
grade: 'bronze',
|
||||
|
||||
@@ -25,7 +25,7 @@ module.exports = class SymfonyInsightStars extends SymfonyInsightBase {
|
||||
{
|
||||
title: 'SymfonyInsight Stars',
|
||||
namedParams: {
|
||||
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
|
||||
projectUuid: '15c5c748-f8d8-4b56-b536-a29a151aac6c',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
grade: 'silver',
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = class SymfonyInsightViolations extends SymfonyInsightBase {
|
||||
{
|
||||
title: 'SymfonyInsight Violations',
|
||||
namedParams: {
|
||||
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
|
||||
projectUuid: '15c5c748-f8d8-4b56-b536-a29a151aac6c',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
numViolations: 0,
|
||||
|
||||
@@ -11,6 +11,6 @@ t.create('valid project violations')
|
||||
.expectBadge({
|
||||
label: 'violations',
|
||||
message: withRegex(
|
||||
/\d* critical|\d* critical, \d* major|\d* critical, \d* major, \d* minor|\d* critical, \d* major, \d* minor, \d* info|\d* critical, \d* minor|\d* critical, \d* info|\d* major|\d* major, \d* minor|\d* major, \d* minor, \d* info|\d* major, \d* info|\d* minor|\d* minor, \d* info/
|
||||
/0|\d* critical|\d* critical, \d* major|\d* critical, \d* major, \d* minor|\d* critical, \d* major, \d* minor, \d* info|\d* critical, \d* minor|\d* critical, \d* info|\d* major|\d* major, \d* minor|\d* major, \d* minor, \d* info|\d* major, \d* info|\d* minor|\d* minor, \d* info/
|
||||
),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87'
|
||||
const sampleProjectUuid = '15c5c748-f8d8-4b56-b536-a29a151aac6c'
|
||||
|
||||
function createMockResponse({ status = 'finished', grade, violations }) {
|
||||
let response = `
|
||||
|
||||
@@ -30,7 +30,7 @@ module.exports = class VisualStudioAppCenterReleasesOSVersion extends (
|
||||
app: 'my-amazing-app',
|
||||
token: 'ac70cv...',
|
||||
},
|
||||
staticPreview: this.render({ min_os: '4.1', app_os: 'Android' }),
|
||||
staticPreview: this.render({ minOS: '4.1', appOS: 'Android' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
@@ -41,15 +41,20 @@ module.exports = class VisualStudioAppCenterReleasesOSVersion extends (
|
||||
color: 'blue',
|
||||
}
|
||||
|
||||
static render({ app_os, min_os }) {
|
||||
static render({ appOS, minOS }) {
|
||||
return {
|
||||
label: `${app_os.toLowerCase()}`,
|
||||
message: `${min_os}+`,
|
||||
label: `${appOS.toLowerCase()}`,
|
||||
message: `${minOS}+`,
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ owner, app, token }) {
|
||||
const { app_os, min_os } = await this.fetch({ owner, app, token, schema })
|
||||
return this.constructor.render({ app_os, min_os })
|
||||
const { app_os: appOS, min_os: minOS } = await this.fetch({
|
||||
owner,
|
||||
app,
|
||||
token,
|
||||
schema,
|
||||
})
|
||||
return this.constructor.render({ appOS, minOS })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ module.exports = class VisualStudioAppCenterReleasesVersion extends (
|
||||
}
|
||||
|
||||
async handle({ owner, app, token }) {
|
||||
const { version, short_version } = await this.fetch({
|
||||
const { version, short_version: shortVersion } = await this.fetch({
|
||||
owner,
|
||||
app,
|
||||
token,
|
||||
schema,
|
||||
})
|
||||
return renderVersionBadge({
|
||||
version: `${short_version} (${version})`,
|
||||
version: `${shortVersion} (${version})`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +86,10 @@ function DownloadsForExtensionType(extensionType) {
|
||||
})
|
||||
downloads = _downloads
|
||||
} else {
|
||||
const ext_type = extensionType === 'plugin' ? 'plugin' : 'themes'
|
||||
const extType = extensionType === 'plugin' ? 'plugin' : 'themes'
|
||||
const json = await this._requestJson({
|
||||
schema: dateSchema,
|
||||
url: `https://api.wordpress.org/stats/${ext_type}/1.0/downloads.php`,
|
||||
url: `https://api.wordpress.org/stats/${extType}/1.0/downloads.php`,
|
||||
options: {
|
||||
qs: {
|
||||
slug,
|
||||
|
||||
@@ -36,17 +36,17 @@ function LastUpdateForType(extensionType) {
|
||||
{
|
||||
title: `WordPress ${capt} Last Updated`,
|
||||
namedParams: { slug: exampleSlug },
|
||||
staticPreview: this.render({ last_updated: '2020-08-11' }),
|
||||
staticPreview: this.render({ lastUpdated: '2020-08-11' }),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'last updated' }
|
||||
|
||||
static render({ last_updated }) {
|
||||
static render({ lastUpdated }) {
|
||||
return {
|
||||
label: 'last updated',
|
||||
message: formatDate(last_updated),
|
||||
color: ageColor(last_updated),
|
||||
message: formatDate(lastUpdated),
|
||||
color: ageColor(lastUpdated),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,15 +61,15 @@ function LastUpdateForType(extensionType) {
|
||||
}
|
||||
|
||||
async handle({ slug }) {
|
||||
const { last_updated } = await this.fetch({
|
||||
const { last_updated: lastUpdated } = await this.fetch({
|
||||
extensionType,
|
||||
slug,
|
||||
})
|
||||
|
||||
const newDate = await this.transform(last_updated)
|
||||
const newDate = await this.transform(lastUpdated)
|
||||
|
||||
return this.constructor.render({
|
||||
last_updated: newDate,
|
||||
lastUpdated: newDate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,28 +144,28 @@ function RequiresPHPVersionForType(extensionType) {
|
||||
}
|
||||
|
||||
async handle({ slug }) {
|
||||
const { requires_php } = await this.fetch({
|
||||
const { requires_php: requiresPhp } = await this.fetch({
|
||||
extensionType,
|
||||
slug,
|
||||
})
|
||||
|
||||
if (requires_php === false) {
|
||||
if (requiresPhp === false) {
|
||||
throw new NotFound({
|
||||
prettyMessage: `not set for this ${extensionType}`,
|
||||
})
|
||||
}
|
||||
|
||||
return this.constructor.render({
|
||||
version: requires_php,
|
||||
version: requiresPhp,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const required_php = ['plugin', 'theme'].map(RequiresPHPVersionForType)
|
||||
const requiredPhp = ['plugin', 'theme'].map(RequiresPHPVersionForType)
|
||||
const requiresVersion = ['plugin', 'theme'].map(WordpressRequiresVersion)
|
||||
module.exports = [
|
||||
...required_php,
|
||||
...requiredPhp,
|
||||
...requiresVersion,
|
||||
WordpressPluginTestedVersion,
|
||||
]
|
||||
|
||||
@@ -5,6 +5,10 @@ const { BaseJsonService, NotFound } = require('..')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const documentation = `
|
||||
<p>By using the YouTube badges provided by Shields.io, you are agreeing to be bound by the YouTube Terms of Service. These can be found here:
|
||||
<code>https://www.youtube.com/t/terms</code></p>`
|
||||
|
||||
const schema = Joi.object({
|
||||
items: Joi.array()
|
||||
.items(
|
||||
@@ -20,7 +24,7 @@ const schema = Joi.object({
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
module.exports = class YouTubeBase extends BaseJsonService {
|
||||
class YouTubeBase extends BaseJsonService {
|
||||
static category = 'social'
|
||||
|
||||
static auth = {
|
||||
@@ -68,3 +72,5 @@ module.exports = class YouTubeBase extends BaseJsonService {
|
||||
return this.constructor.render({ statistics, videoId }, queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { documentation, YouTubeBase }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const YouTubeBase = require('./youtube-base')
|
||||
const { documentation, YouTubeBase } = require('./youtube-base')
|
||||
|
||||
module.exports = class YouTubeComments extends YouTubeBase {
|
||||
static route = {
|
||||
@@ -20,6 +20,7 @@ module.exports = class YouTubeComments extends YouTubeBase {
|
||||
title: 'YouTube Video Comments',
|
||||
namedParams: { videoId: 'wGJHwc5ksMA' },
|
||||
staticPreview: preview,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../text-formatters')
|
||||
const YouTubeBase = require('./youtube-base')
|
||||
const { documentation, YouTubeBase } = require('./youtube-base')
|
||||
|
||||
const documentationWithDislikes = `
|
||||
${documentation}
|
||||
<p>
|
||||
When enabling the <code>withDislikes</code> option, 👍 corresponds to the number
|
||||
of likes of a given video, 👎 corresponds to the number of dislikes.
|
||||
</p>
|
||||
`
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
withDislikes: Joi.equal(''),
|
||||
@@ -37,33 +45,34 @@ module.exports = class YouTubeLikes extends YouTubeBase {
|
||||
title: 'YouTube Video Likes',
|
||||
namedParams: { videoId: 'abBdk8bSPKU' },
|
||||
staticPreview: previewLikes,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'YouTube Video Votes',
|
||||
title: 'YouTube Video Likes and Dislikes',
|
||||
namedParams: { videoId: 'pU9Q6oiQNd0' },
|
||||
staticPreview: previewVotes,
|
||||
queryParams: {
|
||||
withDislikes: null,
|
||||
},
|
||||
documentation: documentationWithDislikes,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ statistics, videoId }, queryParams) {
|
||||
if (queryParams && typeof queryParams.withDislikes !== 'undefined') {
|
||||
return {
|
||||
label: 'votes',
|
||||
message: `${metric(statistics.likeCount)} 👍 ${metric(
|
||||
statistics.dislikeCount
|
||||
)} 👎`,
|
||||
style: 'social',
|
||||
link: `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`,
|
||||
}
|
||||
}
|
||||
return super.renderSingleStat({
|
||||
let renderedBadge = super.renderSingleStat({
|
||||
statistics,
|
||||
statisticName: 'like',
|
||||
videoId,
|
||||
})
|
||||
if (queryParams && typeof queryParams.withDislikes !== 'undefined') {
|
||||
renderedBadge = {
|
||||
...renderedBadge,
|
||||
message: `${metric(statistics.likeCount)} 👍 ${metric(
|
||||
statistics.dislikeCount
|
||||
)} 👎`,
|
||||
}
|
||||
}
|
||||
return renderedBadge
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ t.create('video vote count')
|
||||
.skipWhen(noYouTubeToken)
|
||||
.get('/pU9Q6oiQNd0.json?withDislikes')
|
||||
.expectBadge({
|
||||
label: 'votes',
|
||||
label: 'likes',
|
||||
message: Joi.string().regex(
|
||||
/^([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) 👍 ([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) 👎$/
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const YouTubeBase = require('./youtube-base')
|
||||
const { documentation, YouTubeBase } = require('./youtube-base')
|
||||
|
||||
module.exports = class YouTubeViews extends YouTubeBase {
|
||||
static route = {
|
||||
@@ -20,6 +20,7 @@ module.exports = class YouTubeViews extends YouTubeBase {
|
||||
title: 'YouTube Video Views',
|
||||
namedParams: { videoId: 'abBdk8bSPKU' },
|
||||
staticPreview: preview,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user