Compare commits
1 Commits
server-202
...
assert-log
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9908df571 |
359
.circleci/config.yml
Normal file
359
.circleci/config.yml
Normal 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 v12
|
||||
nvm use v12
|
||||
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/v10/results.xml
|
||||
NODE_VERSION: v10
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 10
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- 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
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
jobs:
|
||||
main:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
main@node-14:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
integration:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
integration@node-14:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
danger:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
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:12
|
||||
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:12
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
services@node-14:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
e2e:
|
||||
docker:
|
||||
- image: cypress/base:12
|
||||
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-14:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- integration@node-14:
|
||||
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-14:
|
||||
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\/.*/
|
||||
33
.dependabot/config.yml
Normal file
33
.dependabot/config.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
version: 1
|
||||
update_configs:
|
||||
# shields.io dependencies
|
||||
- package_manager: 'javascript'
|
||||
directory: '/'
|
||||
update_schedule: 'weekly'
|
||||
automerged_updates:
|
||||
- match:
|
||||
dependency_name: 'chai*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'cypress'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'eslint*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'mocha*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'sazerac'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'sinon*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'snap-shot-it'
|
||||
update_type: 'semver:minor'
|
||||
|
||||
# badge-maker package dependencies
|
||||
- package_manager: 'javascript'
|
||||
directory: '/badge-maker'
|
||||
update_schedule: 'weekly'
|
||||
@@ -3,7 +3,6 @@ shields.env
|
||||
.git/
|
||||
.gitignore
|
||||
.vscode/
|
||||
fly.toml
|
||||
|
||||
# Improve layer cacheability.
|
||||
Dockerfile
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
/build
|
||||
/coverage
|
||||
/__snapshots__
|
||||
public
|
||||
/public
|
||||
badge-maker/node_modules/
|
||||
!.github/
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
extends:
|
||||
- standard
|
||||
- standard-jsx
|
||||
- standard-react
|
||||
- plugin:@typescript-eslint/recommended
|
||||
- prettier
|
||||
- prettier/@typescript-eslint
|
||||
- prettier/standard
|
||||
- prettier/react
|
||||
- eslint:recommended
|
||||
|
||||
globals:
|
||||
JSX: 'readonly'
|
||||
|
||||
parserOptions:
|
||||
# Override eslint-config-standard, which incorrectly sets this to "module",
|
||||
# though that setting is only for ES6 modules, not CommonJS modules.
|
||||
@@ -17,13 +17,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 +34,38 @@ 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
|
||||
parserOptions:
|
||||
sourceType: 'module'
|
||||
parser: '@typescript-eslint/parser'
|
||||
rules:
|
||||
no-console: 'off'
|
||||
|
||||
- files:
|
||||
- '**/*.ts'
|
||||
- '**/*.@(ts|tsx)'
|
||||
parserOptions:
|
||||
sourceType: 'module'
|
||||
parser: '@typescript-eslint/parser'
|
||||
rules:
|
||||
# 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'
|
||||
|
||||
- files:
|
||||
- 'frontend/**/*.js'
|
||||
- core/**/*.ts
|
||||
parserOptions:
|
||||
sourceType: 'module'
|
||||
parser: '@typescript-eslint/parser'
|
||||
- files:
|
||||
- gatsby-browser.js
|
||||
- 'frontend/**/*.@(js|ts|tsx)'
|
||||
parserOptions:
|
||||
sourceType: 'module'
|
||||
env:
|
||||
@@ -107,21 +110,19 @@ 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 }]
|
||||
|
||||
no-use-before-define: 'off'
|
||||
# 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'
|
||||
|
||||
# These should be disabled by eslint-config-prettier, but are not.
|
||||
no-extra-semi: 'off'
|
||||
@@ -129,6 +130,7 @@ rules:
|
||||
# Shields additions.
|
||||
no-var: 'error'
|
||||
prefer-const: 'error'
|
||||
strict: 'error'
|
||||
arrow-body-style: ['error', 'as-needed']
|
||||
no-extension-in-require/main: 'error'
|
||||
object-shorthand: ['error', 'properties']
|
||||
@@ -137,22 +139,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
|
||||
# Based on original rule configuration from eslint-config-standard
|
||||
camelcase:
|
||||
[
|
||||
'error',
|
||||
{
|
||||
ignoreDestructuring: true,
|
||||
properties: 'never',
|
||||
ignoreGlobals: true,
|
||||
allow: ['^UNSAFE_'],
|
||||
},
|
||||
]
|
||||
|
||||
# Chai friendly.
|
||||
no-unused-expressions: 'off'
|
||||
@@ -171,7 +157,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 +168,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'
|
||||
|
||||
29
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report errors and problems
|
||||
labels: 'question'
|
||||
---
|
||||
|
||||
Are you experiencing an issue with...
|
||||
|
||||
- [ ] [shields.io](https://shields.io/#/)
|
||||
- [ ] My own instance
|
||||
- [ ] [badge-maker NPM package](https://www.npmjs.com/package/badge-maker)
|
||||
|
||||
:beetle: **Description**
|
||||
|
||||
<!-- A clear and concise description of the problem. -->
|
||||
|
||||
:link: **Link to the badge**
|
||||
|
||||
<!--
|
||||
If you are reporting a problem with a specific badge on shields.io,
|
||||
provide a link to a badge demonstrating the error
|
||||
-->
|
||||
|
||||
:bulb: **Possible Solution**
|
||||
|
||||
<!--- Optional: only if you have suggestions on a fix/reason for the bug -->
|
||||
|
||||
<!-- Love Shields? Please consider donating $10 to sustain our activities:
|
||||
👉 https://opencollective.com/shields -->
|
||||
44
.github/ISSUE_TEMPLATE/1_Bug_report.yml
vendored
44
.github/ISSUE_TEMPLATE/1_Bug_report.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: '🐛 Bug Report'
|
||||
description: Report errors and problems
|
||||
labels: [question]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: product
|
||||
attributes:
|
||||
label: Are you experiencing an issue with...
|
||||
options:
|
||||
- shields.io
|
||||
- My own instance of shields
|
||||
- badge-maker NPM package
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: '🐞 Description'
|
||||
description: A clear and concise description of the problem.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: link
|
||||
attributes:
|
||||
label: '🔗 Link to the badge'
|
||||
description: If you are reporting a problem with a specific badge on shields.io, provide a link to a badge demonstrating the error
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: possible-solution
|
||||
attributes:
|
||||
label: '💡 Possible Solution'
|
||||
description: 'Optional: only if you have suggestions on a fix/reason for the bug'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## :heart: Love Shields?
|
||||
Please consider donating $10 to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)
|
||||
@@ -22,7 +22,7 @@ labels: 'keep-service-tests-green'
|
||||
|
||||
<!-- Provide a link to the failing test in CircleCI. -->
|
||||
|
||||
:lady_beetle: **Stack trace**
|
||||
:beetle: **Stack trace**
|
||||
|
||||
```
|
||||
<!-- Provide the complete stack trace from the CircleCI test summary. -->
|
||||
|
||||
37
.github/ISSUE_TEMPLATE/3_Badge_request.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE/3_Badge_request.md
vendored
Normal 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 -->
|
||||
62
.github/ISSUE_TEMPLATE/3_Badge_request.yml
vendored
62
.github/ISSUE_TEMPLATE/3_Badge_request.yml
vendored
@@ -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)
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,4 @@
|
||||
contact_links:
|
||||
- name: 🎨 Simple Icons
|
||||
url: https://github.com/badges/shields/discussions/5369
|
||||
about: Please read this before posting a question about SimpleIcons
|
||||
- name: ❓ Support Question
|
||||
url: https://github.com/badges/shields/discussions
|
||||
about: Ask a question about Shields.io
|
||||
|
||||
21
.github/actions/core-tests/action.yml
vendored
21
.github/actions/core-tests/action.yml
vendored
@@ -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
|
||||
@@ -1,12 +0,0 @@
|
||||
name: 'docusaurus-theme-openapi swizzled component changes warning'
|
||||
description: 'Check for changes in docusaurus-theme-openapi components which are swizzled and prints out a warning'
|
||||
branding:
|
||||
icon: 'alert-triangle'
|
||||
color: 'yellow'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'index.js'
|
||||
@@ -1,105 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Returns info about all files changed in a PR (max 3000 results)
|
||||
*
|
||||
* @param {object} client hydrated octokit ready to use for GitHub Actions
|
||||
* @param {string} owner repo owner
|
||||
* @param {string} repo repo name
|
||||
* @param {number} pullNumber pull request number
|
||||
* @returns {object[]} array of object that describe pr changed files - see https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files
|
||||
*/
|
||||
async function getAllFilesForPullRequest(client, owner, repo, pullNumber) {
|
||||
const perPage = 100 // Max number of items per page
|
||||
let page = 1 // Start with the first page
|
||||
let allFiles = []
|
||||
while (true) {
|
||||
const response = await client.rest.pulls.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber,
|
||||
per_page: perPage,
|
||||
page,
|
||||
})
|
||||
|
||||
if (response.data.length === 0) {
|
||||
// Break the loop if no more results
|
||||
break
|
||||
}
|
||||
|
||||
allFiles = allFiles.concat(response.data)
|
||||
page++ // Move to the next page
|
||||
}
|
||||
return allFiles
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of files changed betwen two tags for a github repo
|
||||
*
|
||||
* @param {object} client hydrated octokit ready to use for GitHub Actions
|
||||
* @param {string} owner repo owner
|
||||
* @param {string} repo repo name
|
||||
* @param {string} baseTag base tag
|
||||
* @param {string} headTag head tag
|
||||
* @returns {string[]} Array listing all changed files betwen the base tag and the head tag
|
||||
*/
|
||||
async function getChangedFilesBetweenTags(
|
||||
client,
|
||||
owner,
|
||||
repo,
|
||||
baseTag,
|
||||
headTag,
|
||||
) {
|
||||
const response = await client.rest.repos.compareCommits({
|
||||
owner,
|
||||
repo,
|
||||
base: baseTag,
|
||||
head: headTag,
|
||||
})
|
||||
|
||||
return response.data.files.map(file => file.filename)
|
||||
}
|
||||
|
||||
function findKeyEndingWith(obj, ending) {
|
||||
for (const key in obj) {
|
||||
if (key.endsWith(ending)) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get large (>1MB) JSON file from git repo on at ref as a json object
|
||||
*
|
||||
* @param {object} client Hydrated octokit ready to use for GitHub Actions
|
||||
* @param {string} owner Repo owner
|
||||
* @param {string} repo Repo name
|
||||
* @param {string} path Path of the file in repo relative to root directory
|
||||
* @param {string} ref Git refrence (commit, branch, tag)
|
||||
* @returns {string[]} Array listing all changed files betwen the base tag and the head tag
|
||||
*/
|
||||
async function getLargeJsonAtRef(client, owner, repo, path, ref) {
|
||||
const fileSha = (
|
||||
await client.rest.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
path,
|
||||
ref,
|
||||
})
|
||||
).data.sha
|
||||
const fileBlob = (
|
||||
await client.rest.git.getBlob({
|
||||
owner,
|
||||
repo,
|
||||
file_sha: fileSha,
|
||||
})
|
||||
).data.content
|
||||
return JSON.parse(Buffer.from(fileBlob, 'base64').toString())
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAllFilesForPullRequest,
|
||||
getChangedFilesBetweenTags,
|
||||
findKeyEndingWith,
|
||||
getLargeJsonAtRef,
|
||||
}
|
||||
148
.github/actions/docusaurus-swizzled-warning/index.js
vendored
148
.github/actions/docusaurus-swizzled-warning/index.js
vendored
@@ -1,148 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const core = require('@actions/core')
|
||||
const github = require('@actions/github')
|
||||
const {
|
||||
getAllFilesForPullRequest,
|
||||
getChangedFilesBetweenTags,
|
||||
findKeyEndingWith,
|
||||
getLargeJsonAtRef,
|
||||
} = require('./helpers')
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const token = core.getInput('github-token', { required: true })
|
||||
|
||||
const { pull_request: pr } = github.context.payload
|
||||
if (!pr) {
|
||||
throw new Error('Event payload missing `pull_request`')
|
||||
}
|
||||
|
||||
const client = github.getOctokit(token)
|
||||
const packageName = 'docusaurus-theme-openapi'
|
||||
const packageParentName = 'docusaurus-preset-openapi'
|
||||
const overideComponents = ['Curl', 'Response']
|
||||
const messageTemplate = `<table><thead><tr><th colspan="2">
|
||||
⚠️ This PR contains changes to components of ${packageName} we've overridden
|
||||
</th></tr>
|
||||
<tr><th colspan="2">
|
||||
We need to watch out for changes to the ${overideComponents.join(
|
||||
', ',
|
||||
)} components
|
||||
</th></tr></thead>
|
||||
`
|
||||
|
||||
if (
|
||||
!['dependabot[bot]', 'dependabot-preview[bot]'].includes(pr.user.login)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const files = await getAllFilesForPullRequest(
|
||||
client,
|
||||
github.context.repo.owner,
|
||||
github.context.repo.repo,
|
||||
pr.number,
|
||||
)
|
||||
|
||||
const file = files.filter(f => f.filename === 'package-lock.json')[0]
|
||||
if (file === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const prCommitRefForFile = file.contents_url.split('ref=')[1]
|
||||
const pkgLockNewJson = await getLargeJsonAtRef(
|
||||
client,
|
||||
github.context.repo.owner,
|
||||
github.context.repo.repo,
|
||||
file.filename,
|
||||
prCommitRefForFile,
|
||||
)
|
||||
const pkgLockOldJson = await getLargeJsonAtRef(
|
||||
client,
|
||||
github.context.repo.owner,
|
||||
github.context.repo.repo,
|
||||
file.filename,
|
||||
'master',
|
||||
)
|
||||
|
||||
const oldVesionModuleKey = findKeyEndingWith(
|
||||
pkgLockOldJson.packages,
|
||||
`node_modules/${packageName}`,
|
||||
)
|
||||
const newVesionModuleKey = findKeyEndingWith(
|
||||
pkgLockNewJson.packages,
|
||||
`node_modules/${packageName}`,
|
||||
)
|
||||
let oldVersion = pkgLockOldJson.packages[oldVesionModuleKey].version
|
||||
let newVersion = pkgLockNewJson.packages[newVesionModuleKey].version
|
||||
|
||||
const oldVesionModuleKeyParent = findKeyEndingWith(
|
||||
pkgLockOldJson.packages,
|
||||
`node_modules/${packageParentName}`,
|
||||
)
|
||||
const newVesionModuleKeyParent = findKeyEndingWith(
|
||||
pkgLockNewJson.packages,
|
||||
`node_modules/${packageParentName}`,
|
||||
)
|
||||
const oldVersionParent =
|
||||
pkgLockOldJson.packages[oldVesionModuleKeyParent].dependencies[
|
||||
packageName
|
||||
].substring(1)
|
||||
const newVersionParent =
|
||||
pkgLockNewJson.packages[newVesionModuleKeyParent].dependencies[
|
||||
packageName
|
||||
].substring(1)
|
||||
|
||||
// if parent dependency is higher version then existing
|
||||
// npm install will retrive the newer version from the parent dependency
|
||||
if (oldVersionParent > oldVersion) {
|
||||
oldVersion = oldVersionParent
|
||||
}
|
||||
if (newVersionParent > newVersion) {
|
||||
newVersion = newVersionParent
|
||||
}
|
||||
core.info(`oldVersion=${oldVersion}`)
|
||||
core.info(`newVersion=${newVersion}`)
|
||||
|
||||
if (newVersion !== oldVersion) {
|
||||
const pkgChangedFiles = await getChangedFilesBetweenTags(
|
||||
client,
|
||||
'cloud-annotations',
|
||||
'docusaurus-openapi',
|
||||
`v${oldVersion}`,
|
||||
`v${newVersion}`,
|
||||
)
|
||||
const changedComponents = overideComponents.filter(
|
||||
componenet =>
|
||||
pkgChangedFiles.filter(
|
||||
path =>
|
||||
path.includes('docusaurus-theme-openapi/src/theme') &&
|
||||
path.includes(componenet),
|
||||
).length > 0,
|
||||
)
|
||||
const versionReport = `<tbody><tr><td> Old version </td><td> ${oldVersion} </td></tr>
|
||||
<tr><td> New version </td><td> ${newVersion} </td></tr>
|
||||
`
|
||||
const changedComponentsReport = `<tr><td> Overide components changed </td><td> ${changedComponents.join(
|
||||
', ',
|
||||
)} </td></tr></tbody></table>
|
||||
`
|
||||
const body = messageTemplate + versionReport + changedComponentsReport
|
||||
await client.rest.issues.createComment({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body,
|
||||
})
|
||||
|
||||
core.info('Found changes and posted comment, done.')
|
||||
return
|
||||
}
|
||||
|
||||
core.info('No changes found, done.')
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
237
.github/actions/docusaurus-swizzled-warning/package-lock.json
generated
vendored
237
.github/actions/docusaurus-swizzled-warning/package-lock.json
generated
vendored
@@ -1,237 +0,0 @@
|
||||
{
|
||||
"name": "docusaurus-swizzled-warning",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "docusaurus-swizzled-warning",
|
||||
"version": "0.0.0",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
|
||||
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
|
||||
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.2.0",
|
||||
"@octokit/core": "^5.0.1",
|
||||
"@octokit/plugin-paginate-rest": "^9.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz",
|
||||
"integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz",
|
||||
"integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.1.tgz",
|
||||
"integrity": "sha512-lyeeeZyESFo+ffI801SaBKmCfsvarO+dgV8/0gD8u1d87clbEdWsP5yC+dSj3zLhb2eIf5SJrn6vDz9AheETHw==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.0.0",
|
||||
"@octokit/request": "^8.0.2",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.1.tgz",
|
||||
"integrity": "sha512-hRlOKAovtINHQPYHZlfyFwaM8OyetxeoC81lAkBy34uLb8exrZB50SQdeW3EROqiY9G9yxQTpp5OHTV54QD+vA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz",
|
||||
"integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^8.0.1",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz",
|
||||
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.0.0.tgz",
|
||||
"integrity": "sha512-oIJzCpttmBTlEhBmRvb+b9rlnGpmFgDtZ0bB6nq39qIod6A5DP+7RkVLMOixIgRCYSHDTeayWqmiJ2SZ6xgfdw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.0.1.tgz",
|
||||
"integrity": "sha512-fgS6HPkPvJiz8CCliewLyym9qAx0RZ/LKh3sATaPfM41y/O2wQ4Z9MrdYeGPVh04wYmHFmWiGlKPC7jWVtZXQA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz",
|
||||
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz",
|
||||
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz",
|
||||
"integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.26.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz",
|
||||
"integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/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/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "docusaurus-swizzled-warning",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "jNullj",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
}
|
||||
8
.github/actions/draft-release/Dockerfile
vendored
8
.github/actions/draft-release/Dockerfile
vendored
@@ -1,8 +0,0 @@
|
||||
FROM node:20-bullseye
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y jq
|
||||
RUN apt-get install -y uuid-runtime
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
5
.github/actions/draft-release/action.yml
vendored
5
.github/actions/draft-release/action.yml
vendored
@@ -1,5 +0,0 @@
|
||||
name: 'draft-release'
|
||||
description: 'Generate a changelog and propose a release PR'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
65
.github/actions/draft-release/entrypoint.sh
vendored
65
.github/actions/draft-release/entrypoint.sh
vendored
@@ -1,65 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
# mark workspace dir as 'safe'
|
||||
git config --system --add safe.directory '/github/workspace'
|
||||
|
||||
# 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))
|
||||
|
||||
# Generate a release name
|
||||
RELEASE_NAME="server-$(date --rfc-3339=date)"
|
||||
|
||||
# Assemble changelog entry
|
||||
rm -f temp-changes.txt
|
||||
touch temp-changes.txt
|
||||
{
|
||||
echo "## $RELEASE_NAME"
|
||||
echo ""
|
||||
git log "$LAST_TAG"..HEAD --no-merges --oneline --pretty="format:- %s" --perl-regexp --author='^((?!dependabot).*)$'
|
||||
echo $'\n- Dependency updates\n'
|
||||
} >> temp-changes.txt
|
||||
BASE_URL="https:\/\/github.com\/badges\/shields\/issues\/"
|
||||
sed -r -i "s/\((\#)([0-9]+)\)$/\[\1\2\]\($BASE_URL\2\)/g" temp-changes.txt
|
||||
|
||||
# Write the changelog
|
||||
sed -i "${INSERT_POINT} r temp-changes.txt" CHANGELOG.md
|
||||
|
||||
# Cleanup
|
||||
rm temp-changes.txt
|
||||
|
||||
# Run prettier (to ensure the markdown file doesn't fail CI)
|
||||
npx prettier@$(cat package.json | jq -r .devDependencies.prettier) --write "CHANGELOG.md"
|
||||
|
||||
# Generate a unique branch name
|
||||
BRANCH_NAME="$RELEASE_NAME"-$(uuidgen | head -c 8)
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
# Commit + push changelog
|
||||
git add CHANGELOG.md
|
||||
git commit -m "Update Changelog"
|
||||
git push origin "$BRANCH_NAME"
|
||||
|
||||
# Submit a PR
|
||||
TITLE="Changelog for Release $RELEASE_NAME"
|
||||
PR_RESP=$(curl https://api.github.com/repos/"$REPO_NAME"/pulls \
|
||||
-X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
--data '{"title": "'"$TITLE"'", "body": "'"$TITLE"'", "head": "'"$BRANCH_NAME"'", "base": "master"}')
|
||||
|
||||
# Add the 'release' label to the PR
|
||||
PR_API_URL=$(echo "$PR_RESP" | jq -r ._links.issue.href)
|
||||
curl "$PR_API_URL" \
|
||||
-X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
--data '{"labels":["release"]}'
|
||||
28
.github/actions/integration-tests/action.yml
vendored
28
.github/actions/integration-tests/action.yml
vendored
@@ -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
|
||||
26
.github/actions/package-tests/action.yml
vendored
26
.github/actions/package-tests/action.yml
vendored
@@ -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
|
||||
91
.github/actions/service-tests/action.yml
vendored
91
.github/actions/service-tests/action.yml
vendored
@@ -1,91 +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: ''
|
||||
pepy-key:
|
||||
description: 'The SERVICETESTS_PEPY_KEY 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 }}'
|
||||
PEPY_KEY: '${{ inputs.pepy-key }}'
|
||||
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
|
||||
44
.github/actions/setup/action.yml
vendored
44
.github/actions/setup/action.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: 'Set up project'
|
||||
description: 'Set up project'
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'Version Spec of the node version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
required: true
|
||||
npm-version:
|
||||
description: 'Version Spec of the npm version to use. Examples: 9.x, 10.2.3, >=10.1.0.'
|
||||
required: false
|
||||
default: '^10'
|
||||
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 ${{ inputs.npm-version }}
|
||||
run: npm install -g npm@${{ inputs.npm-version }}
|
||||
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
|
||||
46
.github/dependabot.yml
vendored
46
.github/dependabot.yml
vendored
@@ -1,46 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# shields.io dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
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'
|
||||
|
||||
# badge-maker package dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/badge-maker'
|
||||
schedule:
|
||||
interval: weekly
|
||||
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
|
||||
|
||||
# docusaurus-swizzled-warning package dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/.github/actions/docusaurus-swizzled-warning'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
rebase-strategy: disabled
|
||||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -1,7 +0,0 @@
|
||||
<!--
|
||||
Be sure to review our Contributing guidelines to help streamline the merging of your PR!
|
||||
|
||||
* PR title conventions - https://github.com/badges/shields/blob/master/CONTRIBUTING.md#running-service-tests-in-pull-requests
|
||||
* Code formatting - https://github.com/badges/shields/blob/master/CONTRIBUTING.md#prettier
|
||||
* Merge processes and reminders - https://github.com/badges/shields/blob/master/CONTRIBUTING.md#pull-requests
|
||||
-->
|
||||
16
.github/scripts/check-docusaurus-versions.sh
vendored
16
.github/scripts/check-docusaurus-versions.sh
vendored
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Docusaurus outputs some important errors as log messages
|
||||
# but doesn't actually fail
|
||||
# https://github.com/facebook/docusaurus/blob/v2.4.3/packages/docusaurus/src/server/siteMetadata.ts#L75-L92
|
||||
# this script runs `docusaurus build`. If it outputs any [ERROR]s, we exit with 1
|
||||
|
||||
if ( npm run build 2>&1 | grep '\[ERROR\]' )
|
||||
then
|
||||
echo "You probably need to run: npm update @docusaurus/preset-classic"
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
10
.github/scripts/cleanup-review-apps.sh
vendored
10
.github/scripts/cleanup-review-apps.sh
vendored
@@ -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
|
||||
35
.github/scripts/deploy-review-app.sh
vendored
35
.github/scripts/deploy-review-app.sh
vendored
@@ -1,35 +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" --dockerfile ./Dockerfile
|
||||
echo $SECRETS | tr " " "\n" | flyctl secrets import --app "$app"
|
||||
fi
|
||||
|
||||
# Deploy
|
||||
flyctl deploy --app "$app" --region "$region"
|
||||
flyctl scale count 1 --app "$app" --yes
|
||||
|
||||
# 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\"}"
|
||||
28
.github/workflows/add_deployment_status.yml
vendored
Normal file
28
.github/workflows/add_deployment_status.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Ddd deployment status
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
add_deployment_status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create comment
|
||||
if: ${{ github.event_name == 'pull_request_target'
|
||||
&& github.event.action == 'closed'
|
||||
&& github.event.pull_request.merged
|
||||
&& !startsWith(github.event.pull_request.head.ref, 'dependabot/')
|
||||
&& github.event.pull_request.base.ref == 'master' }}
|
||||
# From a security perspective it's good practice to reference the commit hash
|
||||
# https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#security
|
||||
uses: peter-evans/create-or-update-comment@41f3207a84f33bd70388036109082784d059dcaa
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
edit-mode: replace
|
||||
body: |
|
||||
This pull request was merged to [${{ github.event.pull_request.base.ref }}](${{ github.event.repository.html_url }}/tree/${{ github.event.pull_request.base.ref }}) branch. This change is now waiting for deployment, which will usually happen within a few days. Stay tuned by joining our `#ops` channel on [Discord](https://discordapp.com/invite/HjJCwm5)!
|
||||
|
||||
After deployment, changes are copied to [gh-pages](${{ github.event.repository.html_url }}/tree/gh-pages) branch: 
|
||||
10
.github/workflows/auto-approve.yml
vendored
Normal file
10
.github/workflows/auto-approve.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
name: Auto approve
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: chris48s/approve-bot@2.0.1
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
30
.github/workflows/build-docker-image.yml
vendored
30
.github/workflows/build-docker-image.yml
vendored
@@ -1,30 +0,0 @@
|
||||
name: Build Docker Image
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'gh-readonly-queue/**'
|
||||
|
||||
jobs:
|
||||
build-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: ghcr.io/badges/shields:pr-validation
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
23
.github/workflows/check-docusaurus-versions.yml
vendored
23
.github/workflows/check-docusaurus-versions.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Check Docusaurus Plugin Versions
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
check-docusaurus-versions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
cypress: true
|
||||
|
||||
- run: .github/scripts/check-docusaurus-versions.sh
|
||||
24
.github/workflows/cleanup-review-apps.yml
vendored
24
.github/workflows/cleanup-review-apps.yml
vendored
@@ -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@v4
|
||||
- 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 }}
|
||||
71
.github/workflows/create-release.yml
vendored
71
.github/workflows/create-release.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.action == 'closed' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
contains(github.event.pull_request.labels.*.name, 'release')
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date --rfc-3339=date)"
|
||||
|
||||
- name: Checkout branch "master"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'master'
|
||||
|
||||
- name: Tag release in GitHub
|
||||
uses: tvdias/github-tagger@v0.0.2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: server-${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to DockerHub
|
||||
uses: docker/build-push-action@v5
|
||||
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@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to GHCR
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/badges/shields:server-${{ steps.date.outputs.date }}
|
||||
build-args: |
|
||||
version=server-${{ steps.date.outputs.date }}
|
||||
29
.github/workflows/danger.yml
vendored
29
.github/workflows/danger.yml
vendored
@@ -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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Danger
|
||||
run: npm run danger ci
|
||||
env:
|
||||
# https://github.com/gatsbyjs/gatsby/pull/11555
|
||||
NODE_ENV: test
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
32
.github/workflows/deploy-docs.yml
vendored
32
.github/workflows/deploy-docs.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Build
|
||||
run: npm run build-docs
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: api-docs
|
||||
clean: true
|
||||
44
.github/workflows/deploy-review-app.yml
vendored
44
.github/workflows/deploy-review-app.yml
vendored
@@ -1,44 +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@v4
|
||||
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 }}
|
||||
PEPY_KEY=${{ secrets.SERVICETESTS_PEPY_KEY }}
|
||||
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 }}
|
||||
@@ -1,22 +0,0 @@
|
||||
name: Docusaurus swizzled component changes warning
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
docusaurus-swizzled-warning:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install action dependencies
|
||||
run: cd .github/actions/docusaurus-swizzled-warning && npm ci
|
||||
|
||||
- uses: ./.github/actions/docusaurus-swizzled-warning
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
23
.github/workflows/draft-release.yml
vendored
23
.github/workflows/draft-release.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Draft Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 1 * *'
|
||||
# At 01:00 on the first day of every month
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Draft Release
|
||||
uses: ./.github/actions/draft-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
13
.github/workflows/enforce-dependency-review.yml
vendored
13
.github/workflows/enforce-dependency-review.yml
vendored
@@ -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@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
58
.github/workflows/publish-docker-next.yml
vendored
58
.github/workflows/publish-docker-next.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: Build and Publish Next Docker Image
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish-docker-next:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
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
|
||||
id: docker_build_push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: shieldsio/shields:next
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
|
||||
- name: Output Image Digest
|
||||
run: echo ${{ steps.docker_build_push.outputs.digest }} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to GHCR
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/badges/shields:next
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
69
.github/workflows/test-bug-run-badge.yml
vendored
69
.github/workflows/test-bug-run-badge.yml
vendored
@@ -1,69 +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
|
||||
env:
|
||||
ISSUE_BODY: '${{ github.event.issue.body }}'
|
||||
run: |
|
||||
product=$(echo "$ISSUE_BODY" | grep -A2 "Are you experiencing an issue with.*" | tail -n 1)
|
||||
link=$(echo "$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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
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@v7
|
||||
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
|
||||
});
|
||||
49
.github/workflows/test-e2e.yml
vendored
49
.github/workflows/test-e2e.yml
vendored
@@ -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@v4
|
||||
|
||||
- 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: 20
|
||||
cypress: true
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: npm run e2e
|
||||
|
||||
- name: Archive videos
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: videos
|
||||
path: cypress/videos
|
||||
|
||||
- name: Archive screenshots
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots
|
||||
path: cypress/screenshots
|
||||
52
.github/workflows/test-integration-21.yml
vendored
52
.github/workflows/test-integration-21.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Integration@node 21
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration-21:
|
||||
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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 21
|
||||
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 }}'
|
||||
50
.github/workflows/test-integration.yml
vendored
50
.github/workflows/test-integration.yml
vendored
@@ -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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- 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 }}'
|
||||
28
.github/workflows/test-lint.yml
vendored
28
.github/workflows/test-lint.yml
vendored
@@ -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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: ESLint
|
||||
if: always()
|
||||
run: npm run lint
|
||||
|
||||
- name: 'Prettier check (quick fix: `npm run prettier`)'
|
||||
if: always()
|
||||
run: npm run prettier:check
|
||||
25
.github/workflows/test-main-21.yml
vendored
25
.github/workflows/test-main-21.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Main@node 21
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main-21:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 21
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
28
.github/workflows/test-main.yml
vendored
28
.github/workflows/test-main.yml
vendored
@@ -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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
40
.github/workflows/test-package-cli.yml
vendored
40
.github/workflows/test-package-cli.yml
vendored
@@ -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@v4
|
||||
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
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
|
||||
38
.github/workflows/test-package-lib.yml
vendored
38
.github/workflows/test-package-lib.yml
vendored
@@ -1,38 +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'
|
||||
npm: '^9'
|
||||
engine-strict: 'false'
|
||||
- node: '18'
|
||||
npm: '^9'
|
||||
engine-strict: 'false'
|
||||
- node: '20'
|
||||
npm: '^10'
|
||||
engine-strict: 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
npm-version: ${{ matrix.npm }}
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
|
||||
- name: Package tests
|
||||
uses: ./.github/actions/package-tests
|
||||
44
.github/workflows/test-services-21.yml
vendored
44
.github/workflows/test-services-21.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Services@node 21
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches:
|
||||
- 'gh-readonly-queue/**'
|
||||
|
||||
jobs:
|
||||
test-services-21:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 21
|
||||
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 }}'
|
||||
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
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 }}'
|
||||
42
.github/workflows/test-services.yml
vendored
42
.github/workflows/test-services.yml
vendored
@@ -1,42 +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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- 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 }}'
|
||||
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
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 }}'
|
||||
33
.github/workflows/update-github-api.yml
vendored
33
.github/workflows/update-github-api.yml
vendored
@@ -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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- 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
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -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,10 @@ frontend/categories/*.yaml
|
||||
# Template for the local runtime configuration.
|
||||
!/config/local*.template.yml
|
||||
|
||||
# Gatsby
|
||||
/.cache
|
||||
/public
|
||||
|
||||
# Cypress
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
@@ -109,11 +116,3 @@ frontend/categories/*.yaml
|
||||
|
||||
# Flamebearer
|
||||
flamegraph.html
|
||||
|
||||
# config file for node-pg-migrate
|
||||
migrations-config.json
|
||||
|
||||
# Frontend/Docusaurus
|
||||
frontend/.docusaurus
|
||||
frontend/.cache-loader
|
||||
/public
|
||||
|
||||
6
.mocharc-frontend.yml
Normal file
6
.mocharc-frontend.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
reporter: mocha-env-reporter
|
||||
require:
|
||||
- '@babel/polyfill'
|
||||
- '@babel/register'
|
||||
- mocha-yaml-loader
|
||||
- frontend/mocha-ignore-pngs
|
||||
10
.nycrc-frontend.json
Normal file
10
.nycrc-frontend.json
Normal 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"]
|
||||
}
|
||||
@@ -22,7 +22,6 @@
|
||||
"scripts",
|
||||
"coverage",
|
||||
"build",
|
||||
".github",
|
||||
"**/public/"
|
||||
".github"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package.json
|
||||
package-lock.json
|
||||
/__snapshots__
|
||||
/.next
|
||||
.cache
|
||||
/.cache
|
||||
/api-docs
|
||||
/build
|
||||
public
|
||||
/public
|
||||
/coverage
|
||||
private/*.json
|
||||
/.nyc_output
|
||||
analytics.json
|
||||
frontend/.docusaurus
|
||||
frontend/categories
|
||||
supported-features.json
|
||||
service-definitions.yml
|
||||
|
||||
511
CHANGELOG.md
511
CHANGELOG.md
@@ -1,511 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
Note: this changelog is for the shields.io server. The changelog for the badge-maker NPM package is at https://github.com/badges/shields/blob/master/badge-maker/CHANGELOG.md
|
||||
|
||||
---
|
||||
|
||||
## server-2024-01-01
|
||||
|
||||
The most important changes in this release for users hosting their own instance are:
|
||||
|
||||
The shields docker image is now based on node 20:
|
||||
|
||||
- deploy on node 20 [#9799](https://github.com/badges/shields/issues/9799)
|
||||
|
||||
It is now possible to use [authentication for DockerHub](https://github.com/badges/shields/blob/master/doc/server-secrets.md#dockerhub) to allow higher API rate limit or access to private repos:
|
||||
|
||||
- call [docker] with auth [#9803](https://github.com/badges/shields/issues/9803)
|
||||
|
||||
### New Badges
|
||||
|
||||
- [Thunderstore] Add Thunderstore Badges [#9782](https://github.com/badges/shields/issues/9782)
|
||||
- Add [Raycast] Badge [#9801](https://github.com/badges/shields/issues/9801)
|
||||
- [GITEA] add new gitea service (release/languages) [#9781](https://github.com/badges/shields/issues/9781)
|
||||
- Add [NpmStatDownloads] Badge [#9783](https://github.com/badges/shields/issues/9783)
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
- improve documentation for [dynamicxml] service [#9798](https://github.com/badges/shields/issues/9798)
|
||||
- add description to interval enums [#9854](https://github.com/badges/shields/issues/9854)
|
||||
- convert 'style' param to enum [#9853](https://github.com/badges/shields/issues/9853)
|
||||
- Ensure social category badges are rendered with social style and logo; affects [gitlab keybase lemmy modrinth thunderstore twitch] gist github reddit [#9859](https://github.com/badges/shields/issues/9859)
|
||||
|
||||
### Fixes
|
||||
|
||||
- [pub] Use official version endpoint for pub-service [#9802](https://github.com/badges/shields/issues/9802)
|
||||
- cache weblate badges for longer [#9786](https://github.com/badges/shields/issues/9786)
|
||||
- [Discourse] Update schema keys to use plural form (`topic_count` -> `topics_count`) [#9778](https://github.com/badges/shields/issues/9778)
|
||||
- cache some badges for longer [#9785](https://github.com/badges/shields/issues/9785)
|
||||
- increase page size for github release badge by semver [#9818](https://github.com/badges/shields/issues/9818)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-12-04
|
||||
|
||||
- move from @renovate/pep440 to @renovatebot/pep440 [#9614](https://github.com/badges/shields/issues/9614)
|
||||
- deprecate/fix [ansible] galaxy services [#9648](https://github.com/badges/shields/issues/9648)
|
||||
- call [pepy] with auth [#9748](https://github.com/badges/shields/issues/9748)
|
||||
- add meaningful descriptions including keywords [#9715](https://github.com/badges/shields/issues/9715)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-11-01
|
||||
|
||||
- fix greasyfork 404 bug [#9632](https://github.com/badges/shields/issues/9632)
|
||||
- Hacktoberfest 2023 support - resolves #9636 [#9637](https://github.com/badges/shields/issues/9637)
|
||||
- switch to fixed OpenCollective images [#9615](https://github.com/badges/shields/issues/9615)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-10-02
|
||||
|
||||
- add python package total downloads from [pepy] badge [#9564](https://github.com/badges/shields/issues/9564)
|
||||
- deprecate [redmine] plugin rating badges [#9568](https://github.com/badges/shields/issues/9568)
|
||||
- fix [bower] version badge [#9567](https://github.com/badges/shields/issues/9567)
|
||||
- Add [PythonVersionFromToml] shield [#9516](https://github.com/badges/shields/issues/9516)
|
||||
- Add [dub] score badge service [#9549](https://github.com/badges/shields/issues/9549)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-09-04
|
||||
|
||||
- Fix [testspace] badges [#9525](https://github.com/badges/shields/issues/9525)
|
||||
- fix rSt code example [#9528](https://github.com/badges/shields/issues/9528)
|
||||
- Add dynamic TOML support via [DynamicToml] Service [#9517](https://github.com/badges/shields/issues/9517)
|
||||
- cache [pypi] downloads for longer [#9522](https://github.com/badges/shields/issues/9522)
|
||||
- [twitter] --> x [#9496](https://github.com/badges/shields/issues/9496)
|
||||
- [bundlejs] add badge for the npm package size [#9055](https://github.com/badges/shields/issues/9055)
|
||||
- Switch [OpenCollective] badges to use GraphQL and auth [#9387](https://github.com/badges/shields/issues/9387)
|
||||
- [Pulsar] Add Pulsar Badges for Stargazers & Downloads [#8767](https://github.com/badges/shields/issues/8767)
|
||||
- Add [CurseForge] badges [#9252](https://github.com/badges/shields/issues/9252)
|
||||
- deploy on node 18 [#9385](https://github.com/badges/shields/issues/9385)
|
||||
- allow calling [github] without auth [#9427](https://github.com/badges/shields/issues/9427)
|
||||
- Dependency updates
|
||||
|
||||
## 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)
|
||||
- remove disableStrictSsl param from [jenkins] [#6887](https://github.com/badges/shields/issues/6887)
|
||||
- refactor(GitHubCommitActivity): switch to v4/GraphQL API [#6959](https://github.com/badges/shields/issues/6959)
|
||||
- feat: add freecodecamp badge [#6958](https://github.com/badges/shields/issues/6958)
|
||||
- use the right version of NPM in docker build [#6941](https://github.com/badges/shields/issues/6941)
|
||||
- [TwitchExtensionVersion] New badge [#6900](https://github.com/badges/shields/issues/6900)
|
||||
- enforce strict SSL checking for [coverity] [#6886](https://github.com/badges/shields/issues/6886)
|
||||
- Update self hosting docs [#6877](https://github.com/badges/shields/issues/6877)
|
||||
- Support optionalDependencies in [GithubPackageJson] [#6749](https://github.com/badges/shields/issues/6749)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-08-01
|
||||
|
||||
- use v5 API for [AUR] badges [#6836](https://github.com/badges/shields/issues/6836)
|
||||
- [Sonar] Fix invalid fetch query to sonarqube >=6.6 [#6636](https://github.com/badges/shields/issues/6636)
|
||||
- Delegate discord logo to simple-icons, which matches the current branding [#6764](https://github.com/badges/shields/issues/6764)
|
||||
- Re-apply 'Migrate request to got (part 1)' [#6755](https://github.com/badges/shields/issues/6755)
|
||||
- Delete old deprecated badges [#6756](https://github.com/badges/shields/issues/6756)
|
||||
- Replace opn-cli with open-cli [#6747](https://github.com/badges/shields/issues/6747)
|
||||
- Verify that Node 14 is installed in development [#6748](https://github.com/badges/shields/issues/6748)
|
||||
- Migrate from CommonJS to ESM [#6651](https://github.com/badges/shields/issues/6651)
|
||||
- Add Wikiapiary Extension Badge [WikiapiaryInstalls] [#6678](https://github.com/badges/shields/issues/6678)
|
||||
- deprecate [beerpay] [#6708](https://github.com/badges/shields/issues/6708)
|
||||
- deprecate [microbadger] [#6709](https://github.com/badges/shields/issues/6709)
|
||||
- [npmsioscore] Support npm score [#6630](https://github.com/badges/shields/issues/6630)
|
||||
- Add [Weblate] badges [#6677](https://github.com/badges/shields/issues/6677)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-07-01
|
||||
|
||||
- improve [MavenCentral], [MavenMetadata], and [GradlePluginPortal] [#6628](https://github.com/badges/shields/issues/6628)
|
||||
- fix: fix regex to match [codecov]'s flags [#6655](https://github.com/badges/shields/issues/6655)
|
||||
- fix usage style [#6638](https://github.com/badges/shields/issues/6638)
|
||||
- update simple-icons to v5 with by-name lookup backwards compatibility [#6591](https://github.com/badges/shields/issues/6591)
|
||||
- [GradlePluginPortal] add gradle plugin portal [#6449](https://github.com/badges/shields/issues/6449)
|
||||
- upgrade some vulnerable packages [#6569](https://github.com/badges/shields/issues/6569)
|
||||
- increase max-age for download and social badges [#6567](https://github.com/badges/shields/issues/6567)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-06-01
|
||||
|
||||
- Changed creating badges to open a new Window/Tab [#6536](https://github.com/badges/shields/issues/6536)
|
||||
- Make for-the-badge letter spacing more predictable, and rewrite layout logic [#5754](https://github.com/badges/shields/issues/5754)
|
||||
- deprecate DockerBuild service [#6529](https://github.com/badges/shields/issues/6529)
|
||||
- Remove rate limiting functionality [#6513](https://github.com/badges/shields/issues/6513)
|
||||
- [GitHub] Move to 'funding' category [#5846](https://github.com/badges/shields/issues/5846)
|
||||
- Add GitHub discussions total badge [GithubTotalDiscussions] [#6472](https://github.com/badges/shields/issues/6472)
|
||||
- Add optional query parameter (include_prereleases) to [GemVersion] [#6451](https://github.com/badges/shields/issues/6451)
|
||||
- Add [PingPong] Service [#6327](https://github.com/badges/shields/issues/6327)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-05-01
|
||||
|
||||
- Add setting which allows setting a timeout on HTTP requests
|
||||
This is configured with the new `REQUEST_TIMEOUT_SECONDS` setting. If a request takes longer
|
||||
than this number of seconds a `408 Request Timeout` response will be returned.
|
||||
- Deprecate [Bintray] service [#6423](https://github.com/badges/shields/issues/6423)
|
||||
- Support git hash in [nexus] SNAPSHOT version [#6369](https://github.com/badges/shields/issues/6369)
|
||||
- Replace 4183C4 with blue [#6366](https://github.com/badges/shields/issues/6366)
|
||||
- [Youtube] Added channel view count and subscriber count badges [#6333](https://github.com/badges/shields/issues/6333)
|
||||
- Fix Netlify badge by adding new color schema [#6340](https://github.com/badges/shields/issues/6340)
|
||||
- [REUSE] Add service badges [#6330](https://github.com/badges/shields/issues/6330)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-04-01
|
||||
|
||||
- Use NPM packages to provide fonts instead of Google Fonts [#6274](https://github.com/badges/shields/issues/6274)
|
||||
- Prevent duplication of parameters in badge examples [#6272](https://github.com/badges/shields/issues/6272)
|
||||
- Add docs for all types of releases [#6210](https://github.com/badges/shields/issues/6210)
|
||||
- refresh self-hosting docs [#6273](https://github.com/badges/shields/issues/6273)
|
||||
- allow missing 'goal' key in [liberapay] badges [#6258](https://github.com/badges/shields/issues/6258)
|
||||
- use got to push influx metrics [#6257](https://github.com/badges/shields/issues/6257)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-03-01
|
||||
|
||||
- ensure redirect target path is correctly encoded [#6229](https://github.com/badges/shields/issues/6229)
|
||||
- [SecurityHeaders] Added a possibility for no follow redirects [#6212](https://github.com/badges/shields/issues/6212)
|
||||
- catch URL parse error in [endpoint] badge [#6214](https://github.com/badges/shields/issues/6214)
|
||||
- [Homebrew] Add homebrew downloads badge [#6209](https://github.com/badges/shields/issues/6209)
|
||||
- update [pkgreview] url [#6189](https://github.com/badges/shields/issues/6189)
|
||||
- Make [Twitch] a social badge [#6183](https://github.com/badges/shields/issues/6183)
|
||||
- update [flathub] error handling [#6185](https://github.com/badges/shields/issues/6185)
|
||||
- Add [Testspace] badges [#6162](https://github.com/badges/shields/issues/6162)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-02-01
|
||||
|
||||
- Docs.rs badge (#6098)
|
||||
- Fix feedz service in case the package page gets paginated (#6101)
|
||||
- [Bitbucket] Server Adding Auth Tokens and Resolving Pull Request api … (#6076)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-01-18
|
||||
|
||||
- Gotta start somewhere
|
||||
@@ -101,7 +101,7 @@ There are three places to get help:
|
||||
used by developers or which are widely used by developers.
|
||||
- The left-hand side of a badge should not advertise. It should be a lowercase _noun_
|
||||
succinctly describing the meaning of the right-hand side.
|
||||
- Except for badges using the `social` style, logos and links should be _turned off by
|
||||
- Except for badges using the `social` style, logos should be _turned off by
|
||||
default_.
|
||||
- Badges should not obtain data from undocumented or reverse-engineered API endpoints.
|
||||
- Badges should not obtain data by scraping web pages - these are likely to break frequently.
|
||||
@@ -131,22 +131,18 @@ Prettier before a commit by default.
|
||||
|
||||
### Tests
|
||||
|
||||
When adding or changing a service [please write tests][service-tests], and ensure the [title of your Pull Requests follows the required conventions](#running-service-tests-in-pull-requests) to ensure your tests are executed.
|
||||
When adding or changing a service [please write tests][service-tests].
|
||||
|
||||
When opening a pull request, include your service name in brackets in the pull
|
||||
request title. That way, those service tests will run in CI.
|
||||
|
||||
e.g. **[Travis] Fix timeout issues**
|
||||
|
||||
When changing other code, please add unit tests.
|
||||
|
||||
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
|
||||
|
||||
@@ -157,35 +153,3 @@ There is a [High-level code walkthrough](doc/code-walkthrough.md) describing the
|
||||
### Logos
|
||||
|
||||
We have [documentation for logo usage](doc/logos.md) which includes [contribution guidance](doc/logos.md#contributing-logos)
|
||||
|
||||
## Pull Requests
|
||||
|
||||
All code changes are incorporated via pull requests, and pull requests are always squashed into a single commit on merging. Therefore there's no requirement to squash commits within your PR, but feel free to squash or restructure the commits on your PR branch if you think it will be helpful. PRs with well structured commits are always easier to review!
|
||||
|
||||
Because all changes are pulled into the main branch via squash merges from PRs, we do **not** support overwriting any aspects of the git history once it hits our main branch. Notably this means we do not support amending commit messages, nor adjusting commit author information once merged.
|
||||
|
||||
Accordingly, it is the responsibility of contributors to review this type of information and adjust as needed before marking PRs as ready for review and merging.
|
||||
|
||||
You can review and modify your local [git configuration][git-config] via `git config`, and also find more information about amending your commit messages [here][amending-commits].
|
||||
|
||||
[git-config]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration
|
||||
[amending-commits]: https://docs.github.com/en/github/committing-changes-to-your-project/changing-a-commit-message#rewriting-the-most-recent-commit-message
|
||||
|
||||
### Running service tests in pull requests
|
||||
|
||||
The affected service names must be included in square brackets in the pull request title so that the CI engine will run those service tests. When a pull request affects multiple services, they should be separated with spaces. The test runner is case-insensitive, so they should be capitalized for readability.
|
||||
|
||||
For example:
|
||||
|
||||
- **[Travis] Fix timeout issues**
|
||||
- **[Travis Sonar] Support user token authentication**
|
||||
- **Add tests for [CRAN] and [CPAN]**
|
||||
|
||||
Note that many services are part of a "family" of related services. Depending on the changes in your PR you may need to run the tests for just a single service, or for _all_ the services within a family.
|
||||
|
||||
For example, a PR title of **[GitHubForks] Foo** will only run the service tests specifically for the GitHub Forks badge, whereas a title of **[GitHub] Foo** will run the service tests for all of the GitHub badges.
|
||||
|
||||
In the rare case when it's necessary to see the output of a full service-test
|
||||
run in a PR (all 2,000+ tests), include `[*****]` in the title. Unless all the tests pass, the build
|
||||
will fail, so likely it will be necessary to remove it and re-run the tests
|
||||
before merging.
|
||||
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS Builder
|
||||
FROM node:12-alpine
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
RUN mkdir /usr/src/app/private
|
||||
@@ -8,30 +8,17 @@ 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"
|
||||
# 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
|
||||
|
||||
COPY . /usr/src/app
|
||||
RUN npm run build
|
||||
RUN npm prune --omit=dev
|
||||
RUN npm prune --production
|
||||
RUN npm cache clean --force
|
||||
|
||||
# Use multi-stage build to reduce size
|
||||
FROM node:20-alpine
|
||||
|
||||
ARG version=dev
|
||||
ENV DOCKER_SHIELDS_VERSION=$version
|
||||
LABEL version=$version
|
||||
LABEL fly.version=$version
|
||||
|
||||
# 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
|
||||
|
||||
CMD node server
|
||||
|
||||
EXPOSE 80 443
|
||||
EXPOSE 80
|
||||
|
||||
70
Makefile
Normal file
70
Makefile
Normal file
@@ -0,0 +1,70 @@
|
||||
SHELL:=/bin/bash
|
||||
|
||||
SERVER_TMP=${TMPDIR}shields-server-deploy
|
||||
FRONTEND_TMP=${TMPDIR}shields-frontend-deploy
|
||||
|
||||
# This branch is reserved for the deploy process and should not be used for
|
||||
# development. The deploy script will clobber it. To avoid accidentally
|
||||
# pushing secrets to GitHub, this branch is configured to reject pushes.
|
||||
WORKING_BRANCH=server-deploy-working-branch
|
||||
|
||||
all: test
|
||||
|
||||
deploy: deploy-s0 deploy-s1 deploy-s2 clean-server-deploy deploy-gh-pages deploy-gh-pages-clean
|
||||
|
||||
deploy-s0: prepare-server-deploy push-s0
|
||||
deploy-s1: prepare-server-deploy push-s1
|
||||
deploy-s2: prepare-server-deploy push-s2
|
||||
|
||||
prepare-server-deploy:
|
||||
# Ship a copy of the front end to each server for debugging.
|
||||
# https://github.com/badges/shields/issues/1220
|
||||
INCLUDE_DEV_PAGES=false \
|
||||
npm run build
|
||||
rm -rf ${SERVER_TMP}
|
||||
git worktree prune
|
||||
git worktree add -B ${WORKING_BRANCH} ${SERVER_TMP}
|
||||
cp -r public ${SERVER_TMP}
|
||||
git -C ${SERVER_TMP} add -f public/
|
||||
git -C ${SERVER_TMP} commit --no-verify -m '[DEPLOY] Add frontend for debugging'
|
||||
cp config/local-shields-io-production.yml ${SERVER_TMP}/config/
|
||||
git -C ${SERVER_TMP} add -f config/local-shields-io-production.yml
|
||||
git -C ${SERVER_TMP} commit --no-verify -m '[DEPLOY] MUST NOT BE ON GITHUB'
|
||||
|
||||
clean-server-deploy:
|
||||
rm -rf ${SERVER_TMP}
|
||||
git worktree prune
|
||||
|
||||
push-s0:
|
||||
git push -f s0 ${WORKING_BRANCH}:master
|
||||
|
||||
push-s1:
|
||||
git push -f s1 ${WORKING_BRANCH}:master
|
||||
|
||||
push-s2:
|
||||
git push -f s2 ${WORKING_BRANCH}:master
|
||||
|
||||
deploy-gh-pages:
|
||||
rm -rf ${FRONTEND_TMP}
|
||||
git worktree prune
|
||||
GATSBY_BASE_URL=https://img.shields.io \
|
||||
INCLUDE_DEV_PAGES=false \
|
||||
npm run build
|
||||
git worktree add -B gh-pages ${FRONTEND_TMP}
|
||||
git -C ${FRONTEND_TMP} ls-files | xargs git -C ${FRONTEND_TMP} rm
|
||||
git -C ${FRONTEND_TMP} commit --no-verify -m '[DEPLOY] Completely clean the index'
|
||||
cp -r public/* ${FRONTEND_TMP}
|
||||
echo shields.io > ${FRONTEND_TMP}/CNAME
|
||||
touch ${FRONTEND_TMP}/.nojekyll
|
||||
git -C ${FRONTEND_TMP} add .
|
||||
git -C ${FRONTEND_TMP} commit --no-verify -m '[DEPLOY] Add built site'
|
||||
git push -f origin gh-pages
|
||||
|
||||
deploy-gh-pages-clean:
|
||||
rm -rf ${FRONTEND_TMP}
|
||||
git worktree prune
|
||||
|
||||
test:
|
||||
npm test
|
||||
|
||||
.PHONY: all deploy prepare-server-deploy clean-server-deploy deploy-s0 deploy-s1 deploy-s2 push-s0 push-s1 push-s2 deploy-gh-pages deploy-gh-pages-clean deploy-heroku setup redis test
|
||||
69
README.md
69
README.md
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/badges/shields/master/readme-logo.svg?sanitize=true"
|
||||
<img src="https://raw.githubusercontent.com/badges/shields/master/frontend/images/logo.svg?sanitize=true"
|
||||
height="130">
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -19,9 +19,18 @@
|
||||
<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://github.com/badges/shields/compare/gh-pages...master">
|
||||
<img src="https://img.shields.io/github/commits-since/badges/shields/gh-pages?label=commits%20to%20be%20deployed"
|
||||
alt="commits to be deployed"></a>
|
||||
<a href="https://discord.gg/HjJCwm5">
|
||||
<img src="https://img.shields.io/discord/308323056592486420?logo=discord"
|
||||
alt="chat on Discord"></a>
|
||||
<a href="https://twitter.com/intent/follow?screen_name=shields_io">
|
||||
<img src="https://img.shields.io/twitter/follow/shields_io?style=social&logo=twitter"
|
||||
alt="follow on Twitter"></a>
|
||||
</p>
|
||||
|
||||
This is home to [Shields.io][shields.io], a service for concise, consistent,
|
||||
@@ -29,13 +38,7 @@ and legible badges in SVG and raster format, which can easily be included in
|
||||
GitHub readmes or any other web page. The service supports dozens of
|
||||
continuous integration services, package registries, distributions, app
|
||||
stores, social networks, code coverage services, and code analysis services.
|
||||
Every month it serves over 870 million images and is used by some of the
|
||||
world's most popular open-source projects, [VS Code][vscode], [Vue.js][vue]
|
||||
and [Bootstrap][bootstrap] to name a few.
|
||||
|
||||
[vscode]: https://github.com/Microsoft/vscode
|
||||
[vue]: https://github.com/vuejs/vue
|
||||
[bootstrap]: https://github.com/twbs/bootstrap
|
||||
Every month it serves over 470 million images.
|
||||
|
||||
This repo hosts:
|
||||
|
||||
@@ -67,7 +70,7 @@ This repo hosts:
|
||||
[Make your own badges!][custom badges]
|
||||
(Quick example: `https://img.shields.io/badge/left-right-f39f37`)
|
||||
|
||||
[custom badges]: https://img.shields.io/badges/static-badge
|
||||
[custom badges]: http://shields.io/#your-badge
|
||||
|
||||
### Quickstart
|
||||
|
||||
@@ -88,18 +91,19 @@ maybe you'd like to open a pull request to address one of them.
|
||||
You can read a [tutorial on how to add a badge][tutorial].
|
||||
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aopen+is%3Aissue+label%3Ahacktoberfest)
|
||||
|
||||
If you intend on reporting or contributing a fix related to security vulnerabilities, please first refer to our [security policy][security].
|
||||
Let's see if we can beat last year!
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aopen+is%3Aissue+label%3Ahacktoberfest)
|
||||
|
||||
[service-tests]: https://github.com/badges/shields/blob/master/doc/service-tests.md
|
||||
[tutorial]: https://github.com/badges/shields/blob/master/doc/TUTORIAL.md
|
||||
[contributing]: https://github.com/badges/shields/blob/master/CONTRIBUTING.md
|
||||
[security]: https://github.com/badges/shields/blob/master/SECURITY.md
|
||||
[tutorial]: doc/TUTORIAL.md
|
||||
[contributing]: CONTRIBUTING.md
|
||||
|
||||
## Development
|
||||
|
||||
1. Install Node 20 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 20 and 21.
|
||||
1. Install Node 12 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 12 and 14.
|
||||
2. Clone this repository.
|
||||
3. Run `npm ci` to install the dependencies.
|
||||
4. Run `npm start` to start the badge server and the frontend dev server.
|
||||
@@ -107,7 +111,7 @@ If you intend on reporting or contributing a fix related to security vulnerabili
|
||||
|
||||
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.
|
||||
|
||||
@@ -139,9 +143,9 @@ Daily tests, including a full run of the service tests and overall code coverage
|
||||
[gitpod]: https://www.gitpod.io/
|
||||
[snapshot tests]: https://glebbahmutov.com/blog/snapshot-testing/
|
||||
[prometheus]: https://prometheus.io/
|
||||
[prometheus configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#prometheus
|
||||
[prometheus configuration]: doc/self-hosting.md#prometheus
|
||||
[sentry]: https://sentry.io/
|
||||
[sentry configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#sentry
|
||||
[sentry configuration]: doc/self-hosting.md#sentry
|
||||
[daily-tests]: https://github.com/badges/daily-tests
|
||||
[nodemon]: https://nodemon.io/
|
||||
[nodemon debug]: https://github.com/Microsoft/vscode-recipes/tree/master/nodemon
|
||||
@@ -151,22 +155,7 @@ Daily tests, including a full run of the service tests and overall code coverage
|
||||
|
||||
There is documentation about [hosting your own server][self-hosting].
|
||||
|
||||
[self-hosting]: https://github.com/badges/shields/blob/master/doc/self-hosting.md
|
||||
|
||||
## Related projects
|
||||
|
||||
[](https://awesome.re)
|
||||
|
||||
Status badges are used widely across open-source and private software projects.
|
||||
Academics have studied the "signal" badges provide about software project
|
||||
quality. There are many existing libraries for rendering these badges, and
|
||||
alternatives to the hosted Shields badge service. [awesome-badges][] is a
|
||||
curated collection of such resources.
|
||||
[Contributions][contributing to awesome-badges] may be considered there.
|
||||
(The presence of a project in that collection should not be interpreted as an endorsement nor promotion from the Shields project)
|
||||
|
||||
[awesome-badges]: https://github.com/badges/awesome-badges
|
||||
[contributing to awesome-badges]: https://github.com/badges/awesome-badges/blob/main/CONTRIBUTING.md
|
||||
[self-hosting]: doc/self-hosting.md
|
||||
|
||||
## History
|
||||
|
||||
@@ -191,8 +180,8 @@ You can read more about [the project's inception][thread],
|
||||
[olivierlacan]: https://github.com/olivierlacan
|
||||
[espadrine]: https://github.com/espadrine
|
||||
[old-gh-badges]: https://github.com/badges/gh-badges
|
||||
[motivation]: https://github.com/badges/shields/blob/master/spec/motivation.md
|
||||
[spec]: https://github.com/badges/shields/blob/master/spec/SPECIFICATION.md
|
||||
[motivation]: spec/motivation.md
|
||||
[spec]: spec/SPECIFICATION.md
|
||||
[thread]: https://github.com/h5bp/lazyweb-requests/issues/150
|
||||
|
||||
## Project leaders
|
||||
@@ -219,6 +208,14 @@ Alumni:
|
||||
- [espadrine](https://github.com/espadrine)
|
||||
- [olivierlacan](https://github.com/olivierlacan)
|
||||
|
||||
## Related projects
|
||||
|
||||
- [poser PHP library][poser]
|
||||
- [pybadges python library][pybadges]
|
||||
|
||||
[poser]: https://github.com/badges/poser
|
||||
[pybadges]: https://github.com/google/pybadges
|
||||
|
||||
## License
|
||||
|
||||
All assets and code are under the [CC0 LICENSE](LICENSE) and in the public
|
||||
|
||||
@@ -7,10 +7,10 @@ Please follow this guidance when reporting security issues affecting:
|
||||
- [Shields.io](https://shields.io)
|
||||
- [Raster.shields.io](https://raster.shields.io)
|
||||
- Self-hosted Shields instances
|
||||
- The [squint](https://github.com/badges/squint) raster proxy
|
||||
- The [svg-to-image-proxy](https://www.npmjs.com/package/svg-to-image-proxy) NPM package
|
||||
- The [badge-maker](https://www.npmjs.com/package/badge-maker) NPM package
|
||||
|
||||
The [gh-badges](https://www.npmjs.com/package/gh-badges) and [svg-to-image-proxy](https://www.npmjs.com/package/svg-to-image-proxy) NPM packages are now deprecated and will no longer receive fixes for bugs or security issues.
|
||||
The [gh-badges](https://www.npmjs.com/package/gh-badges) NPM package is now deprecated and will no longer receive fixes for bugs or security issues.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
@@ -402,126 +402,6 @@ exports['The badge generator "flat" template badge generation should match snaps
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "flat" template badge generation should match snapshots: black text when the label color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="20"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||
<stop offset="1" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff" /></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="45" height="20" fill="#f3f3f3" />
|
||||
<rect x="45" width="45" height="20" fill="#000" />
|
||||
<rect width="90" height="20" fill="url(#s)" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="235"
|
||||
y="150"
|
||||
fill="#ccc"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
cactus
|
||||
</text>
|
||||
<text x="235" y="140" transform="scale(.1)" fill="#333" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="665"
|
||||
y="150"
|
||||
fill="#010101"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
grown
|
||||
</text>
|
||||
<text x="665" y="140" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "flat" template badge generation should match snapshots: black text when the message color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="20"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||
<stop offset="1" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff" /></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="45" height="20" fill="#000" />
|
||||
<rect x="45" width="45" height="20" fill="#e2ffe1" />
|
||||
<rect width="90" height="20" fill="url(#s)" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="235"
|
||||
y="150"
|
||||
fill="#010101"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
cactus
|
||||
</text>
|
||||
<text x="235" y="140" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="665"
|
||||
y="150"
|
||||
fill="#ccc"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
grown
|
||||
</text>
|
||||
<text x="665" y="140" transform="scale(.1)" fill="#333" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, no logo 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -729,70 +609,6 @@ exports['The badge generator "flat-square" template badge generation should matc
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "flat-square" template badge generation should match snapshots: black text when the label color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="20"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="45" height="20" fill="#f3f3f3" />
|
||||
<rect x="45" width="45" height="20" fill="#000" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text x="235" y="140" transform="scale(.1)" fill="#333" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text x="665" y="140" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "flat-square" template badge generation should match snapshots: black text when the message color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="20"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="45" height="20" fill="#000" />
|
||||
<rect x="45" width="45" height="20" fill="#e2ffe1" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text x="235" y="140" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text x="665" y="140" transform="scale(.1)" fill="#333" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, no logo 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -1149,143 +965,19 @@ exports['The badge generator "plastic" template badge generation should match sn
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "plastic" template badge generation should match snapshots: black text when the label color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="18"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7" />
|
||||
<stop offset=".1" stop-color="#aaa" stop-opacity=".1" />
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3" />
|
||||
<stop offset="1" stop-color="#000" stop-opacity=".5" />
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="90" height="18" rx="4" fill="#fff" /></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="45" height="18" fill="#f3f3f3" />
|
||||
<rect x="45" width="45" height="18" fill="#000" />
|
||||
<rect width="90" height="18" fill="url(#s)" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="235"
|
||||
y="140"
|
||||
fill="#ccc"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
cactus
|
||||
</text>
|
||||
<text x="235" y="130" transform="scale(.1)" fill="#333" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="665"
|
||||
y="140"
|
||||
fill="#010101"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
grown
|
||||
</text>
|
||||
<text x="665" y="130" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "plastic" template badge generation should match snapshots: black text when the message color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="18"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7" />
|
||||
<stop offset=".1" stop-color="#aaa" stop-opacity=".1" />
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3" />
|
||||
<stop offset="1" stop-color="#000" stop-opacity=".5" />
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="90" height="18" rx="4" fill="#fff" /></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="45" height="18" fill="#000" />
|
||||
<rect x="45" width="45" height="18" fill="#e2ffe1" />
|
||||
<rect width="90" height="18" fill="url(#s)" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="235"
|
||||
y="140"
|
||||
fill="#010101"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
cactus
|
||||
</text>
|
||||
<text x="235" y="130" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="665"
|
||||
y="140"
|
||||
fill="#ccc"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
grown
|
||||
</text>
|
||||
<text x="665" y="130" transform="scale(.1)" fill="#333" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, no logo 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="146.75"
|
||||
width="145.5"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="72.5" height="28" fill="#0f0" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#b3e" />
|
||||
<rect width="73" height="28" fill="#0f0" />
|
||||
<rect x="73" width="72.5" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1294,16 +986,16 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="100"
|
||||
>
|
||||
<text transform="scale(.1)" x="362.5" y="175" textLength="485" fill="#fff">
|
||||
<text fill="#fff" x="365" y="175" transform="scale(.1)" textLength="490">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
x="1092.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1316,15 +1008,15 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="163.75"
|
||||
width="162.5"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="89.5" height="28" fill="#0f0" />
|
||||
<rect x="89.5" width="74.25" height="28" fill="#b3e" />
|
||||
<rect width="90" height="28" fill="#0f0" />
|
||||
<rect x="90" width="72.5" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1340,16 +1032,16 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
height="14"
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
|
||||
/>
|
||||
<text transform="scale(.1)" x="532.5" y="175" textLength="485" fill="#fff">
|
||||
<text fill="#fff" x="535" y="175" transform="scale(.1)" textLength="490">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="1266.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
x="1262.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1362,14 +1054,15 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="74.25"
|
||||
width="72.5"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="GROWN"
|
||||
>
|
||||
<title>GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="74.25" height="28" fill="#b3e" />
|
||||
<rect width="0" height="28" fill="#b3e" />
|
||||
<rect x="0" width="72.5" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1379,12 +1072,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
font-size="100"
|
||||
>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="371.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
x="362.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1397,14 +1090,15 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="94.25"
|
||||
width="90.5"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="GROWN"
|
||||
>
|
||||
<title>GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="94.25" height="28" fill="#b3e" />
|
||||
<rect width="0" height="28" fill="#555" />
|
||||
<rect x="0" width="90.5" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1421,12 +1115,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
|
||||
/>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="571.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
x="542.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1439,7 +1133,7 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="106.25"
|
||||
width="104.5"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="GROWN"
|
||||
@@ -1447,7 +1141,7 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<title>GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="32" height="28" fill="#0f0" />
|
||||
<rect x="32" width="74.25" height="28" fill="#b3e" />
|
||||
<rect x="32" width="72.5" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1464,12 +1158,19 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
|
||||
/>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="691.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
x="230"
|
||||
y="175"
|
||||
transform="scale(.1)"
|
||||
textLength="-60"
|
||||
></text>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="682.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1482,12 +1183,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="146.75"
|
||||
width="145.5"
|
||||
height="28"
|
||||
>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="72.5" height="28" fill="#0f0" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#b3e" />
|
||||
<rect width="73" height="28" fill="#0f0" />
|
||||
<rect x="73" width="72.5" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1497,26 +1198,20 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
font-size="100"
|
||||
>
|
||||
<a target="_blank" xlink:href="https://shields.io/">
|
||||
<rect width="72.5" height="28" fill="rgba(0,0,0,0)" />
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="362.5"
|
||||
y="175"
|
||||
textLength="485"
|
||||
fill="#fff"
|
||||
>
|
||||
<rect width="73" height="28" fill="rgba(0,0,0,0)" />
|
||||
<text fill="#fff" x="365" y="175" transform="scale(.1)" textLength="490">
|
||||
CACTUS
|
||||
</text>
|
||||
</a>
|
||||
<a target="_blank" xlink:href="https://www.google.co.uk/">
|
||||
<rect width="74.25" height="28" x="72.5" fill="rgba(0,0,0,0)" />
|
||||
<rect width="72.5" height="28" x="73" fill="rgba(0,0,0,0)" />
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
x="1092.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1526,84 +1221,6 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "for-the-badge" template badge generation should match snapshots: black text when the label color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="146.75"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="72.5" height="28" fill="#f3f3f3" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#000" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="100"
|
||||
>
|
||||
<text transform="scale(.1)" x="362.5" y="175" textLength="485" fill="#333">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "for-the-badge" template badge generation should match snapshots: black text when the message color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="146.75"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="72.5" height="28" fill="#000" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#e2ffe1" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="100"
|
||||
>
|
||||
<text transform="scale(.1)" x="362.5" y="175" textLength="485" fill="#fff">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#333"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator "social" template badge generation should match snapshots: message/label, no logo 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -2229,3 +1846,102 @@ exports['The badge generator badges with logos should always produce the same ba
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator text colors should use black text when the label color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90"
|
||||
height="20"
|
||||
role="img"
|
||||
aria-label="cactus: grown"
|
||||
>
|
||||
<title>cactus: grown</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||
<stop offset="1" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff" /></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="45" height="20" fill="#f3f3f3" />
|
||||
<rect x="45" width="45" height="20" fill="#000" />
|
||||
<rect width="90" height="20" fill="url(#s)" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="110"
|
||||
>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="235"
|
||||
y="150"
|
||||
fill="#ccc"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
cactus
|
||||
</text>
|
||||
<text x="235" y="140" transform="scale(.1)" fill="#333" textLength="350">
|
||||
cactus
|
||||
</text>
|
||||
<text
|
||||
aria-hidden="true"
|
||||
x="665"
|
||||
y="150"
|
||||
fill="#010101"
|
||||
fill-opacity=".3"
|
||||
transform="scale(.1)"
|
||||
textLength="350"
|
||||
>
|
||||
grown
|
||||
</text>
|
||||
<text x="665" y="140" transform="scale(.1)" fill="#fff" textLength="350">
|
||||
grown
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
exports['The badge generator text colors should use black text when the message color is light 1'] = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="145.5"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="73" height="28" fill="#000" />
|
||||
<rect x="73" width="72.5" height="28" fill="#e2ffe1" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
text-anchor="middle"
|
||||
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="100"
|
||||
>
|
||||
<text fill="#fff" x="365" y="175" transform="scale(.1)" textLength="490">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
fill="#333"
|
||||
x="1092.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
`
|
||||
|
||||
16
app.json
16
app.json
@@ -4,7 +4,7 @@
|
||||
"keywords": ["badge", "github", "svg", "status"],
|
||||
"website": "https://shields.io/",
|
||||
"repository": "https://github.com/badges/shields",
|
||||
"logo": "https://shields.io/favicon.png",
|
||||
"logo": "http://shields.io/favicon.png",
|
||||
"env": {
|
||||
"CYPRESS_INSTALL_BINARY": {
|
||||
"description": "Disable the cypress binary installation",
|
||||
@@ -31,20 +31,6 @@
|
||||
"TWITCH_CLIENT_SECRET": {
|
||||
"description": "Configure the client secret to be used for the Twitch service.",
|
||||
"required": false
|
||||
},
|
||||
"WEBLATE_API_KEY": {
|
||||
"description": "Configure the API key to be used for the Weblate service.",
|
||||
"required": false
|
||||
},
|
||||
"METRICS_INFLUX_ENABLED": {
|
||||
"description": "Disable influx metrics",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"REQUIRE_CLOUDFLARE": {
|
||||
"description": "Allow direct traffic",
|
||||
"value": "false",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"formation": {
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 4.0.0 [WIP]
|
||||
|
||||
- Drop compatibility with Node < 16
|
||||
|
||||
## 3.3.1
|
||||
|
||||
- Improve font measuring in for-the-badge and social styles
|
||||
- Make for-the-badge letter spacing more predictable
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Readability improvements: a dark font color is automatically used when the badge's background is too light. For example: 
|
||||
@@ -275,7 +266,7 @@ badge.loadFont('/path/to/Verdana.ttf', err => {
|
||||
{ text: ['build', 'passed'], colorscheme: 'green', template: 'flat' },
|
||||
(svg, err) => {
|
||||
// svg is a string containing your badge
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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')
|
||||
|
||||
@@ -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()})`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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)'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
const { normalizeColor, toSvgColor } = require('./color')
|
||||
const badgeRenderers = require('./badge-renderers')
|
||||
const { stripXmlWhitespace } = require('./xml')
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
@@ -20,6 +23,10 @@ module.exports = function makeBadge({
|
||||
logoWidth,
|
||||
links = ['', ''],
|
||||
}) {
|
||||
if (!logo && (logoPosition !== undefined || logoWidth !== undefined)) {
|
||||
throw Error('`logoPosition` and `logoWidth` require `logo`')
|
||||
}
|
||||
|
||||
// String coercion and whitespace removal.
|
||||
label = `${label}`.trim()
|
||||
message = `${message}`.trim()
|
||||
@@ -58,6 +65,6 @@ module.exports = function makeBadge({
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
@@ -211,33 +210,11 @@ describe('The badge generator', function () {
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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 +224,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 +236,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 +246,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 +257,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 +269,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',
|
||||
@@ -303,33 +280,11 @@ describe('The badge generator', function () {
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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 +294,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 +306,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 +316,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 +327,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 +339,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',
|
||||
@@ -395,28 +350,6 @@ describe('The badge generator', function () {
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"for-the-badge" template badge generation', function () {
|
||||
@@ -428,7 +361,7 @@ describe('The badge generator', function () {
|
||||
message: 1999,
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
}),
|
||||
})
|
||||
)
|
||||
.to.include('1998')
|
||||
.and.to.include('1999')
|
||||
@@ -441,14 +374,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 +391,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 +403,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 +413,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 +424,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 +436,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',
|
||||
@@ -514,28 +447,6 @@ describe('The badge generator', function () {
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the label color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: black text when the message color is light', async function () {
|
||||
await expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"social" template badge generation', function () {
|
||||
@@ -546,7 +457,7 @@ describe('The badge generator', function () {
|
||||
message: 'some-value',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
}),
|
||||
})
|
||||
)
|
||||
.to.include('Some-key')
|
||||
.and.to.include('some-value')
|
||||
@@ -560,14 +471,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 +488,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 +500,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 +510,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 +521,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 +533,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 +547,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',
|
||||
@@ -645,4 +556,28 @@ describe('The badge generator', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('text colors', function () {
|
||||
it('should use black text when the label color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should use black text when the message color is light', function () {
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
function escapeXml(s) {
|
||||
if (typeof s === 'number') {
|
||||
return s
|
||||
} else if (s === undefined || typeof s !== 'string') {
|
||||
return undefined
|
||||
} else {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of an XML element
|
||||
*/
|
||||
class XmlElement {
|
||||
/**
|
||||
* Xml Element Constructor
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.name
|
||||
* Name of the XML tag
|
||||
* @param {Array.<string|module:badge-maker/lib/xml~XmlElement>} [attrs.content=[]]
|
||||
* Array of objects to render inside the tag. content may contain a mix of
|
||||
* string and XmlElement objects. If content is `[]` or omitted the
|
||||
* element will be rendered as a self-closing element.
|
||||
* @param {object} [attrs.attrs={}]
|
||||
* Object representing the tag's attributes as name/value pairs
|
||||
*/
|
||||
constructor({ name, content = [], attrs = {} }) {
|
||||
this.name = name
|
||||
this.content = content
|
||||
this.attrs = attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the XML element to a string, applying appropriate escaping
|
||||
*
|
||||
* @returns {string} String representation of the XML element
|
||||
*/
|
||||
render() {
|
||||
const attrsStr = Object.entries(this.attrs)
|
||||
.map(([k, v]) => ` ${k}="${escapeXml(v)}"`)
|
||||
.join('')
|
||||
if (this.content.length > 0) {
|
||||
const content = this.content
|
||||
.map(function (el) {
|
||||
if (typeof el.render === 'function') {
|
||||
return el.render()
|
||||
} else {
|
||||
return escapeXml(el)
|
||||
}
|
||||
})
|
||||
.join(' ')
|
||||
return stripXmlWhitespace(
|
||||
`<${this.name}${attrsStr}>${content}</${this.name}>`,
|
||||
)
|
||||
}
|
||||
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience class. Sometimes it is useful to return an object that behaves
|
||||
* like an XmlElement but renders multiple XML tags (not wrapped in a <g>).
|
||||
*/
|
||||
class ElementList {
|
||||
constructor({ content = [] }) {
|
||||
this.content = content
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.content.reduce(
|
||||
(acc, el) =>
|
||||
typeof el.render === 'function'
|
||||
? acc + el.render()
|
||||
: acc + escapeXml(el),
|
||||
'',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { escapeXml, stripXmlWhitespace, XmlElement, ElementList }
|
||||
@@ -1,50 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const { XmlElement } = require('./xml')
|
||||
|
||||
function testRender(params) {
|
||||
return new XmlElement(params).render()
|
||||
}
|
||||
|
||||
describe('XmlElement class', function () {
|
||||
test(testRender, () => {
|
||||
given({ name: 'tag' }).expect('<tag/>')
|
||||
|
||||
given({ name: 'tag', content: ['text'] }).expect('<tag>text</tag>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
content: ['not xml>>>', 'text', new XmlElement({ name: 'xml' })],
|
||||
}).expect('<tag>not xml>>> text <xml/></tag>')
|
||||
|
||||
given({
|
||||
name: 'nested1',
|
||||
content: [
|
||||
new XmlElement({
|
||||
name: 'nested2',
|
||||
content: [new XmlElement({ name: 'nested3' })],
|
||||
}),
|
||||
],
|
||||
}).expect('<nested1><nested2><nested3/></nested2></nested1>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
attrs: {
|
||||
int: 47,
|
||||
text: 'text',
|
||||
escape: '<escape me>',
|
||||
},
|
||||
}).expect('<tag int="47" text="text" escape="<escape me>"/>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
content: ['text'],
|
||||
attrs: {
|
||||
int: 47,
|
||||
text: 'text',
|
||||
escape: '<escape me>',
|
||||
},
|
||||
}).expect('<tag int="47" text="text" escape="<escape me>">text</tag>')
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "3.3.1",
|
||||
"version": "3.3.0",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
@@ -21,12 +21,13 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/badges/shields/issues"
|
||||
},
|
||||
"homepage": "https://shields.io",
|
||||
"homepage": "http://shields.io",
|
||||
"bin": {
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">= 10",
|
||||
"npm": ">= 5"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
key: 'HTTPS_KEY'
|
||||
cert: 'HTTPS_CRT'
|
||||
|
||||
redirectUrl: 'REDIRECT_URI'
|
||||
redirectUri: 'REDIRECT_URI'
|
||||
|
||||
rasterUrl: 'RASTER_URL'
|
||||
|
||||
@@ -40,8 +40,6 @@ public:
|
||||
debug:
|
||||
enabled: 'GITHUB_DEBUG_ENABLED'
|
||||
intervalSeconds: 'GITHUB_DEBUG_INTERVAL_SECONDS'
|
||||
gitlab:
|
||||
authorizedOrigins: 'GITLAB_ORIGINS'
|
||||
jenkins:
|
||||
authorizedOrigins: 'JENKINS_ORIGINS'
|
||||
jira:
|
||||
@@ -50,61 +48,47 @@ public:
|
||||
authorizedOrigins: 'NEXUS_ORIGINS'
|
||||
npm:
|
||||
authorizedOrigins: 'NPM_ORIGINS'
|
||||
obs:
|
||||
authorizedOrigins: 'OBS_ORIGINS'
|
||||
sonar:
|
||||
authorizedOrigins: 'SONAR_ORIGINS'
|
||||
teamcity:
|
||||
authorizedOrigins: 'TEAMCITY_ORIGINS'
|
||||
weblate:
|
||||
authorizedOrigins: 'WEBLATE_ORIGINS'
|
||||
trace: 'TRACE_SERVICES'
|
||||
|
||||
cacheHeaders:
|
||||
defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS'
|
||||
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
userAgentBase: 'USER_AGENT_BASE'
|
||||
rateLimit: 'RATE_LIMIT'
|
||||
|
||||
requestTimeoutSeconds: 'REQUEST_TIMEOUT_SECONDS'
|
||||
requestTimeoutMaxAgeSeconds: 'REQUEST_TIMEOUT_MAX_AGE_SECONDS'
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
|
||||
requireCloudflare: 'REQUIRE_CLOUDFLARE'
|
||||
|
||||
private:
|
||||
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
|
||||
bintray_user: 'BINTRAY_USER'
|
||||
bintray_apikey: 'BINTRAY_API_KEY'
|
||||
bitbucket_username: 'BITBUCKET_USER'
|
||||
bitbucket_password: 'BITBUCKET_PASS'
|
||||
bitbucket_server_username: 'BITBUCKET_SERVER_USER'
|
||||
bitbucket_server_password: 'BITBUCKET_SERVER_PASS'
|
||||
curseforge_api_key: 'CURSEFORGE_API_KEY'
|
||||
discord_bot_token: 'DISCORD_BOT_TOKEN'
|
||||
dockerhub_username: 'DOCKERHUB_USER'
|
||||
dockerhub_pat: 'DOCKERHUB_PAT'
|
||||
drone_token: 'DRONE_TOKEN'
|
||||
gh_client_id: 'GH_CLIENT_ID'
|
||||
gh_client_secret: 'GH_CLIENT_SECRET'
|
||||
gh_token: 'GH_TOKEN'
|
||||
gitlab_token: 'GITLAB_TOKEN'
|
||||
jenkins_user: 'JENKINS_USER'
|
||||
jenkins_pass: 'JENKINS_PASS'
|
||||
jira_user: 'JIRA_USER'
|
||||
jira_pass: 'JIRA_PASS'
|
||||
librariesio_tokens: 'LIBRARIESIO_TOKENS'
|
||||
nexus_user: 'NEXUS_USER'
|
||||
nexus_pass: 'NEXUS_PASS'
|
||||
npm_token: 'NPM_TOKEN'
|
||||
obs_user: 'OBS_USER'
|
||||
obs_pass: 'OBS_PASS'
|
||||
redis_url: 'REDIS_URL'
|
||||
opencollective_token: 'OPENCOLLECTIVE_TOKEN'
|
||||
pepy_key: 'PEPY_KEY'
|
||||
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'
|
||||
@@ -112,5 +96,4 @@ private:
|
||||
wheelmap_token: 'WHEELMAP_TOKEN'
|
||||
influx_username: 'INFLUX_USERNAME'
|
||||
influx_password: 'INFLUX_PASSWORD'
|
||||
weblate_api_key: 'WEBLATE_API_KEY'
|
||||
youtube_api_key: 'YOUTUBE_API_KEY'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
public:
|
||||
bind:
|
||||
address: '::'
|
||||
|
||||
metrics:
|
||||
prometheus:
|
||||
enabled: false
|
||||
@@ -11,26 +12,27 @@ 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
|
||||
|
||||
rateLimit: true
|
||||
|
||||
handleInternalErrors: true
|
||||
|
||||
fetchLimit: '10MB'
|
||||
userAgentBase: 'shields (self-hosted)'
|
||||
requestTimeoutSeconds: 120
|
||||
requestTimeoutMaxAgeSeconds: 30
|
||||
|
||||
requireCloudflare: false
|
||||
|
||||
private: {}
|
||||
|
||||
@@ -5,4 +5,6 @@ public:
|
||||
cors:
|
||||
allowedOrigin: ['http://localhost:3000']
|
||||
|
||||
rateLimit: false
|
||||
|
||||
handleInternalErrors: false
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
private:
|
||||
# These are the keys which are set on the production servers.
|
||||
curseforge_api_key: ...
|
||||
discord_bot_token: ...
|
||||
gh_client_id: ...
|
||||
gh_client_secret: ...
|
||||
gitlab_token: ...
|
||||
redis_url: ...
|
||||
sentry_dsn: ...
|
||||
shields_secret: ...
|
||||
sl_insight_userUuid: ...
|
||||
sl_insight_apiToken: ...
|
||||
twitch_client_id: ...
|
||||
twitch_client_secret: ...
|
||||
weblate_api_key: ...
|
||||
wheelmap_token: ...
|
||||
youtube_api_key: ...
|
||||
|
||||
@@ -4,13 +4,8 @@ private:
|
||||
# The possible values are documented in `doc/server-secrets.md`. Note that
|
||||
# you can also set these values through environment variables, which may be
|
||||
# preferable for self hosting.
|
||||
curseforge_api_key: '...'
|
||||
gh_token: '...'
|
||||
gitlab_token: '...'
|
||||
obs_user: '...'
|
||||
obs_pass: '...'
|
||||
twitch_client_id: '...'
|
||||
twitch_client_secret: '...'
|
||||
weblate_api_key: '...'
|
||||
wheelmap_token: '...'
|
||||
youtube_api_key: '...'
|
||||
|
||||
@@ -6,20 +6,15 @@ 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'
|
||||
redirectUrl: 'https://shields.io/'
|
||||
|
||||
rasterUrl: 'https://raster.shields.io'
|
||||
userAgentBase: 'Shields.io'
|
||||
requireCloudflare: true
|
||||
requestTimeoutSeconds: 20
|
||||
|
||||
@@ -3,6 +3,8 @@ public:
|
||||
address: 'localhost'
|
||||
port: 1111
|
||||
|
||||
rateLimit: false
|
||||
|
||||
redirectUrl: 'http://frontend.example.test'
|
||||
|
||||
rasterUrl: 'http://raster.example.test'
|
||||
|
||||
112
core/badge-urls/make-badge-url.d.ts
vendored
Normal file
112
core/badge-urls/make-badge-url.d.ts
vendored
Normal 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
|
||||
@@ -1,13 +1,164 @@
|
||||
// Avoid "Attempted import error: 'URL' is not exported from 'url'" in frontend.
|
||||
import url from 'url'
|
||||
'use strict'
|
||||
|
||||
const { URL } = require('url')
|
||||
const queryString = require('query-string')
|
||||
const { compile } = require('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
|
||||
// the request URL.
|
||||
const { pathname, search } = new url.URL(badgeUrl, 'https://bogus.test')
|
||||
const result = new url.URL(pathname, rasterUrl)
|
||||
const { pathname, search } = new URL(badgeUrl, 'https://bogus.test')
|
||||
const result = new URL(pathname, rasterUrl)
|
||||
result.search = search
|
||||
return result
|
||||
}
|
||||
|
||||
export { rasterRedirectUrl }
|
||||
module.exports = {
|
||||
badgeUrlFromPath,
|
||||
badgeUrlFromPattern,
|
||||
encodeField,
|
||||
staticBadgeUrl,
|
||||
queryStringStaticBadgeUrl,
|
||||
dynamicBadgeUrl,
|
||||
rasterRedirectUrl,
|
||||
}
|
||||
|
||||
157
core/badge-urls/make-badge-url.spec.js
Normal file
157
core/badge-urls/make-badge-url.spec.js
Normal file
@@ -0,0 +1,157 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const {
|
||||
badgeUrlFromPath,
|
||||
badgeUrlFromPattern,
|
||||
encodeField,
|
||||
staticBadgeUrl,
|
||||
queryStringStaticBadgeUrl,
|
||||
dynamicBadgeUrl,
|
||||
} = require('./make-badge-url')
|
||||
|
||||
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('')
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
// Escapes `t` using the format specified in
|
||||
// <https://github.com/espadrine/gh-badges/issues/12#issuecomment-31518129>
|
||||
function escapeFormat(t) {
|
||||
return (
|
||||
t
|
||||
// Single underscore.
|
||||
.replace(/(^|[^_])((?:__)*)_(?!_)/g, '$1$2 ')
|
||||
// Inline single underscore.
|
||||
.replace(/([^_])_([^_])/g, '$1 $2')
|
||||
// Leading or trailing underscore.
|
||||
.replace(/([^_])_$/, '$1 ')
|
||||
.replace(/^_([^_])/, ' $1')
|
||||
// Double underscore and double dash.
|
||||
.replace(/__/g, '_')
|
||||
.replace(/--/g, '-')
|
||||
)
|
||||
}
|
||||
|
||||
export { escapeFormat }
|
||||
module.exports = {
|
||||
escapeFormat,
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import { escapeFormat } from './path-helpers.js'
|
||||
|
||||
describe('Badge URL helper functions', function () {
|
||||
test(escapeFormat, () => {
|
||||
given('_single leading underscore').expect(' single leading underscore')
|
||||
given('single trailing underscore_').expect('single trailing underscore ')
|
||||
given('__double leading underscores').expect('_double leading underscores')
|
||||
given('double trailing underscores__').expect(
|
||||
'double trailing underscores_',
|
||||
)
|
||||
given('treble___underscores').expect('treble_ underscores')
|
||||
given('fourfold____underscores').expect('fourfold__underscores')
|
||||
given('double--dashes').expect('double-dashes')
|
||||
given('treble---dashes').expect('treble--dashes')
|
||||
given('fourfold----dashes').expect('fourfold--dashes')
|
||||
given('once_in_a_blue--moon').expect('once in a blue-moon')
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,7 @@
|
||||
import { URL } from 'url'
|
||||
import dayjs from 'dayjs'
|
||||
import Joi from 'joi'
|
||||
import checkErrorResponse from './check-error-response.js'
|
||||
import { InvalidParameter, InvalidResponse } from './errors.js'
|
||||
import { fetch } from './got.js'
|
||||
import { parseJson } from './json.js'
|
||||
import validate from './validate.js'
|
||||
'use strict'
|
||||
|
||||
let jwtCache = Object.create(null)
|
||||
const { URL } = require('url')
|
||||
const { InvalidParameter } = require('./errors')
|
||||
|
||||
class AuthHelper {
|
||||
constructor(
|
||||
@@ -19,7 +13,7 @@ class AuthHelper {
|
||||
isRequired = false,
|
||||
defaultToEmptyStringForUser = false,
|
||||
},
|
||||
config,
|
||||
config
|
||||
) {
|
||||
if (!userKey && !passKey) {
|
||||
throw Error('Expected userKey or passKey to be set')
|
||||
@@ -82,7 +76,7 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
static _isInsecureSslRequest({ options = {} }) {
|
||||
const strictSSL = options?.https?.rejectUnauthorized ?? true
|
||||
const { strictSSL = true } = options
|
||||
return strictSSL !== true
|
||||
}
|
||||
|
||||
@@ -95,7 +89,7 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
isAllowedOrigin(url) {
|
||||
shouldAuthenticateRequest({ url, options = {} }) {
|
||||
let parsed
|
||||
try {
|
||||
parsed = new URL(url)
|
||||
@@ -105,11 +99,7 @@ class AuthHelper {
|
||||
|
||||
const { protocol, host } = parsed
|
||||
const origin = `${protocol}//${host}`
|
||||
return this._authorizedOrigins.includes(origin)
|
||||
}
|
||||
|
||||
shouldAuthenticateRequest({ url, options = {} }) {
|
||||
const originViolation = !this.isAllowedOrigin(url)
|
||||
const originViolation = !this._authorizedOrigins.includes(origin)
|
||||
|
||||
const strictSslCheckViolation =
|
||||
this._requireStrictSslToAuthenticate &&
|
||||
@@ -119,10 +109,8 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
get _basicAuth() {
|
||||
const { _user: username, _pass: password } = this
|
||||
return this.isConfigured
|
||||
? { username: username || '', password: password || '' }
|
||||
: undefined
|
||||
const { _user: user, _pass: pass } = this
|
||||
return this.isConfigured ? { user, pass } : undefined
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -145,7 +133,7 @@ class AuthHelper {
|
||||
const { options, ...rest } = requestParams
|
||||
return {
|
||||
options: {
|
||||
...auth,
|
||||
auth,
|
||||
...options,
|
||||
},
|
||||
...rest,
|
||||
@@ -154,7 +142,7 @@ class AuthHelper {
|
||||
|
||||
withBasicAuth(requestParams) {
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeAuth(requestParams, this._basicAuth),
|
||||
this.constructor._mergeAuth(requestParams, this._basicAuth)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,11 +153,6 @@ class AuthHelper {
|
||||
: undefined
|
||||
}
|
||||
|
||||
_apiKeyHeader(apiKeyHeader) {
|
||||
const { _pass: pass } = this
|
||||
return this.isConfigured ? { [apiKeyHeader]: pass } : undefined
|
||||
}
|
||||
|
||||
static _mergeHeaders(requestParams, headers) {
|
||||
const {
|
||||
options: { headers: existingHeaders, ...restOptions } = {},
|
||||
@@ -187,32 +170,26 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
withApiKeyHeader(requestParams, header = 'x-api-key') {
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(requestParams, this._apiKeyHeader(header)),
|
||||
)
|
||||
}
|
||||
|
||||
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 } = {},
|
||||
options: { qs: existingQuery, ...restOptions } = {},
|
||||
...rest
|
||||
} = requestParams
|
||||
return {
|
||||
options: {
|
||||
searchParams: {
|
||||
qs: {
|
||||
...existingQuery,
|
||||
...query,
|
||||
},
|
||||
@@ -227,106 +204,9 @@ class AuthHelper {
|
||||
this.constructor._mergeQueryParams(requestParams, {
|
||||
...(userKey ? { [userKey]: this._user } : undefined),
|
||||
...(passKey ? { [passKey]: this._pass } : undefined),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
static _getJwtExpiry(token, max = dayjs().add(1, 'hours').unix()) {
|
||||
// get the expiry timestamp for this JWT (capped at a max length)
|
||||
const parts = token.split('.')
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'invalid response data from auth endpoint',
|
||||
})
|
||||
}
|
||||
|
||||
const json = validate(
|
||||
{
|
||||
ErrorClass: InvalidResponse,
|
||||
prettyErrorMessage: 'invalid response data from auth endpoint',
|
||||
},
|
||||
parseJson(Buffer.from(parts[1], 'base64').toString()),
|
||||
Joi.object({ exp: Joi.number().required() }).required(),
|
||||
)
|
||||
|
||||
return Math.min(json.exp, max)
|
||||
}
|
||||
|
||||
static _isJwtValid(expiry) {
|
||||
// we consider the token valid if the expiry
|
||||
// datetime is later than (now + 1 minute)
|
||||
return dayjs.unix(expiry).isAfter(dayjs().add(1, 'minutes'))
|
||||
}
|
||||
|
||||
async _getJwt(loginEndpoint) {
|
||||
const { _user: username, _pass: password } = this
|
||||
|
||||
// attempt to get JWT from cache
|
||||
if (
|
||||
jwtCache?.[loginEndpoint]?.[username]?.token &&
|
||||
jwtCache?.[loginEndpoint]?.[username]?.expiry &&
|
||||
this.constructor._isJwtValid(jwtCache[loginEndpoint][username].expiry)
|
||||
) {
|
||||
// cache hit
|
||||
return jwtCache[loginEndpoint][username].token
|
||||
}
|
||||
|
||||
// cache miss - request a new JWT
|
||||
const originViolation = !this.isAllowedOrigin(loginEndpoint)
|
||||
if (originViolation) {
|
||||
throw new InvalidParameter({
|
||||
prettyMessage: 'requested origin not authorized',
|
||||
})
|
||||
}
|
||||
|
||||
const { buffer } = await checkErrorResponse({})(
|
||||
await fetch(loginEndpoint, {
|
||||
method: 'POST',
|
||||
form: { username, password },
|
||||
}),
|
||||
)
|
||||
|
||||
const json = validate(
|
||||
{
|
||||
ErrorClass: InvalidResponse,
|
||||
prettyErrorMessage: 'invalid response data from auth endpoint',
|
||||
},
|
||||
parseJson(buffer),
|
||||
Joi.object({ token: Joi.string().required() }).required(),
|
||||
)
|
||||
|
||||
const token = json.token
|
||||
const expiry = this.constructor._getJwtExpiry(token)
|
||||
|
||||
// store in the cache
|
||||
if (!(loginEndpoint in jwtCache)) {
|
||||
jwtCache[loginEndpoint] = {}
|
||||
}
|
||||
jwtCache[loginEndpoint][username] = { token, expiry }
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
async _getJwtAuthHeader(loginEndpoint) {
|
||||
if (!this.isConfigured) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const token = await this._getJwt(loginEndpoint)
|
||||
return { Authorization: `Bearer ${token}` }
|
||||
}
|
||||
|
||||
async withJwtAuth(requestParams, loginEndpoint) {
|
||||
const authHeader = await this._getJwtAuthHeader(loginEndpoint)
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(requestParams, authHeader),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function clearJwtCache() {
|
||||
jwtCache = Object.create(null)
|
||||
}
|
||||
|
||||
export { AuthHelper, clearJwtCache }
|
||||
module.exports = { AuthHelper }
|
||||
|
||||
@@ -1,45 +1,21 @@
|
||||
import dayjs from 'dayjs'
|
||||
import nock from 'nock'
|
||||
import { expect } from 'chai'
|
||||
import { test, given, forCases } from 'sazerac'
|
||||
import { AuthHelper, clearJwtCache } from './auth-helper.js'
|
||||
import { InvalidParameter, InvalidResponse } from './errors.js'
|
||||
'use strict'
|
||||
|
||||
function base64UrlEncode(input) {
|
||||
const base64 = btoa(JSON.stringify(input))
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
||||
}
|
||||
|
||||
function getMockJwt(extras) {
|
||||
// this function returns a mock JWT that contains enough
|
||||
// for a unit test but ignores important aspects e.g: signing
|
||||
|
||||
const header = {
|
||||
alg: 'HS256',
|
||||
typ: 'JWT',
|
||||
}
|
||||
const payload = {
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
...extras,
|
||||
}
|
||||
|
||||
const encodedHeader = base64UrlEncode(header)
|
||||
const encodedPayload = base64UrlEncode(payload)
|
||||
return `${encodedHeader}.${encodedPayload}`
|
||||
}
|
||||
const { expect } = require('chai')
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const { AuthHelper } = require('./auth-helper')
|
||||
const { InvalidParameter } = require('./errors')
|
||||
|
||||
describe('AuthHelper', function () {
|
||||
describe('constructor checks', function () {
|
||||
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 () {
|
||||
@@ -51,8 +27,8 @@ describe('AuthHelper', function () {
|
||||
passKey: 'myci_pass',
|
||||
authorizedOrigins: true,
|
||||
},
|
||||
{ private: {} },
|
||||
),
|
||||
{ private: {} }
|
||||
)
|
||||
).to.throw(Error, 'Expected authorizedOrigins to be an array of origins')
|
||||
})
|
||||
})
|
||||
@@ -61,7 +37,7 @@ describe('AuthHelper', function () {
|
||||
function validate(config, privateConfig) {
|
||||
return new AuthHelper(
|
||||
{ authorizedOrigins: ['https://example.test'], ...config },
|
||||
{ private: privateConfig },
|
||||
{ private: privateConfig }
|
||||
).isValid
|
||||
}
|
||||
test(validate, () => {
|
||||
@@ -69,20 +45,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' }),
|
||||
@@ -96,16 +72,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 }, {}),
|
||||
@@ -117,37 +93,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',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -157,18 +133,15 @@ describe('AuthHelper', function () {
|
||||
forCases([
|
||||
given({ url: 'http://example.test' }),
|
||||
given({ url: 'http://example.test', options: {} }),
|
||||
given({ url: 'http://example.test', options: { strictSSL: true } }),
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: true } },
|
||||
}),
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: undefined } },
|
||||
options: { strictSSL: undefined },
|
||||
}),
|
||||
]).expect(false)
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(true)
|
||||
})
|
||||
})
|
||||
@@ -192,9 +165,7 @@ describe('AuthHelper', function () {
|
||||
})
|
||||
it('throws for insecure requests', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
}),
|
||||
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
@@ -216,9 +187,7 @@ describe('AuthHelper', function () {
|
||||
})
|
||||
it('does not throw for insecure requests', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
}),
|
||||
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
|
||||
).not.to.throw()
|
||||
})
|
||||
})
|
||||
@@ -253,7 +222,7 @@ describe('AuthHelper', function () {
|
||||
test(shouldAuthenticateRequest, () => {
|
||||
given({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(false)
|
||||
})
|
||||
})
|
||||
@@ -291,7 +260,7 @@ describe('AuthHelper', function () {
|
||||
test(shouldAuthenticateRequest, () => {
|
||||
given({
|
||||
url: 'https://myci.test',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(true)
|
||||
})
|
||||
})
|
||||
@@ -344,7 +313,7 @@ describe('AuthHelper', function () {
|
||||
},
|
||||
},
|
||||
private: { myci_user: 'admin', myci_pass: 'abc123' },
|
||||
},
|
||||
}
|
||||
)
|
||||
const withBasicAuth = requestOptions =>
|
||||
authHelper.withBasicAuth(requestOptions)
|
||||
@@ -356,8 +325,7 @@ describe('AuthHelper', function () {
|
||||
}).expect({
|
||||
url: 'https://myci.test/api',
|
||||
options: {
|
||||
username: 'admin',
|
||||
password: 'abc123',
|
||||
auth: { user: 'admin', pass: 'abc123' },
|
||||
},
|
||||
})
|
||||
given({
|
||||
@@ -369,8 +337,7 @@ describe('AuthHelper', function () {
|
||||
url: 'https://myci.test/api',
|
||||
options: {
|
||||
headers: { Accept: 'application/json' },
|
||||
username: 'admin',
|
||||
password: 'abc123',
|
||||
auth: { user: 'admin', pass: 'abc123' },
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -401,158 +368,9 @@ describe('AuthHelper', function () {
|
||||
expect(() =>
|
||||
withBasicAuth({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
}),
|
||||
options: { strictSSL: false },
|
||||
})
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
|
||||
context('JTW Auth', function () {
|
||||
describe('_isJwtValid', function () {
|
||||
test(AuthHelper._isJwtValid, () => {
|
||||
given(dayjs().add(1, 'month').unix()).expect(true)
|
||||
given(dayjs().add(2, 'minutes').unix()).expect(true)
|
||||
given(dayjs().add(30, 'seconds').unix()).expect(false)
|
||||
given(dayjs().unix()).expect(false)
|
||||
given(dayjs().subtract(1, 'seconds').unix()).expect(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_getJwtExpiry', function () {
|
||||
it('extracts expiry from valid JWT', function () {
|
||||
const nowPlus30Mins = dayjs().add(30, 'minutes').unix()
|
||||
expect(
|
||||
AuthHelper._getJwtExpiry(getMockJwt({ exp: nowPlus30Mins })),
|
||||
).to.equal(nowPlus30Mins)
|
||||
})
|
||||
|
||||
it('caps expiry at max', function () {
|
||||
const nowPlus1Hour = dayjs().add(1, 'hours').unix()
|
||||
const nowPlus2Hours = dayjs().add(2, 'hours').unix()
|
||||
expect(
|
||||
AuthHelper._getJwtExpiry(getMockJwt({ exp: nowPlus2Hours })),
|
||||
).to.equal(nowPlus1Hour)
|
||||
})
|
||||
|
||||
it('throws if JWT does not contain exp', function () {
|
||||
expect(() => {
|
||||
AuthHelper._getJwtExpiry(getMockJwt({}))
|
||||
}).to.throw(InvalidResponse)
|
||||
})
|
||||
|
||||
it('throws if JWT is invalid', function () {
|
||||
expect(() => {
|
||||
AuthHelper._getJwtExpiry('abc')
|
||||
}).to.throw(InvalidResponse)
|
||||
})
|
||||
})
|
||||
|
||||
describe('withJwtAuth', function () {
|
||||
const authHelper = new AuthHelper(
|
||||
{
|
||||
userKey: 'jwt_user',
|
||||
passKey: 'jwt_pass',
|
||||
authorizedOrigins: ['https://example.com'],
|
||||
isRequired: false,
|
||||
},
|
||||
{ private: { jwt_user: 'fred', jwt_pass: 'abc123' } },
|
||||
)
|
||||
|
||||
beforeEach(function () {
|
||||
clearJwtCache()
|
||||
})
|
||||
|
||||
it('should use cached response if valid', async function () {
|
||||
// the expiry is far enough in the future that the token
|
||||
// will still be valid on the second hit
|
||||
const mockToken = getMockJwt({ exp: dayjs().add(1, 'hours').unix() })
|
||||
|
||||
// .times(1) ensures if we try to make a second call to this endpoint,
|
||||
// we will throw `Nock: No match for request`
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken })
|
||||
const params1 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params1).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
// second time round, we'll get the same response again
|
||||
// but this time served from cache
|
||||
const params2 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(params2).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
nock.cleanAll()
|
||||
})
|
||||
|
||||
it('should not use cached response if expired', async function () {
|
||||
// this time we define a token expiry is close enough
|
||||
// that the token will not be valid on the second call
|
||||
const mockToken1 = getMockJwt({
|
||||
exp: dayjs().add(20, 'seconds').unix(),
|
||||
})
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken1 })
|
||||
const params1 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params1).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken1}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
// second time round we make another network request
|
||||
const mockToken2 = getMockJwt({
|
||||
exp: dayjs().add(20, 'seconds').unix(),
|
||||
})
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken2 })
|
||||
const params2 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params2).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken2}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
nock.cleanAll()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { print } from 'graphql/language/printer.js'
|
||||
import BaseService from './base.js'
|
||||
import { InvalidResponse, ShieldsRuntimeError } from './errors.js'
|
||||
import { parseJson } from './json.js'
|
||||
'use strict'
|
||||
|
||||
const { print } = require('graphql/language/printer')
|
||||
const BaseService = require('./base')
|
||||
const { InvalidResponse, ShieldsRuntimeError } = require('./errors')
|
||||
const { parseJson } = require('./json')
|
||||
|
||||
function defaultTransformErrors(errors) {
|
||||
return new InvalidResponse({ prettyMessage: errors[0].message })
|
||||
@@ -20,7 +22,7 @@ class BaseGraphqlService extends BaseService {
|
||||
/**
|
||||
* Parse data from JSON endpoint
|
||||
*
|
||||
* @param {string} buffer JSON response from upstream API
|
||||
* @param {string} buffer JSON repsonse from upstream API
|
||||
* @returns {object} Parsed response
|
||||
*/
|
||||
_parseJson(buffer) {
|
||||
@@ -38,22 +40,14 @@ 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 {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {Function} [attrs.transformJson=data => data] Function which takes the raw json and transforms it before
|
||||
* further processing. In case of multiple query in a single graphql call and few of them
|
||||
* 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.
|
||||
* @param {Function} [attrs.transformErrors=defaultTransformErrors]
|
||||
* Function which takes an errors object from a GraphQL
|
||||
@@ -61,7 +55,7 @@ class BaseGraphqlService extends BaseService {
|
||||
* The default is to return the first entry of the `errors` array as
|
||||
* an InvalidResponse.
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestGraphql({
|
||||
schema,
|
||||
@@ -70,8 +64,6 @@ class BaseGraphqlService extends BaseService {
|
||||
variables = {},
|
||||
options = {},
|
||||
httpErrorMessages = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
transformJson = data => data,
|
||||
transformErrors = defaultTransformErrors,
|
||||
}) {
|
||||
@@ -84,9 +76,7 @@ class BaseGraphqlService extends BaseService {
|
||||
const { buffer } = await this._request({
|
||||
url,
|
||||
options: mergedOptions,
|
||||
httpErrors: httpErrorMessages,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
errorMessages: httpErrorMessages,
|
||||
})
|
||||
const json = transformJson(this._parseJson(buffer))
|
||||
if (json.errors) {
|
||||
@@ -95,7 +85,7 @@ class BaseGraphqlService extends BaseService {
|
||||
throw exception
|
||||
} else {
|
||||
throw Error(
|
||||
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`,
|
||||
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,4 +93,4 @@ class BaseGraphqlService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseGraphqlService
|
||||
module.exports = BaseGraphqlService
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { expect } from 'chai'
|
||||
import gql from 'graphql-tag'
|
||||
import sinon from 'sinon'
|
||||
import BaseGraphqlService from './base-graphql.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const gql = require('graphql-tag')
|
||||
const sinon = require('sinon')
|
||||
const BaseGraphqlService = require('./base-graphql')
|
||||
const { InvalidResponse } = require('./errors')
|
||||
|
||||
const dummySchema = Joi.object({
|
||||
requiredString: Joi.string().required(),
|
||||
@@ -29,33 +31,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 +68,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 +125,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 +144,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 +183,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',
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import BaseService from './base.js'
|
||||
import { parseJson } from './json.js'
|
||||
'use strict'
|
||||
|
||||
const BaseService = require('./base')
|
||||
const { parseJson } = require('./json')
|
||||
|
||||
/**
|
||||
* Services which query a JSON endpoint should extend BaseJsonService
|
||||
@@ -14,7 +16,7 @@ class BaseJsonService extends BaseService {
|
||||
/**
|
||||
* Parse data from JSON endpoint
|
||||
*
|
||||
* @param {string} buffer JSON response from upstream API
|
||||
* @param {string} buffer JSON repsonse from upstream API
|
||||
* @returns {object} Parsed response
|
||||
*/
|
||||
_parseJson(buffer) {
|
||||
@@ -28,31 +30,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
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @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 = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
|
||||
const mergedOptions = {
|
||||
...{ headers: { Accept: 'application/json' } },
|
||||
...options,
|
||||
@@ -60,13 +47,11 @@ class BaseJsonService extends BaseService {
|
||||
const { buffer } = await this._request({
|
||||
url,
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
errorMessages,
|
||||
})
|
||||
const json = this._parseJson(buffer)
|
||||
return this.constructor._validate(json, schema)
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseJsonService
|
||||
module.exports = BaseJsonService
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import Joi from 'joi'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import BaseJsonService from './base-json.js'
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseJsonService = require('./base-json')
|
||||
|
||||
const dummySchema = Joi.object({
|
||||
requiredString: Joi.string().required(),
|
||||
@@ -22,84 +24,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 +110,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',
|
||||
|
||||
74
core/base-service/base-non-memory-caching.js
Normal file
74
core/base-service/base-non-memory-caching.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const BaseService = require('./base')
|
||||
const { MetricHelper } = require('./metric-helper')
|
||||
const { setCacheHeaders } = require('./cache-headers')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const { prepareRoute, namedParamsForMatch } = require('./route')
|
||||
|
||||
// Badges are subject to two independent types of caching: in-memory and
|
||||
// downstream.
|
||||
//
|
||||
// Services deriving from `NonMemoryCachingBaseService` are not cached in
|
||||
// memory on the server. This means that each request that hits the server
|
||||
// triggers another call to the handler. When using badges for server
|
||||
// diagnostics, that's useful!
|
||||
//
|
||||
// In contrast, The `handle()` function of most other `BaseService`
|
||||
// subclasses is wrapped in onboard, in-memory caching. See `lib /request-
|
||||
// handler.js` and `BaseService.prototype.register()`.
|
||||
//
|
||||
// All services, including those extending NonMemoryCachingBaseServices, may
|
||||
// be cached _downstream_. This is governed by cache headers, which are
|
||||
// configured by the service, the user's request, and the server's default
|
||||
// cache length.
|
||||
module.exports = class NonMemoryCachingBaseService extends BaseService {
|
||||
static register({ camp, metricInstance }, serviceConfig) {
|
||||
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
|
||||
const { _cacheLength: serviceDefaultCacheLengthSeconds } = this
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
|
||||
const metricHelper = MetricHelper.create({
|
||||
metricInstance,
|
||||
ServiceClass: this,
|
||||
})
|
||||
|
||||
camp.route(regex, async (queryParams, match, end, ask) => {
|
||||
const metricHandle = metricHelper.startRequest()
|
||||
|
||||
const namedParams = namedParamsForMatch(captureNames, match, this)
|
||||
const serviceData = await this.invoke(
|
||||
{},
|
||||
serviceConfig,
|
||||
namedParams,
|
||||
queryParams
|
||||
)
|
||||
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
serviceData,
|
||||
this.defaultBadgeData,
|
||||
this
|
||||
)
|
||||
|
||||
// The final capture group is the extension.
|
||||
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
|
||||
badgeData.format = format
|
||||
|
||||
const svg = makeBadge(badgeData)
|
||||
|
||||
setCacheHeaders({
|
||||
cacheHeaderConfig,
|
||||
serviceDefaultCacheLengthSeconds,
|
||||
queryParams,
|
||||
res: ask.res,
|
||||
})
|
||||
|
||||
makeSend(format, ask.res, end)(svg)
|
||||
|
||||
metricHandle.noteResponseSent()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import makeBadge from '../../badge-maker/lib/make-badge.js'
|
||||
import BaseService from './base.js'
|
||||
import {
|
||||
'use strict'
|
||||
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const BaseService = require('./base')
|
||||
const {
|
||||
serverHasBeenUpSinceResourceCached,
|
||||
setCacheHeadersForStaticResource,
|
||||
} from './cache-headers.js'
|
||||
import { makeSend } from './legacy-result-sender.js'
|
||||
import { MetricHelper } from './metric-helper.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
import { prepareRoute, namedParamsForMatch } from './route.js'
|
||||
} = require('./cache-headers')
|
||||
const { makeSend } = require('./legacy-result-sender')
|
||||
const { MetricHelper } = require('./metric-helper')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
const { prepareRoute, namedParamsForMatch } = require('./route')
|
||||
|
||||
export default class BaseStaticService extends BaseService {
|
||||
module.exports = class BaseStaticService extends BaseService {
|
||||
static register({ camp, metricInstance }, serviceConfig) {
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
|
||||
@@ -33,14 +35,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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user