Compare commits
2 Commits
server-202
...
requires-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332a496e84 | ||
|
|
5f28ac34cc |
@@ -6,9 +6,7 @@ main_steps: &main_steps
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm install --dry-run
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
# https://docs.cypress.io/guides/getting-started/installing-cypress.html#Skipping-installation
|
||||
# We don't need to install the Cypress binary in jobs that aren't actually running Cypress.
|
||||
@@ -49,9 +47,7 @@ integration_steps: &integration_steps
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm install --dry-run
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -72,9 +68,7 @@ services_steps: &services_steps
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm install --dry-run
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -92,6 +86,33 @@ services_steps: &services_steps
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
run_package_tests: &run_package_tests
|
||||
when: always
|
||||
command: |
|
||||
# https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/3
|
||||
set +e
|
||||
export NVM_DIR="/opt/circleci/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
nvm install $NODE_VERSION
|
||||
nvm use $NODE_VERSION
|
||||
node --version
|
||||
|
||||
# install the shields.io dependencies
|
||||
npm ci
|
||||
|
||||
# run the package tests
|
||||
npm run test:package
|
||||
npm run check-types:package
|
||||
|
||||
# delete the sheilds.io dependencies
|
||||
rm -rf node_modules/
|
||||
|
||||
# run a smoke test (render a badge with the CLI)
|
||||
# with only the package dependencies installed
|
||||
cd badge-maker
|
||||
npm link
|
||||
badge cactus grown :green @flat
|
||||
|
||||
package_steps: &package_steps
|
||||
steps:
|
||||
- checkout
|
||||
@@ -102,8 +123,8 @@ package_steps: &package_steps
|
||||
set +e
|
||||
export NVM_DIR="/opt/circleci/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
nvm install v14
|
||||
nvm use v14
|
||||
nvm install v12
|
||||
nvm use v12
|
||||
npm install -g npm
|
||||
|
||||
# Run the package tests on each currently supported node version. See:
|
||||
@@ -111,31 +132,31 @@ package_steps: &package_steps
|
||||
# https://nodejs.org/en/about/releases/
|
||||
|
||||
- run:
|
||||
<<: *run_package_tests
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v10/results.xml
|
||||
NODE_VERSION: v10
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 10
|
||||
|
||||
- run:
|
||||
<<: *run_package_tests
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v12/results.xml
|
||||
NODE_VERSION: v12
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 12
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
<<: *run_package_tests
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v14/results.xml
|
||||
NODE_VERSION: v14
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 14
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v16/results.xml
|
||||
NODE_VERSION: v16
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 16
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
@@ -143,39 +164,33 @@ package_steps: &package_steps
|
||||
jobs:
|
||||
main:
|
||||
docker:
|
||||
- image: cimg/node:16.14
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'true'
|
||||
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
|
||||
- image: circleci/node:12
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
main@node-17:
|
||||
main@node-14:
|
||||
docker:
|
||||
- image: cimg/node:17.7
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
integration:
|
||||
docker:
|
||||
- image: cimg/node:16.14
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'true'
|
||||
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
|
||||
- image: circleci/node:12
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
integration@node-17:
|
||||
integration@node-14:
|
||||
docker:
|
||||
- image: cimg/node:17.7
|
||||
- image: circleci/node:14
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
danger:
|
||||
docker:
|
||||
- image: cimg/node:16.14
|
||||
- image: circleci/node:12
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
@@ -195,18 +210,13 @@ jobs:
|
||||
|
||||
frontend:
|
||||
docker:
|
||||
- image: cimg/node:16.14
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'true'
|
||||
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
|
||||
- image: circleci/node:12
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm install --dry-run
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -235,32 +245,25 @@ jobs:
|
||||
command: npm run build
|
||||
|
||||
package:
|
||||
machine:
|
||||
image: 'ubuntu-2004:202111-02'
|
||||
machine: true
|
||||
|
||||
<<: *package_steps
|
||||
|
||||
services:
|
||||
docker:
|
||||
- image: cimg/node:16.14
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'true'
|
||||
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
|
||||
- image: circleci/node:12
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
services@node-17:
|
||||
services@node-14:
|
||||
docker:
|
||||
- image: cimg/node:17.7
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
e2e:
|
||||
docker:
|
||||
- image: cypress/base:16.13.0
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'true'
|
||||
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
|
||||
- image: cypress/base:12
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
@@ -271,9 +274,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm install --dry-run
|
||||
npm ci
|
||||
command: npm ci
|
||||
|
||||
- run:
|
||||
name: Frontend build
|
||||
@@ -311,11 +312,11 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- main@node-17:
|
||||
- main@node-14:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- integration@node-17:
|
||||
- integration@node-14:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
@@ -333,7 +334,7 @@ workflows:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- services@node-17:
|
||||
- services@node-14:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
|
||||
36
.dependabot/config.yml
Normal file
36
.dependabot/config.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
version: 1
|
||||
update_configs:
|
||||
# shields.io dependencies
|
||||
- package_manager: 'javascript'
|
||||
directory: '/'
|
||||
update_schedule: 'weekly'
|
||||
automerged_updates:
|
||||
- match:
|
||||
dependency_name: 'chai*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'cypress'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'eslint*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'enzyme*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'mocha*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'sazerac'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'sinon*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'snap-shot-it'
|
||||
update_type: 'semver:minor'
|
||||
|
||||
# badge-maker package dependencies
|
||||
- package_manager: 'javascript'
|
||||
directory: '/badge-maker'
|
||||
update_schedule: 'weekly'
|
||||
@@ -2,6 +2,5 @@
|
||||
/build
|
||||
/coverage
|
||||
/__snapshots__
|
||||
public
|
||||
/public
|
||||
badge-maker/node_modules/
|
||||
!.github/
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
extends:
|
||||
- standard
|
||||
- standard-jsx
|
||||
- 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,7 +42,6 @@ overrides:
|
||||
es6: true
|
||||
rules:
|
||||
no-console: 'off'
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off'
|
||||
|
||||
- files:
|
||||
- '**/*.@(ts|tsx)'
|
||||
@@ -59,7 +57,6 @@ 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
|
||||
@@ -127,15 +124,13 @@ 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'
|
||||
|
||||
# Shields additions.
|
||||
no-var: 'error'
|
||||
prefer-const: 'error'
|
||||
strict: 'error'
|
||||
arrow-body-style: ['error', 'as-needed']
|
||||
no-extension-in-require/main: 'error'
|
||||
object-shorthand: ['error', 'properties']
|
||||
@@ -145,20 +140,6 @@ 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'
|
||||
|
||||
29
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report errors and problems
|
||||
labels: 'question'
|
||||
---
|
||||
|
||||
Are you experiencing an issue with...
|
||||
|
||||
- [ ] [shields.io](https://shields.io/#/)
|
||||
- [ ] My own instance
|
||||
- [ ] [badge-maker NPM package](https://www.npmjs.com/package/badge-maker)
|
||||
|
||||
:beetle: **Description**
|
||||
|
||||
<!-- A clear and concise description of the problem. -->
|
||||
|
||||
:link: **Link to the badge**
|
||||
|
||||
<!--
|
||||
If you are reporting a problem with a specific badge on shields.io,
|
||||
provide a link to a badge demonstrating the error
|
||||
-->
|
||||
|
||||
:bulb: **Possible Solution**
|
||||
|
||||
<!--- Optional: only if you have suggestions on a fix/reason for the bug -->
|
||||
|
||||
<!-- Love Shields? Please consider donating $10 to sustain our activities:
|
||||
👉 https://opencollective.com/shields -->
|
||||
44
.github/ISSUE_TEMPLATE/1_Bug_report.yml
vendored
44
.github/ISSUE_TEMPLATE/1_Bug_report.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: '🐛 Bug Report'
|
||||
description: Report errors and problems
|
||||
labels: [question]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: product
|
||||
attributes:
|
||||
label: Are you experiencing an issue with...
|
||||
options:
|
||||
- shields.io
|
||||
- My own instance of shields
|
||||
- badge-maker NPM package
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: '🐞 Description'
|
||||
description: A clear and concise description of the problem.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: link
|
||||
attributes:
|
||||
label: '🔗 Link to the badge'
|
||||
description: If you are reporting a problem with a specific badge on shields.io, provide a link to a badge demonstrating the error
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: possible-solution
|
||||
attributes:
|
||||
label: '💡 Possible Solution'
|
||||
description: 'Optional: only if you have suggestions on a fix/reason for the bug'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## :heart: Love Shields?
|
||||
Please consider donating $10 to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)
|
||||
@@ -22,7 +22,7 @@ labels: 'keep-service-tests-green'
|
||||
|
||||
<!-- Provide a link to the failing test in CircleCI. -->
|
||||
|
||||
:lady_beetle: **Stack trace**
|
||||
:beetle: **Stack trace**
|
||||
|
||||
```
|
||||
<!-- Provide the complete stack trace from the CircleCI test summary. -->
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,4 @@
|
||||
contact_links:
|
||||
- name: 🎨 Simple Icons
|
||||
url: https://github.com/badges/shields/discussions/5369
|
||||
about: Please read this before posting a question about SimpleIcons
|
||||
- name: ❓ Support Question
|
||||
url: https://github.com/badges/shields/discussions
|
||||
about: Ask a question about Shields.io
|
||||
|
||||
12
.github/actions/close-bot/action.yml
vendored
12
.github/actions/close-bot/action.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: 'Auto Approve'
|
||||
description: 'Automatically approve/close selected pull requests for shields.io'
|
||||
branding:
|
||||
icon: 'check-circle'
|
||||
color: 'green'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'index.js'
|
||||
68
.github/actions/close-bot/helpers.js
vendored
68
.github/actions/close-bot/helpers.js
vendored
@@ -1,68 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
function findChangelogStart(lines) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
if (
|
||||
line === '<summary>Changelog</summary>' &&
|
||||
lines[i + 2] === '<blockquote>'
|
||||
) {
|
||||
return i + 3
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function findChangelogEnd(lines, start) {
|
||||
for (let i = start; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
if (line === '</blockquote>') {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function allChangelogLinesAreVersionBump(changelogLines) {
|
||||
return (
|
||||
changelogLines.length > 0 &&
|
||||
changelogLines.length ===
|
||||
changelogLines.filter(line =>
|
||||
line.includes('Version bump only for package')
|
||||
).length
|
||||
)
|
||||
}
|
||||
|
||||
function isPointlessVersionBump(body) {
|
||||
const pointlessBumpLinks = [
|
||||
'https://github.com/gatsbyjs/gatsby',
|
||||
'https://github.com/typescript-eslint/typescript-eslint',
|
||||
]
|
||||
|
||||
const lines = body.split(/\r?\n/)
|
||||
if (!pointlessBumpLinks.some(link => lines[0].includes(link))) {
|
||||
return false
|
||||
}
|
||||
const start = findChangelogStart(lines)
|
||||
const end = findChangelogEnd(lines, start)
|
||||
if (!start || !end) {
|
||||
return false
|
||||
}
|
||||
const changelogLines = lines
|
||||
.slice(start, end)
|
||||
.filter(line => !line.startsWith('<h'))
|
||||
.filter(line => !line.startsWith('<p>All notable changes'))
|
||||
.filter(
|
||||
line => !line.startsWith('See <a href="https://conventionalcommits.org">')
|
||||
)
|
||||
.filter(line => !line.startsWith('<!--'))
|
||||
.filter(
|
||||
line =>
|
||||
!line.startsWith(
|
||||
'<p><a href="https://www.gatsbyjs.com/docs/reference/release-notes/'
|
||||
)
|
||||
)
|
||||
return allChangelogLinesAreVersionBump(changelogLines)
|
||||
}
|
||||
|
||||
module.exports = { isPointlessVersionBump }
|
||||
38
.github/actions/close-bot/index.js
vendored
38
.github/actions/close-bot/index.js
vendored
@@ -1,38 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const core = require('@actions/core')
|
||||
const github = require('@actions/github')
|
||||
const { isPointlessVersionBump } = require('./helpers')
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const token = core.getInput('github-token', { required: true })
|
||||
|
||||
const { pull_request: pr } = github.context.payload
|
||||
if (!pr) {
|
||||
throw new Error('Event payload missing `pull_request`')
|
||||
}
|
||||
|
||||
const client = github.getOctokit(token)
|
||||
|
||||
if (
|
||||
['dependabot[bot]', 'dependabot-preview[bot]'].includes(pr.user.login)
|
||||
) {
|
||||
if (isPointlessVersionBump(pr.body)) {
|
||||
core.debug(`Closing pull request #${pr.number}`)
|
||||
await client.rest.pulls.update({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
state: 'closed',
|
||||
})
|
||||
|
||||
core.debug(`Done.`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
416
.github/actions/close-bot/package-lock.json
generated
vendored
416
.github/actions/close-bot/package-lock.json
generated
vendored
@@ -1,416 +0,0 @@
|
||||
{
|
||||
"name": "close-bot",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "close-bot",
|
||||
"version": "0.0.0",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.6.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz",
|
||||
"integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@octokit/core": "^3.4.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.13.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"dependencies": {
|
||||
"tunnel": "0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
|
||||
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.4.12",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz",
|
||||
"integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.2.tgz",
|
||||
"integrity": "sha512-WmsIR1OzOr/3IqfG9JIczI8gMJUMzzyx5j0XXQ4YihHtKlQc+u35VpVoOXhlKAlaBntvry1WpAzPl/a+s3n89Q==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.3.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.0.0.tgz",
|
||||
"integrity": "sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz",
|
||||
"integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.1.1.tgz",
|
||||
"integrity": "sha512-u4zy0rVA8darm/AYsIeWkRalhQR99qPL1D/EXHejV2yaECMdHfxXiTXtba8NMBSajOJe8+C9g+EqMKSvysx0dg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.14.1",
|
||||
"deprecation": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz",
|
||||
"integrity": "sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.0.0",
|
||||
"@octokit/types": "^6.7.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz",
|
||||
"integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.14.2.tgz",
|
||||
"integrity": "sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz",
|
||||
"integrity": "sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw=="
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"@actions/github": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz",
|
||||
"integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@octokit/core": "^3.4.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.13.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
}
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
|
||||
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"@octokit/core": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==",
|
||||
"requires": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.4.12",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/endpoint": {
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz",
|
||||
"integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.2.tgz",
|
||||
"integrity": "sha512-WmsIR1OzOr/3IqfG9JIczI8gMJUMzzyx5j0XXQ4YihHtKlQc+u35VpVoOXhlKAlaBntvry1WpAzPl/a+s3n89Q==",
|
||||
"requires": {
|
||||
"@octokit/request": "^5.3.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.0.0.tgz",
|
||||
"integrity": "sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw=="
|
||||
},
|
||||
"@octokit/plugin-paginate-rest": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz",
|
||||
"integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.1.1.tgz",
|
||||
"integrity": "sha512-u4zy0rVA8darm/AYsIeWkRalhQR99qPL1D/EXHejV2yaECMdHfxXiTXtba8NMBSajOJe8+C9g+EqMKSvysx0dg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.14.1",
|
||||
"deprecation": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "5.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz",
|
||||
"integrity": "sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==",
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.0.0",
|
||||
"@octokit/types": "^6.7.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/request-error": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz",
|
||||
"integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.14.2.tgz",
|
||||
"integrity": "sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA==",
|
||||
"requires": {
|
||||
"@octokit/openapi-types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz",
|
||||
"integrity": "sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw=="
|
||||
},
|
||||
"deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||
},
|
||||
"universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.github/actions/close-bot/package.json
vendored
16
.github/actions/close-bot/package.json
vendored
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "close-bot",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "chris48s",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.6.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
}
|
||||
8
.github/actions/draft-release/Dockerfile
vendored
8
.github/actions/draft-release/Dockerfile
vendored
@@ -1,8 +0,0 @@
|
||||
FROM node:12-buster
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y jq
|
||||
RUN apt-get install -y uuid-runtime
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
5
.github/actions/draft-release/action.yml
vendored
5
.github/actions/draft-release/action.yml
vendored
@@ -1,5 +0,0 @@
|
||||
name: 'draft-release'
|
||||
description: 'Generate a changelog and propose a release PR'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
62
.github/actions/draft-release/entrypoint.sh
vendored
62
.github/actions/draft-release/entrypoint.sh
vendored
@@ -1,62 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
# Set up a git user
|
||||
git config user.name "release[bot]"
|
||||
git config user.email "actions@users.noreply.github.com"
|
||||
|
||||
# Find last server-YYYY-MM-DD tag
|
||||
git fetch --unshallow --tags
|
||||
LAST_TAG=$(git tag | grep server | tail -n 1)
|
||||
|
||||
# Find the marker in CHANGELOG.md
|
||||
INSERT_POINT=$(grep -n "^\-\-\-$" CHANGELOG.md | cut -f1 -d:)
|
||||
INSERT_POINT=$((INSERT_POINT+1))
|
||||
|
||||
# Generate a release name
|
||||
RELEASE_NAME="server-$(date --rfc-3339=date)"
|
||||
|
||||
# Assemble changelog entry
|
||||
rm -f temp-changes.txt
|
||||
touch temp-changes.txt
|
||||
{
|
||||
echo "## $RELEASE_NAME"
|
||||
echo ""
|
||||
git log "$LAST_TAG"..HEAD --no-merges --oneline --pretty="format:- %s" --perl-regexp --author='^((?!dependabot).*)$'
|
||||
echo $'\n- Dependency updates\n'
|
||||
} >> temp-changes.txt
|
||||
BASE_URL="https:\/\/github.com\/badges\/shields\/issues\/"
|
||||
sed -r -i "s/\((\#)([0-9]+)\)$/\[\1\2\]\($BASE_URL\2\)/g" temp-changes.txt
|
||||
|
||||
# Write the changelog
|
||||
sed -i "${INSERT_POINT} r temp-changes.txt" CHANGELOG.md
|
||||
|
||||
# Cleanup
|
||||
rm temp-changes.txt
|
||||
|
||||
# Run prettier (to ensure the markdown file doesn't fail CI)
|
||||
npx prettier@$(cat package.json | jq -r .devDependencies.prettier) --write "CHANGELOG.md"
|
||||
|
||||
# Generate a unique branch name
|
||||
BRANCH_NAME="$RELEASE_NAME"-$(uuidgen | head -c 8)
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
# Commit + push changelog
|
||||
git add CHANGELOG.md
|
||||
git commit -m "Update Changelog"
|
||||
git push origin "$BRANCH_NAME"
|
||||
|
||||
# Submit a PR
|
||||
TITLE="Changelog for Release $RELEASE_NAME"
|
||||
PR_RESP=$(curl https://api.github.com/repos/"$REPO_NAME"/pulls \
|
||||
-X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
--data '{"title": "'"$TITLE"'", "body": "'"$TITLE"'", "head": "'"$BRANCH_NAME"'", "base": "master"}')
|
||||
|
||||
# Add the 'release' label to the PR
|
||||
PR_API_URL=$(echo "$PR_RESP" | jq -r ._links.issue.href)
|
||||
curl "$PR_API_URL" \
|
||||
-X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
--data '{"labels":["release"]}'
|
||||
38
.github/dependabot.yml
vendored
38
.github/dependabot.yml
vendored
@@ -1,38 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# shields.io dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
ignore:
|
||||
# https://github.com/badges/shields/issues/7324
|
||||
# https://github.com/badges/shields/issues/7447
|
||||
# we're stuck with these versions until Safari is compatible with lookbehind regex syntax
|
||||
# https://caniuse.com/js-regexp-lookbehind
|
||||
- dependency-name: 'decamelize'
|
||||
- dependency-name: 'humanize-string'
|
||||
|
||||
# https://github.com/badges/shields/pull/7288#issuecomment-974699240
|
||||
- dependency-name: '@types/node'
|
||||
|
||||
# badge-maker package dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/badge-maker'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
|
||||
# close-bot package dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/.github/actions/close-bot'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
10
.github/probot.js
vendored
Normal file
10
.github/probot.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
on('pull_request.closed')
|
||||
.filter(context => context.payload.pull_request.merged)
|
||||
.filter(
|
||||
context =>
|
||||
context.payload.pull_request.head.ref.slice(0, 11) !== 'dependabot/'
|
||||
)
|
||||
.filter(context => context.payload.pull_request.base.ref === 'master')
|
||||
.comment(`This pull request was merged to [{{ pull_request.base.ref }}]({{ repository.html_url }}/tree/{{ pull_request.base.ref }}) branch. This change is now waiting for deployment, which will usually happen within a few days. Stay tuned by joining our \`#ops\` channel on [Discord](https://discordapp.com/invite/HjJCwm5)!
|
||||
|
||||
After deployment, changes are copied to [gh-pages]({{ repository.html_url }}/tree/gh-pages) branch: `)
|
||||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -1,7 +0,0 @@
|
||||
<!--
|
||||
Be sure to review our Contributing guidelines to help streamline the merging of your PR!
|
||||
|
||||
* PR title conventions - https://github.com/badges/shields/blob/master/CONTRIBUTING.md#running-service-tests-in-pull-requests
|
||||
* Code formatting - https://github.com/badges/shields/blob/master/CONTRIBUTING.md#prettier
|
||||
* Merge processes and reminders - https://github.com/badges/shields/blob/master/CONTRIBUTING.md#pull-requests
|
||||
-->
|
||||
10
.github/workflows/auto-approve.yml
vendored
Normal file
10
.github/workflows/auto-approve.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
name: Auto approve
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: chris48s/approve-bot@2.0.1
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
20
.github/workflows/auto-close.yml
vendored
20
.github/workflows/auto-close.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Auto close
|
||||
on: pull_request_target
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install action dependencies
|
||||
run: cd .github/actions/close-bot && npm ci
|
||||
|
||||
- uses: ./.github/actions/close-bot
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
25
.github/workflows/build-docker-image.yml
vendored
25
.github/workflows/build-docker-image.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Build Docker Image
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: shieldsio/shields:pr-validation
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
49
.github/workflows/create-release.yml
vendored
49
.github/workflows/create-release.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.action == 'closed' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
contains(github.event.pull_request.labels.*.name, 'release')
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date --rfc-3339=date)"
|
||||
|
||||
- name: Checkout branch "master"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: 'master'
|
||||
|
||||
- name: Tag release in GitHub
|
||||
uses: tvdias/github-tagger@v0.0.2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: server-${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to DockerHub
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: shieldsio/shields:server-${{ steps.date.outputs.date }}
|
||||
build-args: |
|
||||
version=server-${{ steps.date.outputs.date }}
|
||||
26
.github/workflows/deploy-docs.yml
vendored
26
.github/workflows/deploy-docs.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build-docs
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: api-docs
|
||||
CLEAN: true
|
||||
19
.github/workflows/draft-release.yml
vendored
19
.github/workflows/draft-release.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Draft Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 1 * *'
|
||||
# At 01:00 on the first day of every month
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Draft Release
|
||||
uses: ./.github/actions/draft-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
33
.github/workflows/publish-docker-next.yml
vendored
33
.github/workflows/publish-docker-next.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Build and Publish Next Docker Image
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: shieldsio/shields:next
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -104,8 +104,7 @@ service-definitions.yml
|
||||
!/config/local*.template.yml
|
||||
|
||||
# Gatsby
|
||||
/frontend/.cache
|
||||
/frontend/public
|
||||
/.cache
|
||||
/public
|
||||
|
||||
# Cypress
|
||||
|
||||
@@ -3,3 +3,4 @@ require:
|
||||
- '@babel/polyfill'
|
||||
- '@babel/register'
|
||||
- mocha-yaml-loader
|
||||
- frontend/mocha-ignore-pngs
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"**/*-test-helpers.js",
|
||||
"**/*-fixtures.js",
|
||||
"**/mocha-*.js",
|
||||
"**/*.test-d.ts",
|
||||
"dangerfile.js",
|
||||
"gatsby-*.js",
|
||||
"core/service-test-runner",
|
||||
@@ -22,7 +21,6 @@
|
||||
"scripts",
|
||||
"coverage",
|
||||
"build",
|
||||
".github",
|
||||
"**/public/"
|
||||
".github"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package.json
|
||||
package-lock.json
|
||||
/__snapshots__
|
||||
/.next
|
||||
.cache
|
||||
/.cache
|
||||
/api-docs
|
||||
/build
|
||||
public
|
||||
/public
|
||||
/coverage
|
||||
private/*.json
|
||||
/.nyc_output
|
||||
|
||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"EditorConfig.EditorConfig",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
||||
213
CHANGELOG.md
213
CHANGELOG.md
@@ -1,213 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
Note: this changelog is for the shields.io server. The changelog for the badge-maker NPM package is at https://github.com/badges/shields/blob/master/badge-maker/CHANGELOG.md
|
||||
|
||||
---
|
||||
|
||||
## server-2022-04-01
|
||||
|
||||
- two fixes for [packagist] schemas [#7782](https://github.com/badges/shields/issues/7782)
|
||||
- allow requireCloudflare setting to work when hosted on fly.io [#7781](https://github.com/badges/shields/issues/7781)
|
||||
- fix [pypi] badges when package has null license [#7761](https://github.com/badges/shields/issues/7761)
|
||||
- Use new CircleCI "convenience" images [#7726](https://github.com/badges/shields/issues/7726)
|
||||
- cache [pub] publisher badge for an hour [#7762](https://github.com/badges/shields/issues/7762)
|
||||
- Add a [pub] publisher badge [#7715](https://github.com/badges/shields/issues/7715)
|
||||
- Update number of badges served each month [#7723](https://github.com/badges/shields/issues/7723)
|
||||
- Switch Steam file size badge to informational color [#7722](https://github.com/badges/shields/issues/7722)
|
||||
- Make W3C and Youtube documentation links clickable [#7721](https://github.com/badges/shields/issues/7721)
|
||||
- Improve Wercker examples [#7720](https://github.com/badges/shields/issues/7720)
|
||||
- Improve Cirrus CI examples [#7719](https://github.com/badges/shields/issues/7719)
|
||||
- Support [CodeClimate] responses with multiple data items [#7716](https://github.com/badges/shields/issues/7716)
|
||||
- Delete [TeamCityCoverage] and [BowerVersion] redirectors [#7718](https://github.com/badges/shields/issues/7718)
|
||||
- Deprecate [Shippable] service [#7717](https://github.com/badges/shields/issues/7717)
|
||||
- fix: restore version comparison updates from #4173 [#4254](https://github.com/badges/shields/issues/4254)
|
||||
- [piwheels], filter out versions with no files [#7696](https://github.com/badges/shields/issues/7696)
|
||||
- set a longer cacheLength on [librariesio] badges [#7692](https://github.com/badges/shields/issues/7692)
|
||||
- improve python version formatting [#7682](https://github.com/badges/shields/issues/7682)
|
||||
- Clarify GitHub All Contributors badge [#7690](https://github.com/badges/shields/issues/7690)
|
||||
- Fix GitHub Deployments example color [#7691](https://github.com/badges/shields/issues/7691)
|
||||
- Support [HexPM] packages with no stable release [#7685](https://github.com/badges/shields/issues/7685)
|
||||
- Fix [SnykVulnerability] test [#7686](https://github.com/badges/shields/issues/7686)
|
||||
- Support for Test at Scale Badge, run [TAS] [#7612](https://github.com/badges/shields/issues/7612)
|
||||
- [packagist] api v2 support [#7681](https://github.com/badges/shields/issues/7681)
|
||||
- Add [piwheels] version badge [#7656](https://github.com/badges/shields/issues/7656)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-03-01
|
||||
|
||||
- Add [Conan] version service (#7460)
|
||||
- remove suspended [github] tokens from the pool [#7654](https://github.com/badges/shields/issues/7654)
|
||||
- generate links without trailing : if port not set [#7655](https://github.com/badges/shields/issues/7655)
|
||||
- Use the latest build status when checking docs.rs [#7613](https://github.com/badges/shields/issues/7613)
|
||||
- Remove no download handling and add API warning to [Wordpress] badges [#7606](https://github.com/badges/shields/issues/7606)
|
||||
- set a higher default cacheLength on rating/star category [#7587](https://github.com/badges/shields/issues/7587)
|
||||
- Update [amo] to use v4 API, set custom `cacheLength`s [#7586](https://github.com/badges/shields/issues/7586)
|
||||
- fix(amo): include trailing slash in API call [#7585](https://github.com/badges/shields/issues/7585)
|
||||
- fix docker image user agent [#7582](https://github.com/badges/shields/issues/7582)
|
||||
- Delete deprecated Codetally and continuousphp services [#7572](https://github.com/badges/shields/issues/7572)
|
||||
- Deprecate [Requires] service [#7571](https://github.com/badges/shields/issues/7571)
|
||||
- [AUR] Fix RPC URL [#7570](https://github.com/badges/shields/issues/7570)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-02-01
|
||||
|
||||
- [Depfu] Add support for Gitlab [#7475](https://github.com/badges/shields/issues/7475)
|
||||
- replace label in hn-user-karma with U/ [#7500](https://github.com/badges/shields/issues/7500)
|
||||
- Support [Feedz] response with multiple pages without items [#7476](https://github.com/badges/shields/issues/7476)
|
||||
- revert decamelize and humanize-string to old versions [#7449](https://github.com/badges/shields/issues/7449)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-01-01
|
||||
|
||||
- minor [reddit] improvements [#7436](https://github.com/badges/shields/issues/7436)
|
||||
- [HackerNews] Show User Karma [#7411](https://github.com/badges/shields/issues/7411)
|
||||
- [YouTube] Drop support for removed dislikes [#7410](https://github.com/badges/shields/issues/7410)
|
||||
- change closed GitHub issue color to purple [#7374](https://github.com/badges/shields/issues/7374)
|
||||
- restore cors header injection from #4171 [#4255](https://github.com/badges/shields/issues/4255)
|
||||
- [GithubPackageJson] Get version from monorepo subfolder package.json [#7350](https://github.com/badges/shields/issues/7350)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-12-01
|
||||
|
||||
- Send better user-agent values [#7309](https://github.com/badges/shields/issues/7309)
|
||||
Self-hosting users now send a user agent which indicates the server version and starts `shields (self-hosted)/` by default.
|
||||
This can be configured using the env var `USER_AGENT_BASE`
|
||||
- upgrade to node 16 [#7271](https://github.com/badges/shields/issues/7271)
|
||||
- feat: deprecate dependabot badges [#7274](https://github.com/badges/shields/issues/7274)
|
||||
- fix: npmversion tagged service test [#7269](https://github.com/badges/shields/issues/7269)
|
||||
- feat: create new Test Results category [#7218](https://github.com/badges/shields/issues/7218)
|
||||
- Migration from Request to Got for all HTTP requests is completed in this release
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-11-04
|
||||
|
||||
- migrate regularUpdate() from request-->got [#7215](https://github.com/badges/shields/issues/7215)
|
||||
- migrate github badges to use got instead of request; affects [github librariesio] [#7212](https://github.com/badges/shields/issues/7212)
|
||||
- deprecate David badges [#7197](https://github.com/badges/shields/issues/7197)
|
||||
- fix: ensure libraries.io header values are processed numerically [#7196](https://github.com/badges/shields/issues/7196)
|
||||
- Add authentication for Libraries.io-based badges, run [Libraries Bower] [#7080](https://github.com/badges/shields/issues/7080)
|
||||
- fixes and tests for pipenv helpers [#7194](https://github.com/badges/shields/issues/7194)
|
||||
- add GitLab Release badge, run all [GitLab] [#7021](https://github.com/badges/shields/issues/7021)
|
||||
- set content-length header on badge responses [#7179](https://github.com/badges/shields/issues/7179)
|
||||
- fix [github] release/tag/download schema [#7170](https://github.com/badges/shields/issues/7170)
|
||||
- Supported nested groups on [GitLabPipeline] badge [#7159](https://github.com/badges/shields/issues/7159)
|
||||
- Support nested groups on [GitLabTag] badge [#7158](https://github.com/badges/shields/issues/7158)
|
||||
- Fixing incorrect JetBrains Plugin rating values for [JetBrainsRating] [#7140](https://github.com/badges/shields/issues/7140)
|
||||
- support using release or tag name in [GitHub] Release version badge [#7075](https://github.com/badges/shields/issues/7075)
|
||||
- feat: support branches in sonar badges [#7065](https://github.com/badges/shields/issues/7065)
|
||||
- Add [Modrinth] total downloads badge [#7132](https://github.com/badges/shields/issues/7132)
|
||||
- remove [github] admin routes [#7105](https://github.com/badges/shields/issues/7105)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-10-04
|
||||
|
||||
- feat: add 2021 support to GitHub Hacktoberfest [#7086](https://github.com/badges/shields/issues/7086)
|
||||
- Add [ClearlyDefined] service [#6944](https://github.com/badges/shields/issues/6944)
|
||||
- handle null licenses in crates.io response schema, run [crates] [#7074](https://github.com/badges/shields/issues/7074)
|
||||
- [OBS] add Open Build Service service-badge [#6993](https://github.com/badges/shields/issues/6993)
|
||||
- Correction of badges url in self-hosting configuration with a custom port. Issue 7025 [#7036](https://github.com/badges/shields/issues/7036)
|
||||
- fix: support gitlab token via env var [#7023](https://github.com/badges/shields/issues/7023)
|
||||
- Add API-based support for [GitLab] badges, add new GitLab Tag badge [#6988](https://github.com/badges/shields/issues/6988)
|
||||
- [freecodecamp]: allow + symbol in username [#7016](https://github.com/badges/shields/issues/7016)
|
||||
- Rename Riot to Element in Matrix badge help [#6996](https://github.com/badges/shields/issues/6996)
|
||||
- Fixed Reddit Negative Karma Issue [#6992](https://github.com/badges/shields/issues/6992)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-09-01
|
||||
|
||||
- use multi-stage build to reduce size of docker images [#6938](https://github.com/badges/shields/issues/6938)
|
||||
- remove disableStrictSsl param from [jenkins] [#6887](https://github.com/badges/shields/issues/6887)
|
||||
- refactor(GitHubCommitActivity): switch to v4/GraphQL API [#6959](https://github.com/badges/shields/issues/6959)
|
||||
- feat: add freecodecamp badge [#6958](https://github.com/badges/shields/issues/6958)
|
||||
- use the right version of NPM in docker build [#6941](https://github.com/badges/shields/issues/6941)
|
||||
- [TwitchExtensionVersion] New badge [#6900](https://github.com/badges/shields/issues/6900)
|
||||
- enforce strict SSL checking for [coverity] [#6886](https://github.com/badges/shields/issues/6886)
|
||||
- Update self hosting docs [#6877](https://github.com/badges/shields/issues/6877)
|
||||
- Support optionalDependencies in [GithubPackageJson] [#6749](https://github.com/badges/shields/issues/6749)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-08-01
|
||||
|
||||
- use v5 API for [AUR] badges [#6836](https://github.com/badges/shields/issues/6836)
|
||||
- [Sonar] Fix invalid fetch query to sonarqube >=6.6 [#6636](https://github.com/badges/shields/issues/6636)
|
||||
- Delegate discord logo to simple-icons, which matches the current branding [#6764](https://github.com/badges/shields/issues/6764)
|
||||
- Re-apply 'Migrate request to got (part 1)' [#6755](https://github.com/badges/shields/issues/6755)
|
||||
- Delete old deprecated badges [#6756](https://github.com/badges/shields/issues/6756)
|
||||
- Replace opn-cli with open-cli [#6747](https://github.com/badges/shields/issues/6747)
|
||||
- Verify that Node 14 is installed in development [#6748](https://github.com/badges/shields/issues/6748)
|
||||
- Migrate from CommonJS to ESM [#6651](https://github.com/badges/shields/issues/6651)
|
||||
- Add Wikiapiary Extension Badge [WikiapiaryInstalls] [#6678](https://github.com/badges/shields/issues/6678)
|
||||
- deprecate [beerpay] [#6708](https://github.com/badges/shields/issues/6708)
|
||||
- deprecate [microbadger] [#6709](https://github.com/badges/shields/issues/6709)
|
||||
- [npmsioscore] Support npm score [#6630](https://github.com/badges/shields/issues/6630)
|
||||
- Add [Weblate] badges [#6677](https://github.com/badges/shields/issues/6677)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-07-01
|
||||
|
||||
- improve [MavenCentral], [MavenMetadata], and [GradlePluginPortal] [#6628](https://github.com/badges/shields/issues/6628)
|
||||
- fix: fix regex to match [codecov]'s flags [#6655](https://github.com/badges/shields/issues/6655)
|
||||
- fix usage style [#6638](https://github.com/badges/shields/issues/6638)
|
||||
- update simple-icons to v5 with by-name lookup backwards compatibility [#6591](https://github.com/badges/shields/issues/6591)
|
||||
- [GradlePluginPortal] add gradle plugin portal [#6449](https://github.com/badges/shields/issues/6449)
|
||||
- upgrade some vulnerable packages [#6569](https://github.com/badges/shields/issues/6569)
|
||||
- increase max-age for download and social badges [#6567](https://github.com/badges/shields/issues/6567)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-06-01
|
||||
|
||||
- Changed creating badges to open a new Window/Tab [#6536](https://github.com/badges/shields/issues/6536)
|
||||
- Make for-the-badge letter spacing more predictable, and rewrite layout logic [#5754](https://github.com/badges/shields/issues/5754)
|
||||
- deprecate DockerBuild service [#6529](https://github.com/badges/shields/issues/6529)
|
||||
- Remove rate limiting functionality [#6513](https://github.com/badges/shields/issues/6513)
|
||||
- [GitHub] Move to 'funding' category [#5846](https://github.com/badges/shields/issues/5846)
|
||||
- Add GitHub discussions total badge [GithubTotalDiscussions] [#6472](https://github.com/badges/shields/issues/6472)
|
||||
- Add optional query parameter (include_prereleases) to [GemVersion] [#6451](https://github.com/badges/shields/issues/6451)
|
||||
- Add [PingPong] Service [#6327](https://github.com/badges/shields/issues/6327)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-05-01
|
||||
|
||||
- Add setting which allows setting a timeout on HTTP requests
|
||||
This is configured with the new `REQUEST_TIMEOUT_SECONDS` setting. If a request takes longer
|
||||
than this number of seconds a `408 Request Timeout` response will be returned.
|
||||
- Deprecate [Bintray] service [#6423](https://github.com/badges/shields/issues/6423)
|
||||
- Support git hash in [nexus] SNAPSHOT version [#6369](https://github.com/badges/shields/issues/6369)
|
||||
- Replace 4183C4 with blue [#6366](https://github.com/badges/shields/issues/6366)
|
||||
- [Youtube] Added channel view count and subscriber count badges [#6333](https://github.com/badges/shields/issues/6333)
|
||||
- Fix Netlify badge by adding new color schema [#6340](https://github.com/badges/shields/issues/6340)
|
||||
- [REUSE] Add service badges [#6330](https://github.com/badges/shields/issues/6330)
|
||||
- Dependency updates
|
||||
|
||||
## 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)
|
||||
- [SecurityHeaders] Added a possibility for no follow redirects [#6212](https://github.com/badges/shields/issues/6212)
|
||||
- catch URL parse error in [endpoint] badge [#6214](https://github.com/badges/shields/issues/6214)
|
||||
- [Homebrew] Add homebrew downloads badge [#6209](https://github.com/badges/shields/issues/6209)
|
||||
- update [pkgreview] url [#6189](https://github.com/badges/shields/issues/6189)
|
||||
- Make [Twitch] a social badge [#6183](https://github.com/badges/shields/issues/6183)
|
||||
- update [flathub] error handling [#6185](https://github.com/badges/shields/issues/6185)
|
||||
- Add [Testspace] badges [#6162](https://github.com/badges/shields/issues/6162)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-02-01
|
||||
|
||||
- Docs.rs badge (#6098)
|
||||
- Fix feedz service in case the package page gets paginated (#6101)
|
||||
- [Bitbucket] Server Adding Auth Tokens and Resolving Pull Request api … (#6076)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-01-18
|
||||
|
||||
- Gotta start somewhere
|
||||
@@ -101,7 +101,7 @@ There are three places to get help:
|
||||
used by developers or which are widely used by developers.
|
||||
- The left-hand side of a badge should not advertise. It should be a lowercase _noun_
|
||||
succinctly describing the meaning of the right-hand side.
|
||||
- Except for badges using the `social` style, logos and links should be _turned off by
|
||||
- Except for badges using the `social` style, logos should be _turned off by
|
||||
default_.
|
||||
- Badges should not obtain data from undocumented or reverse-engineered API endpoints.
|
||||
- Badges should not obtain data by scraping web pages - these are likely to break frequently.
|
||||
@@ -131,7 +131,13 @@ Prettier before a commit by default.
|
||||
|
||||
### Tests
|
||||
|
||||
When adding or changing a service [please write tests][service-tests], and ensure the [title of your Pull Requests follows the required conventions](#running-service-tests-in-pull-requests) to ensure your tests are executed.
|
||||
When adding or changing a service [please write tests][service-tests].
|
||||
|
||||
When opening a pull request, include your service name in brackets in the pull
|
||||
request title. That way, those service tests will run in CI.
|
||||
|
||||
e.g. **[Travis] Fix timeout issues**
|
||||
|
||||
When changing other code, please add unit tests.
|
||||
|
||||
To run the integration tests, you must have redis installed and in your PATH.
|
||||
@@ -147,35 +153,3 @@ There is a [High-level code walkthrough](doc/code-walkthrough.md) describing the
|
||||
### Logos
|
||||
|
||||
We have [documentation for logo usage](doc/logos.md) which includes [contribution guidance](doc/logos.md#contributing-logos)
|
||||
|
||||
## Pull Requests
|
||||
|
||||
All code changes are incorporated via pull requests, and pull requests are always squashed into a single commit on merging. Therefore there's no requirement to squash commits within your PR, but feel free to squash or restructure the commits on your PR branch if you think it will be helpful. PRs with well structured commits are always easier to review!
|
||||
|
||||
Because all changes are pulled into the main branch via squash merges from PRs, we do **not** support overwriting any aspects of the git history once it hits our main branch. Notably this means we do not support amending commit messages, nor adjusting commit author information once merged.
|
||||
|
||||
Accordingly, it is the responsibility of contributors to review this type of information and adjust as needed before marking PRs as ready for review and merging.
|
||||
|
||||
You can review and modify your local [git configuration][git-config] via `git config`, and also find more information about amending your commit messages [here][amending-commits].
|
||||
|
||||
[git-config]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration
|
||||
[amending-commits]: https://docs.github.com/en/github/committing-changes-to-your-project/changing-a-commit-message#rewriting-the-most-recent-commit-message
|
||||
|
||||
### Running service tests in pull requests
|
||||
|
||||
The affected service names must be included in square brackets in the pull request title so that the CI engine will run those service tests. When a pull request affects multiple services, they should be separated with spaces. The test runner is case-insensitive, so they should be capitalized for readability.
|
||||
|
||||
For example:
|
||||
|
||||
- **[Travis] Fix timeout issues**
|
||||
- **[Travis Sonar] Support user token authentication**
|
||||
- **Add tests for [CRAN] and [CPAN]**
|
||||
|
||||
Note that many services are part of a "family" of related services. Depending on the changes in your PR you may need to run the tests for just a single service, or for _all_ the services within a family.
|
||||
|
||||
For example, a PR title of **[GitHubForks] Foo** will only run the service tests specifically for the GitHub Forks badge, whereas a title of **[GitHub] Foo** will run the service tests for all of the GitHub badges.
|
||||
|
||||
In the rare case when it's necessary to see the output of a full service-test
|
||||
run in a PR (all 2,000+ tests), include `[*****]` in the title. Unless all the tests pass, the build
|
||||
will fail, so likely it will be necessary to remove it and re-run the tests
|
||||
before merging.
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:16-alpine AS Builder
|
||||
FROM node:12-alpine
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
RUN mkdir /usr/src/app/private
|
||||
@@ -8,8 +8,6 @@ COPY package.json package-lock.json /usr/src/app/
|
||||
# Without the badge-maker package.json and CLI script in place, `npm ci` will fail.
|
||||
COPY badge-maker /usr/src/app/badge-maker/
|
||||
|
||||
RUN apk add python3 make g++
|
||||
RUN npm install -g "npm@>=7"
|
||||
# We need dev deps to build the front end. We don't need Cypress, though.
|
||||
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci
|
||||
|
||||
@@ -18,18 +16,9 @@ RUN npm run build
|
||||
RUN npm prune --production
|
||||
RUN npm cache clean --force
|
||||
|
||||
# Use multi-stage build to reduce size
|
||||
FROM node:16-alpine
|
||||
|
||||
ARG version=dev
|
||||
ENV DOCKER_SHIELDS_VERSION=$version
|
||||
|
||||
# Run the server using production configs.
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --from=Builder /usr/src/app /usr/src/app
|
||||
|
||||
CMD node server
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
70
Makefile
Normal file
70
Makefile
Normal file
@@ -0,0 +1,70 @@
|
||||
SHELL:=/bin/bash
|
||||
|
||||
SERVER_TMP=${TMPDIR}shields-server-deploy
|
||||
FRONTEND_TMP=${TMPDIR}shields-frontend-deploy
|
||||
|
||||
# This branch is reserved for the deploy process and should not be used for
|
||||
# development. The deploy script will clobber it. To avoid accidentally
|
||||
# pushing secrets to GitHub, this branch is configured to reject pushes.
|
||||
WORKING_BRANCH=server-deploy-working-branch
|
||||
|
||||
all: test
|
||||
|
||||
deploy: deploy-s0 deploy-s1 deploy-s2 clean-server-deploy deploy-gh-pages deploy-gh-pages-clean
|
||||
|
||||
deploy-s0: prepare-server-deploy push-s0
|
||||
deploy-s1: prepare-server-deploy push-s1
|
||||
deploy-s2: prepare-server-deploy push-s2
|
||||
|
||||
prepare-server-deploy:
|
||||
# Ship a copy of the front end to each server for debugging.
|
||||
# https://github.com/badges/shields/issues/1220
|
||||
INCLUDE_DEV_PAGES=false \
|
||||
npm run build
|
||||
rm -rf ${SERVER_TMP}
|
||||
git worktree prune
|
||||
git worktree add -B ${WORKING_BRANCH} ${SERVER_TMP}
|
||||
cp -r public ${SERVER_TMP}
|
||||
git -C ${SERVER_TMP} add -f public/
|
||||
git -C ${SERVER_TMP} commit --no-verify -m '[DEPLOY] Add frontend for debugging'
|
||||
cp config/local-shields-io-production.yml ${SERVER_TMP}/config/
|
||||
git -C ${SERVER_TMP} add -f config/local-shields-io-production.yml
|
||||
git -C ${SERVER_TMP} commit --no-verify -m '[DEPLOY] MUST NOT BE ON GITHUB'
|
||||
|
||||
clean-server-deploy:
|
||||
rm -rf ${SERVER_TMP}
|
||||
git worktree prune
|
||||
|
||||
push-s0:
|
||||
git push -f s0 ${WORKING_BRANCH}:master
|
||||
|
||||
push-s1:
|
||||
git push -f s1 ${WORKING_BRANCH}:master
|
||||
|
||||
push-s2:
|
||||
git push -f s2 ${WORKING_BRANCH}:master
|
||||
|
||||
deploy-gh-pages:
|
||||
rm -rf ${FRONTEND_TMP}
|
||||
git worktree prune
|
||||
GATSBY_BASE_URL=https://img.shields.io \
|
||||
INCLUDE_DEV_PAGES=false \
|
||||
npm run build
|
||||
git worktree add -B gh-pages ${FRONTEND_TMP}
|
||||
git -C ${FRONTEND_TMP} ls-files | xargs git -C ${FRONTEND_TMP} rm
|
||||
git -C ${FRONTEND_TMP} commit --no-verify -m '[DEPLOY] Completely clean the index'
|
||||
cp -r public/* ${FRONTEND_TMP}
|
||||
echo shields.io > ${FRONTEND_TMP}/CNAME
|
||||
touch ${FRONTEND_TMP}/.nojekyll
|
||||
git -C ${FRONTEND_TMP} add .
|
||||
git -C ${FRONTEND_TMP} commit --no-verify -m '[DEPLOY] Add built site'
|
||||
git push -f origin gh-pages
|
||||
|
||||
deploy-gh-pages-clean:
|
||||
rm -rf ${FRONTEND_TMP}
|
||||
git worktree prune
|
||||
|
||||
test:
|
||||
npm test
|
||||
|
||||
.PHONY: all deploy prepare-server-deploy clean-server-deploy deploy-s0 deploy-s1 deploy-s2 push-s0 push-s1 push-s2 deploy-gh-pages deploy-gh-pages-clean deploy-heroku setup redis test
|
||||
62
README.md
62
README.md
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/badges/shields/master/readme-logo.svg?sanitize=true"
|
||||
<img src="https://raw.githubusercontent.com/badges/shields/master/frontend/images/logo.svg?sanitize=true"
|
||||
height="130">
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -22,6 +22,9 @@
|
||||
<a href="https://lgtm.com/projects/g/badges/shields/alerts/">
|
||||
<img src="https://img.shields.io/lgtm/alerts/g/badges/shields"
|
||||
alt="Total alerts"/></a>
|
||||
<a href="https://github.com/badges/shields/compare/gh-pages...master">
|
||||
<img src="https://img.shields.io/github/commits-since/badges/shields/gh-pages?label=commits%20to%20be%20deployed"
|
||||
alt="commits to be deployed"></a>
|
||||
<a href="https://discord.gg/HjJCwm5">
|
||||
<img src="https://img.shields.io/discord/308323056592486420?logo=discord"
|
||||
alt="chat on Discord"></a>
|
||||
@@ -35,13 +38,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 870 million images and is used by some of the
|
||||
world's most popular open-source projects, [VS Code][vscode], [Vue.js][vue]
|
||||
and [Bootstrap][bootstrap] to name a few.
|
||||
|
||||
[vscode]: https://github.com/Microsoft/vscode
|
||||
[vue]: https://github.com/vuejs/vue
|
||||
[bootstrap]: https://github.com/twbs/bootstrap
|
||||
Every month it serves over 470 million images.
|
||||
|
||||
This repo hosts:
|
||||
|
||||
@@ -73,7 +70,7 @@ This repo hosts:
|
||||
[Make your own badges!][custom badges]
|
||||
(Quick example: `https://img.shields.io/badge/left-right-f39f37`)
|
||||
|
||||
[custom badges]: https://shields.io/#your-badge
|
||||
[custom badges]: http://shields.io/#your-badge
|
||||
|
||||
### Quickstart
|
||||
|
||||
@@ -89,20 +86,20 @@ and pull requests! You can peruse the [contributing guidelines][contributing].
|
||||
When adding or changing a service [please add tests][service-tests].
|
||||
|
||||
This project has quite a backlog of suggestions! If you're new to the project,
|
||||
maybe you'd like to open a pull request to address one of them.
|
||||
|
||||
You can read a [tutorial on how to add a badge][tutorial].
|
||||
maybe you'd like to open a pull request to address one of them:
|
||||
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
|
||||
You can read a [tutorial on how to add a badge][tutorial].
|
||||
|
||||
[service-tests]: https://github.com/badges/shields/blob/master/doc/service-tests.md
|
||||
[tutorial]: https://github.com/badges/shields/blob/master/doc/TUTORIAL.md
|
||||
[contributing]: https://github.com/badges/shields/blob/master/CONTRIBUTING.md
|
||||
[tutorial]: doc/TUTORIAL.md
|
||||
[contributing]: CONTRIBUTING.md
|
||||
|
||||
## Development
|
||||
|
||||
1. Install Node 16 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 16 and 17.
|
||||
1. Install Node 12 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 12 and 14.
|
||||
2. Clone this repository.
|
||||
3. Run `npm ci` to install the dependencies.
|
||||
4. Run `npm start` to start the badge server and the frontend dev server.
|
||||
@@ -142,9 +139,9 @@ Daily tests, including a full run of the service tests and overall code coverage
|
||||
[gitpod]: https://www.gitpod.io/
|
||||
[snapshot tests]: https://glebbahmutov.com/blog/snapshot-testing/
|
||||
[prometheus]: https://prometheus.io/
|
||||
[prometheus configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#prometheus
|
||||
[prometheus configuration]: doc/self-hosting.md#prometheus
|
||||
[sentry]: https://sentry.io/
|
||||
[sentry configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#sentry
|
||||
[sentry configuration]: doc/self-hosting.md#sentry
|
||||
[daily-tests]: https://github.com/badges/daily-tests
|
||||
[nodemon]: https://nodemon.io/
|
||||
[nodemon debug]: https://github.com/Microsoft/vscode-recipes/tree/master/nodemon
|
||||
@@ -154,22 +151,7 @@ Daily tests, including a full run of the service tests and overall code coverage
|
||||
|
||||
There is documentation about [hosting your own server][self-hosting].
|
||||
|
||||
[self-hosting]: https://github.com/badges/shields/blob/master/doc/self-hosting.md
|
||||
|
||||
## Related projects
|
||||
|
||||
[](https://awesome.re)
|
||||
|
||||
Status badges are used widely across open-source and private software projects.
|
||||
Academics have studied the "signal" badges provide about software project
|
||||
quality. There are many existing libraries for rendering these badges, and
|
||||
alternatives to the hosted Shields badge service. [awesome-badges][] is a
|
||||
curated collection of such resources.
|
||||
[Contributions][contributing to awesome-badges] may be considered there.
|
||||
(The presence of a project in that collection should not be interpreted as an endorsement nor promotion from the Shields project)
|
||||
|
||||
[awesome-badges]: https://github.com/badges/awesome-badges
|
||||
[contributing to awesome-badges]: https://github.com/badges/awesome-badges/blob/main/CONTRIBUTING.md
|
||||
[self-hosting]: doc/self-hosting.md
|
||||
|
||||
## History
|
||||
|
||||
@@ -194,8 +176,8 @@ You can read more about [the project's inception][thread],
|
||||
[olivierlacan]: https://github.com/olivierlacan
|
||||
[espadrine]: https://github.com/espadrine
|
||||
[old-gh-badges]: https://github.com/badges/gh-badges
|
||||
[motivation]: https://github.com/badges/shields/blob/master/spec/motivation.md
|
||||
[spec]: https://github.com/badges/shields/blob/master/spec/SPECIFICATION.md
|
||||
[motivation]: spec/motivation.md
|
||||
[spec]: spec/SPECIFICATION.md
|
||||
[thread]: https://github.com/h5bp/lazyweb-requests/issues/150
|
||||
|
||||
## Project leaders
|
||||
@@ -222,6 +204,14 @@ Alumni:
|
||||
- [espadrine](https://github.com/espadrine)
|
||||
- [olivierlacan](https://github.com/olivierlacan)
|
||||
|
||||
## Related projects
|
||||
|
||||
- [poser PHP library][poser]
|
||||
- [pybadges python library][pybadges]
|
||||
|
||||
[poser]: https://github.com/badges/poser
|
||||
[pybadges]: https://github.com/google/pybadges
|
||||
|
||||
## License
|
||||
|
||||
All assets and code are under the [CC0 LICENSE](LICENSE) and in the public
|
||||
|
||||
@@ -7,10 +7,10 @@ Please follow this guidance when reporting security issues affecting:
|
||||
- [Shields.io](https://shields.io)
|
||||
- [Raster.shields.io](https://raster.shields.io)
|
||||
- Self-hosted Shields instances
|
||||
- The [squint](https://github.com/badges/squint) raster proxy
|
||||
- The [svg-to-image-proxy](https://www.npmjs.com/package/svg-to-image-proxy) NPM package
|
||||
- The [badge-maker](https://www.npmjs.com/package/badge-maker) NPM package
|
||||
|
||||
The [gh-badges](https://www.npmjs.com/package/gh-badges) and [svg-to-image-proxy](https://www.npmjs.com/package/svg-to-image-proxy) NPM packages are now deprecated and will no longer receive fixes for bugs or security issues.
|
||||
The [gh-badges](https://www.npmjs.com/package/gh-badges) NPM package is now deprecated and will no longer receive fixes for bugs or security issues.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
6
app.json
6
app.json
@@ -4,7 +4,7 @@
|
||||
"keywords": ["badge", "github", "svg", "status"],
|
||||
"website": "https://shields.io/",
|
||||
"repository": "https://github.com/badges/shields",
|
||||
"logo": "https://shields.io/favicon.png",
|
||||
"logo": "http://shields.io/favicon.png",
|
||||
"env": {
|
||||
"CYPRESS_INSTALL_BINARY": {
|
||||
"description": "Disable the cypress binary installation",
|
||||
@@ -31,10 +31,6 @@
|
||||
"TWITCH_CLIENT_SECRET": {
|
||||
"description": "Configure the client secret to be used for the Twitch service.",
|
||||
"required": false
|
||||
},
|
||||
"WEBLATE_API_KEY": {
|
||||
"description": "Configure the API key to be used for the Weblate service.",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"formation": {
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 4.0.0 [WIP]
|
||||
|
||||
- Drop compatibility with Node 10
|
||||
|
||||
## 3.3.1
|
||||
|
||||
- Improve font measuring in for-the-badge and social styles
|
||||
- Make for-the-badge letter spacing more predictable
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Readability improvements: a dark font color is automatically used when the badge's background is too light. For example: 
|
||||
- Better CSS color compliance: thanks to a switch from _is-css-color_ to _[css-color-converter](https://www.npmjs.com/package/css-color-converter)_, you can use a wider range of color formats from the latest CSS specification, for example `rgb(0 255 0)`
|
||||
- Less dependencies: _badge-maker_ no longer depends on _camelcase_
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Accessibility improvements: Help users of assistive technologies to read the badges when used inline
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const { fromString } = require('css-color-converter')
|
||||
const cssColorConverter = require('css-color-converter')
|
||||
|
||||
// When updating these, be sure also to update the list in `badge-maker/README.md`.
|
||||
const namedColors = {
|
||||
@@ -38,7 +38,10 @@ function isHexColor(s = '') {
|
||||
}
|
||||
|
||||
function isCSSColor(color) {
|
||||
return typeof color === 'string' && fromString(color.trim())
|
||||
return (
|
||||
typeof color === 'string' &&
|
||||
typeof cssColorConverter(color.trim()).toRgbaArray() !== 'undefined'
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeColor(color) {
|
||||
@@ -70,9 +73,8 @@ function toSvgColor(color) {
|
||||
|
||||
function brightness(color) {
|
||||
if (color) {
|
||||
const cssColor = fromString(color)
|
||||
if (cssColor) {
|
||||
const rgb = cssColor.toRgbaArray()
|
||||
const rgb = cssColorConverter(color).toRgbaArray()
|
||||
if (rgb) {
|
||||
return +((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 255000).toFixed(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ test(normalizeColor, () => {
|
||||
given(' blue ').expect(' blue ')
|
||||
given('rgb(100%, 200%, 222%)').expect('rgb(100%, 200%, 222%)')
|
||||
given('rgb(122, 200, 222)').expect('rgb(122, 200, 222)')
|
||||
given('rgb(122, 200, 222, 1)').expect('rgb(122, 200, 222, 1)')
|
||||
given('rgb(100%, 200, 222)').expect('rgb(100%, 200, 222)')
|
||||
given('rgba(100, 20, 111, 1)').expect('rgba(100, 20, 111, 1)')
|
||||
given('hsl(122, 200%, 222%)').expect('hsl(122, 200%, 222%)')
|
||||
given('hsla(122, 200%, 222%, 1)').expect('hsla(122, 200%, 222%, 1)')
|
||||
@@ -46,8 +46,8 @@ test(normalizeColor, () => {
|
||||
given(''),
|
||||
given('not-a-color'),
|
||||
given('#ABCFGH'),
|
||||
given('rgb(122, 200, 222, 1)'),
|
||||
given('rgb(-100, 20, 111)'),
|
||||
given('rgb(100%, 200, 222)'),
|
||||
given('rgba(-100, 20, 111, 1.1)'),
|
||||
given('hsl(122, 200, 222, 1)'),
|
||||
given('hsl(122, 200, 222)'),
|
||||
|
||||
@@ -51,8 +51,14 @@ function _clean(format) {
|
||||
}
|
||||
})
|
||||
|
||||
// Legacy.
|
||||
cleaned.label = cleaned.label || ''
|
||||
// convert "public" format to "internal" format
|
||||
cleaned.text = [cleaned.label || '', cleaned.message]
|
||||
delete cleaned.label
|
||||
delete cleaned.message
|
||||
if ('style' in cleaned) {
|
||||
cleaned.template = cleaned.style
|
||||
delete cleaned.style
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
const { normalizeColor, toSvgColor } = require('./color')
|
||||
const badgeRenderers = require('./badge-renderers')
|
||||
const { stripXmlWhitespace } = require('./xml')
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
@@ -10,9 +9,8 @@ it is likely this will impact on the package's public interface in index.js
|
||||
*/
|
||||
module.exports = function makeBadge({
|
||||
format,
|
||||
style = 'flat',
|
||||
label,
|
||||
message,
|
||||
template = 'flat',
|
||||
text,
|
||||
color,
|
||||
labelColor,
|
||||
logo,
|
||||
@@ -21,8 +19,9 @@ module.exports = function makeBadge({
|
||||
links = ['', ''],
|
||||
}) {
|
||||
// String coercion and whitespace removal.
|
||||
label = `${label}`.trim()
|
||||
message = `${message}`.trim()
|
||||
text = text.map(value => `${value}`.trim())
|
||||
|
||||
const [label, message] = text
|
||||
|
||||
// This ought to be the responsibility of the server, not `makeBadge`.
|
||||
if (format === 'json') {
|
||||
@@ -40,24 +39,23 @@ module.exports = function makeBadge({
|
||||
})
|
||||
}
|
||||
|
||||
const render = badgeRenderers[style]
|
||||
const render = badgeRenderers[template]
|
||||
if (!render) {
|
||||
throw new Error(`Unknown badge style: '${style}'`)
|
||||
throw new Error(`Unknown template: '${template}'`)
|
||||
}
|
||||
|
||||
logoWidth = +logoWidth || (logo ? 14 : 0)
|
||||
|
||||
return stripXmlWhitespace(
|
||||
render({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
})
|
||||
)
|
||||
return render({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
minify: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,18 +4,12 @@ const { test, given, forCases } = require('sazerac')
|
||||
const { expect } = require('chai')
|
||||
const snapshot = require('snap-shot-it')
|
||||
const isSvg = require('is-svg')
|
||||
const prettier = require('prettier')
|
||||
const makeBadge = require('./make-badge')
|
||||
|
||||
function expectBadgeToMatchSnapshot(format) {
|
||||
snapshot(prettier.format(makeBadge(format), { parser: 'html' }))
|
||||
}
|
||||
|
||||
function testColor(color = '', colorAttr = 'color') {
|
||||
return JSON.parse(
|
||||
makeBadge({
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
text: ['name', 'Bob'],
|
||||
[colorAttr]: color,
|
||||
format: 'json',
|
||||
})
|
||||
@@ -40,14 +34,10 @@ describe('The badge generator', function () {
|
||||
]).expect('#abc123')
|
||||
// valid rgb(a)
|
||||
given('rgb(0,128,255)').expect('rgb(0,128,255)')
|
||||
given('rgb(220,128,255,0.5)').expect('rgb(220,128,255,0.5)')
|
||||
given('rgba(0,0,255)').expect('rgba(0,0,255)')
|
||||
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)')
|
||||
// valid hsl(a)
|
||||
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)')
|
||||
given('hsl(360,50%,50%,0.5)').expect('hsl(360,50%,50%,0.5)')
|
||||
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)')
|
||||
given('hsla(0,50%,101%)').expect('hsla(0,50%,101%)')
|
||||
// CSS named color.
|
||||
given('papayawhip').expect('papayawhip')
|
||||
// Shields named color.
|
||||
@@ -63,6 +53,12 @@ describe('The badge generator', function () {
|
||||
// invalid hex
|
||||
given('#123red'), // contains letter above F
|
||||
given('#red'), // contains letter above F
|
||||
// invalid rgb(a)
|
||||
given('rgb(220,128,255,0.5)'), // has alpha
|
||||
given('rgba(0,0,255)'), // no alpha
|
||||
// invalid hsl(a)
|
||||
given('hsl(360,50%,50%,0.5)'), // has alpha
|
||||
given('hsla(0,50%,101%)'), // no alpha
|
||||
// neither a css named color nor colorscheme
|
||||
given('notacolor'),
|
||||
given('bluish'),
|
||||
@@ -81,26 +77,23 @@ describe('The badge generator', function () {
|
||||
|
||||
describe('SVG', function () {
|
||||
it('should produce SVG', function () {
|
||||
expect(makeBadge({ label: 'cactus', message: 'grown', format: 'svg' }))
|
||||
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
|
||||
expect(svg)
|
||||
.to.satisfy(isSvg)
|
||||
.and.to.include('cactus')
|
||||
.and.to.include('grown')
|
||||
})
|
||||
|
||||
it('should match snapshot', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
})
|
||||
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
|
||||
snapshot(svg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('JSON', function () {
|
||||
it('should produce the expected JSON', function () {
|
||||
const json = makeBadge({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'json',
|
||||
links: ['https://example.com/', 'https://other.example.com/'],
|
||||
})
|
||||
@@ -113,535 +106,484 @@ describe('The badge generator', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('should replace undefined svg badge style with "flat"', function () {
|
||||
it('should replace undefined svg template with "flat"', function () {
|
||||
const jsonBadgeWithUnknownStyle = makeBadge({
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
text: ['name', 'Bob'],
|
||||
format: 'svg',
|
||||
})
|
||||
const jsonBadgeWithDefaultStyle = makeBadge({
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
text: ['name', 'Bob'],
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
template: 'flat',
|
||||
})
|
||||
expect(jsonBadgeWithUnknownStyle)
|
||||
.to.equal(jsonBadgeWithDefaultStyle)
|
||||
.and.to.satisfy(isSvg)
|
||||
})
|
||||
|
||||
it('should fail with unknown svg badge style', function () {
|
||||
it('should fail with unknown svg template', function () {
|
||||
expect(() =>
|
||||
makeBadge({
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
text: ['name', 'Bob'],
|
||||
format: 'svg',
|
||||
style: 'unknown_style',
|
||||
template: 'unknown_style',
|
||||
})
|
||||
).to.throw(Error, "Unknown badge style: 'unknown_style'")
|
||||
).to.throw(Error, "Unknown template: 'unknown_style'")
|
||||
})
|
||||
})
|
||||
|
||||
describe('"flat" template badge generation', function () {
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('"flat-square" template badge generation', function () {
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('"plastic" template badge generation', function () {
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('"for-the-badge" template badge generation', function () {
|
||||
// https://github.com/badges/shields/issues/1280
|
||||
it('numbers should produce a string', function () {
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 1998,
|
||||
message: 1999,
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
})
|
||||
)
|
||||
.to.include('1998')
|
||||
.and.to.include('1999')
|
||||
const svg = makeBadge({
|
||||
text: [1998, 1999],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
})
|
||||
expect(svg).to.include('1998').and.to.include('1999')
|
||||
})
|
||||
|
||||
it('lowercase/mixedcase string should produce uppercase string', function () {
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 'Label',
|
||||
message: '1 string',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
})
|
||||
)
|
||||
.to.include('LABEL')
|
||||
.and.to.include('1 STRING')
|
||||
const svg = makeBadge({
|
||||
text: ['Label', '1 string'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
})
|
||||
expect(svg).to.include('LABEL').and.to.include('1 STRING')
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('"social" template badge generation', function () {
|
||||
it('should produce capitalized string for badge key', function () {
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 'some-key',
|
||||
message: 'some-value',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
})
|
||||
)
|
||||
.to.include('Some-key')
|
||||
.and.to.include('some-value')
|
||||
const svg = makeBadge({
|
||||
text: ['some-key', 'some-value'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
})
|
||||
expect(svg).to.include('Some-key').and.to.include('some-value')
|
||||
})
|
||||
|
||||
// https://github.com/badges/shields/issues/1606
|
||||
it('should handle empty strings used as badge keys', function () {
|
||||
expect(
|
||||
makeBadge({
|
||||
label: '',
|
||||
message: 'some-value',
|
||||
format: 'json',
|
||||
style: 'social',
|
||||
})
|
||||
)
|
||||
.to.include('""')
|
||||
.and.to.include('some-value')
|
||||
const svg = makeBadge({
|
||||
text: ['', 'some-value'],
|
||||
format: 'json',
|
||||
template: 'social',
|
||||
})
|
||||
expect(svg).to.include('""').and.to.include('some-value')
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('badges with logos should always produce the same badge', function () {
|
||||
it('badge with logo', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'label',
|
||||
message: 'message',
|
||||
const svg = makeBadge({
|
||||
text: ['label', 'message'],
|
||||
format: 'svg',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(svg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('text colors', function () {
|
||||
it('should use black text when the label color is light', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should use black text when the message color is light', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
function escapeXml(s) {
|
||||
if (typeof s === 'number') {
|
||||
return s
|
||||
} else if (s === undefined || typeof s !== 'string') {
|
||||
return undefined
|
||||
} else {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of an XML element
|
||||
*/
|
||||
class XmlElement {
|
||||
/**
|
||||
* Xml Element Constructor
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.name
|
||||
* Name of the XML tag
|
||||
* @param {Array.<string|module:badge-maker/lib/xml~XmlElement>} [attrs.content=[]]
|
||||
* Array of objects to render inside the tag. content may contain a mix of
|
||||
* string and XmlElement objects. If content is `[]` or ommitted the
|
||||
* element will be rendered as a self-closing element.
|
||||
* @param {object} [attrs.attrs={}]
|
||||
* Object representing the tag's attributes as name/value pairs
|
||||
*/
|
||||
constructor({ name, content = [], attrs = {} }) {
|
||||
this.name = name
|
||||
this.content = content
|
||||
this.attrs = attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the XML element to a string, applying appropriate escaping
|
||||
*
|
||||
* @returns {string} String representation of the XML element
|
||||
*/
|
||||
render() {
|
||||
const attrsStr = Object.entries(this.attrs)
|
||||
.map(([k, v]) => ` ${k}="${escapeXml(v)}"`)
|
||||
.join('')
|
||||
if (this.content.length > 0) {
|
||||
const content = this.content
|
||||
.map(function (el) {
|
||||
if (typeof el.render === 'function') {
|
||||
return el.render()
|
||||
} else {
|
||||
return escapeXml(el)
|
||||
}
|
||||
})
|
||||
.join(' ')
|
||||
return stripXmlWhitespace(
|
||||
`<${this.name}${attrsStr}>${content}</${this.name}>`
|
||||
)
|
||||
}
|
||||
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience class. Sometimes it is useful to return an object that behaves
|
||||
* like an XmlElement but renders multiple XML tags (not wrapped in a <g>).
|
||||
*/
|
||||
class ElementList {
|
||||
constructor({ content = [] }) {
|
||||
this.content = content
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.content.reduce(
|
||||
(acc, el) =>
|
||||
typeof el.render === 'function'
|
||||
? acc + el.render()
|
||||
: acc + escapeXml(el),
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { escapeXml, stripXmlWhitespace, XmlElement, ElementList }
|
||||
@@ -1,50 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const { XmlElement } = require('./xml')
|
||||
|
||||
function testRender(params) {
|
||||
return new XmlElement(params).render()
|
||||
}
|
||||
|
||||
describe('XmlElement class', function () {
|
||||
test(testRender, () => {
|
||||
given({ name: 'tag' }).expect('<tag/>')
|
||||
|
||||
given({ name: 'tag', content: ['text'] }).expect('<tag>text</tag>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
content: ['not xml>>>', 'text', new XmlElement({ name: 'xml' })],
|
||||
}).expect('<tag>not xml>>> text <xml/></tag>')
|
||||
|
||||
given({
|
||||
name: 'nested1',
|
||||
content: [
|
||||
new XmlElement({
|
||||
name: 'nested2',
|
||||
content: [new XmlElement({ name: 'nested3' })],
|
||||
}),
|
||||
],
|
||||
}).expect('<nested1><nested2><nested3/></nested2></nested1>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
attrs: {
|
||||
int: 47,
|
||||
text: 'text',
|
||||
escape: '<escape me>',
|
||||
},
|
||||
}).expect('<tag int="47" text="text" escape="<escape me>"/>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
content: ['text'],
|
||||
attrs: {
|
||||
int: 47,
|
||||
text: 'text',
|
||||
escape: '<escape me>',
|
||||
},
|
||||
}).expect('<tag int="47" text="text" escape="<escape me>">text</tag>')
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "3.3.1",
|
||||
"version": "3.2.0",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
@@ -21,13 +21,13 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/badges/shields/issues"
|
||||
},
|
||||
"homepage": "https://shields.io",
|
||||
"homepage": "http://shields.io",
|
||||
"bin": {
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12",
|
||||
"npm": ">= 6"
|
||||
"node": ">= 10",
|
||||
"npm": ">= 5"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
@@ -35,8 +35,8 @@
|
||||
"logo": "https://opencollective.com/opencollective/logo.txt"
|
||||
},
|
||||
"dependencies": {
|
||||
"anafanafo": "2.0.0",
|
||||
"css-color-converter": "^2.0.0"
|
||||
"anafanafo": "^1.0.0",
|
||||
"css-color-converter": "^1.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo 'Run tests from parent dir'; false"
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
key: 'HTTPS_KEY'
|
||||
cert: 'HTTPS_CRT'
|
||||
|
||||
redirectUrl: 'REDIRECT_URI'
|
||||
redirectUri: 'REDIRECT_URI'
|
||||
|
||||
rasterUrl: 'RASTER_URL'
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
__name: 'ALLOWED_ORIGIN'
|
||||
__format: 'json'
|
||||
|
||||
persistence:
|
||||
dir: 'PERSISTENCE_DIR'
|
||||
|
||||
services:
|
||||
bitbucketServer:
|
||||
authorizedOrigins: 'BITBUCKET_SERVER_ORIGINS'
|
||||
@@ -40,8 +43,6 @@ public:
|
||||
debug:
|
||||
enabled: 'GITHUB_DEBUG_ENABLED'
|
||||
intervalSeconds: 'GITHUB_DEBUG_INTERVAL_SECONDS'
|
||||
gitlab:
|
||||
authorizedOrigins: 'GITLAB_ORIGINS'
|
||||
jenkins:
|
||||
authorizedOrigins: 'JENKINS_ORIGINS'
|
||||
jira:
|
||||
@@ -50,29 +51,23 @@ public:
|
||||
authorizedOrigins: 'NEXUS_ORIGINS'
|
||||
npm:
|
||||
authorizedOrigins: 'NPM_ORIGINS'
|
||||
obs:
|
||||
authorizedOrigins: 'OBS_ORIGINS'
|
||||
sonar:
|
||||
authorizedOrigins: 'SONAR_ORIGINS'
|
||||
teamcity:
|
||||
authorizedOrigins: 'TEAMCITY_ORIGINS'
|
||||
weblate:
|
||||
authorizedOrigins: 'WEBLATE_ORIGINS'
|
||||
trace: 'TRACE_SERVICES'
|
||||
|
||||
cacheHeaders:
|
||||
defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS'
|
||||
|
||||
rateLimit: 'RATE_LIMIT'
|
||||
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
userAgentBase: 'USER_AGENT_BASE'
|
||||
|
||||
requestTimeoutSeconds: 'REQUEST_TIMEOUT_SECONDS'
|
||||
requestTimeoutMaxAgeSeconds: 'REQUEST_TIMEOUT_MAX_AGE_SECONDS'
|
||||
|
||||
requireCloudflare: 'REQUIRE_CLOUDFLARE'
|
||||
|
||||
private:
|
||||
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
|
||||
bintray_user: 'BINTRAY_USER'
|
||||
bintray_apikey: 'BINTRAY_API_KEY'
|
||||
bitbucket_username: 'BITBUCKET_USER'
|
||||
bitbucket_password: 'BITBUCKET_PASS'
|
||||
bitbucket_server_username: 'BITBUCKET_SERVER_USER'
|
||||
@@ -82,19 +77,16 @@ private:
|
||||
gh_client_id: 'GH_CLIENT_ID'
|
||||
gh_client_secret: 'GH_CLIENT_SECRET'
|
||||
gh_token: 'GH_TOKEN'
|
||||
gitlab_token: 'GITLAB_TOKEN'
|
||||
jenkins_user: 'JENKINS_USER'
|
||||
jenkins_pass: 'JENKINS_PASS'
|
||||
jira_user: 'JIRA_USER'
|
||||
jira_pass: 'JIRA_PASS'
|
||||
librariesio_tokens: 'LIBRARIESIO_TOKENS'
|
||||
nexus_user: 'NEXUS_USER'
|
||||
nexus_pass: 'NEXUS_PASS'
|
||||
npm_token: 'NPM_TOKEN'
|
||||
obs_user: 'OBS_USER'
|
||||
obs_pass: 'OBS_PASS'
|
||||
redis_url: 'REDIS_URL'
|
||||
sentry_dsn: 'SENTRY_DSN'
|
||||
shields_secret: 'SHIELDS_SECRET'
|
||||
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'
|
||||
sl_insight_apiToken: 'SL_INSIGHT_API_TOKEN'
|
||||
sonarqube_token: 'SONARQUBE_TOKEN'
|
||||
@@ -105,5 +97,4 @@ private:
|
||||
wheelmap_token: 'WHEELMAP_TOKEN'
|
||||
influx_username: 'INFLUX_USERNAME'
|
||||
influx_password: 'INFLUX_PASSWORD'
|
||||
weblate_api_key: 'WEBLATE_API_KEY'
|
||||
youtube_api_key: 'YOUTUBE_API_KEY'
|
||||
|
||||
@@ -16,29 +16,24 @@ public:
|
||||
cors:
|
||||
allowedOrigin: []
|
||||
|
||||
persistence:
|
||||
dir: './private'
|
||||
|
||||
services:
|
||||
github:
|
||||
baseUri: 'https://api.github.com/'
|
||||
debug:
|
||||
enabled: false
|
||||
intervalSeconds: 200
|
||||
obs:
|
||||
authorizedOrigins: 'https://api.opensuse.org'
|
||||
weblate:
|
||||
authorizedOrigins: 'https://hosted.weblate.org'
|
||||
trace: false
|
||||
|
||||
cacheHeaders:
|
||||
defaultCacheLengthSeconds: 120
|
||||
|
||||
rateLimit: true
|
||||
|
||||
handleInternalErrors: true
|
||||
|
||||
fetchLimit: '10MB'
|
||||
userAgentBase: 'shields (self-hosted)'
|
||||
|
||||
requestTimeoutSeconds: 120
|
||||
requestTimeoutMaxAgeSeconds: 30
|
||||
|
||||
requireCloudflare: false
|
||||
|
||||
private: {}
|
||||
|
||||
@@ -5,4 +5,6 @@ public:
|
||||
cors:
|
||||
allowedOrigin: ['http://localhost:3000']
|
||||
|
||||
rateLimit: false
|
||||
|
||||
handleInternalErrors: false
|
||||
|
||||
@@ -3,7 +3,6 @@ private:
|
||||
discord_bot_token: ...
|
||||
gh_client_id: ...
|
||||
gh_client_secret: ...
|
||||
gitlab_token: ...
|
||||
redis_url: ...
|
||||
sentry_dsn: ...
|
||||
shields_secret: ...
|
||||
@@ -11,6 +10,5 @@ private:
|
||||
sl_insight_apiToken: ...
|
||||
twitch_client_id: ...
|
||||
twitch_client_secret: ...
|
||||
weblate_api_key: ...
|
||||
wheelmap_token: ...
|
||||
youtube_api_key: ...
|
||||
|
||||
@@ -5,11 +5,7 @@ private:
|
||||
# you can also set these values through environment variables, which may be
|
||||
# preferable for self hosting.
|
||||
gh_token: '...'
|
||||
gitlab_token: '...'
|
||||
obs_user: '...'
|
||||
obs_pass: '...'
|
||||
twitch_client_id: '...'
|
||||
twitch_client_secret: '...'
|
||||
weblate_api_key: '...'
|
||||
wheelmap_token: '...'
|
||||
youtube_api_key: '...'
|
||||
|
||||
@@ -15,4 +15,10 @@ public:
|
||||
cors:
|
||||
allowedOrigin: ['http://shields.io', 'https://shields.io']
|
||||
|
||||
redirectUrl: 'https://shields.io/'
|
||||
|
||||
rasterUrl: 'https://raster.shields.io'
|
||||
|
||||
private:
|
||||
# These are not really private; they should be moved to `public`.
|
||||
shields_ips: ['192.99.59.72', '51.254.114.150', '149.56.96.133']
|
||||
|
||||
@@ -3,6 +3,8 @@ public:
|
||||
address: 'localhost'
|
||||
port: 1111
|
||||
|
||||
rateLimit: false
|
||||
|
||||
redirectUrl: 'http://frontend.example.test'
|
||||
|
||||
rasterUrl: 'http://raster.example.test'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Avoid "Attempted import error: 'URL' is not exported from 'url'" in frontend.
|
||||
import url from 'url'
|
||||
import queryString from 'query-string'
|
||||
import { compile } from 'path-to-regexp'
|
||||
'use strict'
|
||||
|
||||
const { URL } = require('url')
|
||||
const queryString = require('query-string')
|
||||
const { compile } = require('path-to-regexp')
|
||||
|
||||
function badgeUrlFromPath({
|
||||
baseUrl = '',
|
||||
@@ -146,13 +147,13 @@ function dynamicBadgeUrl({
|
||||
function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
|
||||
// Ensure we're always using the `rasterUrl` by using just the path from
|
||||
// the request URL.
|
||||
const { pathname, search } = new url.URL(badgeUrl, 'https://bogus.test')
|
||||
const result = new url.URL(pathname, rasterUrl)
|
||||
const { pathname, search } = new URL(badgeUrl, 'https://bogus.test')
|
||||
const result = new URL(pathname, rasterUrl)
|
||||
result.search = search
|
||||
return result
|
||||
}
|
||||
|
||||
export {
|
||||
module.exports = {
|
||||
badgeUrlFromPath,
|
||||
badgeUrlFromPattern,
|
||||
encodeField,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const {
|
||||
badgeUrlFromPath,
|
||||
badgeUrlFromPattern,
|
||||
encodeField,
|
||||
staticBadgeUrl,
|
||||
queryStringStaticBadgeUrl,
|
||||
dynamicBadgeUrl,
|
||||
} from './make-badge-url.js'
|
||||
} = require('./make-badge-url')
|
||||
|
||||
describe('Badge URL generation functions', function () {
|
||||
test(badgeUrlFromPath, () => {
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
// Escapes `t` using the format specified in
|
||||
// <https://github.com/espadrine/gh-badges/issues/12#issuecomment-31518129>
|
||||
function escapeFormat(t) {
|
||||
return (
|
||||
t
|
||||
// Single underscore.
|
||||
.replace(/(^|[^_])((?:__)*)_(?!_)/g, '$1$2 ')
|
||||
// Inline single underscore.
|
||||
.replace(/([^_])_([^_])/g, '$1 $2')
|
||||
// Leading or trailing underscore.
|
||||
.replace(/([^_])_$/, '$1 ')
|
||||
.replace(/^_([^_])/, ' $1')
|
||||
// Double underscore and double dash.
|
||||
.replace(/__/g, '_')
|
||||
.replace(/--/g, '-')
|
||||
)
|
||||
}
|
||||
|
||||
export { escapeFormat }
|
||||
module.exports = {
|
||||
escapeFormat,
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import { escapeFormat } from './path-helpers.js'
|
||||
|
||||
describe('Badge URL helper functions', function () {
|
||||
test(escapeFormat, () => {
|
||||
given('_single leading underscore').expect(' single leading underscore')
|
||||
given('single trailing underscore_').expect('single trailing underscore ')
|
||||
given('__double leading underscores').expect('_double leading underscores')
|
||||
given('double trailing underscores__').expect(
|
||||
'double trailing underscores_'
|
||||
)
|
||||
given('treble___underscores').expect('treble_ underscores')
|
||||
given('fourfold____underscores').expect('fourfold__underscores')
|
||||
given('double--dashes').expect('double-dashes')
|
||||
given('treble---dashes').expect('treble--dashes')
|
||||
given('fourfold----dashes').expect('fourfold--dashes')
|
||||
given('once_in_a_blue--moon').expect('once in a blue-moon')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import { URL } from 'url'
|
||||
import { InvalidParameter } from './errors.js'
|
||||
'use strict'
|
||||
|
||||
const { URL } = require('url')
|
||||
const { InvalidParameter } = require('./errors')
|
||||
|
||||
class AuthHelper {
|
||||
constructor(
|
||||
@@ -74,7 +76,7 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
static _isInsecureSslRequest({ options = {} }) {
|
||||
const strictSSL = options?.https?.rejectUnauthorized ?? true
|
||||
const { strictSSL = true } = options
|
||||
return strictSSL !== true
|
||||
}
|
||||
|
||||
@@ -107,10 +109,8 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
get _basicAuth() {
|
||||
const { _user: username, _pass: password } = this
|
||||
return this.isConfigured
|
||||
? { username: username || '', password: password || '' }
|
||||
: undefined
|
||||
const { _user: user, _pass: pass } = this
|
||||
return this.isConfigured ? { user, pass } : undefined
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -133,7 +133,7 @@ class AuthHelper {
|
||||
const { options, ...rest } = requestParams
|
||||
return {
|
||||
options: {
|
||||
...auth,
|
||||
auth,
|
||||
...options,
|
||||
},
|
||||
...rest,
|
||||
@@ -184,12 +184,12 @@ class AuthHelper {
|
||||
|
||||
static _mergeQueryParams(requestParams, query) {
|
||||
const {
|
||||
options: { searchParams: existingQuery, ...restOptions } = {},
|
||||
options: { qs: existingQuery, ...restOptions } = {},
|
||||
...rest
|
||||
} = requestParams
|
||||
return {
|
||||
options: {
|
||||
searchParams: {
|
||||
qs: {
|
||||
...existingQuery,
|
||||
...query,
|
||||
},
|
||||
@@ -209,4 +209,4 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthHelper }
|
||||
module.exports = { AuthHelper }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { expect } from 'chai'
|
||||
import { test, given, forCases } from 'sazerac'
|
||||
import { AuthHelper } from './auth-helper.js'
|
||||
import { InvalidParameter } from './errors.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const { AuthHelper } = require('./auth-helper')
|
||||
const { InvalidParameter } = require('./errors')
|
||||
|
||||
describe('AuthHelper', function () {
|
||||
describe('constructor checks', function () {
|
||||
@@ -104,14 +106,14 @@ describe('AuthHelper', function () {
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' }
|
||||
),
|
||||
]).expect({ username: 'admin', password: 'abc123' })
|
||||
]).expect({ user: 'admin', pass: 'abc123' })
|
||||
given({ userKey: 'myci_user' }, { myci_user: 'admin' }).expect({
|
||||
username: 'admin',
|
||||
password: '',
|
||||
user: 'admin',
|
||||
pass: undefined,
|
||||
})
|
||||
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }).expect({
|
||||
username: '',
|
||||
password: 'abc123',
|
||||
user: undefined,
|
||||
pass: 'abc123',
|
||||
})
|
||||
given({ userKey: 'myci_user', passKey: 'myci_pass' }, {}).expect(
|
||||
undefined
|
||||
@@ -120,8 +122,8 @@ describe('AuthHelper', function () {
|
||||
{ passKey: 'myci_pass', defaultToEmptyStringForUser: true },
|
||||
{ myci_pass: 'abc123' }
|
||||
).expect({
|
||||
username: '',
|
||||
password: 'abc123',
|
||||
user: '',
|
||||
pass: 'abc123',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -131,18 +133,15 @@ describe('AuthHelper', function () {
|
||||
forCases([
|
||||
given({ url: 'http://example.test' }),
|
||||
given({ url: 'http://example.test', options: {} }),
|
||||
given({ url: 'http://example.test', options: { strictSSL: true } }),
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: true } },
|
||||
}),
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: undefined } },
|
||||
options: { strictSSL: undefined },
|
||||
}),
|
||||
]).expect(false)
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(true)
|
||||
})
|
||||
})
|
||||
@@ -166,9 +165,7 @@ describe('AuthHelper', function () {
|
||||
})
|
||||
it('throws for insecure requests', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
})
|
||||
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
@@ -190,9 +187,7 @@ describe('AuthHelper', function () {
|
||||
})
|
||||
it('does not throw for insecure requests', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
})
|
||||
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
|
||||
).not.to.throw()
|
||||
})
|
||||
})
|
||||
@@ -227,7 +222,7 @@ describe('AuthHelper', function () {
|
||||
test(shouldAuthenticateRequest, () => {
|
||||
given({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(false)
|
||||
})
|
||||
})
|
||||
@@ -265,7 +260,7 @@ describe('AuthHelper', function () {
|
||||
test(shouldAuthenticateRequest, () => {
|
||||
given({
|
||||
url: 'https://myci.test',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(true)
|
||||
})
|
||||
})
|
||||
@@ -330,8 +325,7 @@ describe('AuthHelper', function () {
|
||||
}).expect({
|
||||
url: 'https://myci.test/api',
|
||||
options: {
|
||||
username: 'admin',
|
||||
password: 'abc123',
|
||||
auth: { user: 'admin', pass: 'abc123' },
|
||||
},
|
||||
})
|
||||
given({
|
||||
@@ -343,8 +337,7 @@ describe('AuthHelper', function () {
|
||||
url: 'https://myci.test/api',
|
||||
options: {
|
||||
headers: { Accept: 'application/json' },
|
||||
username: 'admin',
|
||||
password: 'abc123',
|
||||
auth: { user: 'admin', pass: 'abc123' },
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -375,7 +368,7 @@ describe('AuthHelper', function () {
|
||||
expect(() =>
|
||||
withBasicAuth({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
})
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { print } from 'graphql/language/printer.js'
|
||||
import BaseService from './base.js'
|
||||
import { InvalidResponse, ShieldsRuntimeError } from './errors.js'
|
||||
import { parseJson } from './json.js'
|
||||
'use strict'
|
||||
|
||||
const { print } = require('graphql/language/printer')
|
||||
const BaseService = require('./base')
|
||||
const { InvalidResponse, ShieldsRuntimeError } = require('./errors')
|
||||
const { parseJson } = require('./json')
|
||||
|
||||
function defaultTransformErrors(errors) {
|
||||
return new InvalidResponse({ prettyMessage: errors[0].message })
|
||||
@@ -38,8 +40,8 @@ class BaseGraphqlService extends BaseService {
|
||||
* representing the query clause of GraphQL POST body
|
||||
* e.g. gql`{ query { ... } }`
|
||||
* @param {object} attrs.variables Variables clause of GraphQL POST body
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.httpErrorMessages={}] Key-value map of HTTP status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
@@ -53,7 +55,7 @@ class BaseGraphqlService extends BaseService {
|
||||
* The default is to return the first entry of the `errors` array as
|
||||
* an InvalidResponse.
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestGraphql({
|
||||
schema,
|
||||
@@ -91,4 +93,4 @@ class BaseGraphqlService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseGraphqlService
|
||||
module.exports = BaseGraphqlService
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { expect } from 'chai'
|
||||
import gql from 'graphql-tag'
|
||||
import sinon from 'sinon'
|
||||
import BaseGraphqlService from './base-graphql.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const { expect } = require('chai')
|
||||
const gql = require('graphql-tag')
|
||||
const sinon = require('sinon')
|
||||
const BaseGraphqlService = require('./base-graphql')
|
||||
const { InvalidResponse } = require('./errors')
|
||||
|
||||
const dummySchema = Joi.object({
|
||||
requiredString: Joi.string().required(),
|
||||
@@ -29,9 +31,9 @@ class DummyGraphqlService extends BaseGraphqlService {
|
||||
|
||||
describe('BaseGraphqlService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: '{"some": "json"}',
|
||||
res: { statusCode: 200 },
|
||||
@@ -39,13 +41,13 @@ describe('BaseGraphqlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/graphql',
|
||||
{
|
||||
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
|
||||
@@ -55,7 +57,7 @@ describe('BaseGraphqlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithOptions extends DummyGraphqlService {
|
||||
async handle() {
|
||||
const { value } = await this._requestGraphql({
|
||||
@@ -66,24 +68,24 @@ describe('BaseGraphqlService', function () {
|
||||
requiredString
|
||||
}
|
||||
`,
|
||||
options: { searchParams: { queryParam: 123 } },
|
||||
options: { qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: value }
|
||||
}
|
||||
}
|
||||
|
||||
await WithOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/graphql',
|
||||
{
|
||||
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
|
||||
headers: { Accept: 'application/json' },
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -91,13 +93,13 @@ describe('BaseGraphqlService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"requiredString": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -106,13 +108,13 @@ describe('BaseGraphqlService', function () {
|
||||
})
|
||||
|
||||
it('handles json responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"unexpectedKey": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -123,13 +125,13 @@ describe('BaseGraphqlService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not json',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -142,13 +144,13 @@ describe('BaseGraphqlService', function () {
|
||||
|
||||
describe('Error handling', function () {
|
||||
it('handles generic error', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{ "errors": [ { "message": "oh noes!!" } ] }',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -181,13 +183,13 @@ describe('BaseGraphqlService', function () {
|
||||
}
|
||||
}
|
||||
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{ "errors": [ { "message": "oh noes!!" } ] }',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await WithErrorHandler.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import BaseService from './base.js'
|
||||
import { parseJson } from './json.js'
|
||||
'use strict'
|
||||
|
||||
const BaseService = require('./base')
|
||||
const { parseJson } = require('./json')
|
||||
|
||||
/**
|
||||
* Services which query a JSON endpoint should extend BaseJsonService
|
||||
@@ -28,14 +30,14 @@ class BaseJsonService extends BaseService {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
|
||||
const mergedOptions = {
|
||||
@@ -52,4 +54,4 @@ class BaseJsonService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseJsonService
|
||||
module.exports = BaseJsonService
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import Joi from 'joi'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import BaseJsonService from './base-json.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseJsonService = require('./base-json')
|
||||
|
||||
const dummySchema = Joi.object({
|
||||
requiredString: Joi.string().required(),
|
||||
@@ -22,9 +24,9 @@ class DummyJsonService extends BaseJsonService {
|
||||
|
||||
describe('BaseJsonService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: '{"some": "json"}',
|
||||
res: { statusCode: 200 },
|
||||
@@ -32,13 +34,13 @@ describe('BaseJsonService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.json',
|
||||
{
|
||||
headers: { Accept: 'application/json' },
|
||||
@@ -46,29 +48,29 @@ describe('BaseJsonService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithOptions extends DummyJsonService {
|
||||
async handle() {
|
||||
const { value } = await this._requestJson({
|
||||
schema: dummySchema,
|
||||
url: 'http://example.com/foo.json',
|
||||
options: { method: 'POST', searchParams: { queryParam: 123 } },
|
||||
options: { method: 'POST', qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: value }
|
||||
}
|
||||
}
|
||||
|
||||
await WithOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.json',
|
||||
{
|
||||
headers: { Accept: 'application/json' },
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -76,13 +78,13 @@ describe('BaseJsonService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"requiredString": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -91,13 +93,13 @@ describe('BaseJsonService', function () {
|
||||
})
|
||||
|
||||
it('handles json responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"unexpectedKey": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -108,13 +110,13 @@ describe('BaseJsonService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not json',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
74
core/base-service/base-non-memory-caching.js
Normal file
74
core/base-service/base-non-memory-caching.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const BaseService = require('./base')
|
||||
const { MetricHelper } = require('./metric-helper')
|
||||
const { setCacheHeaders } = require('./cache-headers')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const { prepareRoute, namedParamsForMatch } = require('./route')
|
||||
|
||||
// Badges are subject to two independent types of caching: in-memory and
|
||||
// downstream.
|
||||
//
|
||||
// Services deriving from `NonMemoryCachingBaseService` are not cached in
|
||||
// memory on the server. This means that each request that hits the server
|
||||
// triggers another call to the handler. When using badges for server
|
||||
// diagnostics, that's useful!
|
||||
//
|
||||
// In contrast, The `handle()` function of most other `BaseService`
|
||||
// subclasses is wrapped in onboard, in-memory caching. See `lib /request-
|
||||
// handler.js` and `BaseService.prototype.register()`.
|
||||
//
|
||||
// All services, including those extending NonMemoryCachingBaseServices, may
|
||||
// be cached _downstream_. This is governed by cache headers, which are
|
||||
// configured by the service, the user's request, and the server's default
|
||||
// cache length.
|
||||
module.exports = class NonMemoryCachingBaseService extends BaseService {
|
||||
static register({ camp, metricInstance }, serviceConfig) {
|
||||
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
|
||||
const { _cacheLength: serviceDefaultCacheLengthSeconds } = this
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
|
||||
const metricHelper = MetricHelper.create({
|
||||
metricInstance,
|
||||
ServiceClass: this,
|
||||
})
|
||||
|
||||
camp.route(regex, async (queryParams, match, end, ask) => {
|
||||
const metricHandle = metricHelper.startRequest()
|
||||
|
||||
const namedParams = namedParamsForMatch(captureNames, match, this)
|
||||
const serviceData = await this.invoke(
|
||||
{},
|
||||
serviceConfig,
|
||||
namedParams,
|
||||
queryParams
|
||||
)
|
||||
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
serviceData,
|
||||
this.defaultBadgeData,
|
||||
this
|
||||
)
|
||||
|
||||
// The final capture group is the extension.
|
||||
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
|
||||
badgeData.format = format
|
||||
|
||||
const svg = makeBadge(badgeData)
|
||||
|
||||
setCacheHeaders({
|
||||
cacheHeaderConfig,
|
||||
serviceDefaultCacheLengthSeconds,
|
||||
queryParams,
|
||||
res: ask.res,
|
||||
})
|
||||
|
||||
makeSend(format, ask.res, end)(svg)
|
||||
|
||||
metricHandle.noteResponseSent()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import makeBadge from '../../badge-maker/lib/make-badge.js'
|
||||
import BaseService from './base.js'
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const BaseService = require('./base')
|
||||
const {
|
||||
serverHasBeenUpSinceResourceCached,
|
||||
setCacheHeadersForStaticResource,
|
||||
} from './cache-headers.js'
|
||||
import { makeSend } from './legacy-result-sender.js'
|
||||
import { MetricHelper } from './metric-helper.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
import { prepareRoute, namedParamsForMatch } from './route.js'
|
||||
} = require('./cache-headers')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const { MetricHelper } = require('./metric-helper')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const { prepareRoute, namedParamsForMatch } = require('./route')
|
||||
|
||||
export default class BaseStaticService extends BaseService {
|
||||
module.exports = class BaseStaticService extends BaseService {
|
||||
static register({ camp, metricInstance }, serviceConfig) {
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
// See available emoji at http://emoji.muan.co/
|
||||
import emojic from 'emojic'
|
||||
import BaseService from './base.js'
|
||||
import trace from './trace.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
const emojic = require('emojic')
|
||||
const BaseService = require('./base')
|
||||
const trace = require('./trace')
|
||||
const { InvalidResponse } = require('./errors')
|
||||
|
||||
const defaultValueMatcher = />([^<>]+)<\/text><\/g>/
|
||||
const leadingWhitespace = /(?:\r\n\s*|\r\s*|\n\s*)/g
|
||||
@@ -51,14 +53,14 @@ class BaseSvgScrapingService extends BaseService {
|
||||
* @param {RegExp} attrs.valueMatcher
|
||||
* RegExp to match the value we want to parse from the SVG
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestSvg({
|
||||
schema,
|
||||
@@ -88,4 +90,4 @@ class BaseSvgScrapingService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseSvgScrapingService
|
||||
module.exports = BaseSvgScrapingService
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import Joi from 'joi'
|
||||
import makeBadge from '../../badge-maker/lib/make-badge.js'
|
||||
import BaseSvgScrapingService from './base-svg-scraping.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const Joi = require('@hapi/joi')
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const BaseSvgScrapingService = require('./base-svg-scraping')
|
||||
|
||||
function makeExampleSvg({ label, message }) {
|
||||
return makeBadge({ text: ['this is the label', 'this is the result!'] })
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.string().required(),
|
||||
@@ -23,7 +29,10 @@ class DummySvgScrapingService extends BaseSvgScrapingService {
|
||||
describe('BaseSvgScrapingService', function () {
|
||||
const exampleLabel = 'this is the label'
|
||||
const exampleMessage = 'this is the result!'
|
||||
const exampleSvg = makeBadge({ label: exampleLabel, message: exampleMessage })
|
||||
const exampleSvg = makeExampleSvg({
|
||||
label: exampleLabel,
|
||||
message: exampleMessage,
|
||||
})
|
||||
|
||||
describe('valueFromSvgBadge', function () {
|
||||
it('should find the correct value', function () {
|
||||
@@ -34,9 +43,9 @@ describe('BaseSvgScrapingService', function () {
|
||||
})
|
||||
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: exampleSvg,
|
||||
res: { statusCode: 200 },
|
||||
@@ -44,13 +53,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher with the expected header', async function () {
|
||||
it('invokes _sendAndCacheRequest with the expected header', async function () {
|
||||
await DummySvgScrapingService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.svg',
|
||||
{
|
||||
headers: { Accept: 'image/svg+xml' },
|
||||
@@ -58,7 +67,7 @@ describe('BaseSvgScrapingService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithCustomOptions extends DummySvgScrapingService {
|
||||
async handle() {
|
||||
const { message } = await this._requestSvg({
|
||||
@@ -66,7 +75,7 @@ describe('BaseSvgScrapingService', function () {
|
||||
url: 'http://example.com/foo.svg',
|
||||
options: {
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
},
|
||||
})
|
||||
return { message }
|
||||
@@ -74,16 +83,16 @@ describe('BaseSvgScrapingService', function () {
|
||||
}
|
||||
|
||||
await WithCustomOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.svg',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { Accept: 'image/svg+xml' },
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -91,13 +100,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid svg responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: exampleSvg,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummySvgScrapingService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -117,13 +126,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
})
|
||||
}
|
||||
}
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '<desc>a different message</desc>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await WithValueMatcher.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -132,13 +141,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable svg responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not svg yo',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummySvgScrapingService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
// See available emoji at http://emoji.muan.co/
|
||||
import emojic from 'emojic'
|
||||
import { XMLParser, XMLValidator } from 'fast-xml-parser'
|
||||
import BaseService from './base.js'
|
||||
import trace from './trace.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
const emojic = require('emojic')
|
||||
const fastXmlParser = require('fast-xml-parser')
|
||||
const BaseService = require('./base')
|
||||
const trace = require('./trace')
|
||||
const { InvalidResponse } = require('./errors')
|
||||
|
||||
/**
|
||||
* Services which query a XML endpoint should extend BaseXmlService
|
||||
@@ -22,8 +24,8 @@ class BaseXmlService extends BaseService {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
@@ -31,7 +33,7 @@ class BaseXmlService extends BaseService {
|
||||
* @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
|
||||
* [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
* @see https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json
|
||||
*/
|
||||
async _requestXml({
|
||||
@@ -51,15 +53,14 @@ class BaseXmlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
errorMessages,
|
||||
})
|
||||
const validateResult = XMLValidator.validate(buffer)
|
||||
const validateResult = fastXmlParser.validate(buffer)
|
||||
if (validateResult !== true) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'unparseable xml response',
|
||||
underlyingError: validateResult.err,
|
||||
})
|
||||
}
|
||||
const parser = new XMLParser(parserOptions)
|
||||
const xml = parser.parse(buffer)
|
||||
const xml = fastXmlParser.parse(buffer, parserOptions)
|
||||
logTrace(emojic.dart, 'Response XML (before validation)', xml, {
|
||||
deep: true,
|
||||
})
|
||||
@@ -67,4 +68,4 @@ class BaseXmlService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseXmlService
|
||||
module.exports = BaseXmlService
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import Joi from 'joi'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import BaseXmlService from './base-xml.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseXmlService = require('./base-xml')
|
||||
|
||||
const dummySchema = Joi.object({
|
||||
requiredString: Joi.string().required(),
|
||||
@@ -22,9 +24,9 @@ class DummyXmlService extends BaseXmlService {
|
||||
|
||||
describe('BaseXmlService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: '<requiredString>some-string</requiredString>',
|
||||
res: { statusCode: 200 },
|
||||
@@ -32,13 +34,13 @@ describe('BaseXmlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.xml',
|
||||
{
|
||||
headers: { Accept: 'application/xml, text/xml' },
|
||||
@@ -46,7 +48,7 @@ describe('BaseXmlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithCustomOptions extends BaseXmlService {
|
||||
static route = {}
|
||||
|
||||
@@ -54,23 +56,23 @@ describe('BaseXmlService', function () {
|
||||
const { requiredString } = await this._requestXml({
|
||||
schema: dummySchema,
|
||||
url: 'http://example.com/foo.xml',
|
||||
options: { method: 'POST', searchParams: { queryParam: 123 } },
|
||||
options: { method: 'POST', qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: requiredString }
|
||||
}
|
||||
}
|
||||
|
||||
await WithCustomOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.xml',
|
||||
{
|
||||
headers: { Accept: 'application/xml, text/xml' },
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -78,13 +80,13 @@ describe('BaseXmlService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid xml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '<requiredString>some-string</requiredString>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -104,14 +106,14 @@ describe('BaseXmlService', function () {
|
||||
return { message: requiredString }
|
||||
}
|
||||
}
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer:
|
||||
'<requiredString>some-string with trailing whitespace </requiredString>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlServiceWithParserOption.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -120,13 +122,13 @@ describe('BaseXmlService', function () {
|
||||
})
|
||||
|
||||
it('handles xml responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '<unexpectedAttribute>some-string</unexpectedAttribute>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -137,13 +139,13 @@ describe('BaseXmlService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable xml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not xml',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import emojic from 'emojic'
|
||||
import yaml from 'js-yaml'
|
||||
import BaseService from './base.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
import trace from './trace.js'
|
||||
'use strict'
|
||||
|
||||
const emojic = require('emojic')
|
||||
const yaml = require('js-yaml')
|
||||
const BaseService = require('./base')
|
||||
const { InvalidResponse } = require('./errors')
|
||||
const trace = require('./trace')
|
||||
|
||||
/**
|
||||
* Services which query a YAML endpoint should extend BaseYamlService
|
||||
@@ -21,15 +23,15 @@ class BaseYamlService extends BaseService {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @param {object} [attrs.encoding='utf8'] Character encoding
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestYaml({
|
||||
schema,
|
||||
@@ -55,7 +57,7 @@ class BaseYamlService extends BaseService {
|
||||
})
|
||||
let parsed
|
||||
try {
|
||||
parsed = yaml.load(buffer.toString(), encoding)
|
||||
parsed = yaml.safeLoad(buffer.toString(), encoding)
|
||||
} catch (err) {
|
||||
logTrace(emojic.dart, 'Response YAML (unparseable)', buffer)
|
||||
throw new InvalidResponse({
|
||||
@@ -70,4 +72,4 @@ class BaseYamlService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseYamlService
|
||||
module.exports = BaseYamlService
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import Joi from 'joi'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import BaseYamlService from './base-yaml.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseYamlService = require('./base-yaml')
|
||||
|
||||
const dummySchema = Joi.object({
|
||||
requiredString: Joi.string().required(),
|
||||
@@ -38,9 +40,9 @@ foo: baz
|
||||
|
||||
describe('BaseYamlService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: expectedYaml,
|
||||
res: { statusCode: 200 },
|
||||
@@ -48,13 +50,13 @@ describe('BaseYamlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.yaml',
|
||||
{
|
||||
headers: {
|
||||
@@ -65,24 +67,24 @@ describe('BaseYamlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithOptions extends DummyYamlService {
|
||||
async handle() {
|
||||
const { requiredString } = await this._requestYaml({
|
||||
schema: dummySchema,
|
||||
url: 'http://example.com/foo.yaml',
|
||||
options: { method: 'POST', searchParams: { queryParam: 123 } },
|
||||
options: { method: 'POST', qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: requiredString }
|
||||
}
|
||||
}
|
||||
|
||||
await WithOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.yaml',
|
||||
{
|
||||
headers: {
|
||||
@@ -90,7 +92,7 @@ describe('BaseYamlService', function () {
|
||||
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
|
||||
},
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -98,13 +100,13 @@ describe('BaseYamlService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid yaml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: expectedYaml,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -113,13 +115,13 @@ describe('BaseYamlService', function () {
|
||||
})
|
||||
|
||||
it('handles yaml responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: unexpectedYaml,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -130,13 +132,13 @@ describe('BaseYamlService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable yaml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: invalidYaml,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
'use strict'
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
// See available emoji at http://emoji.muan.co/
|
||||
import emojic from 'emojic'
|
||||
import Joi from 'joi'
|
||||
import log from '../server/log.js'
|
||||
import { AuthHelper } from './auth-helper.js'
|
||||
import { MetricHelper, MetricNames } from './metric-helper.js'
|
||||
import { assertValidCategory } from './categories.js'
|
||||
import checkErrorResponse from './check-error-response.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
import {
|
||||
const emojic = require('emojic')
|
||||
const Joi = require('@hapi/joi')
|
||||
const log = require('../server/log')
|
||||
const { AuthHelper } = require('./auth-helper')
|
||||
const { MetricHelper, MetricNames } = require('./metric-helper')
|
||||
const { assertValidCategory } = require('./categories')
|
||||
const checkErrorResponse = require('./check-error-response')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const {
|
||||
NotFound,
|
||||
InvalidResponse,
|
||||
Inaccessible,
|
||||
ImproperlyConfigured,
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
} from './errors.js'
|
||||
import { validateExample, transformExample } from './examples.js'
|
||||
import { fetch } from './got.js'
|
||||
import {
|
||||
} = require('./errors')
|
||||
const { validateExample, transformExample } = require('./examples')
|
||||
const {
|
||||
makeFullUrl,
|
||||
assertValidRoute,
|
||||
prepareRoute,
|
||||
namedParamsForMatch,
|
||||
getQueryParamNames,
|
||||
} from './route.js'
|
||||
import { assertValidServiceDefinition } from './service-definitions.js'
|
||||
import trace from './trace.js'
|
||||
import validate from './validate.js'
|
||||
} = require('./route')
|
||||
const { assertValidServiceDefinition } = require('./service-definitions')
|
||||
const trace = require('./trace')
|
||||
const validate = require('./validate')
|
||||
|
||||
const defaultBadgeDataSchema = Joi.object({
|
||||
label: Joi.string(),
|
||||
@@ -108,14 +108,11 @@ class BaseService {
|
||||
*
|
||||
* See also the config schema in `./server.js` and `doc/server-secrets.md`.
|
||||
*
|
||||
* To use the configured auth in the handler or fetch method, wrap the
|
||||
* _request() input params in a call to one of:
|
||||
* - this.authHelper.withBasicAuth()
|
||||
* - this.authHelper.withBearerAuthHeader()
|
||||
* - this.authHelper.withQueryStringAuth()
|
||||
*
|
||||
* For example:
|
||||
* this._request(this.authHelper.withBasicAuth({ url, schema, options }))
|
||||
* To use the configured auth in the handler or fetch method, pass the
|
||||
* credentials to the request. For example:
|
||||
* - `{ options: { auth: this.authHelper.basicAuth } }`
|
||||
* - `{ options: { headers: this.authHelper.bearerAuthHeader } }`
|
||||
* - `{ options: { qs: { token: this.authHelper._pass } } }`
|
||||
*
|
||||
* @abstract
|
||||
* @type {module:core/base-service/base~Auth}
|
||||
@@ -146,9 +143,6 @@ class BaseService {
|
||||
license: 3600,
|
||||
version: 300,
|
||||
debug: 60,
|
||||
downloads: 900,
|
||||
rating: 900,
|
||||
social: 900,
|
||||
}
|
||||
return cacheLengths[this.category]
|
||||
}
|
||||
@@ -208,10 +202,10 @@ class BaseService {
|
||||
}
|
||||
|
||||
constructor(
|
||||
{ requestFetcher, authHelper, metricHelper },
|
||||
{ sendAndCacheRequest, authHelper, metricHelper },
|
||||
{ handleInternalErrors }
|
||||
) {
|
||||
this._requestFetcher = requestFetcher
|
||||
this._requestFetcher = sendAndCacheRequest
|
||||
this.authHelper = authHelper
|
||||
this._handleInternalErrors = handleInternalErrors
|
||||
this._metricHelper = metricHelper
|
||||
@@ -219,18 +213,7 @@ class BaseService {
|
||||
|
||||
async _request({ url, options = {}, errorMessages = {} }) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
let logUrl = url
|
||||
const logOptions = Object.assign({}, options)
|
||||
if ('searchParams' in options) {
|
||||
const params = new URLSearchParams(options.searchParams)
|
||||
logUrl = `${url}?${params.toString()}`
|
||||
delete logOptions.searchParams
|
||||
}
|
||||
logTrace(
|
||||
emojic.bowAndArrow,
|
||||
'Request',
|
||||
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`
|
||||
)
|
||||
logTrace(emojic.bowAndArrow, 'Request', url, '\n', options)
|
||||
const { res, buffer } = await this._requestFetcher(url, options)
|
||||
await this._meterResponse(res, buffer)
|
||||
logTrace(emojic.dart, 'Response status code', res.statusCode)
|
||||
@@ -279,7 +262,7 @@ class BaseService {
|
||||
/**
|
||||
* Asynchronous function to handle requests for this service. Take the route
|
||||
* parameters (as defined in the `route` property), perform a request using
|
||||
* `this._requestFetcher`, and return the badge data.
|
||||
* `this._sendAndCacheRequest`, and return the badge data.
|
||||
*
|
||||
* @abstract
|
||||
* @param {object} namedParams Params parsed from route pattern
|
||||
@@ -424,16 +407,10 @@ class BaseService {
|
||||
}
|
||||
|
||||
static register(
|
||||
{
|
||||
camp,
|
||||
handleRequest,
|
||||
githubApiProvider,
|
||||
librariesIoApiProvider,
|
||||
metricInstance,
|
||||
},
|
||||
{ camp, handleRequest, githubApiProvider, metricInstance },
|
||||
serviceConfig
|
||||
) {
|
||||
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
|
||||
const { cacheHeaders: cacheHeaderConfig, fetchLimitBytes } = serviceConfig
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
const queryParams = getQueryParamNames(this.route)
|
||||
|
||||
@@ -446,15 +423,15 @@ class BaseService {
|
||||
regex,
|
||||
handleRequest(cacheHeaderConfig, {
|
||||
queryParams,
|
||||
handler: async (queryParams, match, sendBadge) => {
|
||||
handler: async (queryParams, match, sendBadge, request) => {
|
||||
const metricHandle = metricHelper.startRequest()
|
||||
|
||||
const namedParams = namedParamsForMatch(captureNames, match, this)
|
||||
const serviceData = await this.invoke(
|
||||
{
|
||||
requestFetcher: fetch,
|
||||
sendAndCacheRequest: request.asPromise,
|
||||
sendAndCacheRequestWithCallbacks: request,
|
||||
githubApiProvider,
|
||||
librariesIoApiProvider,
|
||||
metricHelper,
|
||||
},
|
||||
serviceConfig,
|
||||
@@ -475,6 +452,7 @@ class BaseService {
|
||||
metricHandle.noteResponseSent()
|
||||
},
|
||||
cacheLength: this._cacheLength,
|
||||
fetchLimitBytes,
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -574,4 +552,4 @@ class BaseService {
|
||||
* An HTML string that is included in the badge popup.
|
||||
*/
|
||||
|
||||
export default BaseService
|
||||
module.exports = BaseService
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import Joi from 'joi'
|
||||
import chai from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import prometheus from 'prom-client'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import PrometheusMetrics from '../server/prometheus-metrics.js'
|
||||
import trace from './trace.js'
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
const prometheus = require('prom-client')
|
||||
const PrometheusMetrics = require('../server/prometheus-metrics')
|
||||
const trace = require('./trace')
|
||||
const {
|
||||
NotFound,
|
||||
Inaccessible,
|
||||
InvalidResponse,
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
} from './errors.js'
|
||||
import BaseService from './base.js'
|
||||
import { MetricHelper, MetricNames } from './metric-helper.js'
|
||||
import '../register-chai-plugins.spec.js'
|
||||
const { expect } = chai
|
||||
chai.use(chaiAsPromised)
|
||||
} = require('./errors')
|
||||
const BaseService = require('./base')
|
||||
const { MetricHelper, MetricNames } = require('./metric-helper')
|
||||
require('../register-chai-plugins.spec')
|
||||
chai.use(require('chai-as-promised'))
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
queryParamA: Joi.string(),
|
||||
@@ -124,11 +125,15 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
describe('Logging', function () {
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.stub(trace, 'logTrace')
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
})
|
||||
beforeEach(function () {
|
||||
sandbox.stub(trace, 'logTrace')
|
||||
})
|
||||
it('Invokes the logger as expected', async function () {
|
||||
await DummyService.invoke(
|
||||
@@ -324,7 +329,7 @@ describe('BaseService', function () {
|
||||
describe('ScoutCamp integration', function () {
|
||||
// TODO Strangly, without the useless escape the regexes do not match in Node 12.
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const expectedRouteRegex = /^\/foo(?:\/([^\/#\?]+?))(|\.svg|\.json)$/
|
||||
const expectedRouteRegex = /^\/foo\/([^\/]+?)(|\.svg|\.json)$/
|
||||
|
||||
let mockCamp
|
||||
let mockHandleRequest
|
||||
@@ -348,8 +353,10 @@ describe('BaseService', function () {
|
||||
it('handles the request', async function () {
|
||||
expect(mockHandleRequest).to.have.been.calledOnce
|
||||
|
||||
const { queryParams: serviceQueryParams, handler: requestHandler } =
|
||||
mockHandleRequest.getCall(0).args[1]
|
||||
const {
|
||||
queryParams: serviceQueryParams,
|
||||
handler: requestHandler,
|
||||
} = mockHandleRequest.getCall(0).args[1]
|
||||
expect(serviceQueryParams).to.deep.equal([
|
||||
'queryParamA',
|
||||
'legacyQueryParamA',
|
||||
@@ -366,10 +373,9 @@ describe('BaseService', function () {
|
||||
const expectedFormat = 'svg'
|
||||
expect(mockSendBadge).to.have.been.calledOnce
|
||||
expect(mockSendBadge).to.have.been.calledWith(expectedFormat, {
|
||||
label: 'cat',
|
||||
message: 'Hello namedParamA: bar with queryParamA: ?',
|
||||
text: ['cat', 'Hello namedParamA: bar with queryParamA: ?'],
|
||||
color: 'lightgrey',
|
||||
style: 'flat',
|
||||
template: 'flat',
|
||||
namedLogo: undefined,
|
||||
logo: undefined,
|
||||
logoWidth: undefined,
|
||||
@@ -383,8 +389,13 @@ describe('BaseService', function () {
|
||||
|
||||
describe('getDefinition', function () {
|
||||
it('returns the expected result', function () {
|
||||
const { category, name, isDeprecated, route, examples } =
|
||||
DummyService.getDefinition()
|
||||
const {
|
||||
category,
|
||||
name,
|
||||
isDeprecated,
|
||||
route,
|
||||
examples,
|
||||
} = DummyService.getDefinition()
|
||||
expect({
|
||||
category,
|
||||
name,
|
||||
@@ -422,20 +433,24 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
describe('request', function () {
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.stub(trace, 'logTrace')
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
})
|
||||
beforeEach(function () {
|
||||
sandbox.stub(trace, 'logTrace')
|
||||
})
|
||||
|
||||
it('logs appropriate information', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -447,7 +462,9 @@ describe('BaseService', function () {
|
||||
'fetch',
|
||||
sinon.match.string,
|
||||
'Request',
|
||||
`${url}\n${JSON.stringify(options, null, 2)}`
|
||||
url,
|
||||
'\n',
|
||||
options
|
||||
)
|
||||
expect(trace.logTrace).to.be.calledWithMatch(
|
||||
'fetch',
|
||||
@@ -458,12 +475,12 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
it('handles errors', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '',
|
||||
res: { statusCode: 404 },
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -490,19 +507,18 @@ describe('BaseService', function () {
|
||||
metricInstance: new PrometheusMetrics({ register }),
|
||||
ServiceClass: DummyServiceWithServiceResponseSizeMetricEnabled,
|
||||
})
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'x'.repeat(65536 + 1),
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance =
|
||||
new DummyServiceWithServiceResponseSizeMetricEnabled(
|
||||
{ requestFetcher, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
const serviceInstance = new DummyServiceWithServiceResponseSizeMetricEnabled(
|
||||
{ sendAndCacheRequest, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
await serviceInstance._request({ url })
|
||||
|
||||
expect(await register.getSingleMetricAsString('service_response_bytes'))
|
||||
expect(register.getSingleMetricAsString('service_response_bytes'))
|
||||
.to.contain(
|
||||
'service_response_bytes_bucket{le="65536",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 0\n'
|
||||
)
|
||||
@@ -516,19 +532,19 @@ describe('BaseService', function () {
|
||||
metricInstance: new PrometheusMetrics({ register }),
|
||||
ServiceClass: DummyService,
|
||||
})
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'x',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher, metricHelper },
|
||||
{ sendAndCacheRequest, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
await serviceInstance._request({ url })
|
||||
|
||||
expect(
|
||||
await register.getSingleMetricAsString('service_response_bytes')
|
||||
register.getSingleMetricAsString('service_response_bytes')
|
||||
).to.not.contain('service_response_bytes_bucket')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import assert from 'assert'
|
||||
import Joi from 'joi'
|
||||
import coalesce from './coalesce.js'
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const Joi = require('@hapi/joi')
|
||||
const coalesce = require('./coalesce')
|
||||
|
||||
const serverStartTimeGMTString = new Date().toGMTString()
|
||||
const serverStartTimestamp = Date.now()
|
||||
@@ -102,7 +104,7 @@ function serverHasBeenUpSinceResourceCached(req) {
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
module.exports = {
|
||||
coalesceCacheLength,
|
||||
setCacheHeaders,
|
||||
setHeadersForCacheLength,
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import chai, { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import httpMocks from 'node-mocks-http'
|
||||
import chaiDatetime from 'chai-datetime'
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const chai = require('chai')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const httpMocks = require('node-mocks-http')
|
||||
const {
|
||||
coalesceCacheLength,
|
||||
setHeadersForCacheLength,
|
||||
setCacheHeaders,
|
||||
setCacheHeadersForStaticResource,
|
||||
serverHasBeenUpSinceResourceCached,
|
||||
} from './cache-headers.js'
|
||||
chai.use(chaiDatetime)
|
||||
} = require('./cache-headers')
|
||||
|
||||
chai.use(require('chai-datetime'))
|
||||
|
||||
describe('Cache header functions', function () {
|
||||
let res
|
||||
@@ -99,11 +102,14 @@ describe('Cache header functions', function () {
|
||||
})
|
||||
|
||||
describe('setHeadersForCacheLength', function () {
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.useFakeTimers()
|
||||
sandbox = sinon.createSandbox()
|
||||
sandbox.useFakeTimers()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
sandbox = undefined
|
||||
})
|
||||
|
||||
it('should set the correct Date header', function () {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import categories from '../../services/categories.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const categories = require('../../services/categories')
|
||||
|
||||
const isRealCategory = Joi.equal(...categories.map(({ id }) => id)).required()
|
||||
|
||||
@@ -11,4 +13,7 @@ function assertValidCategory(category, message = undefined) {
|
||||
Joi.assert(category, isValidCategory, message)
|
||||
}
|
||||
|
||||
export { isValidCategory, assertValidCategory }
|
||||
module.exports = {
|
||||
isValidCategory,
|
||||
assertValidCategory,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { NotFound, InvalidResponse, Inaccessible } from './errors.js'
|
||||
'use strict'
|
||||
|
||||
const { NotFound, InvalidResponse, Inaccessible } = require('./errors')
|
||||
|
||||
const defaultErrorMessages = {
|
||||
404: 'not found',
|
||||
}
|
||||
|
||||
export default function checkErrorResponse(errorMessages = {}) {
|
||||
module.exports = function checkErrorResponse(errorMessages = {}) {
|
||||
return async function ({ buffer, res }) {
|
||||
let error
|
||||
errorMessages = { ...defaultErrorMessages, ...errorMessages }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { expect } from 'chai'
|
||||
import { NotFound, InvalidResponse, Inaccessible } from './errors.js'
|
||||
import checkErrorResponse from './check-error-response.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { NotFound, InvalidResponse, Inaccessible } = require('./errors')
|
||||
const checkErrorResponse = require('./check-error-response')
|
||||
|
||||
describe('async error handler', function () {
|
||||
const buffer = Buffer.from('some stuff')
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
decodeDataUrlFromQueryParam,
|
||||
prepareNamedLogo,
|
||||
} from '../../lib/logos.js'
|
||||
import { svg2base64 } from '../../lib/svg-helpers.js'
|
||||
import coalesce from './coalesce.js'
|
||||
import toArray from './to-array.js'
|
||||
} = require('../../lib/logos')
|
||||
const { svg2base64 } = require('../../lib/svg-helpers')
|
||||
const coalesce = require('./coalesce')
|
||||
const toArray = require('./to-array')
|
||||
|
||||
// Translate modern badge data to the legacy schema understood by the badge
|
||||
// maker. Allow the user to override the label, color, logo, etc. through the
|
||||
@@ -32,7 +34,7 @@ import toArray from './to-array.js'
|
||||
// 3. In the case of the `social` style only, the last precedence is the
|
||||
// service's default logo. The `logoColor` can be overridden by the query
|
||||
// string.
|
||||
export default function coalesceBadge(
|
||||
module.exports = function coalesceBadge(
|
||||
overrides,
|
||||
serviceData,
|
||||
// These two parameters were kept separate to make tests clearer.
|
||||
@@ -158,10 +160,12 @@ export default function coalesceBadge(
|
||||
}
|
||||
|
||||
return {
|
||||
// Use `coalesce()` to support empty labels and messages, as in the static
|
||||
// badge.
|
||||
label: coalesce(overrideLabel, serviceLabel, defaultLabel, category),
|
||||
message: coalesce(serviceMessage, 'n/a'),
|
||||
text: [
|
||||
// Use `coalesce()` to support empty labels and messages, as in the
|
||||
// static badge.
|
||||
coalesce(overrideLabel, serviceLabel, defaultLabel, category),
|
||||
coalesce(serviceMessage, 'n/a'),
|
||||
],
|
||||
color: coalesce(
|
||||
// In case of an error, disregard user's color override.
|
||||
isError ? undefined : overrideColor,
|
||||
@@ -175,7 +179,7 @@ export default function coalesceBadge(
|
||||
serviceLabelColor,
|
||||
defaultLabelColor
|
||||
),
|
||||
style,
|
||||
template: style,
|
||||
namedLogo,
|
||||
logo: logoSvgBase64,
|
||||
logoWidth,
|
||||
|
||||
@@ -1,65 +1,69 @@
|
||||
import { expect } from 'chai'
|
||||
import { getShieldsIcon, getSimpleIcon } from '../../lib/logos.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { getShieldsIcon, getSimpleIcon } = require('../../lib/logos')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
|
||||
describe('coalesceBadge', function () {
|
||||
describe('Label', function () {
|
||||
it('uses the default label', function () {
|
||||
expect(coalesceBadge({}, {}, { label: 'heyo' })).to.include({
|
||||
label: 'heyo',
|
||||
})
|
||||
expect(coalesceBadge({}, {}, { label: 'heyo' }).text).to.deep.equal([
|
||||
'heyo',
|
||||
'n/a',
|
||||
])
|
||||
})
|
||||
|
||||
// This behavior isn't great and we might want to remove it.
|
||||
it('uses the category as a default label', function () {
|
||||
expect(coalesceBadge({}, {}, {}, { category: 'cat' })).to.include({
|
||||
label: 'cat',
|
||||
})
|
||||
expect(
|
||||
coalesceBadge({}, {}, {}, { category: 'cat' }).text
|
||||
).to.deep.equal(['cat', 'n/a'])
|
||||
})
|
||||
|
||||
it('preserves an empty label', function () {
|
||||
expect(coalesceBadge({}, { label: '', message: '10k' }, {})).to.include({
|
||||
label: '',
|
||||
})
|
||||
expect(
|
||||
coalesceBadge({}, { label: '', message: '10k' }, {}).text
|
||||
).to.deep.equal(['', '10k'])
|
||||
})
|
||||
|
||||
it('overrides the label', function () {
|
||||
expect(
|
||||
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {})
|
||||
).to.include({ label: 'purr count' })
|
||||
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {}).text
|
||||
).to.deep.equal(['purr count', 'n/a'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Message', function () {
|
||||
it('applies the service message', function () {
|
||||
expect(coalesceBadge({}, { message: '10k' }, {})).to.include({
|
||||
message: '10k',
|
||||
})
|
||||
expect(coalesceBadge({}, { message: '10k' }, {}).text).to.deep.equal([
|
||||
undefined,
|
||||
'10k',
|
||||
])
|
||||
})
|
||||
|
||||
// https://github.com/badges/shields/issues/1280
|
||||
it('converts a number to a string', function () {
|
||||
it('applies a numeric service message', function () {
|
||||
// While a number of badges use this, in the long run we may want
|
||||
// `render()` to always return a string.
|
||||
expect(coalesceBadge({}, { message: 10 }, {})).to.include({
|
||||
message: 10,
|
||||
})
|
||||
expect(coalesceBadge({}, { message: 10 }, {}).text).to.deep.equal([
|
||||
undefined,
|
||||
10,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Right color', function () {
|
||||
it('uses the default color', function () {
|
||||
expect(coalesceBadge({}, {}, {})).to.include({ color: 'lightgrey' })
|
||||
expect(coalesceBadge({}, {}, {}).color).to.equal('lightgrey')
|
||||
})
|
||||
|
||||
it('overrides the color', function () {
|
||||
expect(
|
||||
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {})
|
||||
).to.include({ color: '10ADED' })
|
||||
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {}).color
|
||||
).to.equal('10ADED')
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {})
|
||||
).to.include({ color: 'B0ADED' })
|
||||
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {}).color
|
||||
).to.equal('B0ADED')
|
||||
})
|
||||
|
||||
context('In case of an error', function () {
|
||||
@@ -69,23 +73,21 @@ describe('coalesceBadge', function () {
|
||||
{ color: '10ADED' },
|
||||
{ isError: true, color: 'lightgray' },
|
||||
{}
|
||||
)
|
||||
).to.include({ color: 'lightgray' })
|
||||
).color
|
||||
).to.equal('lightgray')
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ colorB: 'B0ADED' },
|
||||
{ isError: true, color: 'lightgray' },
|
||||
{}
|
||||
)
|
||||
).to.include({ color: 'lightgray' })
|
||||
).color
|
||||
).to.equal('lightgray')
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the service color', function () {
|
||||
expect(coalesceBadge({}, { color: 'red' }, {})).to.include({
|
||||
color: 'red',
|
||||
})
|
||||
expect(coalesceBadge({}, { color: 'red' }, {}).color).to.equal('red')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -95,19 +97,20 @@ describe('coalesceBadge', function () {
|
||||
})
|
||||
|
||||
it('applies the service label color', function () {
|
||||
expect(coalesceBadge({}, { labelColor: 'red' }, {})).to.include({
|
||||
labelColor: 'red',
|
||||
})
|
||||
expect(coalesceBadge({}, { labelColor: 'red' }, {}).labelColor).to.equal(
|
||||
'red'
|
||||
)
|
||||
})
|
||||
|
||||
it('overrides the label color', function () {
|
||||
expect(
|
||||
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {})
|
||||
).to.include({ labelColor: '42f483' })
|
||||
.labelColor
|
||||
).to.equal('42f483')
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {})
|
||||
).to.include({ labelColor: 'B2f483' })
|
||||
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {}).labelColor
|
||||
).to.equal('B2f483')
|
||||
})
|
||||
|
||||
it('converts a query-string numeric color to a string', function () {
|
||||
@@ -117,8 +120,8 @@ describe('coalesceBadge', function () {
|
||||
{ color: 123 },
|
||||
{ color: 'green' },
|
||||
{}
|
||||
)
|
||||
).to.include({ color: '123' })
|
||||
).color
|
||||
).to.equal('123')
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge(
|
||||
@@ -126,8 +129,8 @@ describe('coalesceBadge', function () {
|
||||
{ colorB: 123 },
|
||||
{ color: 'green' },
|
||||
{}
|
||||
)
|
||||
).to.include({ color: '123' })
|
||||
).color
|
||||
).to.equal('123')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -145,9 +148,9 @@ describe('coalesceBadge', function () {
|
||||
})
|
||||
|
||||
it('applies the named logo', function () {
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {})).to.include({
|
||||
namedLogo: 'npm',
|
||||
})
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).namedLogo).to.equal(
|
||||
'npm'
|
||||
)
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).logo).to.equal(
|
||||
getShieldsIcon({ name: 'npm' })
|
||||
).and.not.to.be.empty
|
||||
@@ -216,8 +219,8 @@ describe('coalesceBadge', function () {
|
||||
it('overrides the logo with custom svg', function () {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
expect(
|
||||
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {})
|
||||
).to.include({ logo: logoSvg })
|
||||
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {}).logo
|
||||
).to.equal(logoSvg)
|
||||
})
|
||||
|
||||
it('ignores the color when custom svg is provided', function () {
|
||||
@@ -227,36 +230,35 @@ describe('coalesceBadge', function () {
|
||||
{ logo: logoSvg, logoColor: 'brightgreen' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{}
|
||||
)
|
||||
).to.include({ logo: logoSvg })
|
||||
).logo
|
||||
).to.equal(logoSvg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo width', function () {
|
||||
it('overrides the logoWidth', function () {
|
||||
expect(coalesceBadge({ logoWidth: 20 }, {}, {})).to.include({
|
||||
logoWidth: 20,
|
||||
})
|
||||
expect(coalesceBadge({ logoWidth: 20 }, {}, {}).logoWidth).to.equal(20)
|
||||
})
|
||||
|
||||
it('applies the logo width', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {})
|
||||
).to.include({ logoWidth: 275 })
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {}).logoWidth
|
||||
).to.equal(275)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo position', function () {
|
||||
it('overrides the logoPosition', function () {
|
||||
expect(coalesceBadge({ logoPosition: -10 }, {}, {})).to.include({
|
||||
logoPosition: -10,
|
||||
})
|
||||
expect(
|
||||
coalesceBadge({ logoPosition: -10 }, {}, {}).logoPosition
|
||||
).to.equal(-10)
|
||||
})
|
||||
|
||||
it('applies the logo position', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {})
|
||||
).to.include({ logoPosition: -10 })
|
||||
.logoPosition
|
||||
).to.equal(-10)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -266,7 +268,8 @@ describe('coalesceBadge', function () {
|
||||
coalesceBadge(
|
||||
{ link: 'https://circleci.com/gh/badges/daily-tests' },
|
||||
{
|
||||
link: 'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
|
||||
link:
|
||||
'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
|
||||
},
|
||||
{}
|
||||
).links
|
||||
@@ -276,24 +279,20 @@ describe('coalesceBadge', function () {
|
||||
|
||||
describe('Style', function () {
|
||||
it('falls back to flat with invalid style', function () {
|
||||
expect(coalesceBadge({ style: 'pill' }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: 7 }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: undefined }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('flat')
|
||||
expect(coalesceBadge({ style: 7 }, {}, {}).template).to.equal('flat')
|
||||
expect(coalesceBadge({ style: undefined }, {}, {}).template).to.equal(
|
||||
'flat'
|
||||
)
|
||||
})
|
||||
|
||||
it('replaces legacy popout styles', function () {
|
||||
expect(coalesceBadge({ style: 'popout' }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: 'popout-square' }, {}, {})).to.include({
|
||||
style: 'flat-square',
|
||||
})
|
||||
expect(coalesceBadge({ style: 'popout' }, {}, {}).template).to.equal(
|
||||
'flat'
|
||||
)
|
||||
expect(
|
||||
coalesceBadge({ style: 'popout-square' }, {}, {}).template
|
||||
).to.equal('flat-square')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -301,7 +300,8 @@ describe('coalesceBadge', function () {
|
||||
it('overrides the cache length', function () {
|
||||
expect(
|
||||
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {})
|
||||
).to.include({ cacheLengthSeconds: 123 })
|
||||
.cacheLengthSeconds
|
||||
).to.equal(123)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export default function coalesce(...candidates) {
|
||||
'use strict'
|
||||
|
||||
module.exports = function coalesce(...candidates) {
|
||||
return candidates.find(c => c !== undefined && c !== null)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import coalesce from './coalesce.js'
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const coalesce = require('./coalesce')
|
||||
|
||||
// Sticking with our one-line spread implementation, and defaulting to
|
||||
// `undefined` instead of `null`, though h/t to
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import camelcase from 'camelcase'
|
||||
import BaseService from './base.js'
|
||||
import { isValidCategory } from './categories.js'
|
||||
import { Deprecated } from './errors.js'
|
||||
import { isValidRoute } from './route.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const camelcase = require('camelcase')
|
||||
const BaseService = require('./base')
|
||||
const { isValidCategory } = require('./categories')
|
||||
const { Deprecated } = require('./errors')
|
||||
const { isValidRoute } = require('./route')
|
||||
|
||||
const attrSchema = Joi.object({
|
||||
route: isValidRoute,
|
||||
@@ -42,4 +44,4 @@ function deprecatedService(attrs) {
|
||||
}
|
||||
}
|
||||
|
||||
export default deprecatedService
|
||||
module.exports = deprecatedService
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { expect } from 'chai'
|
||||
import deprecatedService from './deprecated-service.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const deprecatedService = require('./deprecated-service')
|
||||
|
||||
describe('DeprecatedService', function () {
|
||||
const route = {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Base error class
|
||||
*
|
||||
@@ -208,7 +210,7 @@ class Deprecated extends ShieldsRuntimeError {
|
||||
* badge when we catch and render the exception (Optional)
|
||||
*/
|
||||
|
||||
export {
|
||||
module.exports = {
|
||||
ShieldsRuntimeError,
|
||||
NotFound,
|
||||
ImproperlyConfigured,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Joi from 'joi'
|
||||
import { pathToRegexp, compile } from 'path-to-regexp'
|
||||
import categories from '../../services/categories.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
import { makeFullUrl } from './route.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const { pathToRegexp, compile } = require('path-to-regexp')
|
||||
const categories = require('../../services/categories')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const { makeFullUrl } = require('./route')
|
||||
|
||||
const optionalObjectOfKeyValues = Joi.object().pattern(
|
||||
/./,
|
||||
@@ -122,7 +124,12 @@ function transformExample(inExample, index, ServiceClass) {
|
||||
documentation,
|
||||
} = validateExample(inExample, index, ServiceClass)
|
||||
|
||||
const { label, message, color, style, namedLogo } = coalesceBadge(
|
||||
const {
|
||||
text: [label, message],
|
||||
color,
|
||||
template: style,
|
||||
namedLogo,
|
||||
} = coalesceBadge(
|
||||
{},
|
||||
staticPreview,
|
||||
ServiceClass.defaultBadgeData,
|
||||
@@ -153,4 +160,7 @@ function transformExample(inExample, index, ServiceClass) {
|
||||
}
|
||||
}
|
||||
|
||||
export { validateExample, transformExample }
|
||||
module.exports = {
|
||||
validateExample,
|
||||
transformExample,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { expect } from 'chai'
|
||||
import { test, given } from 'sazerac'
|
||||
import { validateExample, transformExample } from './examples.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { test, given } = require('sazerac')
|
||||
const { validateExample, transformExample } = require('./examples')
|
||||
|
||||
describe('validateExample function', function () {
|
||||
it('passes valid examples', function () {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import bytes from 'bytes'
|
||||
import configModule from 'config'
|
||||
import Joi from 'joi'
|
||||
import { fileSize } from '../../services/validators.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
fetchLimit: fileSize,
|
||||
userAgentBase: Joi.string().required(),
|
||||
}).required()
|
||||
const config = configModule.util.toObject()
|
||||
const publicConfig = Joi.attempt(config.public, schema, { allowUnknown: true })
|
||||
|
||||
const fetchLimitBytes = bytes(publicConfig.fetchLimit)
|
||||
|
||||
function getUserAgent(userAgentBase = publicConfig.userAgentBase) {
|
||||
let version = 'dev'
|
||||
if (process.env.DOCKER_SHIELDS_VERSION) {
|
||||
version = process.env.DOCKER_SHIELDS_VERSION
|
||||
}
|
||||
if (process.env.HEROKU_SLUG_COMMIT) {
|
||||
version = process.env.HEROKU_SLUG_COMMIT.substring(0, 7)
|
||||
}
|
||||
return `${userAgentBase}/${version}`
|
||||
}
|
||||
|
||||
export { fetchLimitBytes, getUserAgent }
|
||||
@@ -1,27 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { getUserAgent } from './got-config.js'
|
||||
|
||||
describe('getUserAgent function', function () {
|
||||
afterEach(function () {
|
||||
delete process.env.HEROKU_SLUG_COMMIT
|
||||
delete process.env.DOCKER_SHIELDS_VERSION
|
||||
})
|
||||
|
||||
it('uses the default userAgentBase', function () {
|
||||
expect(getUserAgent()).to.equal('shields (self-hosted)/dev')
|
||||
})
|
||||
|
||||
it('applies custom userAgentBase', function () {
|
||||
expect(getUserAgent('custom')).to.equal('custom/dev')
|
||||
})
|
||||
|
||||
it('uses short commit SHA from HEROKU_SLUG_COMMIT if available', function () {
|
||||
process.env.HEROKU_SLUG_COMMIT = '92090bd44742a5fac03bcb117002088fc7485834'
|
||||
expect(getUserAgent('custom')).to.equal('custom/92090bd')
|
||||
})
|
||||
|
||||
it('uses short commit SHA from DOCKER_SHIELDS_VERSION if available', function () {
|
||||
process.env.DOCKER_SHIELDS_VERSION = 'server-2021-11-22'
|
||||
expect(getUserAgent('custom')).to.equal('custom/server-2021-11-22')
|
||||
})
|
||||
})
|
||||
@@ -1,60 +0,0 @@
|
||||
import got, { CancelError } from 'got'
|
||||
import { Inaccessible, InvalidResponse } from './errors.js'
|
||||
import {
|
||||
fetchLimitBytes as fetchLimitBytesDefault,
|
||||
getUserAgent,
|
||||
} from './got-config.js'
|
||||
|
||||
const userAgent = getUserAgent()
|
||||
|
||||
async function sendRequest(gotWrapper, url, options) {
|
||||
const gotOptions = Object.assign({}, options)
|
||||
gotOptions.throwHttpErrors = false
|
||||
gotOptions.retry = { limit: 0 }
|
||||
gotOptions.headers = gotOptions.headers || {}
|
||||
gotOptions.headers['User-Agent'] = userAgent
|
||||
try {
|
||||
const resp = await gotWrapper(url, gotOptions)
|
||||
return { res: resp, buffer: resp.body }
|
||||
} catch (err) {
|
||||
if (err instanceof CancelError) {
|
||||
throw new InvalidResponse({
|
||||
underlyingError: new Error('Maximum response size exceeded'),
|
||||
})
|
||||
}
|
||||
throw new Inaccessible({ underlyingError: err })
|
||||
}
|
||||
}
|
||||
|
||||
function _fetchFactory(fetchLimitBytes = fetchLimitBytesDefault) {
|
||||
const gotWithLimit = got.extend({
|
||||
handlers: [
|
||||
(options, next) => {
|
||||
const promiseOrStream = next(options)
|
||||
promiseOrStream.on('downloadProgress', progress => {
|
||||
if (
|
||||
progress.transferred > fetchLimitBytes &&
|
||||
// just accept the file if we've already finished downloading
|
||||
// the entire file before we went over the limit
|
||||
progress.percent !== 1
|
||||
) {
|
||||
/*
|
||||
TODO: we should be able to pass cancel() a message
|
||||
https://github.com/sindresorhus/got/blob/main/documentation/advanced-creation.md#examples
|
||||
but by the time we catch it, err.message is just "Promise was canceled"
|
||||
*/
|
||||
promiseOrStream.cancel('Maximum response size exceeded')
|
||||
}
|
||||
})
|
||||
|
||||
return promiseOrStream
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return sendRequest.bind(sendRequest, gotWithLimit)
|
||||
}
|
||||
|
||||
const fetch = _fetchFactory()
|
||||
|
||||
export { fetch, _fetchFactory }
|
||||
@@ -1,67 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import nock from 'nock'
|
||||
import { _fetchFactory } from './got.js'
|
||||
import { Inaccessible, InvalidResponse } from './errors.js'
|
||||
|
||||
describe('got wrapper', function () {
|
||||
it('should not throw an error if the response <= fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(100))
|
||||
const sendRequest = _fetchFactory(100)
|
||||
const { res } = await sendRequest('https://www.google.com/foo/bar')
|
||||
expect(res.statusCode).to.equal(200)
|
||||
})
|
||||
|
||||
it('should throw an InvalidResponse error if the response is > fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(101))
|
||||
const sendRequest = _fetchFactory(100)
|
||||
return expect(
|
||||
sendRequest('https://www.google.com/foo/bar')
|
||||
).to.be.rejectedWith(InvalidResponse, 'Maximum response size exceeded')
|
||||
})
|
||||
|
||||
it('should throw an Inaccessible error if the request throws a (non-HTTP) error', async function () {
|
||||
nock('https://www.google.com').get('/foo/bar').replyWithError('oh no')
|
||||
const sendRequest = _fetchFactory(1024)
|
||||
return expect(
|
||||
sendRequest('https://www.google.com/foo/bar')
|
||||
).to.be.rejectedWith(Inaccessible, 'oh no')
|
||||
})
|
||||
|
||||
it('should throw an Inaccessible error if the host can not be accessed', async function () {
|
||||
this.timeout(5000)
|
||||
nock.disableNetConnect()
|
||||
const sendRequest = _fetchFactory(1024)
|
||||
return expect(
|
||||
sendRequest('https://www.google.com/foo/bar')
|
||||
).to.be.rejectedWith(
|
||||
Inaccessible,
|
||||
'Nock: Disallowed net connect for "www.google.com:443/foo/bar"'
|
||||
)
|
||||
})
|
||||
|
||||
it('should pass a custom user agent header', async function () {
|
||||
nock('https://www.google.com', {
|
||||
reqheaders: {
|
||||
'user-agent': function (agent) {
|
||||
return agent.startsWith('shields (self-hosted)')
|
||||
},
|
||||
},
|
||||
})
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200)
|
||||
const sendRequest = _fetchFactory(1024)
|
||||
await sendRequest('https://www.google.com/foo/bar')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
nock.cleanAll()
|
||||
nock.enableNetConnect()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
'use strict'
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
@@ -48,4 +49,4 @@ function mergeQueries(...queries) {
|
||||
return merged
|
||||
}
|
||||
|
||||
export { mergeQueries }
|
||||
module.exports = { mergeQueries }
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { expect } from 'chai'
|
||||
import gql from 'graphql-tag'
|
||||
import { print } from 'graphql/language/printer.js'
|
||||
import { mergeQueries } from './graphql.js'
|
||||
'use strict'
|
||||
|
||||
import '../register-chai-plugins.spec.js'
|
||||
const { expect } = require('chai')
|
||||
const gql = require('graphql-tag')
|
||||
const { print } = require('graphql/language/printer')
|
||||
const { mergeQueries } = require('./graphql')
|
||||
|
||||
require('../register-chai-plugins.spec')
|
||||
|
||||
describe('mergeQueries function', function () {
|
||||
it('merges valid gql queries', function () {
|
||||
@@ -11,7 +13,7 @@ describe('mergeQueries function', function () {
|
||||
print(
|
||||
mergeQueries(
|
||||
gql`
|
||||
query ($param: String!) {
|
||||
query($param: String!) {
|
||||
foo(param: $param) {
|
||||
bar
|
||||
}
|
||||
@@ -27,7 +29,7 @@ describe('mergeQueries function', function () {
|
||||
print(
|
||||
mergeQueries(
|
||||
gql`
|
||||
query ($param: String!) {
|
||||
query($param: String!) {
|
||||
foo(param: $param) {
|
||||
bar
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import BaseService from './base.js'
|
||||
import BaseJsonService from './base-json.js'
|
||||
import BaseGraphqlService from './base-graphql.js'
|
||||
import BaseStaticService from './base-static.js'
|
||||
import BaseSvgScrapingService from './base-svg-scraping.js'
|
||||
import BaseXmlService from './base-xml.js'
|
||||
import BaseYamlService from './base-yaml.js'
|
||||
import deprecatedService from './deprecated-service.js'
|
||||
import redirector from './redirector.js'
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const BaseService = require('./base')
|
||||
const BaseJsonService = require('./base-json')
|
||||
const BaseGraphqlService = require('./base-graphql')
|
||||
const NonMemoryCachingBaseService = require('./base-non-memory-caching')
|
||||
const BaseStaticService = require('./base-static')
|
||||
const BaseSvgScrapingService = require('./base-svg-scraping')
|
||||
const BaseXmlService = require('./base-xml')
|
||||
const BaseYamlService = require('./base-yaml')
|
||||
const deprecatedService = require('./deprecated-service')
|
||||
const redirector = require('./redirector')
|
||||
const {
|
||||
NotFound,
|
||||
InvalidResponse,
|
||||
Inaccessible,
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
ImproperlyConfigured,
|
||||
} from './errors.js'
|
||||
} = require('./errors')
|
||||
|
||||
export {
|
||||
module.exports = {
|
||||
BaseService,
|
||||
BaseJsonService,
|
||||
BaseGraphqlService,
|
||||
NonMemoryCachingBaseService,
|
||||
BaseStaticService,
|
||||
BaseSvgScrapingService,
|
||||
BaseXmlService,
|
||||
@@ -30,6 +33,5 @@ export {
|
||||
InvalidResponse,
|
||||
Inaccessible,
|
||||
InvalidParameter,
|
||||
ImproperlyConfigured,
|
||||
Deprecated,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
// See available emoji at http://emoji.muan.co/
|
||||
import emojic from 'emojic'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
import trace from './trace.js'
|
||||
const emojic = require('emojic')
|
||||
const { InvalidResponse } = require('./errors')
|
||||
const trace = require('./trace')
|
||||
|
||||
function parseJson(buffer) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
@@ -21,4 +23,6 @@ function parseJson(buffer) {
|
||||
return json
|
||||
}
|
||||
|
||||
export { parseJson }
|
||||
module.exports = {
|
||||
parseJson,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
import makeBadge from '../../badge-maker/lib/make-badge.js'
|
||||
import { setCacheHeaders } from './cache-headers.js'
|
||||
import { makeSend } from './legacy-result-sender.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
'use strict'
|
||||
|
||||
const request = require('request')
|
||||
const queryString = require('query-string')
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const { setCacheHeaders } = require('./cache-headers')
|
||||
const {
|
||||
Inaccessible,
|
||||
InvalidResponse,
|
||||
ShieldsRuntimeError,
|
||||
} = require('./errors')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const LruCache = require('./lru-cache')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
|
||||
const userAgent = 'Shields.io/2003a'
|
||||
|
||||
// We avoid calling the vendor's server for computation of the information in a
|
||||
// number of badges.
|
||||
const minAccuracy = 0.75
|
||||
|
||||
// The quotient of (vendor) data change frequency by badge request frequency
|
||||
// must be lower than this to trigger sending the cached data *before*
|
||||
// updating our data from the vendor's server.
|
||||
// Indeed, the accuracy of our badges are:
|
||||
// A(Δt) = 1 - min(# data change over Δt, # requests over Δt)
|
||||
// / (# requests over Δt)
|
||||
// = 1 - max(1, df) / rf
|
||||
const freqRatioMax = 1 - minAccuracy
|
||||
|
||||
// Request cache size of 5MB (~5000 bytes/image).
|
||||
const requestCache = new LruCache(1000)
|
||||
|
||||
// These query parameters are available to any badge. They are handled by
|
||||
// `coalesceBadge`.
|
||||
@@ -28,12 +56,32 @@ function flattenQueryParams(queryParams) {
|
||||
return Array.from(union).sort()
|
||||
}
|
||||
|
||||
function promisify(cachingRequest) {
|
||||
return (uri, options) =>
|
||||
new Promise((resolve, reject) => {
|
||||
cachingRequest(uri, options, (err, res, buffer) => {
|
||||
if (err) {
|
||||
if (err instanceof ShieldsRuntimeError) {
|
||||
reject(err)
|
||||
} else {
|
||||
// Wrap the error in an Inaccessible so it can be identified
|
||||
// by the BaseService handler.
|
||||
reject(new Inaccessible({ underlyingError: err }))
|
||||
}
|
||||
} else {
|
||||
resolve({ res, buffer })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// handlerOptions can contain:
|
||||
// - handler: The service's request handler function
|
||||
// - queryParams: An array of the field names of any custom query parameters
|
||||
// the service uses
|
||||
// - cacheLength: An optional badge or category-specific cache length
|
||||
// (in number of seconds) to be used in preference to the default
|
||||
// - fetchLimitBytes: A limit on the response size we're willing to parse
|
||||
//
|
||||
// For safety, the service must declare the query parameters it wants to use.
|
||||
// Only the declared parameters (and the global parameters) are provided to
|
||||
@@ -53,7 +101,10 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
}
|
||||
|
||||
const allowedKeys = flattenQueryParams(handlerOptions.queryParams)
|
||||
const { cacheLength: serviceDefaultCacheLengthSeconds } = handlerOptions
|
||||
const {
|
||||
cacheLength: serviceDefaultCacheLengthSeconds,
|
||||
fetchLimitBytes,
|
||||
} = handlerOptions
|
||||
|
||||
return (queryParams, match, end, ask) => {
|
||||
/*
|
||||
@@ -70,6 +121,8 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
const reqTime = new Date()
|
||||
|
||||
// `defaultCacheLengthSeconds` can be overridden by
|
||||
// `serviceDefaultCacheLengthSeconds` (either by category or on a badge-
|
||||
// by-badge basis). Then in turn that can be overridden by
|
||||
@@ -98,10 +151,49 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
filteredQueryParams[key] = queryParams[key]
|
||||
})
|
||||
|
||||
// Use sindresorhus query-string because it sorts the keys, whereas the
|
||||
// builtin querystring module relies on the iteration order.
|
||||
const stringified = queryString.stringify(filteredQueryParams)
|
||||
const cacheIndex = `${match[0]}?${stringified}`
|
||||
|
||||
// Should we return the data right away?
|
||||
const cached = requestCache.get(cacheIndex)
|
||||
let cachedVersionSent = false
|
||||
if (cached !== undefined) {
|
||||
// A request was made not long ago.
|
||||
const tooSoon = +reqTime - cached.time < cached.interval
|
||||
if (tooSoon || cached.dataChange / cached.reqs <= freqRatioMax) {
|
||||
const svg = makeBadge(cached.data.badgeData)
|
||||
setCacheHeadersOnResponse(
|
||||
ask.res,
|
||||
cached.data.badgeData.cacheLengthSeconds
|
||||
)
|
||||
makeSend(cached.data.format, ask.res, end)(svg)
|
||||
cachedVersionSent = true
|
||||
// We do not wish to call the vendor servers.
|
||||
if (tooSoon) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In case our vendor servers are unresponsive.
|
||||
let serverUnresponsive = false
|
||||
const serverResponsive = setTimeout(() => {
|
||||
serverUnresponsive = true
|
||||
if (cachedVersionSent) {
|
||||
return
|
||||
}
|
||||
if (requestCache.has(cacheIndex)) {
|
||||
const cached = requestCache.get(cacheIndex)
|
||||
const svg = makeBadge(cached.data.badgeData)
|
||||
setCacheHeadersOnResponse(
|
||||
ask.res,
|
||||
cached.data.badgeData.cacheLengthSeconds
|
||||
)
|
||||
makeSend(cached.data.format, ask.res, end)(svg)
|
||||
return
|
||||
}
|
||||
ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
const badgeData = coalesceBadge(
|
||||
filteredQueryParams,
|
||||
@@ -114,6 +206,55 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
makeSend(extension, ask.res, end)(svg)
|
||||
}, 25000)
|
||||
|
||||
// Only call vendor servers when last request is older than…
|
||||
let cacheInterval = 5000 // milliseconds
|
||||
function cachingRequest(uri, options, callback) {
|
||||
if (typeof options === 'function' && !callback) {
|
||||
callback = options
|
||||
}
|
||||
if (options && typeof options === 'object') {
|
||||
options.uri = uri
|
||||
} else if (typeof uri === 'string') {
|
||||
options = { uri }
|
||||
} else {
|
||||
options = uri
|
||||
}
|
||||
options.headers = options.headers || {}
|
||||
options.headers['User-Agent'] = userAgent
|
||||
|
||||
let bufferLength = 0
|
||||
const r = request(options, (err, res, body) => {
|
||||
if (res != null && res.headers != null) {
|
||||
const cacheControl = res.headers['cache-control']
|
||||
if (cacheControl != null) {
|
||||
const age = cacheControl.match(/max-age=([0-9]+)/)
|
||||
// Would like to get some more test coverage on this before changing it.
|
||||
// eslint-disable-next-line no-self-compare
|
||||
if (age != null && +age[1] === +age[1]) {
|
||||
cacheInterval = +age[1] * 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(err, res, body)
|
||||
})
|
||||
r.on('data', chunk => {
|
||||
bufferLength += chunk.length
|
||||
if (bufferLength > fetchLimitBytes) {
|
||||
r.abort()
|
||||
r.emit(
|
||||
'error',
|
||||
new InvalidResponse({
|
||||
prettyMessage: 'Maximum response size exceeded',
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapper around `cachingRequest` that returns a promise rather than needing
|
||||
// to pass a callback.
|
||||
cachingRequest.asPromise = promisify(cachingRequest)
|
||||
|
||||
const result = handlerOptions.handler(
|
||||
filteredQueryParams,
|
||||
match,
|
||||
@@ -123,16 +264,34 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
return
|
||||
}
|
||||
clearTimeout(serverResponsive)
|
||||
// Check for a change in the data.
|
||||
let dataHasChanged = false
|
||||
if (
|
||||
cached !== undefined &&
|
||||
cached.data.badgeData.text[1] !== badgeData.text[1]
|
||||
) {
|
||||
dataHasChanged = true
|
||||
}
|
||||
// Add format to badge data.
|
||||
badgeData.format = format
|
||||
const svg = makeBadge(badgeData)
|
||||
setCacheHeadersOnResponse(ask.res, badgeData.cacheLengthSeconds)
|
||||
makeSend(format, ask.res, end)(svg)
|
||||
}
|
||||
// Update information in the cache.
|
||||
const updatedCache = {
|
||||
reqs: cached ? cached.reqs + 1 : 1,
|
||||
dataChange: cached ? cached.dataChange + (dataHasChanged ? 1 : 0) : 1,
|
||||
time: +reqTime,
|
||||
interval: cacheInterval,
|
||||
data: { format, badgeData },
|
||||
}
|
||||
requestCache.set(cacheIndex, updatedCache)
|
||||
if (!cachedVersionSent) {
|
||||
const svg = makeBadge(badgeData)
|
||||
setCacheHeadersOnResponse(ask.res, badgeData.cacheLengthSeconds)
|
||||
makeSend(format, ask.res, end)(svg)
|
||||
}
|
||||
},
|
||||
cachingRequest
|
||||
)
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
if (result && result.catch) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
result.catch(err => {
|
||||
throw err
|
||||
})
|
||||
@@ -140,4 +299,15 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
export { handleRequest }
|
||||
function clearRequestCache() {
|
||||
requestCache.clear()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
handleRequest,
|
||||
promisify,
|
||||
clearRequestCache,
|
||||
// Expose for testing.
|
||||
_requestCache: requestCache,
|
||||
userAgent,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { expect } from 'chai'
|
||||
import portfinder from 'portfinder'
|
||||
import Camp from '@shields_io/camp'
|
||||
import got from '../got-test-client.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
import { handleRequest } from './legacy-request-handler.js'
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const nock = require('nock')
|
||||
const portfinder = require('portfinder')
|
||||
const Camp = require('@shields_io/camp')
|
||||
const got = require('../got-test-client')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const {
|
||||
handleRequest,
|
||||
clearRequestCache,
|
||||
_requestCache,
|
||||
} = require('./legacy-request-handler')
|
||||
|
||||
async function performTwoRequests(baseUrl, first, second) {
|
||||
expect((await got(`${baseUrl}${first}`)).statusCode).to.equal(200)
|
||||
@@ -41,6 +48,28 @@ function createFakeHandlerWithCacheLength(cacheLengthSeconds) {
|
||||
}
|
||||
}
|
||||
|
||||
function fakeHandlerWithNetworkIo(queryParams, match, sendBadge, request) {
|
||||
const [, someValue, format] = match
|
||||
request('https://www.google.com/foo/bar', (err, res, buffer) => {
|
||||
let message
|
||||
if (err) {
|
||||
message = err.prettyMessage
|
||||
} else {
|
||||
message = someValue
|
||||
}
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
{
|
||||
label: 'testing',
|
||||
message,
|
||||
format,
|
||||
},
|
||||
{}
|
||||
)
|
||||
sendBadge(format, badgeData)
|
||||
})
|
||||
}
|
||||
|
||||
describe('The request handler', function () {
|
||||
let port, baseUrl
|
||||
beforeEach(async function () {
|
||||
@@ -54,6 +83,7 @@ describe('The request handler', function () {
|
||||
camp.on('listening', () => done())
|
||||
})
|
||||
afterEach(function (done) {
|
||||
clearRequestCache()
|
||||
if (camp) {
|
||||
camp.close(() => done())
|
||||
camp = null
|
||||
@@ -110,20 +140,113 @@ describe('The request handler', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('the response size limit', function () {
|
||||
beforeEach(function () {
|
||||
camp.route(
|
||||
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
|
||||
handleRequest(standardCacheHeaders, {
|
||||
handler: fakeHandlerWithNetworkIo,
|
||||
fetchLimitBytes: 100,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should not throw an error if the response <= fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(100))
|
||||
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
|
||||
responseType: 'json',
|
||||
})
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
name: 'testing',
|
||||
value: '123',
|
||||
label: 'testing',
|
||||
message: '123',
|
||||
color: 'lightgrey',
|
||||
link: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw an error if the response is > fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(101))
|
||||
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
|
||||
responseType: 'json',
|
||||
})
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
name: 'testing',
|
||||
value: 'Maximum response size exceeded',
|
||||
label: 'testing',
|
||||
message: 'Maximum response size exceeded',
|
||||
color: 'lightgrey',
|
||||
link: [],
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
nock.cleanAll()
|
||||
})
|
||||
})
|
||||
|
||||
describe('caching', function () {
|
||||
describe('standard query parameters', function () {
|
||||
let handlerCallCount
|
||||
beforeEach(function () {
|
||||
handlerCallCount = 0
|
||||
})
|
||||
|
||||
function register({ cacheHeaderConfig }) {
|
||||
camp.route(
|
||||
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
|
||||
handleRequest(
|
||||
cacheHeaderConfig,
|
||||
(queryParams, match, sendBadge, request) => {
|
||||
++handlerCallCount
|
||||
fakeHandler(queryParams, match, sendBadge, request)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
context('With standard cache settings', function () {
|
||||
beforeEach(function () {
|
||||
register({ cacheHeaderConfig: standardCacheHeaders })
|
||||
})
|
||||
|
||||
it('should cache identical requests', async function () {
|
||||
await performTwoRequests(
|
||||
baseUrl,
|
||||
'/testing/123.svg',
|
||||
'/testing/123.svg'
|
||||
)
|
||||
expect(handlerCallCount).to.equal(1)
|
||||
})
|
||||
|
||||
it('should differentiate known query parameters', async function () {
|
||||
await performTwoRequests(
|
||||
baseUrl,
|
||||
'/testing/123.svg?label=foo',
|
||||
'/testing/123.svg?label=bar'
|
||||
)
|
||||
expect(handlerCallCount).to.equal(2)
|
||||
})
|
||||
|
||||
it('should ignore unknown query parameters', async function () {
|
||||
await performTwoRequests(
|
||||
baseUrl,
|
||||
'/testing/123.svg?foo=1',
|
||||
'/testing/123.svg?foo=2'
|
||||
)
|
||||
expect(handlerCallCount).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should set the expires header to current time + defaultCacheLengthSeconds', async function () {
|
||||
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
|
||||
const { headers } = await got(`${baseUrl}/testing/123.json`)
|
||||
@@ -154,6 +277,7 @@ describe('The request handler', function () {
|
||||
handleRequest(
|
||||
{ defaultCacheLengthSeconds: 300 },
|
||||
(queryParams, match, sendBadge, request) => {
|
||||
++handlerCallCount
|
||||
createFakeHandlerWithCacheLength(400)(
|
||||
queryParams,
|
||||
match,
|
||||
@@ -174,6 +298,7 @@ describe('The request handler', function () {
|
||||
handleRequest(
|
||||
{ defaultCacheLengthSeconds: 300 },
|
||||
(queryParams, match, sendBadge, request) => {
|
||||
++handlerCallCount
|
||||
createFakeHandlerWithCacheLength(200)(
|
||||
queryParams,
|
||||
match,
|
||||
@@ -220,6 +345,21 @@ describe('The request handler', function () {
|
||||
'no-cache, no-store, must-revalidate'
|
||||
)
|
||||
})
|
||||
|
||||
describe('the cache key', function () {
|
||||
beforeEach(function () {
|
||||
register({ cacheHeaderConfig: standardCacheHeaders })
|
||||
})
|
||||
const expectedCacheKey = '/testing/123.json?color=123&label=foo'
|
||||
it('should match expected and use canonical order - 1', async function () {
|
||||
await got(`${baseUrl}/testing/123.json?color=123&label=foo`)
|
||||
expect(_requestCache.cache).to.have.keys(expectedCacheKey)
|
||||
})
|
||||
it('should match expected and use canonical order - 2', async function () {
|
||||
await got(`${baseUrl}/testing/123.json?label=foo&color=123`)
|
||||
expect(_requestCache.cache).to.have.keys(expectedCacheKey)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom query parameters', function () {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user