Compare commits

..

5 Commits

Author SHA1 Message Date
Caleb Cartwright
c6e31d7f32 load and manage persisted tokens with scope support 2021-09-19 11:41:26 -05:00
Caleb Cartwright
3aadb79325 allow github service classes to define scope requirements 2021-09-19 11:40:21 -05:00
Caleb Cartwright
b8412fd80b support scoped and unscoped tokens in API Provider 2021-09-19 11:39:48 -05:00
Caleb Cartwright
345188e34b expose token pools internal counts of held tokens 2021-09-19 11:38:53 -05:00
Caleb Cartwright
a92dc72ff5 support including scopes on oauth authorization 2021-09-19 11:37:18 -05:00
1064 changed files with 56285 additions and 36887 deletions

359
.circleci/config.yml Normal file
View File

@@ -0,0 +1,359 @@
version: 2
main_steps: &main_steps
steps:
- checkout
- run:
name: Install dependencies
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.
CYPRESS_INSTALL_BINARY: 0
- run:
name: Linter
when: always
command: npm run lint
- run:
name: Core tests
when: always
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/core/results.xml
command: npm run test:core
- run:
name: Entrypoint tests
when: always
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/entrypoint/results.xml
command: npm run test:entrypoint
- store_test_results:
path: junit
- run:
name: 'Prettier check (quick fix: `npm run prettier`)'
when: always
command: npm run prettier:check
integration_steps: &integration_steps
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
- run:
name: Integration tests
when: always
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/integration/results.xml
command: npm run test:integration
- store_test_results:
path: junit
services_steps: &services_steps
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
- run:
name: Identify services tagged in the PR title
command: npm run test:services:pr:prepare
- run:
name: Run tests for tagged services
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/services/results.xml
command: RETRY_COUNT=3 npm run test:services:pr:run
- store_test_results:
path: junit
package_steps: &package_steps
steps:
- checkout
- run:
name: Install node and npm
command: |
set +e
export NVM_DIR="/opt/circleci/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install v14
nvm use v14
npm install -g npm
# Run the package tests on each currently supported node version. See:
# https://github.com/badges/shields/blob/master/badge-maker/README.md#node-version-support
# https://nodejs.org/en/about/releases/
- run:
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:
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
jobs:
main:
docker:
- image: circleci/node:14
<<: *main_steps
main@node-16:
docker:
- image: circleci/node:16
<<: *main_steps
integration:
docker:
- image: circleci/node:14
- image: redis
<<: *integration_steps
integration@node-16:
docker:
- image: circleci/node:16
- image: redis
<<: *integration_steps
danger:
docker:
- image: circleci/node:14
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
- run:
name: Danger
when: always
environment:
# https://github.com/gatsbyjs/gatsby/pull/11555
NODE_ENV: test
command: npm run danger ci
frontend:
docker:
- image: circleci/node:14
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
- run:
name: Prepare frontend tests
command: npm run defs && npm run features
- run:
name: Check types
command: npm run check-types:frontend
- run:
name: Frontend unit tests
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/frontend/results.xml
when: always
command: npm run test:frontend
- store_test_results:
path: junit
- run:
name: Frontend build completes successfully
when: always
command: npm run build
package:
machine: true
<<: *package_steps
services:
docker:
- image: circleci/node:14
<<: *services_steps
services@node-16:
docker:
- image: circleci/node:16
<<: *services_steps
e2e:
docker:
- image: cypress/base:14.16.0
steps:
- checkout
- restore_cache:
name: Restore Cypress binary
keys:
- v2-cypress-dependencies-{{ checksum "package-lock.json" }}
- run:
name: Install dependencies
command: npm ci
- run:
name: Frontend build
command: GATSBY_BASE_URL=http://localhost:8080 npm run build
- run:
name: Run tests
environment:
CYPRESS_REPORTER: junit
MOCHA_FILE: junit/e2e/results.xml
command: npm run e2e-on-build
- store_test_results:
path: junit
- store_artifacts:
path: cypress/videos
- store_artifacts:
path: cypress/screenshots
- save_cache:
name: Cache Cypress binary
paths:
# https://docs.cypress.io/guides/getting-started/installing-cypress.html#Binary-cache
- ~/.cache/Cypress
key: v2-cypress-dependencies-{{ checksum "package-lock.json" }}
workflows:
version: 2
on-commit:
jobs:
- main:
filters:
branches:
ignore: gh-pages
- main@node-16:
filters:
branches:
ignore: gh-pages
- integration@node-16:
filters:
branches:
ignore: gh-pages
- frontend:
filters:
branches:
ignore: gh-pages
- package:
filters:
branches:
ignore: gh-pages
- services:
filters:
branches:
ignore:
- master
- gh-pages
- services@node-16:
filters:
branches:
ignore:
- master
- gh-pages
- danger:
filters:
branches:
ignore:
- master
- gh-pages
- /dependabot\/.*/
- e2e:
filters:
branches:
ignore: gh-pages
# on-commit-with-cache:
# jobs:
# - npm-install:
# filters:
# branches:
# ignore: gh-pages
# - main:
# requires:
# - npm-install
# - main@node-latest:
# requires:
# - npm-install
# - frontend:
# requires:
# - npm-install
# - services:
# requires:
# - npm-install
# filters:
# branches:
# ignore: master
# - services@node-latest:
# requires:
# - npm-install
# filters:
# branches:
# ignore: master
# - danger:
# requires:
# - npm-install
# filters:
# branches:
# ignore: /dependabot\/.*/

View File

@@ -3,7 +3,6 @@ shields.env
.git/
.gitignore
.vscode/
fly.toml
# Improve layer cacheability.
Dockerfile

View File

@@ -2,6 +2,7 @@ extends:
- standard
- standard-jsx
- standard-react
- plugin:@typescript-eslint/recommended
- prettier
- eslint:recommended
@@ -17,13 +18,12 @@ settings:
react:
version: '16.8'
jsdoc:
mode: typescript
mode: jsdoc
plugins:
- chai-friendly
- jsdoc
- mocha
- icedfrisby
- no-extension-in-require
- sort-class-members
- import
@@ -35,34 +35,40 @@ overrides:
# list of rules, even if they only apply to certain files. That way the
# rules listed here are only ones which conflict.
- files:
- 'badge-maker/**/*.js'
- '**/*.cjs'
env:
node: true
es6: true
- files:
- '**/*.js'
- '!frontend/**/*.js'
- '!badge-maker/**/*.js'
env:
node: true
es6: true
rules:
no-console: 'off'
'@typescript-eslint/explicit-module-boundary-types': 'off'
- files:
- '**/*.@(ts|tsx)'
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
rules:
no-console: 'off'
# Argh.
'@typescript-eslint/explicit-function-return-type':
['error', { 'allowExpressions': true }]
'@typescript-eslint/no-empty-function': 'error'
'@typescript-eslint/no-var-requires': 'error'
'@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:
- '**/*.ts'
- core/**/*.ts
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
- files:
- 'frontend/**/*.js'
- gatsby-browser.js
- 'frontend/**/*.@(js|ts|tsx)'
parserOptions:
sourceType: 'module'
env:
@@ -107,20 +113,21 @@ overrides:
mocha: true
rules:
mocha/no-exclusive-tests: 'error'
mocha/no-skipped-tests: 'error'
mocha/no-mocha-arrows: 'error'
mocha/prefer-arrow-callback: 'error'
- files:
- 'services/**/*.tester.js'
rules:
icedfrisby/no-exclusive-tests: 'error'
icedfrisby/no-skipped-tests: 'error'
rules:
# Disable some rules from eslint:recommended.
no-empty: ['error', { 'allowEmptyCatch': true }]
# Allow unused parameters. In callbacks, removing them seems to obscure
# what the functions are doing.
'@typescript-eslint/no-unused-vars': ['error', { 'args': 'none' }]
no-unused-vars: 'off'
'@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.
@@ -137,8 +144,6 @@ rules:
func-style: ['error', 'declaration', { 'allowArrowFunctions': true }]
new-cap: ['error', { 'capIsNew': true }]
import/order: ['error', { 'newlines-between': 'never' }]
quotes:
['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }]
# Account for destructuring responses from upstream services,
# many of which do not follow camelcase
@@ -171,7 +176,7 @@ rules:
jsdoc/check-tag-names: 'error'
jsdoc/check-types: 'error'
jsdoc/implements-on-classes: 'error'
jsdoc/tag-lines: ['error', 'any', { 'startLines': 1 }]
jsdoc/newline-after-description: 'error'
jsdoc/require-param: 'error'
jsdoc/require-param-description: 'error'
jsdoc/require-param-name: 'error'
@@ -182,7 +187,11 @@ rules:
jsdoc/require-returns-type: 'error'
jsdoc/valid-types: 'error'
react/prop-types: 'off'
# Disable some from TypeScript.
'@typescript-eslint/camelcase': off
'@typescript-eslint/explicit-function-return-type': 'off'
'@typescript-eslint/no-empty-function': 'off'
react/jsx-sort-props: 'error'
react-hooks/rules-of-hooks: 'error'
react-hooks/exhaustive-deps: 'error'

View File

@@ -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. -->

View File

@@ -0,0 +1,37 @@
---
name: 💡 Badge Request
about: Ideas for new badges
labels: 'service-badge'
---
:clipboard: **Description**
<!--
A clear and concise description of the new badge.
- Which service is this badge for e.g: GitHub, Travis CI
- What sort of information should this badge show?
Provide an example in plain text e.g: "version | v1.01" or as a static badge
(static badge generator can be found at https://shields.io)
-->
:link: **Data**
<!--
Where can we get the data from?
- Is there a public API?
- Does the API requires an API key?
- Link to the API documentation.
-->
:microphone: **Motivation**
<!--
Please explain why this feature should be implemented and how it would be used.
- What is the specific use case?
-->
<!-- Love Shields? Please consider donating $10 to sustain our activities:
👉 https://opencollective.com/shields -->

View File

@@ -1,62 +0,0 @@
name: '💡 Badge Request'
description: Ideas for new badges
labels: ['service-badge']
body:
- type: markdown
attributes:
value: >
## Ideas for new badges
This issue template is for suggesting new badges which
**fetch and display data from an upstream service**.
If your suggestion is for a static badge
(which shows the same information every time it is requested), it is
[already possible to make these](https://shields.io/docs/static-badges).
We don't add specific routes for badges which only show static information.
- type: textarea
id: description
attributes:
label: '📋 Description'
description: |
A clear and concise description of the new badge.
- Which service is this badge for e.g: GitHub, Travis CI
- What sort of information should this badge show?
Provide an example in plain text e.g: "version | v1.01" or as a static badge
(static badge generator can be found at https://shields.io/badges/static-badge )
validations:
required: true
- type: textarea
id: data
attributes:
label: '🔗 Data'
description: |
Where can we get the data from?
Please consider and cover details like:
- Is there a public API?
- Does the API require authentication or an API key?
If so, please review our documentation on [Badges Requiring Authentication](https://github.com/badges/shields/blob/master/doc/authentication.md)
- Link to the API documentation.
validations:
required: true
- type: textarea
id: motivation
attributes:
label: '🎤 Motivation'
description: |
Please explain why this feature should be implemented and how it would be used.
- What is the specific use case?
validations:
required: true
- type: markdown
attributes:
value: |
## :heart: Love Shields?
Please consider donating $10 to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)

View File

@@ -8,5 +8,5 @@ inputs:
description: 'The GITHUB_TOKEN secret'
required: true
runs:
using: 'node16'
using: 'node12'
main: 'index.js'

View File

@@ -28,13 +28,14 @@ function allChangelogLinesAreVersionBump(changelogLines) {
changelogLines.length > 0 &&
changelogLines.length ===
changelogLines.filter(line =>
line.includes('Version bump only for package'),
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',
]
@@ -52,16 +53,9 @@ function isPointlessVersionBump(body) {
.filter(line => !line.startsWith('<h'))
.filter(line => !line.startsWith('<p>All notable changes'))
.filter(
line =>
!line.startsWith('See <a href="https://conventionalcommits.org">'),
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)
}

View File

@@ -27,7 +27,7 @@ async function run() {
state: 'closed',
})
core.debug('Done.')
core.debug(`Done.`)
}
}
} catch (error) {

View File

@@ -9,54 +9,50 @@
"version": "0.0.0",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
"@actions/core": "^1.5.0",
"@actions/github": "^5.0.0"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"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": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
"@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": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"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"
"tunnel": "0.0.6"
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"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.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"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.6.3",
"@octokit/request": "^5.4.12",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
@@ -64,9 +60,9 @@
}
},
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"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",
@@ -74,37 +70,37 @@
}
},
"node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"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.6.0",
"@octokit/request": "^5.3.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
"integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA=="
"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.17.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
"integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
"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.34.0"
"@octokit/types": "^6.11.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz",
"integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==",
"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.34.0",
"@octokit/types": "^6.14.1",
"deprecation": "^2.3.1"
},
"peerDependencies": {
@@ -112,22 +108,22 @@
}
},
"node_modules/@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"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.1.0",
"@octokit/types": "^6.16.1",
"@octokit/request-error": "^2.0.0",
"@octokit/types": "^6.7.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"node-fetch": "^2.6.1",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"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",
@@ -135,17 +131,17 @@
}
},
"node_modules/@octokit/types": {
"version": "6.34.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
"integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
"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": "^11.2.0"
"@octokit/openapi-types": "^7.0.0"
}
},
"node_modules/before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
"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",
@@ -161,22 +157,11 @@
}
},
"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"
},
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/once": {
@@ -187,11 +172,6 @@
"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",
@@ -205,28 +185,6 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"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",
@@ -235,49 +193,45 @@
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
},
"@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"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": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
"@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": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"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"
"tunnel": "0.0.6"
}
},
"@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"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.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"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.6.3",
"@octokit/request": "^5.4.12",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
@@ -285,9 +239,9 @@
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"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",
@@ -295,54 +249,54 @@
}
},
"@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"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.6.0",
"@octokit/request": "^5.3.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
"integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA=="
"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.17.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
"integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
"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.34.0"
"@octokit/types": "^6.11.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz",
"integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==",
"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.34.0",
"@octokit/types": "^6.14.1",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"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.1.0",
"@octokit/types": "^6.16.1",
"@octokit/request-error": "^2.0.0",
"@octokit/types": "^6.7.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"node-fetch": "^2.6.1",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"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",
@@ -350,17 +304,17 @@
}
},
"@octokit/types": {
"version": "6.34.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
"integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
"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": "^11.2.0"
"@octokit/openapi-types": "^7.0.0"
}
},
"before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
"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",
@@ -373,12 +327,9 @@
"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"
}
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"once": {
"version": "1.4.0",
@@ -388,11 +339,6 @@
"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",
@@ -403,25 +349,6 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"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",

View File

@@ -10,7 +10,7 @@
"author": "chris48s",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
"@actions/core": "^1.5.0",
"@actions/github": "^5.0.0"
}
}

View File

@@ -1,21 +0,0 @@
name: 'Core tests'
description: 'Run core and entrypoint tests'
runs:
using: 'composite'
steps:
- name: Core tests
if: always()
run: npm run test:core -- --reporter json --reporter-option 'output=reports/core.json'
shell: bash
- name: Entrypoint tests
if: always()
run: npm run test:entrypoint -- --reporter json --reporter-option 'output=reports/entrypoint.json'
shell: bash
- name: Write Markdown Summary
if: always()
run: |
node scripts/mocha2md.js Core reports/core.json >> $GITHUB_STEP_SUMMARY
node scripts/mocha2md.js Entrypoint reports/entrypoint.json >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -1,4 +1,4 @@
FROM node:18-bullseye
FROM node:12-buster
RUN apt-get update
RUN apt-get install -y jq

View File

@@ -2,17 +2,14 @@
set -euxo pipefail
# mark workspace dir as 'safe'
git config --system --add safe.directory '/github/workspace'
# 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)
# Set up a git user
git config user.name "release[bot]"
git config user.email "actions@users.noreply.github.com"
# Find the marker in CHANGELOG.md
INSERT_POINT=$(grep -n "^\-\-\-$" CHANGELOG.md | cut -f1 -d:)
INSERT_POINT=$((INSERT_POINT+1))

View File

@@ -1,28 +0,0 @@
name: 'Integration tests'
description: 'Run integration tests'
inputs:
github-token:
description: 'The GITHUB_TOKEN secret'
required: true
runs:
using: 'composite'
steps:
- name: Migrate DB
if: always()
run: npm run migrate up
env:
POSTGRES_URL: postgresql://postgres:postgres@localhost:5432/ci_test
shell: bash
- name: Integration Tests
if: always()
run: npm run test:integration -- --reporter json --reporter-option 'output=reports/integration-tests.json'
env:
GH_TOKEN: '${{ inputs.github-token }}'
POSTGRES_URL: postgresql://postgres:postgres@localhost:5432/ci_test
shell: bash
- name: Write Markdown Summary
if: always()
run: node scripts/mocha2md.js Integration reports/integration-tests.json >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -1,26 +0,0 @@
name: 'Package tests'
description: 'Run package tests and check types'
runs:
using: 'composite'
steps:
- name: Tests
if: always()
run: npm run test:package -- --reporter json --reporter-option 'output=reports/package-tests.json'
shell: bash
- name: Type Checks
if: always()
run: |
set -o pipefail
npm run check-types:package 2>&1 | tee reports/package-types.txt
shell: bash
- name: Write Markdown Summary
if: always()
run: |
node scripts/mocha2md.js 'Package Tests' reports/package-tests.json >> $GITHUB_STEP_SUMMARY
echo '# Package Types' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat reports/package-types.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -1,86 +0,0 @@
name: 'Service tests'
description: 'Run tests for selected services'
inputs:
github-token:
description: 'The GITHUB_TOKEN secret'
required: true
librariesio-tokens:
description: 'The SERVICETESTS_LIBRARIESIO_TOKENS secret'
required: false
default: ''
obs-user:
description: 'The SERVICETESTS_OBS_USER secret'
required: false
default: ''
obs-pass:
description: 'The SERVICETESTS_OBS_PASS secret'
required: false
default: ''
sl-insight-user-uuid:
description: 'The SERVICETESTS_SL_INSIGHT_USER_UUID secret'
required: false
default: ''
sl-insight-api-token:
description: 'The SERVICETESTS_SL_INSIGHT_API_TOKEN secret'
required: false
default: ''
twitch-client-id:
description: 'The SERVICETESTS_TWITCH_CLIENT_ID secret'
required: false
default: ''
twitch-client-secret:
description: 'The SERVICETESTS_TWITCH_CLIENT_SECRET secret'
required: false
default: ''
wheelmap-token:
description: 'The SERVICETESTS_WHEELMAP_TOKEN secret'
required: false
default: ''
youtube-api-key:
description: 'The SERVICETESTS_YOUTUBE_API_KEY secret'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Derive list of service tests to run
# Note: In this step we are using an intermediate env var instead of
# passing github.event.pull_request.title as an argument
# to prevent a shell injection attack. Further reading:
# https://securitylab.github.com/research/github-actions-untrusted-input/#exploitability-and-impact
# https://securitylab.github.com/research/github-actions-untrusted-input/#remediation
if: always()
env:
TITLE: ${{ github.event.pull_request.title }}
run: npm run test:services:pr:prepare "$TITLE"
shell: bash
- name: Run service tests
if: always()
run: npm run test:services:pr:run -- --reporter json --reporter-option 'output=reports/service-tests.json'
shell: bash
env:
RETRY_COUNT: 3
GH_TOKEN: '${{ inputs.github-token }}'
LIBRARIESIO_TOKENS: '${{ inputs.librariesio-tokens }}'
OBS_USER: '${{ inputs.obs-user }}'
OBS_PASS: '${{ inputs.obs-pass }}'
SL_INSIGHT_USER_UUID: '${{ inputs.sl-insight-user-uuid }}'
SL_INSIGHT_API_TOKEN: '${{ inputs.sl-insight-api-token }}'
TWITCH_CLIENT_ID: '${{ inputs.twitch-client-id }}'
TWITCH_CLIENT_SECRET: '${{ inputs.twitch-client-secret }}'
WHEELMAP_TOKEN: '${{ inputs.wheelmap-token }}'
YOUTUBE_API_KEY: '${{ inputs.youtube-api-key }}'
- name: Write Markdown Summary
if: always()
run: |
if test -f 'reports/service-tests.json'; then
echo '# Services' >> $GITHUB_STEP_SUMMARY
sed -e 's/^/- /' pull-request-services.log >> $GITHUB_STEP_SUMMARY
node scripts/mocha2md.js Report reports/service-tests.json >> $GITHUB_STEP_SUMMARY
else
echo 'No services found. Nothing to do.' >> $GITHUB_STEP_SUMMARY
fi
shell: bash

View File

@@ -1,40 +0,0 @@
name: 'Set up project'
description: 'Set up project'
inputs:
node-version:
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
required: true
cypress:
description: 'Install Cypress binary (boolean)'
type: boolean
# 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.
required: false
default: false
runs:
using: 'composite'
steps:
- name: Install Node JS ${{ inputs.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
- name: Install NPM 9
run: npm install -g npm@^9.0.0
shell: bash
- name: Install dependencies
if: ${{ inputs.cypress == 'false' }}
env:
CYPRESS_INSTALL_BINARY: 0
run: |
echo "skipping cypress binary"
npm ci
shell: bash
- name: Install dependencies (including cypress binary)
if: ${{ inputs.cypress == 'true' }}
run: |
echo "installing cypress binary"
npm ci
shell: bash

View File

@@ -8,17 +8,6 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
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
@@ -28,7 +17,6 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
# close-bot package dependencies
- package-ecosystem: npm
@@ -38,12 +26,3 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
# GH actions
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99
rebase-strategy: disabled

View File

@@ -1,10 +0,0 @@
#!/bin/bash
set -euxo pipefail
apps=$(flyctl apps list --json | jq -r .[].ID | grep -E "pr-[0-9]+-badges-shields") || exit 0
for app in $apps
do
flyctl apps destroy "$app" -y
done

View File

@@ -1,34 +0,0 @@
#!/bin/bash
set -euxo pipefail
app="pr-$PR_NUMBER-badges-shields"
region="ewr"
org="shields-io"
# Get PR JSON from the API
# This will fail if $PR_NUMBER is not a valid PR
pr_json=$(curl --fail "https://api.github.com/repos/badges/shields/pulls/$PR_NUMBER")
# Checkout the PR branch
git config user.name "actions[bot]"
git config user.email "actions@users.noreply.github.com"
git fetch origin "pull/$PR_NUMBER/head:pr-$PR_NUMBER"
git checkout "pr-$PR_NUMBER"
# If the app does not already exist, create it
if ! flyctl status --app "$app"; then
flyctl launch --no-deploy --copy-config --name "$app" --region "$region" --org "$org"
echo $SECRETS | tr " " "\n" | flyctl secrets import --app "$app"
fi
# Deploy
flyctl deploy --app "$app" --region "$region"
# Post a comment on the PR
app_url=$(flyctl status --app "$app" --json | jq -r .Hostname)
comment_url=$(echo "$pr_json" | jq .comments_url -r)
curl "$comment_url" \
-X POST \
-H "Authorization: token $GITHUB_TOKEN" \
--data "{\"body\":\"🚀 Updated review app: https://$app_url\"}"

View File

@@ -1,18 +1,16 @@
name: Auto close
on:
pull_request_target:
types: [opened]
on: pull_request_target
permissions:
pull-requests: write
jobs:
auto-close:
build:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Install action dependencies
run: cd .github/actions/close-bot && npm ci

View File

@@ -1,30 +1,20 @@
name: Build Docker Image
on:
pull_request:
push:
branches:
- 'gh-readonly-queue/**'
jobs:
build-docker-image:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
version: v0.9.1
- name: Set Git Short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
uses: docker/setup-buildx-action@v1
- name: Build
uses: docker/build-push-action@v4
uses: docker/build-push-action@v2
with:
context: .
push: false
tags: ghcr.io/badges/shields:pr-validation
build-args: |
version=${{ env.SHORT_SHA }}
tags: shieldsio/shields:pr-validation

View File

@@ -1,24 +0,0 @@
name: Cleanup Review Apps
on:
schedule:
- cron: '0 7 * * *'
# At 07:00, daily
workflow_dispatch:
jobs:
cleanup-review-apps:
runs-on: ubuntu-latest
environment: 'Review Apps'
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- name: install jq
run: |
sudo apt-get -qq update
sudo apt-get install -y jq
- run: .github/scripts/cleanup-review-apps.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

View File

@@ -4,10 +4,6 @@ on:
pull_request:
types: [closed]
permissions:
contents: write
packages: write
jobs:
create-release:
if: |
@@ -24,7 +20,7 @@ jobs:
run: echo "::set-output name=date::$(date --rfc-3339=date)"
- name: Checkout branch "master"
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
ref: 'master'
@@ -35,37 +31,17 @@ jobs:
tag: server-${{ steps.date.outputs.date }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
version: v0.9.1
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v2
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@v4
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 }}
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push snapshot release to GHCR
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ghcr.io/badges/shields:server-${{ steps.date.outputs.date }}
build-args: |
version=server-${{ steps.date.outputs.date }}

View File

@@ -1,29 +0,0 @@
name: Danger
on:
pull_request_target:
types: [opened, reopened, synchronize]
permissions:
checks: write
pull-requests: write
statuses: write
jobs:
danger:
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]' && github.actor != 'repo-ranger[bot]'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Danger
run: npm run danger ci
env:
# https://github.com/gatsbyjs/gatsby/pull/11555
NODE_ENV: test
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -3,30 +3,24 @@ on:
push:
branches:
- master
permissions:
contents: write
jobs:
deploy-docs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Build
run: npm run build-docs
run: |
npm ci
npm run build-docs
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
branch: gh-pages
folder: api-docs
clean: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: api-docs
CLEAN: true

View File

@@ -1,43 +0,0 @@
name: Create/Update Review App
on:
workflow_dispatch:
inputs:
pr_number:
description: 'PR Number to deploy e.g: 1234'
required: true
permissions:
pull-requests: write
jobs:
deploy-review-app:
runs-on: ubuntu-latest
environment: 'Review Apps'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: superfly/flyctl-actions/setup-flyctl@master
- name: install jq
run: |
sudo apt-get -qq update
sudo apt-get install -y jq
- run: .github/scripts/deploy-review-app.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
PR_NUMBER: ${{ github.event.inputs.pr_number }}
# credentials to set when we create the review app
SECRETS: |
GH_TOKEN=${{ secrets.GH_PAT }}
LIBRARIESIO_TOKENS=${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}
OBS_USER=${{ secrets.SERVICETESTS_OBS_USER }}
OBS_PASS=${{ secrets.SERVICETESTS_OBS_PASS }}
SL_INSIGHT_API_TOKEN=${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}
SL_INSIGHT_USER_UUID=${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}
TWITCH_CLIENT_ID=${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}
TWITCH_CLIENT_SECRET=${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}
WHEELMAP_TOKEN=${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}
YOUTUBE_API_KEY=${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}

View File

@@ -5,16 +5,12 @@ on:
# At 01:00 on the first day of every month
workflow_dispatch:
permissions:
pull-requests: write
contents: write
jobs:
draft-release:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Draft Release
uses: ./.github/actions/draft-release

View File

@@ -1,13 +0,0 @@
name: 'Dependency Review'
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
enforce-dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3

View File

@@ -4,51 +4,25 @@ on:
branches:
- master
permissions:
packages: write
jobs:
publish-docker-next:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
version: v0.9.1
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v2
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 to DockerHub
uses: docker/build-push-action@v4
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: shieldsio/shields:next
build-args: |
version=${{ env.SHORT_SHA }}
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push to GHCR
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ghcr.io/badges/shields:next
build-args: |
version=${{ env.SHORT_SHA }}

View File

@@ -1,67 +0,0 @@
name: Test new bug report badge
run-name: Test bug report on issue ${{ github.event.issue.number }}
on:
issues:
types: [opened]
jobs:
extract-bug-badge-url:
if: ${{ contains(github.event.issue.labels.*.name, 'question') }}
runs-on: ubuntu-latest
outputs:
runBadgeTest: ${{ steps.testCondition.outputs.runNext }}
link: ${{ steps.testCondition.outputs.link }}
steps:
- name: Test badge test run conditions
id: testCondition
run: |
product=$(echo "${{ github.event.issue.body }}" | grep -A2 "Are you experiencing an issue with.*" | tail -n 1)
link=$(echo "${{ github.event.issue.body }}" | grep -A2 "Link to the badge.*" | tail -n 1)
if [[ "$product" == "shields.io" && "$link" == "https://img.shields.io"* ]]; then
echo "runNext=true" >> "$GITHUB_OUTPUT"
echo "link=$link" >> "$GITHUB_OUTPUT"
else
echo "Conditions not met. Skipping the workflow..."
echo "runNext=false" >> "$GITHUB_OUTPUT"
fi
run-bug-badge-url-test:
needs: extract-bug-badge-url
if: needs.extract-bug-badge-url.outputs.runBadgeTest == 'true'
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
cypress: false
- name: Output debug info
env:
TEST_BADGE_LINK: '${{ needs.extract-bug-badge-url.outputs.link }}'
run: npm run badge $TEST_BADGE_LINK
- name: Add Comment to Issue
uses: actions/github-script@v6
with:
script: |
const issueNumber = context.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const runId = context.runId;
const jobUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`;
const issueComment = `
Badge tested using \`npm run badge ${{ needs.extract-bug-badge-url.outputs.link }}\`
Output is available [here](${jobUrl})
`;
github.rest.issues.createComment({
issue_number: issueNumber,
owner: owner,
repo: repo,
body: issueComment
});

View File

@@ -1,49 +0,0 @@
name: E2E
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache Cypress binary
id: cache-cypress
uses: actions/cache@v3
env:
cache-name: cache-cypress
with:
path: ~/.cache/Cypress
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
cypress: true
- name: Run tests
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: npm run e2e
- name: Archive videos
if: always()
uses: actions/upload-artifact@v3
with:
name: videos
path: cypress/videos
- name: Archive screenshots
if: always()
uses: actions/upload-artifact@v3
with:
name: screenshots
path: cypress/screenshots

View File

@@ -1,52 +0,0 @@
name: Integration@node 18
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-integration-18:
runs-on: ubuntu-latest
env:
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
services:
postgres:
image: postgres
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: ci_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
env:
NPM_CONFIG_ENGINE_STRICT: 'false'
- name: Integration Tests (with PAT)
if: ${{ env.PAT_EXISTS == 'true' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GH_PAT }}'
- name: Integration Tests (with workflow token)
if: ${{ env.PAT_EXISTS == 'false' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,50 +0,0 @@
name: Integration
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-integration:
runs-on: ubuntu-latest
env:
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
services:
postgres:
image: postgres
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: ci_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Integration Tests (with PAT)
if: ${{ env.PAT_EXISTS == 'true' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GH_PAT }}'
- name: Integration Tests (with workflow token)
if: ${{ env.PAT_EXISTS == 'false' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,28 +0,0 @@
name: Lint
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: ESLint
if: always()
run: npm run lint
- name: 'Prettier check (quick fix: `npm run prettier`)'
if: always()
run: npm run prettier:check

View File

@@ -1,25 +0,0 @@
name: Main@node 18
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-main-18:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
env:
NPM_CONFIG_ENGINE_STRICT: 'false'
- name: Core tests
uses: ./.github/actions/core-tests

View File

@@ -1,28 +0,0 @@
name: Main
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-main:
strategy:
matrix:
os: ['ubuntu-latest', 'windows-latest']
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Core tests
uses: ./.github/actions/core-tests

View File

@@ -1,40 +0,0 @@
name: Package CLI
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
# Smoke test (render a badge with the CLI) with only the package
# dependencies installed.
jobs:
test-package-cli:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- node: '16'
- node: '18'
- node: '20'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node JS ${{ inputs.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: |
cd badge-maker
npm install
npm link
- name: Render a badge with the CLI
run: |
cd badge-maker
badge cactus grown :green @flat

View File

@@ -1,34 +0,0 @@
name: Package Library
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-package-lib:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- node: '16'
engine-strict: 'true'
- node: '18'
engine-strict: 'false'
- node: '20'
engine-strict: 'false'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ matrix.node }}
env:
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
- name: Package tests
uses: ./.github/actions/package-tests

View File

@@ -1,43 +0,0 @@
name: Services@node 18
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services-18:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
env:
NPM_CONFIG_ENGINE_STRICT: 'false'
- name: Service tests (triggered from local branch)
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/service-tests
with:
github-token: '${{ secrets.GH_PAT }}'
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
twitch-client-secret: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
wheelmap-token: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
youtube-api-key: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
- name: Service tests (triggered from fork)
if: github.event.pull_request.head.repo.full_name != github.repository
uses: ./.github/actions/service-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,41 +0,0 @@
name: Services
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Service tests (triggered from local branch)
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/service-tests
with:
github-token: '${{ secrets.GH_PAT }}'
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
twitch-client-secret: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
wheelmap-token: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
youtube-api-key: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
- name: Service tests (triggered from fork)
if: github.event.pull_request.head.repo.full_name != github.repository
uses: ./.github/actions/service-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,33 +0,0 @@
name: Update GitHub API Version
on:
schedule:
- cron: '0 7 * * 6'
# At 07:00 on Saturday
workflow_dispatch:
permissions:
pull-requests: write
contents: write
jobs:
update-github-api:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Check for new GitHub API version
run: node scripts/update-github-api.js
- name: Create Pull Request if config has changed
uses: peter-evans/create-pull-request@v5
with:
token: '${{ secrets.GITHUB_TOKEN }}'
commit-message: Update GitHub API Version
title: Update [GitHub] API Version
branch-suffix: random

18
.gitignore vendored
View File

@@ -92,7 +92,10 @@ typings/
# Temporary build artifacts.
/build
frontend/categories/*.yaml
.next
badge-examples.json
supported-features.json
service-definitions.yml
# Local runtime configuration.
/config/local*.yml
@@ -100,6 +103,11 @@ frontend/categories/*.yaml
# Template for the local runtime configuration.
!/config/local*.template.yml
# Gatsby
/frontend/.cache
/frontend/public
/public
# Cypress
/cypress/videos/
/cypress/screenshots/
@@ -109,11 +117,3 @@ frontend/categories/*.yaml
# Flamebearer
flamegraph.html
# config file for node-pg-migrate
migrations-config.json
# Frontend/Docusaurus
frontend/.docusaurus
frontend/.cache-loader
/public

5
.mocharc-frontend.yml Normal file
View File

@@ -0,0 +1,5 @@
reporter: mocha-env-reporter
require:
- '@babel/polyfill'
- '@babel/register'
- mocha-yaml-loader

2
.npmrc
View File

@@ -1,2 +0,0 @@
engine-strict=true
strict-peer-deps=true

10
.nycrc-frontend.json Normal file
View File

@@ -0,0 +1,10 @@
{
"reporter": ["lcov"],
"all": false,
"silent": true,
"clean": false,
"sourceMap": false,
"instrument": false,
"include": ["frontend/**/*.js"],
"exclude": ["**/*.spec.js", "**/mocha-*.js"]
}

View File

@@ -10,5 +10,5 @@ public
private/*.json
/.nyc_output
analytics.json
frontend/.docusaurus
frontend/categories
supported-features.json
service-definitions.yml

View File

@@ -4,339 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2023-08-01
- Convert `examples` arrays to `openApi` objects (part 1) [#9320](https://github.com/badges/shields/issues/9320)
- Migrate from docs.rs' builds API to status API [#9422](https://github.com/badges/shields/issues/9422)
- [OpenVSX] Fix OpenVSX API call for unversioned package URLs [#9408](https://github.com/badges/shields/issues/9408)
- Add support for [Lemmy] [#9368](https://github.com/badges/shields/issues/9368)
- upgrade to npm 9 [#9323](https://github.com/badges/shields/issues/9323)
- Go back to default YouTube cache [#9372](https://github.com/badges/shields/issues/9372)
- Add [GitHubDiscussionsSearch] and GitHubRepoDiscussionsSearch service [#9340](https://github.com/badges/shields/issues/9340)
- Allow user to filter github tags and releases [#9193](https://github.com/badges/shields/issues/9193)
- don't URL encode slash in [githubactionsworkflow] badge [#9322](https://github.com/badges/shields/issues/9322)
- add a bit of border to select boxes [#9348](https://github.com/badges/shields/issues/9348)
- deprecate [snyk] badges [#9349](https://github.com/badges/shields/issues/9349)
- increase max-age on [docker] badges, again [#9350](https://github.com/badges/shields/issues/9350) [#9369](https://github.com/badges/shields/issues/9369)
- Dependency updates
## server-2023-07-02
By far the most significant change in this release is the long-awaited launch of the re-designed frontend:
- migrate frontend to docusaurus [#9014](https://github.com/badges/shields/issues/9014)
- fix a load of spacing issues in frontend content [#9281](https://github.com/badges/shields/issues/9281)
- set a sensible meta description [#9283](https://github.com/badges/shields/issues/9283)
- chore(frontend): open homepage feature links in new tab [#9300](https://github.com/badges/shields/issues/9300)
- adapt opencollective images to theme background [#9298](https://github.com/badges/shields/issues/9298)
- temp fix: wrap code examples tabs in narrow browser windows [#9302](https://github.com/badges/shields/issues/9302)
- add a bit of border to text boxes [#9324](https://github.com/badges/shields/issues/9324)
Other changes in this release:
- cache [dockerpulls] badges for an hour [#9343](https://github.com/badges/shields/issues/9343)
- Mention YouTube API services and link to Google Privacy Policy [#9339](https://github.com/badges/shields/issues/9339)
- allow negative timestamps in relative [date] badge [#9321](https://github.com/badges/shields/issues/9321)
- upgrade to graphql 16 [#9290](https://github.com/badges/shields/issues/9290)
- remove obsolete travis .org examples [#9284](https://github.com/badges/shields/issues/9284)
- increase max age on reddit badges [#9282](https://github.com/badges/shields/issues/9282)
- feat: Add author filter option for [GithubCommitActivity] [#9251](https://github.com/badges/shields/issues/9251)
- Fix: [GithubCommitActivity] invalid branch error handling [#9258](https://github.com/badges/shields/issues/9258)
- Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] [#9233](https://github.com/badges/shields/issues/9233)
- Delete old deprecated services [#9254](https://github.com/badges/shields/issues/9254)
- feat: add 'canceled' status to netlify deploy badge [#9240](https://github.com/badges/shields/issues/9240)
- increase default cache on youtube badges [#9238](https://github.com/badges/shields/issues/9238)
- embiggen youtube cache, again [#9250](https://github.com/badges/shields/issues/9250)
- Dependency updates
## server-2023-06-01
- feat: Add total commits to [GitHubCommitActivity] [#9196](https://github.com/badges/shields/issues/9196)
- set a custom error on 429 [#9159](https://github.com/badges/shields/issues/9159)
- deprecate [travis].org badges [#9171](https://github.com/badges/shields/issues/9171)
- count private sponsors on [GithubSponsors] badge [#9170](https://github.com/badges/shields/issues/9170)
- Dependency updates
## server-2023-05-01
** Removal:** For users who need to maintain a Github Token pool, storage has been provided via the `RedisTokenPersistence` and `REDIS_URL` settings. This feature was deprecated in `server-2023-03-01`. As of this release, the `RedisTokenPersistence` backend is now removed. If you are using this feature, you will need to migrate to using the `SQLTokenPersistence` backend for storage and provide a postgres connection string via the `POSTGRES_URL` setting. [#8922](https://github.com/badges/shields/issues/8922)
- fail to start server if there are duplicate service names [#9099](https://github.com/badges/shields/issues/9099)
- [SourceForge] Added badges for SourceForge [#9078](https://github.com/badges/shields/issues/9078) [#9102](https://github.com/badges/shields/issues/9102)
- crates: Use `?include=` to reduce crates.io backend load [#9081](https://github.com/badges/shields/issues/9081)
- Dependency updates
## server-2023-04-02
- [JenkinsCoverage] Update Jenkins Code Coverage API for new plugin version [#9010](https://github.com/badges/shields/issues/9010)
- [CTAN] fallback to date if version is empty [#9036](https://github.com/badges/shields/issues/9036)
- Update to [CTAN] API version 2.0 [#9016](https://github.com/badges/shields/issues/9016)
- handle missing statistics array in [VisualStudioMarketplace] badges [#8985](https://github.com/badges/shields/issues/8985)
- [Netlify] upgrade colors for SVG parsing [#8971](https://github.com/badges/shields/issues/8971)
- Fix [Vcpkg] version service for different version fields [#8945](https://github.com/badges/shields/issues/8945)
- only try to close pool if one exists [#8947](https://github.com/badges/shields/issues/8947)
- misc minor fixes to [githubsize node pypi] [#8946](https://github.com/badges/shields/issues/8946)
- Dependency updates
## server-2023-03-01
**Deprecation:** For users who need to maintain a Github Token pool, storage has been provided via the `RedisTokenPersistence` and `REDIS_URL` settings. As of this release, the `RedisTokenPersistence` backend is now deprecated and will be removed in a future release. If you are using this feature, you will need to migrate to using the `SQLTokenPersistence` backend for storage and provide a postgres connection string via the `POSTGRES_URL` setting. [#8922](https://github.com/badges/shields/issues/8922)
- fix: for crates.io versions, use max_stable_version if it exists [#8687](https://github.com/badges/shields/issues/8687)
- don't autofocus search [#8927](https://github.com/badges/shields/issues/8927)
- Add [Vcpkg] version service [#8923](https://github.com/badges/shields/issues/8923)
- fix: Set uid/gid in docker image to 0 [#8908](https://github.com/badges/shields/issues/8908)
- expose port 443 in Dockerfile [#8889](https://github.com/badges/shields/issues/8889)
- Dependency updates
## server-2023-02-01
- replace [twitter] badge with static fallback [#8842](https://github.com/badges/shields/issues/8842)
- Add various [Polymart] badges [#8811](https://github.com/badges/shields/issues/8811)
- update [githubpipenv] tests/examples [#8797](https://github.com/badges/shields/issues/8797)
- deprecate [apm] service [#8773](https://github.com/badges/shields/issues/8773)
- deprecate lgtm [#8771](https://github.com/badges/shields/issues/8771)
- Dependency updates
## server-2023-01-01
- Breaking change: Routes for GitHub workflows badge have changed. See https://github.com/badges/shields/issues/8671 for more details
- Behaviour change: In this release we fixed a long standing bug. GitHub badges were previously not reading the base URL from the `config.service.baseUri`.
This release fixes that bug, bringing the code into line with the documented behaviour. This should not cause a behaviour change for most users,
but users who had previously set a value in `config.service.baseUri` which was previously ignored could see this now have an effect.
Users who configure their instance using env vars rather than yaml should see no change.
- Send `X-GitHub-Api-Version` when calling [GitHub] v3 API [#8669](https://github.com/badges/shields/issues/8669)
- add [VpmVersion] badge [#8755](https://github.com/badges/shields/issues/8755)
- Add [modrinth] game versions [#8673](https://github.com/badges/shields/issues/8673)
- fix debug logging of undefined query params [#8540](https://github.com/badges/shields/issues/8540), [#8757](https://github.com/badges/shields/issues/8757)
- fall back to classifiers if [pypi] license text is really long [#8690](https://github.com/badges/shields/issues/8690)
- allow passing key to [stackexchange] [#8539](https://github.com/badges/shields/issues/8539)
- Dependency updates
## server-2022-12-01
- fix: support logoColor to shield icons. [#8263](https://github.com/badges/shields/issues/8263)
- handle missing properties array in [VisualStudioMarketplaceVersion] [#8603](https://github.com/badges/shields/issues/8603)
- deprecate [wercker] service [#8642](https://github.com/badges/shields/issues/8642)
- Add [Coincap] Cryptocurrency badges [#8623](https://github.com/badges/shields/issues/8623)
- Add [modrinth] version [#8604](https://github.com/badges/shields/issues/8604)
- [factorio-mod-portal] services [#8625](https://github.com/badges/shields/issues/8625)
- [Coveralls] for GitLab [#8584](https://github.com/badges/shields/issues/8584), [#8644](https://github.com/badges/shields/issues/8644)
- Remove 'suggest badges' feature [#8311](https://github.com/badges/shields/issues/8311)
- Add [modrinth] followers [#8601](https://github.com/badges/shields/issues/8601)
- Update the [modrinth] API to v2 [#8600](https://github.com/badges/shields/issues/8600)
- tidy up [GitHubGist] routes [#8510](https://github.com/badges/shields/issues/8510)
- fix [flathub] version error handling [#8500](https://github.com/badges/shields/issues/8500)
- Dependency updates
## server-2022-11-01
- [Ansible] Add collection badge [#8578](https://github.com/badges/shields/issues/8578)
- [VisualStudioMarketplace] Add support to prerelease extensions version (Issue #8207) [#8561](https://github.com/badges/shields/issues/8561)
- feat: add [GitlabLastCommit] service [#8508](https://github.com/badges/shields/issues/8508)
- fix [swagger] service tests (allow 0 items in array) [#8564](https://github.com/badges/shields/issues/8564)
- fix codecov badge for non-default branch [#8565](https://github.com/badges/shields/issues/8565)
- Add [GitHubLastCommit] by committer badge [#8537](https://github.com/badges/shields/issues/8537)
- [GitHubReleaseDate] - published_at field [#8543](https://github.com/badges/shields/issues/8543)
- Fix [Testspace] with new "untested" value in case_counts array [#8544](https://github.com/badges/shields/issues/8544)
- fix: Support WAITING status for GitHub deployments [#8521](https://github.com/badges/shields/issues/8521)
- [Whatpulse] badge for a user and for a team [#8466](https://github.com/badges/shields/issues/8466)
- deprecate [pkgreview] service [#8499](https://github.com/badges/shields/issues/8499)
- Dependency updates
## server-2022-10-08
- deprecate [criterion] service [#8501](https://github.com/badges/shields/issues/8501)
- fix formatRelativeDate error handling; run [date] [#8497](https://github.com/badges/shields/issues/8497)
- allow/validate bitbucket_username / bitbucket_password in private config schema [#8472](https://github.com/badges/shields/issues/8472)
- fix [pub] points badge test and example [#8498](https://github.com/badges/shields/issues/8498)
- feat: add [GitlabLanguageCount] service [#8377](https://github.com/badges/shields/issues/8377)
- [GitHubGistStars] add GitHub Gist Stars [#8471](https://github.com/badges/shields/issues/8471)
- fix display/search of CII badge examples [#8473](https://github.com/badges/shields/issues/8473)
- feat: add 2022 support to GitHub Hacktoberfest [#8468](https://github.com/badges/shields/issues/8468)
- fix [GitLabCoverage] subgroup bug [#8401](https://github.com/badges/shields/issues/8401)
- implement ruby gems-specific version sort/color functions [#8434](https://github.com/badges/shields/issues/8434)
- Add `rc` to pre-release identifiers [#8435](https://github.com/badges/shields/issues/8435)
- add [GitHub] Number of commits between branches/tags/commits [#8394](https://github.com/badges/shields/issues/8394)
- add [Packagist] dependency version [#8371](https://github.com/badges/shields/issues/8371)
- fix Docker build status invalid response data bug [#8392](https://github.com/badges/shields/issues/8392)
- Dependency updates
## server-2022-09-04
- fix frontend compile for users running on Windows [#8350](https://github.com/badges/shields/issues/8350)
- [DockerSize] Docker image size multi arch [#8290](https://github.com/badges/shields/issues/8290)
- upgrade gatsby [#8334](https://github.com/badges/shields/issues/8334)
- Custom domains for [JitPack] artifacts [#8333](https://github.com/badges/shields/issues/8333)
- fix [dockerstars] service [#8316](https://github.com/badges/shields/issues/8316)
- [BountySource] Fix: Broken Badge generation for decimal activity values [#8315](https://github.com/badges/shields/issues/8315)
- feat: add [gitlabmergerequests] service [#8166](https://github.com/badges/shields/issues/8166)
- Fix terminology for [ROS] version service [#8292](https://github.com/badges/shields/issues/8292)
- feat: add [GitlabStars] service [#8209](https://github.com/badges/shields/issues/8209)
- Fix invalid `rst` format when `alt` or `target` is present [#8275](https://github.com/badges/shields/issues/8275)
- [GithubGistLastCommit] GitHub gist last commit [#8272](https://github.com/badges/shields/issues/8272)
- [GitHub] GitHub file size for a specific branch [#8262](https://github.com/badges/shields/issues/8262)
- Dependency updates
## server-2022-08-01
- [pypi] Add Framework Version Badges support [#8261](https://github.com/badges/shields/issues/8261)
- feat: add [GitlabForks] server [#8208](https://github.com/badges/shields/issues/8208)
- Update PyPI api according to https://warehouse.pypa.io/api-reference/json.html [#8251](https://github.com/badges/shields/issues/8251)
- Add [galaxytoolshed] Activity [#8164](https://github.com/badges/shields/issues/8164)
- [greasyfork] Add Greasy Fork rating badges [#8087](https://github.com/badges/shields/issues/8087)
- refactor(deps): Replace moment with dayjs [#8192](https://github.com/badges/shields/issues/8192)
- add spaces round pipe in [conda] badge [#8189](https://github.com/badges/shields/issues/8189)
- Add [ROS] version service [#8169](https://github.com/badges/shields/issues/8169)
- feat: add [gitlabissues] service [#8108](https://github.com/badges/shields/issues/8108)
- Dependency updates
## server-2022-07-03
- Add [galaxytoolshed] services [#8114](https://github.com/badges/shields/issues/8114)
- fix [gitlab] auth [#8145](https://github.com/badges/shields/issues/8145) [#8162](https://github.com/badges/shields/issues/8162)
- increase cache length on AUR version badge, run [AUR] [#8110](https://github.com/badges/shields/issues/8110)
- Use GraphQL to fix GitHub file count badges [github] [#8112](https://github.com/badges/shields/issues/8112)
- feat: add [gitlab] contributors service [#8084](https://github.com/badges/shields/issues/8084)
- [greasyfork] Add Greasy Fork service badges [#8080](https://github.com/badges/shields/issues/8080)
- Add [gitlablicense] services [#8024](https://github.com/badges/shields/issues/8024)
- [Spack] Package Manager: Update Domain [#8046](https://github.com/badges/shields/issues/8046)
- switch [jitpack] to use latestOk endpoint [#8041](https://github.com/badges/shields/issues/8041)
- Dependency updates
## server-2022-06-01
- Update GitLab logo (2022) [#7984](https://github.com/badges/shields/issues/7984)
- [GitHub] Added milestone property to GitHub issue details service [#7864](https://github.com/badges/shields/issues/7864)
- [Spack] Package Manager: Update Endpoint [#7957](https://github.com/badges/shields/issues/7957)
- Update Chocolatey API endpoint URL [#7952](https://github.com/badges/shields/issues/7952)
- [Flathub]Add downloads badge [#7724](https://github.com/badges/shields/issues/7724)
- replace the outdated Telegram logo with the newest [#7831](https://github.com/badges/shields/issues/7831)
- add [PUB] points badge [#7918](https://github.com/badges/shields/issues/7918)
- add [PUB] popularity badge [#7920](https://github.com/badges/shields/issues/7920)
- add [PUB] likes badge [#7916](https://github.com/badges/shields/issues/7916)
- Dependency updates
## server-2022-05-03
- [OSSFScorecard] Create scorecard badge service [#7687](https://github.com/badges/shields/issues/7687)
- Stringify [githublanguagecount] message [#7881](https://github.com/badges/shields/issues/7881)
- Stringify and trim whitespace from a few services [#7880](https://github.com/badges/shields/issues/7880)
- add labels to Dockerfile [#7862](https://github.com/badges/shields/issues/7862)
- handle missing 'fly-client-ip' [#7814](https://github.com/badges/shields/issues/7814)
- Dependency updates
## server-2022-04-03
- Breaking change: This release updates ioredis from v4 to v5.
If you are using redis for GitHub token pooling, redis connection strings of the form
`redis://junkusername:authpassword@example.com:1234` will need to be updated to
`redis://:authpassword@example.com:1234`. See the
[ioredis upgrade guide](https://github.com/luin/ioredis/wiki/Upgrading-from-v4-to-v5)
for further details.
- fix installation issue on npm >= 8.5.5 [#7809](https://github.com/badges/shields/issues/7809)
- 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)
- Add a [pub] publisher badge [#7715](https://github.com/badges/shields/issues/7715)
- 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)
- Support [HexPM] packages with no stable release [#7685](https://github.com/badges/shields/issues/7685)
- Add Test at Scale Badge [#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)

View File

@@ -134,19 +134,9 @@ Prettier before a commit by default.
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 changing other code, please add unit tests.
The integration tests are not run by default. For most contributions it is OK to skip these unless you're working directly on the code for storing the GitHub token pool in postgres.
To run the integration tests:
- You must have PostgreSQL installed. Use `brew install postgresql`, `apt-get install postgresql`, etc.
- Set a connection string either with an env var `POSTGRES_URL=postgresql://user:pass@127.0.0.1:5432/db_name` or by using
```yaml
private:
postgres_url: 'postgresql://user:pass@127.0.0.1:5432/db_name'
```
in a yaml config file.
- Run `npm run migrate up` to apply DB migrations
- Run `npm run test:integration` to run the tests
To run the integration tests, you must have redis installed and in your PATH.
Use `brew install redis`, `yum install redis`, etc. The test runner will
start the server automatically.
[service-tests]: https://github.com/badges/shields/blob/master/doc/service-tests.md

View File

@@ -1,4 +1,4 @@
FROM node:16-alpine AS Builder
FROM node:14-alpine AS Builder
RUN mkdir -p /usr/src/app
RUN mkdir /usr/src/app/private
@@ -8,8 +8,7 @@ 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@^9.0.0"
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
@@ -19,19 +18,13 @@ 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
LABEL version=$version
LABEL fly.version=$version
FROM node:14-alpine
# Run the server using production configs.
ENV NODE_ENV production
WORKDIR /usr/src/app
COPY --from=Builder --chown=0:0 /usr/src/app /usr/src/app
COPY --from=Builder /usr/src/app /usr/src/app
CMD node server
EXPOSE 80 443
EXPOSE 80

View File

@@ -19,6 +19,9 @@
<a href="https://coveralls.io/github/badges/shields">
<img src="https://img.shields.io/coveralls/github/badges/shields"
alt="coverage"></a>
<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://discord.gg/HjJCwm5">
<img src="https://img.shields.io/discord/308323056592486420?logo=discord"
alt="chat on Discord"></a>
@@ -32,7 +35,7 @@ and legible badges in SVG and raster format, which can easily be included in
GitHub readmes or any other web page. The service supports dozens of
continuous integration services, package registries, distributions, app
stores, social networks, code coverage services, and code analysis services.
Every month it serves over 870 million images and is used by some of the
Every month it serves over 770 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.
@@ -70,7 +73,7 @@ This repo hosts:
[Make your own badges!][custom badges]
(Quick example: `https://img.shields.io/badge/left-right-f39f37`)
[custom badges]: http://localhost:3000/badges/static-badge
[custom badges]: https://shields.io/#your-badge
### Quickstart
@@ -98,8 +101,8 @@ You can read a [tutorial on how to add a badge][tutorial].
## 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 14 or later. You can use the [package manager][] of your choice.
Tests need to pass in Node 14 and 16.
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.
@@ -107,7 +110,7 @@ You can read a [tutorial on how to add a badge][tutorial].
When server source files change, the badge server should automatically restart
itself (using [nodemon][]). When the frontend files change, the frontend dev
server (`docusaurus start`) should also automatically reload. However the badge
server (`gatsby dev`) should also automatically reload. However the badge
definitions are built only before the server first starts. To regenerate those,
either run `npm run defs` or manually restart the server.

View File

@@ -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

View File

@@ -35,16 +35,6 @@
"WEBLATE_API_KEY": {
"description": "Configure the API key to be used for the Weblate service.",
"required": false
},
"METRICS_INFLUX_ENABLED": {
"description": "Disable influx metrics",
"value": "false",
"required": false
},
"REQUIRE_CLOUDFLARE": {
"description": "Allow direct traffic",
"value": "false",
"required": false
}
},
"formation": {

View File

@@ -2,7 +2,7 @@
## 4.0.0 [WIP]
- Drop compatibility with Node < 16
- Drop compatibility with Node 10
## 3.3.1
@@ -275,7 +275,7 @@ badge.loadFont('/path/to/Verdana.ttf', err => {
{ text: ['build', 'passed'], colorscheme: 'green', template: 'flat' },
(svg, err) => {
// svg is a string containing your badge
},
}
)
})
```

View File

@@ -7,19 +7,19 @@ expectError(
makeBadge({
message: 'passed',
style: 'invalid style',
}),
})
)
expectType<string>(
makeBadge({
message: 'passed',
}),
})
)
expectType<string>(
makeBadge({
label: 'build',
message: 'passed',
}),
})
)
expectType<string>(
makeBadge({
@@ -28,7 +28,7 @@ expectType<string>(
labelColor: 'green',
color: 'red',
style: 'flat',
}),
})
)
const error = new ValidationError()

View File

@@ -1,6 +1,7 @@
'use strict'
const path = require('path')
const isSvg = require('is-svg')
const { spawn } = require('child-process-promise')
const { expect, use } = require('chai')
use(require('chai-string'))
@@ -19,7 +20,6 @@ describe('The CLI', function () {
})
it('should produce default badges', async function () {
const { default: isSvg } = await import('is-svg')
const { stdout } = await runCli(['cactus', 'grown'])
expect(stdout)
.to.satisfy(isSvg)
@@ -28,13 +28,11 @@ describe('The CLI', function () {
})
it('should produce colorschemed badges', async function () {
const { default: isSvg } = await import('is-svg')
const { stdout } = await runCli(['cactus', 'grown', ':green'])
expect(stdout).to.satisfy(isSvg)
})
it('should produce right-color badges', async function () {
const { default: isSvg } = await import('is-svg')
const { stdout } = await runCli(['cactus', 'grown', '#abcdef'])
expect(stdout).to.satisfy(isSvg).and.to.include('#abcdef')
})

View File

@@ -69,7 +69,7 @@ function getLogoElement({ logo, horizPadding, badgeHeight, logoWidth }) {
function renderBadge(
{ links, leftWidth, rightWidth, height, accessibleText },
content,
content
) {
const width = leftWidth + rightWidth
const leftLink = links[0]
@@ -397,7 +397,7 @@ class Plastic extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement],
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -446,7 +446,7 @@ class Flat extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement],
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -478,7 +478,7 @@ class FlatSquare extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[backgroundGroup, this.foregroundGroupElement],
[backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -748,7 +748,7 @@ function social({
accessibleText,
height: externalHeight,
},
[style, gradients, backgroundGroup, logoElement, foregroundGroup],
[style, gradients, backgroundGroup, logoElement, foregroundGroup]
)
}
@@ -978,7 +978,7 @@ function forTheBadge({
accessibleText: createAccessibleText({ label, message }),
height: BADGE_HEIGHT,
},
[backgroundGroup, foregroundGroup],
[backgroundGroup, foregroundGroup]
)
}

View File

@@ -75,7 +75,7 @@ test(toSvgColor, () => {
given('papayawhip').expect('papayawhip')
given('purple').expect('purple')
forCases([given(''), given(undefined), given('not-a-color')]).expect(
undefined,
undefined
)
given('lightgray').expect('#9f9f9f')
given('informational').expect('#007ec6')

View File

@@ -32,7 +32,7 @@ function _validate(format) {
]
if ('style' in format && !styleValues.includes(format.style)) {
throw new ValidationError(
`Field \`style\` must be one of (${styleValues.toString()})`,
`Field \`style\` must be one of (${styleValues.toString()})`
)
}
}
@@ -46,7 +46,7 @@ function _clean(format) {
cleaned[key] = format[key]
} else {
throw new ValidationError(
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`,
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
)
}
})

View File

@@ -1,21 +1,21 @@
'use strict'
const { expect } = require('chai')
const isSvg = require('is-svg')
const { makeBadge, ValidationError } = require('.')
describe('makeBadge function', function () {
it('should produce badge with valid input', async function () {
const { default: isSvg } = await import('is-svg')
it('should produce badge with valid input', function () {
expect(
makeBadge({
label: 'build',
message: 'passed',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
message: 'passed',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
@@ -23,7 +23,7 @@ describe('makeBadge function', function () {
message: 'passed',
color: 'green',
style: 'flat',
}),
})
).to.satisfy(isSvg)
})
@@ -32,44 +32,44 @@ describe('makeBadge function', function () {
console.log(x)
expect(() => makeBadge(x)).to.throw(
ValidationError,
'makeBadge takes an argument of type object',
'makeBadge takes an argument of type object'
)
})
expect(() => makeBadge({})).to.throw(
ValidationError,
'Field `message` is required',
'Field `message` is required'
)
expect(() => makeBadge({ label: 'build' })).to.throw(
ValidationError,
'Field `message` is required',
'Field `message` is required'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', labelColor: 7 }),
makeBadge({ label: 'build', message: 'passed', labelColor: 7 })
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
expect(() =>
makeBadge({ label: 'build', message: 'passed', format: 'png' }),
makeBadge({ label: 'build', message: 'passed', format: 'png' })
).to.throw(ValidationError, "Unexpected field 'format'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', template: 'flat' }),
makeBadge({ label: 'build', message: 'passed', template: 'flat' })
).to.throw(ValidationError, "Unexpected field 'template'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', foo: 'bar' }),
makeBadge({ label: 'build', message: 'passed', foo: 'bar' })
).to.throw(ValidationError, "Unexpected field 'foo'")
expect(() =>
makeBadge({
label: 'build',
message: 'passed',
style: 'something else',
}),
})
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', style: 'popout' }),
makeBadge({ label: 'build', message: 'passed', style: 'popout' })
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
})
})

View File

@@ -58,6 +58,6 @@ module.exports = function makeBadge({
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),
}),
})
)
}

View File

@@ -3,11 +3,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')
async function expectBadgeToMatchSnapshot(format) {
snapshot(await prettier.format(makeBadge(format), { parser: 'html' }))
function expectBadgeToMatchSnapshot(format) {
snapshot(prettier.format(makeBadge(format), { parser: 'html' }))
}
function testColor(color = '', colorAttr = 'color') {
@@ -17,7 +18,7 @@ function testColor(color = '', colorAttr = 'color') {
message: 'Bob',
[colorAttr]: color,
format: 'json',
}),
})
).color
}
@@ -67,7 +68,7 @@ describe('The badge generator', function () {
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus'),
given('cactus')
).expect(undefined)
})
})
@@ -79,16 +80,15 @@ describe('The badge generator', function () {
})
describe('SVG', function () {
it('should produce SVG', async function () {
const { default: isSvg } = await import('is-svg')
it('should produce SVG', function () {
expect(makeBadge({ label: 'cactus', message: 'grown', format: 'svg' }))
.to.satisfy(isSvg)
.and.to.include('cactus')
.and.to.include('grown')
})
it('should match snapshot', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshot', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -113,8 +113,7 @@ describe('The badge generator', function () {
})
})
it('should replace undefined svg badge style with "flat"', async function () {
const { default: isSvg } = await import('is-svg')
it('should replace undefined svg badge style with "flat"', function () {
const jsonBadgeWithUnknownStyle = makeBadge({
label: 'name',
message: 'Bob',
@@ -138,14 +137,14 @@ describe('The badge generator', function () {
message: 'Bob',
format: 'svg',
style: 'unknown_style',
}),
})
).to.throw(Error, "Unknown badge style: 'unknown_style'")
})
})
describe('"flat" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -155,8 +154,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -167,8 +166,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -177,8 +176,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -188,8 +187,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -200,8 +199,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -212,8 +211,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -223,8 +222,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -236,8 +235,8 @@ describe('The badge generator', function () {
})
describe('"flat-square" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -247,8 +246,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -259,8 +258,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -269,8 +268,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -280,8 +279,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -292,8 +291,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -304,8 +303,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -315,8 +314,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -328,8 +327,8 @@ describe('The badge generator', function () {
})
describe('"plastic" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -339,8 +338,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -351,8 +350,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -361,8 +360,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -372,8 +371,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -384,8 +383,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -396,8 +395,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -407,8 +406,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -428,7 +427,7 @@ describe('The badge generator', function () {
message: 1999,
format: 'svg',
style: 'for-the-badge',
}),
})
)
.to.include('1998')
.and.to.include('1999')
@@ -441,14 +440,14 @@ describe('The badge generator', function () {
message: '1 string',
format: 'svg',
style: 'for-the-badge',
}),
})
)
.to.include('LABEL')
.and.to.include('1 STRING')
})
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -458,8 +457,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -470,8 +469,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -480,8 +479,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -491,8 +490,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -503,8 +502,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -515,8 +514,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -526,8 +525,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -546,7 +545,7 @@ describe('The badge generator', function () {
message: 'some-value',
format: 'svg',
style: 'social',
}),
})
)
.to.include('Some-key')
.and.to.include('some-value')
@@ -560,14 +559,14 @@ describe('The badge generator', function () {
message: 'some-value',
format: 'json',
style: 'social',
}),
})
)
.to.include('""')
.and.to.include('some-value')
})
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -577,8 +576,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -589,8 +588,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -599,8 +598,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -610,8 +609,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -622,8 +621,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -636,8 +635,8 @@ describe('The badge generator', function () {
})
describe('badges with logos should always produce the same badge', function () {
it('badge with logo', async function () {
await expectBadgeToMatchSnapshot({
it('badge with logo', function () {
expectBadgeToMatchSnapshot({
label: 'label',
message: 'message',
format: 'svg',

View File

@@ -33,7 +33,7 @@ class XmlElement {
* @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=[]]
* @param {Array.<string|module:badge-maker/lib/xml-element~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.
@@ -66,7 +66,7 @@ class XmlElement {
})
.join(' ')
return stripXmlWhitespace(
`<${this.name}${attrsStr}>${content}</${this.name}>`,
`<${this.name}${attrsStr}>${content}</${this.name}>`
)
}
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
@@ -88,7 +88,7 @@ class ElementList {
typeof el.render === 'function'
? acc + el.render()
: acc + escapeXml(el),
'',
''
)
}
}

View File

@@ -26,7 +26,8 @@
"badge": "lib/badge-cli.js"
},
"engines": {
"node": ">=16"
"node": ">= 12",
"npm": ">= 6"
},
"collective": {
"type": "opencollective",

View File

@@ -50,8 +50,6 @@ public:
authorizedOrigins: 'NEXUS_ORIGINS'
npm:
authorizedOrigins: 'NPM_ORIGINS'
obs:
authorizedOrigins: 'OBS_ORIGINS'
sonar:
authorizedOrigins: 'SONAR_ORIGINS'
teamcity:
@@ -64,7 +62,6 @@ public:
defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS'
fetchLimit: 'FETCH_LIMIT'
userAgentBase: 'USER_AGENT_BASE'
requestTimeoutSeconds: 'REQUEST_TIMEOUT_SECONDS'
requestTimeoutMaxAgeSeconds: 'REQUEST_TIMEOUT_MAX_AGE_SECONDS'
@@ -87,19 +84,15 @@ private:
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'
postgres_url: 'POSTGRES_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'
stackapps_api_key: 'STACKAPPS_API_KEY'
teamcity_user: 'TEAMCITY_USER'
teamcity_pass: 'TEAMCITY_PASS'
twitch_client_id: 'TWITCH_CLIENT_ID'

View File

@@ -1,6 +1,7 @@
public:
bind:
address: '::'
metrics:
prometheus:
enabled: false
@@ -11,26 +12,30 @@ public:
intervalSeconds: 15
ssl:
isSecure: false
cors:
allowedOrigin: []
services:
github:
baseUri: 'https://api.github.com'
baseUri: 'https://api.github.com/'
debug:
enabled: false
intervalSeconds: 200
restApiVersion: '2022-11-28'
obs:
authorizedOrigins: 'https://api.opensuse.org'
weblate:
authorizedOrigins: 'https://hosted.weblate.org'
trace: false
cacheHeaders:
defaultCacheLengthSeconds: 120
handleInternalErrors: true
fetchLimit: '10MB'
userAgentBase: 'shields (self-hosted)'
requestTimeoutSeconds: 120
requestTimeoutMaxAgeSeconds: 30
requireCloudflare: false
private: {}

View File

@@ -4,6 +4,7 @@ private:
gh_client_id: ...
gh_client_secret: ...
gitlab_token: ...
redis_url: ...
sentry_dsn: ...
shields_secret: ...
sl_insight_userUuid: ...

View File

@@ -6,8 +6,6 @@ private:
# preferable for self hosting.
gh_token: '...'
gitlab_token: '...'
obs_user: '...'
obs_pass: '...'
twitch_client_id: '...'
twitch_client_secret: '...'
weblate_api_key: '...'

View File

@@ -6,20 +6,13 @@ public:
enabled: true
url: https://metrics.shields.io/telegraf
instanceIdFrom: env-var
instanceIdEnvVarName: FLY_ALLOC_ID
instanceIdEnvVarName: HEROKU_DYNO_ID
envLabel: shields-production
ssl:
isSecure: false
isSecure: true
cors:
allowedOrigin: ['http://shields.io', 'https://shields.io']
services:
gitlab:
authorizedOrigins: 'https://gitlab.com'
rasterUrl: 'https://raster.shields.io'
userAgentBase: 'Shields.io'
requireCloudflare: true
requestTimeoutSeconds: 20

112
core/badge-urls/make-badge-url.d.ts vendored Normal file
View File

@@ -0,0 +1,112 @@
export function badgeUrlFromPath({
baseUrl,
path,
queryParams,
style,
format,
longCache,
}: {
baseUrl?: string
path: string
queryParams: { [k: string]: string | number | boolean }
style?: string
format?: string
longCache?: boolean
}): string
export function badgeUrlFromPattern({
baseUrl,
pattern,
namedParams,
queryParams,
style,
format,
longCache,
}: {
baseUrl?: string
pattern: string
namedParams: { [k: string]: string }
queryParams: { [k: string]: string | number | boolean }
style?: string
format?: string
longCache?: boolean
}): string
export function encodeField(s: string): string
export function staticBadgeUrl({
baseUrl,
label,
message,
labelColor,
color,
style,
namedLogo,
format,
links,
}: {
baseUrl?: string
label: string
message: string
labelColor?: string
color?: string
style?: string
namedLogo?: string
format?: string
links?: string[]
}): string
export function queryStringStaticBadgeUrl({
baseUrl,
label,
message,
color,
labelColor,
style,
namedLogo,
logoColor,
logoWidth,
logoPosition,
format,
}: {
baseUrl?: string
label: string
message: string
color?: string
labelColor?: string
style?: string
namedLogo?: string
logoColor?: string
logoWidth?: number
logoPosition?: number
format?: string
}): string
export function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format,
}: {
baseUrl?: string
datatype: string
label: string
dataUrl: string
query: string
prefix: string
suffix: string
color?: string
style?: string
format?: string
}): string
export function rasterRedirectUrl(
{ rasterUrl }: { rasterUrl: string },
badgeUrl: string
): string

View File

@@ -1,5 +1,147 @@
// 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'
function badgeUrlFromPath({
baseUrl = '',
path,
queryParams,
style,
format = '',
longCache = false,
}) {
const outExt = format.length ? `.${format}` : ''
const outQueryString = queryString.stringify({
cacheSeconds: longCache ? '2592000' : undefined,
style,
...queryParams,
})
const suffix = outQueryString ? `?${outQueryString}` : ''
return `${baseUrl}${path}${outExt}${suffix}`
}
function badgeUrlFromPattern({
baseUrl = '',
pattern,
namedParams,
queryParams,
style,
format = '',
longCache = false,
}) {
const toPath = compile(pattern, {
strict: true,
sensitive: true,
encode: encodeURIComponent,
})
const path = toPath(namedParams)
return badgeUrlFromPath({
baseUrl,
path,
queryParams,
style,
format,
longCache,
})
}
function encodeField(s) {
return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__'))
}
function staticBadgeUrl({
baseUrl = '',
label,
message,
labelColor,
color = 'lightgray',
style,
namedLogo,
format = '',
links = [],
}) {
const path = [label, message, color].map(encodeField).join('-')
const outQueryString = queryString.stringify({
labelColor,
style,
logo: namedLogo,
link: links,
})
const outExt = format.length ? `.${format}` : ''
const suffix = outQueryString ? `?${outQueryString}` : ''
return `${baseUrl}/badge/${path}${outExt}${suffix}`
}
function queryStringStaticBadgeUrl({
baseUrl = '',
label,
message,
color,
labelColor,
style,
namedLogo,
logoColor,
logoWidth,
logoPosition,
format = '',
}) {
// schemaVersion could be a parameter if we iterate on it,
// for now it's hardcoded to the only supported version.
const schemaVersion = '1'
const suffix = `?${queryString.stringify({
label,
message,
color,
labelColor,
style,
logo: namedLogo,
logoColor,
logoWidth,
logoPosition,
})}`
const outExt = format.length ? `.${format}` : ''
return `${baseUrl}/static/v${schemaVersion}${outExt}${suffix}`
}
function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format = '',
}) {
const outExt = format.length ? `.${format}` : ''
const queryParams = {
label,
url: dataUrl,
query,
style,
}
if (color) {
queryParams.color = color
}
if (prefix) {
queryParams.prefix = prefix
}
if (suffix) {
queryParams.suffix = suffix
}
const outQueryString = queryString.stringify(queryParams)
return `${baseUrl}/badge/dynamic/${datatype}${outExt}?${outQueryString}`
}
function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
// Ensure we're always using the `rasterUrl` by using just the path from
@@ -10,4 +152,12 @@ function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
return result
}
export { rasterRedirectUrl }
export {
badgeUrlFromPath,
badgeUrlFromPattern,
encodeField,
staticBadgeUrl,
queryStringStaticBadgeUrl,
dynamicBadgeUrl,
rasterRedirectUrl,
}

View File

@@ -0,0 +1,155 @@
import { test, given } from 'sazerac'
import {
badgeUrlFromPath,
badgeUrlFromPattern,
encodeField,
staticBadgeUrl,
queryStringStaticBadgeUrl,
dynamicBadgeUrl,
} from './make-badge-url.js'
describe('Badge URL generation functions', function () {
test(badgeUrlFromPath, () => {
given({
baseUrl: 'http://example.com',
path: '/npm/v/gh-badges',
style: 'flat-square',
longCache: true,
}).expect(
'http://example.com/npm/v/gh-badges?cacheSeconds=2592000&style=flat-square'
)
})
test(badgeUrlFromPattern, () => {
given({
baseUrl: 'http://example.com',
pattern: '/npm/v/:packageName',
namedParams: { packageName: 'gh-badges' },
style: 'flat-square',
longCache: true,
}).expect(
'http://example.com/npm/v/gh-badges?cacheSeconds=2592000&style=flat-square'
)
})
test(encodeField, () => {
given('foo').expect('foo')
given('').expect('')
given('happy go lucky').expect('happy%20go%20lucky')
given('do-right').expect('do--right')
given('it_is_a_snake').expect('it__is__a__snake')
})
test(staticBadgeUrl, () => {
given({
label: 'foo',
message: 'bar',
color: 'blue',
style: 'flat-square',
}).expect('/badge/foo-bar-blue?style=flat-square')
given({
label: 'foo',
message: 'bar',
color: 'blue',
style: 'flat-square',
format: 'png',
namedLogo: 'github',
}).expect('/badge/foo-bar-blue.png?logo=github&style=flat-square')
given({
label: 'Hello World',
message: 'Привет Мир',
color: '#aabbcc',
}).expect(
'/badge/Hello%20World-%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80-%23aabbcc'
)
given({
label: '123-123',
message: 'abc-abc',
color: 'blue',
}).expect('/badge/123--123-abc--abc-blue')
given({
label: '123-123',
message: '',
color: 'blue',
style: 'social',
}).expect('/badge/123--123--blue?style=social')
given({
label: '',
message: 'blue',
color: 'blue',
}).expect('/badge/-blue-blue')
})
test(queryStringStaticBadgeUrl, () => {
// the query-string library sorts parameters by name
given({
label: 'foo',
message: 'bar',
color: 'blue',
style: 'flat-square',
}).expect('/static/v1?color=blue&label=foo&message=bar&style=flat-square')
given({
label: 'foo Bar',
message: 'bar Baz',
color: 'blue',
style: 'flat-square',
format: 'png',
namedLogo: 'github',
}).expect(
'/static/v1.png?color=blue&label=foo%20Bar&logo=github&message=bar%20Baz&style=flat-square'
)
given({
label: 'Hello World',
message: 'Привет Мир',
color: '#aabbcc',
}).expect(
'/static/v1?color=%23aabbcc&label=Hello%20World&message=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80'
)
})
test(dynamicBadgeUrl, () => {
const dataUrl = 'http://example.com/foo.json'
const query = '$.bar'
const prefix = 'value: '
given({
baseUrl: 'http://img.example.com',
datatype: 'json',
label: 'foo',
dataUrl,
query,
prefix,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json',
'?label=foo',
`&prefix=${encodeURIComponent(prefix)}`,
`&query=${encodeURIComponent(query)}`,
'&style=plastic',
`&url=${encodeURIComponent(dataUrl)}`,
].join('')
)
const suffix = '<- value'
const color = 'blue'
given({
baseUrl: 'http://img.example.com',
datatype: 'json',
label: 'foo',
dataUrl,
query,
suffix,
color,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json',
'?color=blue',
'&label=foo',
`&query=${encodeURIComponent(query)}`,
'&style=plastic',
`&suffix=${encodeURIComponent(suffix)}`,
`&url=${encodeURIComponent(dataUrl)}`,
].join('')
)
})
})

View File

@@ -7,7 +7,7 @@ describe('Badge URL helper functions', function () {
given('single trailing underscore_').expect('single trailing underscore ')
given('__double leading underscores').expect('_double leading underscores')
given('double trailing underscores__').expect(
'double trailing underscores_',
'double trailing underscores_'
)
given('treble___underscores').expect('treble_ underscores')
given('fourfold____underscores').expect('fourfold__underscores')

View File

@@ -11,7 +11,7 @@ class AuthHelper {
isRequired = false,
defaultToEmptyStringForUser = false,
},
config,
config
) {
if (!userKey && !passKey) {
throw Error('Expected userKey or passKey to be set')
@@ -74,7 +74,7 @@ class AuthHelper {
}
static _isInsecureSslRequest({ options = {} }) {
const strictSSL = options?.https?.rejectUnauthorized ?? true
const { strictSSL = true } = options
return strictSSL !== true
}
@@ -107,10 +107,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 +131,7 @@ class AuthHelper {
const { options, ...rest } = requestParams
return {
options: {
...auth,
auth,
...options,
},
...rest,
@@ -142,7 +140,7 @@ class AuthHelper {
withBasicAuth(requestParams) {
return this._withAnyAuth(requestParams, requestParams =>
this.constructor._mergeAuth(requestParams, this._basicAuth),
this.constructor._mergeAuth(requestParams, this._basicAuth)
)
}
@@ -172,24 +170,22 @@ class AuthHelper {
withBearerAuthHeader(
requestParams,
bearerKey = 'Bearer', // lgtm [js/hardcoded-credentials]
bearerKey = 'Bearer' // lgtm [js/hardcoded-credentials]
) {
return this._withAnyAuth(requestParams, requestParams =>
this.constructor._mergeHeaders(
requestParams,
this._bearerAuthHeader(bearerKey),
),
this._bearerAuthHeader(bearerKey)
)
)
}
static _mergeQueryParams(requestParams, query) {
const {
options: { searchParams: existingQuery, ...restOptions } = {},
...rest
} = requestParams
const { options: { qs: existingQuery, ...restOptions } = {}, ...rest } =
requestParams
return {
options: {
searchParams: {
qs: {
...existingQuery,
...query,
},
@@ -204,7 +200,7 @@ class AuthHelper {
this.constructor._mergeQueryParams(requestParams, {
...(userKey ? { [userKey]: this._user } : undefined),
...(passKey ? { [passKey]: this._pass } : undefined),
}),
})
)
}
}

View File

@@ -8,13 +8,12 @@ describe('AuthHelper', function () {
it('throws without userKey or passKey', function () {
expect(() => new AuthHelper({}, {})).to.throw(
Error,
'Expected userKey or passKey to be set',
'Expected userKey or passKey to be set'
)
})
it('throws without serviceKey or authorizedOrigins', function () {
expect(
() =>
new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {}),
() => new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {})
).to.throw(Error, 'Expected authorizedOrigins or serviceKey to be set')
})
it('throws when authorizedOrigins is not an array', function () {
@@ -26,8 +25,8 @@ describe('AuthHelper', function () {
passKey: 'myci_pass',
authorizedOrigins: true,
},
{ private: {} },
),
{ private: {} }
)
).to.throw(Error, 'Expected authorizedOrigins to be an array of origins')
})
})
@@ -36,7 +35,7 @@ describe('AuthHelper', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
{ private: privateConfig },
{ private: privateConfig }
).isValid
}
test(validate, () => {
@@ -44,20 +43,20 @@ describe('AuthHelper', function () {
// Fully configured user + pass.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
// Fully configured user or pass.
given(
{ userKey: 'myci_user', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ passKey: 'myci_pass', isRequired: true },
{ myci_pass: 'abc123' },
{ myci_pass: 'abc123' }
),
given({ userKey: 'myci_user' }, { myci_user: 'admin' }),
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }),
@@ -71,16 +70,16 @@ describe('AuthHelper', function () {
// Partly configured.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
// Missing required config.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{},
{}
),
given({ userKey: 'myci_user', isRequired: true }, {}),
given({ passKey: 'myci_pass', isRequired: true }, {}),
@@ -92,37 +91,37 @@ describe('AuthHelper', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
{ private: privateConfig },
{ private: privateConfig }
)._basicAuth
}
test(validate, () => {
forCases([
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ 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,
undefined
)
given(
{ passKey: 'myci_pass', defaultToEmptyStringForUser: true },
{ myci_pass: 'abc123' },
{ myci_pass: 'abc123' }
).expect({
username: '',
password: 'abc123',
user: '',
pass: 'abc123',
})
})
})
@@ -132,18 +131,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)
})
})
@@ -167,9 +163,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)
})
})
@@ -191,9 +185,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()
})
})
@@ -228,7 +220,7 @@ describe('AuthHelper', function () {
test(shouldAuthenticateRequest, () => {
given({
url: 'https://myci.test/api',
options: { https: { rejectUnauthorized: false } },
options: { strictSSL: false },
}).expect(false)
})
})
@@ -266,7 +258,7 @@ describe('AuthHelper', function () {
test(shouldAuthenticateRequest, () => {
given({
url: 'https://myci.test',
options: { https: { rejectUnauthorized: false } },
options: { strictSSL: false },
}).expect(true)
})
})
@@ -319,7 +311,7 @@ describe('AuthHelper', function () {
},
},
private: { myci_user: 'admin', myci_pass: 'abc123' },
},
}
)
const withBasicAuth = requestOptions =>
authHelper.withBasicAuth(requestOptions)
@@ -331,8 +323,7 @@ describe('AuthHelper', function () {
}).expect({
url: 'https://myci.test/api',
options: {
username: 'admin',
password: 'abc123',
auth: { user: 'admin', pass: 'abc123' },
},
})
given({
@@ -344,8 +335,7 @@ describe('AuthHelper', function () {
url: 'https://myci.test/api',
options: {
headers: { Accept: 'application/json' },
username: 'admin',
password: 'abc123',
auth: { user: 'admin', pass: 'abc123' },
},
})
})
@@ -376,8 +366,8 @@ describe('AuthHelper', function () {
expect(() =>
withBasicAuth({
url: 'https://myci.test/api',
options: { https: { rejectUnauthorized: false } },
}),
options: { strictSSL: false },
})
).to.throw(InvalidParameter)
})
})

View File

@@ -38,18 +38,12 @@ 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
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @param {Function} [attrs.transformJson=data => data] Function which takes the raw json and transforms it before
* further procesing. In case of multiple query in a single graphql call and few of them
* throw error, partial data might be used ignoring the error.
@@ -59,7 +53,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,
@@ -68,7 +62,6 @@ class BaseGraphqlService extends BaseService {
variables = {},
options = {},
httpErrorMessages = {},
systemErrors = {},
transformJson = data => data,
transformErrors = defaultTransformErrors,
}) {
@@ -81,8 +74,7 @@ class BaseGraphqlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors: httpErrorMessages,
systemErrors,
errorMessages: httpErrorMessages,
})
const json = transformJson(this._parseJson(buffer))
if (json.errors) {
@@ -91,7 +83,7 @@ class BaseGraphqlService extends BaseService {
throw exception
} else {
throw Error(
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`,
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`
)
}
}

View File

@@ -29,33 +29,33 @@ 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 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
it('invokes _sendAndCacheRequest', async function () {
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/graphql',
{
body: '{"query":"{\\n requiredString\\n}","variables":{}}',
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
headers: { Accept: 'application/json' },
method: 'POST',
},
}
)
})
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,55 +66,55 @@ describe('BaseGraphqlService', function () {
requiredString
}
`,
options: { searchParams: { queryParam: 123 } },
options: { qs: { queryParam: 123 } },
})
return { message: value }
}
}
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/graphql',
{
body: '{"query":"{\\n requiredString\\n}","variables":{}}',
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
headers: { Accept: 'application/json' },
method: 'POST',
searchParams: { queryParam: 123 },
},
qs: { queryParam: 123 },
}
)
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -123,15 +123,15 @@ 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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -142,15 +142,15 @@ 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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -181,15 +181,15 @@ describe('BaseGraphqlService', function () {
}
}
const requestFetcher = async () => ({
const sendAndCacheRequest = async () => ({
buffer: '{ "errors": [ { "message": "oh noes!!" } ] }',
res: { statusCode: 200 },
})
expect(
await WithErrorHandler.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -28,28 +28,16 @@ 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.httpErrors={}] Key-value map of status codes
* @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.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @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 = {},
httpErrors = {},
systemErrors = {},
}) {
async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
const mergedOptions = {
...{ headers: { Accept: 'application/json' } },
...options,
@@ -57,8 +45,7 @@ class BaseJsonService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
errorMessages,
})
const json = this._parseJson(buffer)
return this.constructor._validate(json, schema)

View File

@@ -22,84 +22,84 @@ 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 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
it('invokes _sendAndCacheRequest', async function () {
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/foo.json',
{
headers: { Accept: 'application/json' },
},
}
)
})
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 },
{ handleInternalErrors: false },
{ 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 },
}
)
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -108,15 +108,15 @@ 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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -33,14 +33,14 @@ export default class BaseStaticService extends BaseService {
{},
serviceConfig,
namedParams,
queryParams,
queryParams
)
const badgeData = coalesceBadge(
queryParams,
serviceData,
this.defaultBadgeData,
this,
this
)
// The final capture group is the extension.

View File

@@ -51,28 +51,21 @@ 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.httpErrors={}] Key-value map of status codes
* @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.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @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,
valueMatcher,
url,
options = {},
httpErrors = {},
systemErrors = {},
errorMessages = {},
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
const mergedOptions = {
@@ -82,8 +75,7 @@ class BaseSvgScrapingService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
errorMessages,
})
logTrace(emojic.dart, 'Response SVG', buffer)
const data = {

View File

@@ -28,37 +28,37 @@ describe('BaseSvgScrapingService', function () {
describe('valueFromSvgBadge', function () {
it('should find the correct value', function () {
expect(BaseSvgScrapingService.valueFromSvgBadge(exampleSvg)).to.equal(
exampleMessage,
exampleMessage
)
})
})
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 },
}),
})
)
})
it('invokes _requestFetcher with the expected header', async function () {
it('invokes _sendAndCacheRequest with the expected header', async function () {
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ 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' },
},
}
)
})
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 +66,7 @@ describe('BaseSvgScrapingService', function () {
url: 'http://example.com/foo.svg',
options: {
method: 'POST',
searchParams: { queryParam: 123 },
qs: { queryParam: 123 },
},
})
return { message }
@@ -74,32 +74,32 @@ describe('BaseSvgScrapingService', function () {
}
await WithCustomOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ 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 },
}
)
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: exampleMessage,
})
@@ -117,30 +117,30 @@ describe('BaseSvgScrapingService', function () {
})
}
}
const requestFetcher = async () => ({
const sendAndCacheRequest = async () => ({
buffer: '<desc>a different message</desc>',
res: { statusCode: 200 },
})
expect(
await WithValueMatcher.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'a different message',
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -4,7 +4,7 @@
// See available emoji at http://emoji.muan.co/
import emojic from 'emojic'
import { XMLParser, XMLValidator } from 'fast-xml-parser'
import fastXmlParser from 'fast-xml-parser'
import BaseService from './base.js'
import trace from './trace.js'
import { InvalidResponse } from './errors.js'
@@ -22,30 +22,23 @@ 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.httpErrors={}] Key-value map of status codes
* @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.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @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({
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
errorMessages = {},
parserOptions = {},
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -56,18 +49,16 @@ class BaseXmlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
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,
})

View File

@@ -22,31 +22,31 @@ 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 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
it('invokes _sendAndCacheRequest', async function () {
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ 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' },
},
}
)
})
it('forwards options to _requestFetcher', async function () {
it('forwards options to _sendAndCacheRequest', async function () {
class WithCustomOptions extends BaseXmlService {
static route = {}
@@ -54,39 +54,39 @@ 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 },
{ handleInternalErrors: false },
{ 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 },
}
)
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -104,31 +104,31 @@ 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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string with trailing whitespace ',
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -137,15 +137,15 @@ 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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -21,28 +21,21 @@ 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.httpErrors={}] Key-value map of status codes
* @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.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @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,
url,
options = {},
httpErrors = {},
systemErrors = {},
errorMessages = {},
encoding = 'utf8',
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -58,8 +51,7 @@ class BaseYamlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
errorMessages,
})
let parsed
try {

View File

@@ -38,51 +38,51 @@ 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 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
it('invokes _sendAndCacheRequest', async function () {
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/foo.yaml',
{
headers: {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
},
}
)
})
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 },
{ handleInternalErrors: false },
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/foo.yaml',
{
headers: {
@@ -90,38 +90,38 @@ describe('BaseYamlService', function () {
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
method: 'POST',
searchParams: { queryParam: 123 },
},
qs: { queryParam: 123 },
}
)
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
})
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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -130,15 +130,15 @@ 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 },
{ handleInternalErrors: false },
),
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -20,7 +20,7 @@ import {
Deprecated,
} from './errors.js'
import { validateExample, transformExample } from './examples.js'
import { fetch } from './got.js'
import { fetchFactory } from './got.js'
import {
makeFullUrl,
assertValidRoute,
@@ -44,7 +44,7 @@ const optionalStringWhenNamedLogoPresent = Joi.alternatives().conditional(
{
is: Joi.string().required(),
then: Joi.string(),
},
}
)
const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
@@ -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}
@@ -140,15 +137,6 @@ class BaseService {
*/
static examples = []
/**
* Optional: an OpenAPI Paths Object describing this service's
* route or routes in OpenAPI format.
*
* @see https://swagger.io/specification/#paths-object
* @abstract
*/
static openApi = undefined
static get _cacheLength() {
const cacheLengths = {
build: 30,
@@ -156,7 +144,6 @@ class BaseService {
version: 300,
debug: 60,
downloads: 900,
rating: 900,
social: 900,
}
return cacheLengths[this.category]
@@ -183,37 +170,21 @@ class BaseService {
Joi.assert(
this.defaultBadgeData,
defaultBadgeDataSchema,
`Default badge data for ${this.name}`,
`Default badge data for ${this.name}`
)
this.examples.forEach((example, index) =>
validateExample(example, index, this),
validateExample(example, index, this)
)
// ensure openApi spec matches route
if (this.openApi) {
const preparedRoute = prepareRoute(this.route)
for (const [key, value] of Object.entries(this.openApi)) {
let example = key
for (const param of value.get.parameters) {
example = example.replace(`{${param.name}}`, param.example)
}
if (!example.match(preparedRoute.regex)) {
throw new Error(
`Inconsistent Open Api spec and Route found for service ${this.name}`,
)
}
}
}
}
static getDefinition() {
const { category, name, isDeprecated, openApi } = this
const { category, name, isDeprecated } = this
const { base, format, pattern } = this.route
const queryParams = getQueryParamNames(this.route)
const examples = this.examples.map((example, index) =>
transformExample(example, index, this),
transformExample(example, index, this)
)
let route
@@ -225,7 +196,7 @@ class BaseService {
route = undefined
}
const result = { category, name, isDeprecated, route, examples, openApi }
const result = { category, name, isDeprecated, route, examples }
assertValidServiceDefinition(result, `getDefinition() for ${this.name}`)
@@ -233,43 +204,33 @@ class BaseService {
}
constructor(
{ requestFetcher, authHelper, metricHelper },
{ handleInternalErrors },
{ sendAndCacheRequest, authHelper, metricHelper },
{ handleInternalErrors }
) {
this._requestFetcher = requestFetcher
this._requestFetcher = sendAndCacheRequest
this.authHelper = authHelper
this._handleInternalErrors = handleInternalErrors
this._metricHelper = metricHelper
}
async _request({ url, options = {}, httpErrors = {}, systemErrors = {} }) {
async _request({ url, options = {}, errorMessages = {} }) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
let logUrl = url
const logOptions = Object.assign({}, options)
if ('searchParams' in options && options.searchParams != null) {
const params = new URLSearchParams(
Object.fromEntries(
Object.entries(options.searchParams).filter(
([k, v]) => v !== undefined,
),
),
)
if ('qs' in options) {
const params = new URLSearchParams(options.qs)
logUrl = `${url}?${params.toString()}`
delete logOptions.searchParams
delete logOptions.qs
}
logTrace(
emojic.bowAndArrow,
'Request',
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`,
)
const { res, buffer } = await this._requestFetcher(
url,
options,
systemErrors,
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`
)
const { res, buffer } = await this._requestFetcher(url, options)
await this._meterResponse(res, buffer)
logTrace(emojic.dart, 'Response status code', res.statusCode)
return checkErrorResponse(httpErrors)({ buffer, res })
return checkErrorResponse(errorMessages)({ buffer, res })
}
static enabledMetrics = []
@@ -295,7 +256,7 @@ class BaseService {
prettyErrorMessage = 'invalid response data',
includeKeys = false,
allowAndStripUnknownKeys = true,
} = {},
} = {}
) {
return validate(
{
@@ -307,14 +268,14 @@ class BaseService {
allowAndStripUnknownKeys,
},
data,
schema,
schema
)
}
/**
* 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
@@ -348,22 +309,18 @@ class BaseService {
error instanceof Deprecated
) {
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
const serviceData = {
return {
isError: true,
message: error.prettyMessage,
color: 'lightgray',
}
if (error.cacheSeconds !== undefined) {
serviceData.cacheSeconds = error.cacheSeconds
}
return serviceData
} else if (this._handleInternalErrors) {
if (
!trace.logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error,
error
)
) {
// This is where we end up if an unhandled exception is thrown in
@@ -381,7 +338,7 @@ class BaseService {
'unhandledError',
emojic.boom,
'Unhandled internal error',
error,
error
)
throw error
}
@@ -391,7 +348,7 @@ class BaseService {
context = {},
config = {},
namedParams = {},
queryParams = {},
queryParams = {}
) {
trace.logTrace('inbound', emojic.womanCook, 'Service class', this.name)
trace.logTrace('inbound', emojic.ticket, 'Named params', namedParams)
@@ -425,13 +382,13 @@ class BaseService {
traceSuccessMessage: 'Query params after validation',
},
queryParams,
queryParamSchema,
queryParamSchema
)
trace.logTrace(
'inbound',
emojic.crayon,
'Query params after validation',
queryParams,
queryParams
)
} catch (error) {
serviceError = error
@@ -445,7 +402,7 @@ class BaseService {
try {
serviceData = await serviceInstance.handle(
namedParams,
transformedQueryParams,
transformedQueryParams
)
serviceInstance._validateServiceData(serviceData)
} catch (error) {
@@ -463,16 +420,10 @@ class BaseService {
}
static register(
{
camp,
handleRequest,
githubApiProvider,
librariesIoApiProvider,
metricInstance,
},
serviceConfig,
{ 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)
@@ -481,31 +432,33 @@ class BaseService {
ServiceClass: this,
})
const fetcher = fetchFactory(fetchLimitBytes)
camp.route(
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: fetcher,
sendAndCacheRequestWithCallbacks: request,
githubApiProvider,
librariesIoApiProvider,
metricHelper,
},
serviceConfig,
namedParams,
queryParams,
queryParams
)
const badgeData = coalesceBadge(
queryParams,
serviceData,
this.defaultBadgeData,
this,
this
)
// The final capture group is the extension.
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
@@ -514,7 +467,8 @@ class BaseService {
metricHandle.noteResponseSent()
},
cacheLength: this._cacheLength,
}),
fetchLimitBytes,
})
)
}
}

View File

@@ -72,8 +72,8 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: '!' },
),
{ queryParamA: '!' }
)
).to.deep.equal({
message: 'Hello namedParamA: bar.bar.bar with queryParamA: !',
})
@@ -85,8 +85,8 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: ['foo', 'bar'] },
),
{ queryParamA: ['foo', 'bar'] }
)
).to.deep.equal({
color: 'red',
isError: true,
@@ -97,13 +97,13 @@ describe('BaseService', function () {
describe('Required overrides', function () {
it('Should throw if render() is not overridden', function () {
expect(() => BaseService.render()).to.throw(
/^render\(\) function not implemented for BaseService$/,
/^render\(\) function not implemented for BaseService$/
)
})
it('Should throw if route is not overridden', function () {
return expect(BaseService.invoke({}, {}, {})).to.be.rejectedWith(
/^Route not defined for BaseService$/,
/^Route not defined for BaseService$/
)
})
@@ -112,48 +112,52 @@ describe('BaseService', function () {
}
it('Should throw if handle() is not overridden', function () {
return expect(WithRoute.invoke({}, {}, {})).to.be.rejectedWith(
/^Handler not implemented for WithRoute$/,
/^Handler not implemented for WithRoute$/
)
})
it('Should throw if category is not overridden', function () {
expect(() => BaseService.category).to.throw(
/^Category not set for BaseService$/,
/^Category not set for BaseService$/
)
})
})
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(
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: '!' },
{ queryParamA: '!' }
)
expect(trace.logTrace).to.be.calledWithMatch(
'inbound',
sinon.match.string,
'Service class',
'DummyService',
'DummyService'
)
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Named params',
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Query params after validation',
{ queryParamA: '!' },
{ queryParamA: '!' }
)
})
})
@@ -171,7 +175,7 @@ describe('BaseService', function () {
const serviceData = await LinkService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect(serviceData).to.deep.equal({
@@ -194,7 +198,7 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect.fail('Expected to throw')
} catch (e) {
@@ -212,7 +216,7 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect.fail('Expected to throw')
} catch (e) {
@@ -233,8 +237,8 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: true },
{ namedParamA: 'bar.bar.bar' },
),
{ namedParamA: 'bar.bar.bar' }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -251,7 +255,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'red',
@@ -266,7 +270,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -281,7 +285,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -296,7 +300,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -311,7 +315,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'red',
@@ -336,7 +340,7 @@ describe('BaseService', function () {
mockHandleRequest = sinon.spy()
DummyService.register(
{ camp: mockCamp, handleRequest: mockHandleRequest },
defaultConfig,
defaultConfig
)
})
@@ -413,8 +417,8 @@ describe('BaseService', function () {
expect(() =>
DummyService._validate(
{ requiredString: ['this', "shouldn't", 'work'] },
dummySchema,
),
dummySchema
)
)
.to.throw()
.instanceof(InvalidResponse)
@@ -422,56 +426,53 @@ 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 },
defaultConfig,
{ sendAndCacheRequest },
defaultConfig
)
const url = 'some-url'
const options = {
headers: { Cookie: 'some-cookie' },
searchParams: { param1: 'foobar', param2: undefined },
}
const options = { headers: { Cookie: 'some-cookie' } }
await serviceInstance._request({ url, options })
expect(trace.logTrace).to.be.calledWithMatch(
'fetch',
sinon.match.string,
'Request',
`${url}?param1=foobar\n${JSON.stringify(
{ headers: options.headers },
null,
2,
)}`,
`${url}\n${JSON.stringify(options, null, 2)}`
)
expect(trace.logTrace).to.be.calledWithMatch(
'fetch',
sinon.match.string,
'Response status code',
200,
200
)
})
it('handles errors', async function () {
const requestFetcher = async () => ({
const sendAndCacheRequest = async () => ({
buffer: '',
res: { statusCode: 404 },
})
const serviceInstance = new DummyService(
{ requestFetcher },
defaultConfig,
{ sendAndCacheRequest },
defaultConfig
)
try {
@@ -497,24 +498,24 @@ 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,
{ sendAndCacheRequest, metricHelper },
defaultConfig
)
await serviceInstance._request({ url })
expect(await 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',
'service_response_bytes_bucket{le="65536",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 0\n'
)
.and.to.contain(
'service_response_bytes_bucket{le="131072",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 1\n',
'service_response_bytes_bucket{le="131072",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 1\n'
)
})
@@ -523,19 +524,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 },
defaultConfig,
{ sendAndCacheRequest, metricHelper },
defaultConfig
)
await serviceInstance._request({ url })
expect(
await register.getSingleMetricAsString('service_response_bytes'),
await register.getSingleMetricAsString('service_response_bytes')
).to.not.contain('service_response_bytes_bucket')
})
})
@@ -565,8 +566,8 @@ describe('BaseService', function () {
},
private: { myci_pass: 'abc123' },
},
{ namedParamA: 'bar.bar.bar' },
),
{ namedParamA: 'bar.bar.bar' }
)
).to.deep.equal({ message: 'The CI password is abc123' })
})
@@ -583,8 +584,8 @@ describe('BaseService', function () {
},
{
namedParamA: 'bar.bar.bar',
},
),
}
)
).to.deep.equal({
color: 'lightgray',
isError: true,

View File

@@ -39,14 +39,14 @@ function coalesceCacheLength({
assert(defaultCacheLengthSeconds !== undefined)
const cacheLength = coalesce(
serviceOverrideCacheLengthSeconds,
serviceDefaultCacheLengthSeconds,
defaultCacheLengthSeconds,
defaultCacheLengthSeconds
)
// Overrides can apply _more_ caching, but not less. Query param overriding
// can request more overriding than service override, but not less.
const candidateOverrides = [
serviceOverrideCacheLengthSeconds,
overrideCacheLengthFromQueryParams(queryParams),
].filter(x => x !== undefined)

View File

@@ -74,12 +74,12 @@ describe('Cache header functions', function () {
serviceDefaultCacheLengthSeconds: 900,
serviceOverrideCacheLengthSeconds: 400,
queryParams: {},
}).expect(400)
}).expect(900)
given({
cacheHeaderConfig,
serviceOverrideCacheLengthSeconds: 400,
queryParams: {},
}).expect(400)
}).expect(777)
given({
cacheHeaderConfig,
serviceOverrideCacheLengthSeconds: 900,
@@ -99,11 +99,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 () {
@@ -125,7 +128,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'no-cache, no-store, must-revalidate',
'no-cache, no-store, must-revalidate'
)
})
@@ -141,7 +144,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'max-age=123, s-maxage=123',
'max-age=123, s-maxage=123'
)
})
@@ -156,7 +159,7 @@ describe('Cache header functions', function () {
it('sets the expected fields', function () {
const expectedFields = ['date', 'cache-control', 'expires']
expectedFields.forEach(field =>
expect(res._headers[field]).to.equal(undefined),
expect(res._headers[field]).to.equal(undefined)
)
setCacheHeaders({
@@ -169,7 +172,7 @@ describe('Cache header functions', function () {
expectedFields.forEach(field =>
expect(res._headers[field])
.to.be.a('string')
.and.have.lengthOf.at.least(1),
.and.have.lengthOf.at.least(1)
)
})
})
@@ -181,7 +184,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
`max-age=${24 * 3600}, s-maxage=${24 * 3600}`,
`max-age=${24 * 3600}, s-maxage=${24 * 3600}`
)
})
@@ -190,7 +193,7 @@ describe('Cache header functions', function () {
expect(new Date(lastModified)).to.be.withinTime(
// Within the last 60 seconds.
new Date(Date.now() - 60 * 1000),
new Date(),
new Date()
)
})
})
@@ -221,7 +224,7 @@ describe('Cache header functions', function () {
})
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(false)
})
},
}
)
context(
'when the If-Modified-Since header is after the process started',
@@ -233,7 +236,7 @@ describe('Cache header functions', function () {
})
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(true)
})
},
}
)
})
})

View File

@@ -2,22 +2,21 @@ import { NotFound, InvalidResponse, Inaccessible } from './errors.js'
const defaultErrorMessages = {
404: 'not found',
429: 'rate limited by upstream service',
}
export default function checkErrorResponse(httpErrors = {}) {
export default function checkErrorResponse(errorMessages = {}) {
return async function ({ buffer, res }) {
let error
httpErrors = { ...defaultErrorMessages, ...httpErrors }
errorMessages = { ...defaultErrorMessages, ...errorMessages }
if (res.statusCode === 404) {
error = new NotFound({ prettyMessage: httpErrors[404] })
error = new NotFound({ prettyMessage: errorMessages[404] })
} else if (res.statusCode !== 200) {
const underlying = Error(
`Got status code ${res.statusCode} (expected 200)`,
`Got status code ${res.statusCode} (expected 200)`
)
const props = { underlyingError: underlying }
if (httpErrors[res.statusCode] !== undefined) {
props.prettyMessage = httpErrors[res.statusCode]
if (errorMessages[res.statusCode] !== undefined) {
props.prettyMessage = errorMessages[res.statusCode]
}
if (res.statusCode >= 500) {
error = new Inaccessible(props)

View File

@@ -45,42 +45,6 @@ describe('async error handler', function () {
})
})
context('when status is 429', function () {
const buffer = Buffer.from('some stuff')
const res = { statusCode: 429 }
it('throws InvalidResponse', async function () {
try {
await checkErrorResponse()({ res, buffer })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)',
)
expect(e.prettyMessage).to.equal('rate limited by upstream service')
expect(e.response).to.equal(res)
expect(e.buffer).to.equal(buffer)
}
})
it('displays the custom too many requests', async function () {
const notFoundMessage = "terribly sorry but that's one too many requests"
try {
await checkErrorResponse({ 429: notFoundMessage })({ res, buffer })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)',
)
expect(e.prettyMessage).to.equal(
"terribly sorry but that's one too many requests",
)
}
})
})
context('when status is 4xx', function () {
it('throws InvalidResponse', async function () {
const res = { statusCode: 499 }
@@ -90,7 +54,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 499 (expected 200)',
'Invalid Response: Got status code 499 (expected 200)'
)
expect(e.prettyMessage).to.equal('invalid')
expect(e.response).to.equal(res)
@@ -106,7 +70,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 403 (expected 200)',
'Invalid Response: Got status code 403 (expected 200)'
)
expect(e.prettyMessage).to.equal('access denied')
}
@@ -122,7 +86,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(Inaccessible)
expect(e.message).to.equal(
'Inaccessible: Got status code 503 (expected 200)',
'Inaccessible: Got status code 503 (expected 200)'
)
expect(e.prettyMessage).to.equal('inaccessible')
expect(e.response).to.equal(res)
@@ -138,7 +102,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(Inaccessible)
expect(e.message).to.equal(
'Inaccessible: Got status code 500 (expected 200)',
'Inaccessible: Got status code 500 (expected 200)'
)
expect(e.prettyMessage).to.equal('server overloaded')
}

View File

@@ -16,15 +16,16 @@ import toArray from './to-array.js'
//
// Logos are resolved in this manner:
//
// 1. When `?logo=` contains a named logo or the name of one of the Shields
// logos or contains base64-encoded SVG, that logo is used. When a
// `&logoColor=` is specified, that color is used (except for the
// base64-encoded logos). Otherwise the default color is used. If the color
// is specified for a multicolor Shield logo, the named logo will be used and
// colored. The appearance of the logo can be customized using `logoWidth`,
// and in the case of the popout badge, `logoPosition`. When `?logo=` is
// specified, any logo-related parameters specified dynamically by the
// service, or by default in the service, are ignored.
// 1. When `?logo=` contains the name of one of the Shields logos, or contains
// base64-encoded SVG, that logo is used. In the case of a named logo, when
// a `&logoColor=` is specified, that color is used. Otherwise the default
// color is used. `logoColor` will not be applied to a custom
// (base64-encoded) logo; if a custom color is desired the logo should be
// recolored prior to making the request. The appearance of the logo can be
// customized using `logoWidth`, and in the case of the popout badge,
// `logoPosition`. When `?logo=` is specified, any logo-related parameters
// specified dynamically by the service, or by default in the service, are
// ignored.
// 2. The second precedence is the dynamic logo returned by a service. This is
// used only by the Endpoint badge. The `logoColor` can be overridden by the
// query string.
@@ -36,7 +37,7 @@ export default function coalesceBadge(
serviceData,
// These two parameters were kept separate to make tests clearer.
defaultBadgeData,
{ category, _cacheLength: defaultCacheSeconds } = {},
{ category, _cacheLength: defaultCacheSeconds } = {}
) {
// The "overrideX" naming is based on services that provide badge
// parameters themselves, which can be overridden by a query string
@@ -141,7 +142,7 @@ export default function coalesceBadge(
} else {
namedLogo = coalesce(
serviceNamedLogo,
style === 'social' ? defaultNamedLogo : undefined,
style === 'social' ? defaultNamedLogo : undefined
)
namedLogoColor = coalesce(overrideLogoColor, serviceLogoColor)
}
@@ -166,13 +167,13 @@ export default function coalesceBadge(
isError ? undefined : overrideColor,
serviceColor,
defaultColor,
'lightgrey',
'lightgrey'
),
labelColor: coalesce(
// In case of an error, disregard user's color override.
isError ? undefined : overrideLabelColor,
serviceLabelColor,
defaultLabelColor,
defaultLabelColor
),
style,
namedLogo,

View File

@@ -25,7 +25,7 @@ describe('coalesceBadge', function () {
it('overrides the label', function () {
expect(
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {}),
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {})
).to.include({ label: 'purr count' })
})
})
@@ -54,11 +54,11 @@ describe('coalesceBadge', function () {
it('overrides the color', function () {
expect(
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {}),
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {})
).to.include({ color: '10ADED' })
// also expected for legacy name
expect(
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {}),
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {})
).to.include({ color: 'B0ADED' })
})
@@ -68,16 +68,16 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ color: '10ADED' },
{ isError: true, color: 'lightgray' },
{},
),
{}
)
).to.include({ color: 'lightgray' })
// also expected for legacy name
expect(
coalesceBadge(
{ colorB: 'B0ADED' },
{ isError: true, color: 'lightgray' },
{},
),
{}
)
).to.include({ color: 'lightgray' })
})
})
@@ -102,11 +102,11 @@ describe('coalesceBadge', function () {
it('overrides the label color', function () {
expect(
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {}),
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {})
).to.include({ labelColor: '42f483' })
// also expected for legacy name
expect(
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {}),
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {})
).to.include({ labelColor: 'B2f483' })
})
@@ -116,8 +116,8 @@ describe('coalesceBadge', function () {
// Scoutcamp converts numeric query params to numbers.
{ color: 123 },
{ color: 'green' },
{},
),
{}
)
).to.include({ color: '123' })
// also expected for legacy name
expect(
@@ -125,8 +125,8 @@ describe('coalesceBadge', function () {
// Scoutcamp converts numeric query params to numbers.
{ colorB: 123 },
{ color: 'green' },
{},
),
{}
)
).to.include({ color: '123' })
})
})
@@ -140,7 +140,7 @@ describe('coalesceBadge', function () {
it('when a social badge, uses the default named logo', function () {
// .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
expect(
coalesceBadge({ style: 'social' }, {}, { namedLogo: 'appveyor' }).logo,
coalesceBadge({ style: 'social' }, {}, { namedLogo: 'appveyor' }).logo
).to.equal(getSimpleIcon({ name: 'appveyor' })).and.not.be.empty
})
@@ -149,50 +149,32 @@ describe('coalesceBadge', function () {
namedLogo: 'npm',
})
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).logo).to.equal(
getShieldsIcon({ name: 'npm' }),
getShieldsIcon({ name: 'npm' })
).and.not.to.be.empty
})
it('applies the named monochrome logo with color', function () {
it('applies the named logo with color', function () {
expect(
coalesceBadge({}, { namedLogo: 'dependabot', logoColor: 'blue' }, {})
.logo,
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
.to.be.empty
})
it('applies the named multicolored logo with color', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoColor: 'blue' }, {}).logo,
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.to.be
coalesceBadge({}, { namedLogo: 'npm', logoColor: 'blue' }, {}).logo
).to.equal(getShieldsIcon({ name: 'npm', color: 'blue' })).and.not.to.be
.empty
})
it('overrides the logo', function () {
expect(
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo,
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
})
it('overrides the monochrome logo with a color', function () {
expect(
coalesceBadge(
{ logo: 'dependabot', logoColor: 'blue' },
{ namedLogo: 'appveyor' },
{},
).logo,
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
.be.empty
})
it('overrides multicolored logo with a color', function () {
it('overrides the logo with a color', function () {
expect(
coalesceBadge(
{ logo: 'npm', logoColor: 'blue' },
{ namedLogo: 'appveyor' },
{},
).logo,
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
{}
).logo
).to.equal(getShieldsIcon({ name: 'npm', color: 'blue' })).and.not.be
.empty
})
it("when the logo is overridden, it ignores the service's logo color, position, and width", function () {
@@ -205,37 +187,27 @@ describe('coalesceBadge', function () {
logoPosition: -3,
logoWidth: 100,
},
{},
).logo,
{}
).logo
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
})
it("overrides the service monochome logo's color", function () {
expect(
coalesceBadge(
{ logoColor: 'blue' },
{ namedLogo: 'dependabot', logoColor: 'red' },
{},
).logo,
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
.be.empty
})
it("overrides the service multicolored logo's color", function () {
it("overrides the service logo's color", function () {
expect(
coalesceBadge(
{ logoColor: 'blue' },
{ namedLogo: 'npm', logoColor: 'red' },
{},
).logo,
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
{}
).logo
).to.equal(getShieldsIcon({ name: 'npm', color: 'blue' })).and.not.be
.empty
})
// https://github.com/badges/shields/issues/2998
it('overrides logoSvg', function () {
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
expect(coalesceBadge({ logo: 'npm' }, { logoSvg }, {}).logo).to.equal(
getShieldsIcon({ name: 'npm' }),
getShieldsIcon({ name: 'npm' })
).and.not.be.empty
})
})
@@ -244,7 +216,7 @@ 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' }, {}),
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {})
).to.include({ logo: logoSvg })
})
@@ -254,8 +226,8 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ logo: logoSvg, logoColor: 'brightgreen' },
{ namedLogo: 'appveyor' },
{},
),
{}
)
).to.include({ logo: logoSvg })
})
})
@@ -269,7 +241,7 @@ describe('coalesceBadge', function () {
it('applies the logo width', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {}),
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {})
).to.include({ logoWidth: 275 })
})
})
@@ -283,7 +255,7 @@ describe('coalesceBadge', function () {
it('applies the logo position', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {}),
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {})
).to.include({ logoPosition: -10 })
})
})
@@ -296,8 +268,8 @@ describe('coalesceBadge', function () {
{
link: 'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
},
{},
).links,
{}
).links
).to.deep.equal(['https://circleci.com/gh/badges/daily-tests'])
})
})
@@ -328,7 +300,7 @@ describe('coalesceBadge', function () {
describe('Cache length', function () {
it('overrides the cache length', function () {
expect(
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {}),
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {})
).to.include({ cacheLengthSeconds: 123 })
})
})

View File

@@ -20,7 +20,7 @@ function deprecatedService(attrs) {
const { route, name, label, category, examples, message } = Joi.attempt(
attrs,
attrSchema,
`Deprecated service for ${attrs.route.base}`,
`Deprecated service for ${attrs.route.base}`
)
return class DeprecatedService extends BaseService {

View File

@@ -42,7 +42,6 @@ class ShieldsRuntimeError extends Error {
if (props.underlyingError) {
this.stack = props.underlyingError.stack
}
this.cacheSeconds = props.cacheSeconds
}
}
@@ -207,9 +206,6 @@ class Deprecated extends ShieldsRuntimeError {
* @property {string} prettyMessage User-facing error message to override the
* value of `defaultPrettyMessage()`. This is the text that will appear on the
* badge when we catch and render the exception (Optional)
* @property {number} cacheSeconds Length of time to cache this error response
* for. Defaults to the cacheLength of the service class throwing the error
* (Optional)
*/
export {

View File

@@ -6,7 +6,7 @@ import { makeFullUrl } from './route.js'
const optionalObjectOfKeyValues = Joi.object().pattern(
/./,
Joi.string().allow(null),
Joi.string().allow(null)
)
const schema = Joi.object({
@@ -32,19 +32,19 @@ function validateExample(example, index, ServiceClass) {
const result = Joi.attempt(
example,
schema,
`Example for ${ServiceClass.name} at index ${index}`,
`Example for ${ServiceClass.name} at index ${index}`
)
const { pattern, namedParams } = result
if (!pattern && !ServiceClass.route.pattern) {
throw new Error(
`Example for ${ServiceClass.name} at index ${index} does not declare a pattern`,
`Example for ${ServiceClass.name} at index ${index} does not declare a pattern`
)
}
if (pattern === ServiceClass.route.pattern) {
throw new Error(
`Example for ${ServiceClass.name} at index ${index} declares a redundant pattern which should be removed`,
`Example for ${ServiceClass.name} at index ${index} declares a redundant pattern which should be removed`
)
}
@@ -57,7 +57,7 @@ function validateExample(example, index, ServiceClass) {
throw Error(
`In example for ${
ServiceClass.name
} at index ${index}, ${e.message.toLowerCase()}`,
} at index ${index}, ${e.message.toLowerCase()}`
)
}
// Make sure there are no extra keys.
@@ -73,8 +73,8 @@ function validateExample(example, index, ServiceClass) {
`In example for ${
ServiceClass.name
} at index ${index}, namedParams contains unknown keys: ${extraKeys.join(
', ',
)}`,
', '
)}`
)
}
@@ -86,22 +86,22 @@ function validateExample(example, index, ServiceClass) {
`In example for ${
ServiceClass.name
} at index ${index}, keywords contains words that are less than two characters long: ${tinyKeywords.join(
', ',
)}`,
', '
)}`
)
}
// Make sure none of the keywords are already included in the title.
const title = (example.title || ServiceClass.name).toLowerCase()
const redundantKeywords = example.keywords.filter(k =>
title.includes(k.toLowerCase()),
title.includes(k.toLowerCase())
)
if (redundantKeywords.length) {
throw Error(
`In example for ${
ServiceClass.name
} at index ${index}, keywords contains words that are already in the title: ${redundantKeywords.join(
', ',
)}`,
', '
)}`
)
}
}
@@ -126,16 +126,15 @@ function transformExample(inExample, index, ServiceClass) {
{},
staticPreview,
ServiceClass.defaultBadgeData,
ServiceClass,
ServiceClass
)
const category = categories.find(c => c.id === ServiceClass.category)
return {
title,
example: {
pattern: makeFullUrl(
ServiceClass.route.base,
pattern || ServiceClass.route.pattern,
pattern || ServiceClass.route.pattern
),
namedParams,
queryParams,
@@ -147,7 +146,9 @@ function transformExample(inExample, index, ServiceClass) {
style: style === 'flat' ? undefined : style,
namedLogo,
},
keywords: category ? keywords.concat(category.keywords) : keywords,
keywords: keywords.concat(
categories.find(c => c.id === ServiceClass.category).keywords
),
documentation: documentation ? { __html: documentation } : undefined,
}
}

View File

@@ -16,7 +16,7 @@ describe('validateExample function', function () {
validExamples.forEach(example => {
expect(() =>
validateExample(example, 0, { route: {}, name: 'mockService' }),
validateExample(example, 0, { route: {}, name: 'mockService' })
).not.to.throw(Error)
})
})
@@ -66,7 +66,7 @@ describe('validateExample function', function () {
invalidExamples.forEach(example => {
expect(() =>
validateExample(example, 0, { route: {}, name: 'mockService' }),
validateExample(example, 0, { route: {}, name: 'mockService' })
).to.throw(Error)
})
})
@@ -93,7 +93,7 @@ test(transformExample, function () {
keywords: ['hello'],
},
0,
ExampleService,
ExampleService
).expect({
title: 'ExampleService',
example: {
@@ -119,7 +119,7 @@ test(transformExample, function () {
keywords: ['hello'],
},
0,
ExampleService,
ExampleService
).expect({
title: 'ExampleService',
example: {
@@ -146,7 +146,7 @@ test(transformExample, function () {
keywords: ['hello'],
},
0,
ExampleService,
ExampleService
).expect({
title: 'ExampleService',
example: {

View File

@@ -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 }

Some files were not shown because too many files have changed in this diff Show More