Compare commits

..

1 Commits

Author SHA1 Message Date
release[bot]
72751067c8 Update Changelog 2023-05-01 01:31:52 +00:00
1272 changed files with 62693 additions and 50608 deletions

View File

@@ -1,14 +0,0 @@
{
"name": "Node.js",
// "Officially" maintained node devcontainer image from Microsoft
// https://github.com/devcontainers/templates/tree/main/src/javascript-node
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm ci"
}

View File

@@ -2,12 +2,8 @@ node_modules/
shields.env
.git/
.gitignore
.github
.vscode/
fly.toml
*.md
doc/
# Improve layer cacheability.
Dockerfile

7
.eslintignore Normal file
View File

@@ -0,0 +1,7 @@
/api-docs/
/build
/coverage
/__snapshots__
public
badge-maker/node_modules/
!.github/

208
.eslintrc.yml Normal file
View File

@@ -0,0 +1,208 @@
extends:
- standard
- standard-jsx
- standard-react
- plugin:@typescript-eslint/recommended
- prettier
- 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.
sourceType: 'script'
settings:
react:
version: '16.8'
jsdoc:
mode: jsdoc
plugins:
- chai-friendly
- jsdoc
- mocha
- icedfrisby
- no-extension-in-require
- sort-class-members
- import
- react-hooks
- promise
overrides:
# For simplicity's sake, when possible prefer to add rules to the top-level
# list of rules, even if they only apply to certain files. That way the
# rules listed here are only ones which conflict.
- files:
- '**/*.js'
- '!frontend/**/*.js'
env:
node: true
es6: true
rules:
no-console: 'off'
'@typescript-eslint/explicit-module-boundary-types': 'off'
- files:
- '**/*.@(ts|tsx)'
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
rules:
# Argh.
'@typescript-eslint/explicit-function-return-type':
['error', { 'allowExpressions': true }]
'@typescript-eslint/no-empty-function': 'error'
'@typescript-eslint/no-var-requires': 'error'
'@typescript-eslint/no-object-literal-type-assertion': 'off'
'@typescript-eslint/no-explicit-any': 'error'
'@typescript-eslint/ban-ts-ignore': 'off'
'@typescript-eslint/explicit-module-boundary-types': 'off'
- files:
- core/**/*.ts
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
- files:
- gatsby-browser.js
- 'frontend/**/*.@(js|ts|tsx)'
parserOptions:
sourceType: 'module'
env:
browser: true
rules:
import/extensions:
['error', 'never', { 'json': 'always', 'yml': 'always' }]
- files:
- 'core/base-service/**/*.js'
- 'services/**/*.js'
rules:
sort-class-members/sort-class-members:
[
'error',
{
order:
[
'name',
'category',
'isDeprecated',
'route',
'auth',
'examples',
'_cacheLength',
'defaultBadgeData',
'render',
'constructor',
'fetch',
'transform',
'handle',
],
},
]
- files:
- '**/*.spec.@(js|ts|tsx)'
- '**/*.integration.js'
- '**/test-helpers.js'
- 'core/service-test-runner/**/*.js'
env:
mocha: true
rules:
mocha/no-exclusive-tests: 'error'
mocha/no-skipped-tests: 'error'
mocha/no-mocha-arrows: 'error'
mocha/prefer-arrow-callback: 'error'
- files:
- 'services/**/*.tester.js'
rules:
icedfrisby/no-exclusive-tests: 'error'
icedfrisby/no-skipped-tests: 'error'
rules:
# Disable some rules from eslint:recommended.
no-empty: ['error', { 'allowEmptyCatch': true }]
# Allow unused parameters. In callbacks, removing them seems to obscure
# what the functions are doing.
'@typescript-eslint/no-unused-vars': ['error', { 'args': 'none' }]
no-unused-vars: 'off'
'@typescript-eslint/no-var-requires': 'off'
'@typescript-eslint/no-use-before-define': 'error'
no-use-before-define: 'off'
# These should be disabled by eslint-config-prettier, but are not.
no-extra-semi: 'off'
# Shields additions.
no-var: 'error'
prefer-const: 'error'
arrow-body-style: ['error', 'as-needed']
no-extension-in-require/main: 'error'
object-shorthand: ['error', 'properties']
prefer-template: 'error'
promise/prefer-await-to-then: 'error'
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'
chai-friendly/no-unused-expressions: 'error'
# jsdoc plugin:
# don't require every class/function to have a docblock
jsdoc/require-jsdoc: 'off'
# allow Joi as an undefined type
jsdoc/no-undefined-types: ['error', { definedTypes: ['Joi'] }]
# all the other recommended rules as errors (not warnings)
jsdoc/check-alignment: 'error'
jsdoc/check-param-names: 'error'
jsdoc/check-tag-names: 'error'
jsdoc/check-types: 'error'
jsdoc/implements-on-classes: 'error'
jsdoc/tag-lines: ['error', 'any', { 'startLines': 1 }]
jsdoc/require-param: 'error'
jsdoc/require-param-description: 'error'
jsdoc/require-param-name: 'error'
jsdoc/require-param-type: 'error'
jsdoc/require-returns: 'error'
jsdoc/require-returns-check: 'error'
jsdoc/require-returns-description: 'error'
jsdoc/require-returns-type: 'error'
jsdoc/valid-types: 'error'
# 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'
jsx-quotes: ['error', 'prefer-double']

View File

@@ -41,4 +41,4 @@ body:
attributes:
value: |
## :heart: Love Shields?
Please consider donating to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)
Please consider donating $10 to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)

View File

@@ -18,6 +18,10 @@ labels: 'keep-service-tests-green'
<!-- Indicate whether or not the live badge is working. -->
:link: **CircleCI link**
<!-- Provide a link to the failing test in CircleCI. -->
:lady_beetle: **Stack trace**
```
@@ -28,5 +32,5 @@ labels: 'keep-service-tests-green'
<!--- Optional: only if you have suggestions on a fix/reason for the bug -->
<!-- Love Shields? Please consider donating to sustain our activities:
<!-- Love Shields? Please consider donating $10 to sustain our activities:
👉 https://opencollective.com/shields -->

View File

@@ -12,7 +12,7 @@ body:
**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).
[already possible to make these](https://github.com/badges/shields/blob/master/doc/static-badges.md).
We don't add specific routes for badges which only show static information.
- type: textarea
@@ -25,7 +25,7 @@ body:
- 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 )
(static badge generator can be found at https://shields.io/#your-badge )
validations:
required: true
@@ -59,4 +59,4 @@ body:
attributes:
value: |
## :heart: Love Shields?
Please consider donating to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)
Please consider donating $10 to sustain our activities: [https://opencollective.com/shields](https://opencollective.com/shields)

View File

@@ -7,5 +7,5 @@ about: Ideas for other new features or improvements
<!-- A clear and concise description of the new feature. -->
<!-- Love Shields? Please consider donating to sustain our activities:
<!-- Love Shields? Please consider donating $10 to sustain our activities:
👉 https://opencollective.com/shields -->

12
.github/actions/close-bot/action.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: 'Auto Approve'
description: 'Automatically approve/close selected pull requests for shields.io'
branding:
icon: 'check-circle'
color: 'green'
inputs:
github-token:
description: 'The GITHUB_TOKEN secret'
required: true
runs:
using: 'node16'
main: 'index.js'

68
.github/actions/close-bot/helpers.js vendored Normal file
View File

@@ -0,0 +1,68 @@
'use strict'
function findChangelogStart(lines) {
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (
line === '<summary>Changelog</summary>' &&
lines[i + 2] === '<blockquote>'
) {
return i + 3
}
}
return null
}
function findChangelogEnd(lines, start) {
for (let i = start; i < lines.length; i++) {
const line = lines[i]
if (line === '</blockquote>') {
return i
}
}
return null
}
function allChangelogLinesAreVersionBump(changelogLines) {
return (
changelogLines.length > 0 &&
changelogLines.length ===
changelogLines.filter(line =>
line.includes('Version bump only for package')
).length
)
}
function isPointlessVersionBump(body) {
const pointlessBumpLinks = [
'https://github.com/gatsbyjs/gatsby',
'https://github.com/typescript-eslint/typescript-eslint',
]
const lines = body.split(/\r?\n/)
if (!pointlessBumpLinks.some(link => lines[0].includes(link))) {
return false
}
const start = findChangelogStart(lines)
const end = findChangelogEnd(lines, start)
if (!start || !end) {
return false
}
const changelogLines = lines
.slice(start, end)
.filter(line => !line.startsWith('<h'))
.filter(line => !line.startsWith('<p>All notable changes'))
.filter(
line => !line.startsWith('See <a href="https://conventionalcommits.org">')
)
.filter(line => !line.startsWith('<!--'))
.filter(
line =>
!line.startsWith(
'<p><a href="https://www.gatsbyjs.com/docs/reference/release-notes/'
)
)
return allChangelogLinesAreVersionBump(changelogLines)
}
module.exports = { isPointlessVersionBump }

38
.github/actions/close-bot/index.js vendored Normal file
View File

@@ -0,0 +1,38 @@
'use strict'
const core = require('@actions/core')
const github = require('@actions/github')
const { isPointlessVersionBump } = require('./helpers')
async function run() {
try {
const token = core.getInput('github-token', { required: true })
const { pull_request: pr } = github.context.payload
if (!pr) {
throw new Error('Event payload missing `pull_request`')
}
const client = github.getOctokit(token)
if (
['dependabot[bot]', 'dependabot-preview[bot]'].includes(pr.user.login)
) {
if (isPointlessVersionBump(pr.body)) {
core.debug(`Closing pull request #${pr.number}`)
await client.rest.pulls.update({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: pr.number,
state: 'closed',
})
core.debug('Done.')
}
}
} catch (error) {
core.setFailed(error.message)
}
}
run()

431
.github/actions/close-bot/package-lock.json generated vendored Normal file
View File

@@ -0,0 +1,431 @@
{
"name": "close-bot",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "close-bot",
"version": "0.0.0",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"dependencies": {
"@octokit/types": "^6.0.3"
}
},
"node_modules/@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
"integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
"integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
"dependencies": {
"@octokit/types": "^6.34.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz",
"integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==",
"dependencies": {
"@octokit/types": "^6.34.0",
"deprecation": "^2.3.1"
},
"peerDependencies": {
"@octokit/core": ">=3"
}
},
"node_modules/@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"dependencies": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"node_modules/@octokit/types": {
"version": "6.34.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
"integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
"dependencies": {
"@octokit/openapi-types": "^11.2.0"
}
},
"node_modules/before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"requires": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": {
"tunnel": "^0.0.6"
}
},
"@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"requires": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"requires": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"requires": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
"integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
"integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
"requires": {
"@octokit/types": "^6.34.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz",
"integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==",
"requires": {
"@octokit/types": "^6.34.0",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"requires": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "6.34.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
"integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
"requires": {
"@octokit/openapi-types": "^11.2.0"
}
},
"before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
},
"deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

16
.github/actions/close-bot/package.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "close-bot",
"version": "0.0.0",
"description": "",
"main": "index.js",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "chris48s",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
}
}

View File

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

View File

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

View File

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

View File

@@ -1,284 +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.11.1",
"@actions/github": "^6.0.0"
}
},
"node_modules/@actions/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"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/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"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.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^23.0.1"
}
},
"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": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.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.4.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^9.0.6",
"@octokit/request-error": "^5.1.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^23.0.1"
}
},
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/request/node_modules/@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^23.0.1"
}
},
"node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.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/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.28.5",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
"integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
"license": "MIT",
"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/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
}
}

View File

@@ -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.11.1",
"@actions/github": "^6.0.0"
}
}

View File

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

View File

@@ -2,17 +2,14 @@
set -euxo pipefail
# mark workspace dir as 'safe'
git config --system --add safe.directory '/github/workspace'
# Set up a git user
git config user.name "release[bot]"
git config user.email "actions@users.noreply.github.com"
# Find last server-YYYY-MM-DD tag
git fetch --unshallow --tags
LAST_TAG=$(git tag | grep server | tail -n 1)
# Set up a git user
git config user.name "release[bot]"
git config user.email "actions@users.noreply.github.com"
# Find the marker in CHANGELOG.md
INSERT_POINT=$(grep -n "^\-\-\-$" CHANGELOG.md | cut -f1 -d:)
INSERT_POINT=$((INSERT_POINT+1))

View File

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

View File

@@ -16,10 +16,6 @@ inputs:
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
@@ -36,6 +32,10 @@ inputs:
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
@@ -66,13 +66,11 @@ runs:
LIBRARIESIO_TOKENS: '${{ inputs.librariesio-tokens }}'
OBS_USER: '${{ inputs.obs-user }}'
OBS_PASS: '${{ inputs.obs-pass }}'
PEPY_KEY: '${{ inputs.pepy-key }}'
REDDIT_CLIENT_ID: '${{ inputs.reddit-client-id }}'
REDDIT_CLIENT_SECRET: '${{ inputs.reddit-client-secret }}'
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

View File

@@ -2,12 +2,8 @@ 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.'
description: 'Version Spec of the 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
@@ -19,14 +15,10 @@ runs:
using: 'composite'
steps:
- name: Install Node JS ${{ inputs.node-version }}
uses: actions/setup-node@v4
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:

View File

@@ -8,7 +8,6 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
ignore:
# https://github.com/badges/shields/issues/7324
# https://github.com/badges/shields/issues/7447
@@ -16,20 +15,10 @@ updates:
# https://caniuse.com/js-regexp-lookbehind
- dependency-name: 'decamelize'
- dependency-name: 'humanize-string'
groups:
# All official @docusaurus/* packages should have the exact same version as @docusaurus/core.
# From https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups:
# "You cannot apply a single grouping set of rules to both version updates and security
# updates [...] you must define two, separately named, grouping sets of rules"
# See https://github.com/badges/shields/issues/10242 for more information.
docusaurus-version-updates:
applies-to: version-updates
patterns:
- '@docusaurus/*'
docusaurus-security-updates:
applies-to: security-updates
patterns:
- '@docusaurus/*'
# https://github.com/badges/shields/pull/7288#issuecomment-974699240
- dependency-name: '@types/node'
# badge-maker package dependencies
- package-ecosystem: npm
directory: '/badge-maker'
@@ -38,30 +27,17 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
# GH actions
- package-ecosystem: 'github-actions'
# all composite actions must be individually listed here
# https://github.com/dependabot/dependabot-core/issues/6704
directories:
- '/'
- '/.github/actions/core-tests'
- '/.github/actions/integration-tests'
- '/.github/actions/package-tests'
- '/.github/actions/service-tests'
- '/.github/actions/setup'
schedule:
interval: weekly
open-pull-requests-limit: 99
rebase-strategy: disabled
# docusaurus-swizzled-warning package dependencies
# close-bot package dependencies
- package-ecosystem: npm
directory: '/.github/actions/docusaurus-swizzled-warning'
directory: '/.github/actions/close-bot'
schedule:
interval: weekly
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99

View File

@@ -10,21 +10,21 @@ org="shields-io"
# 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
# Attempt to apply the PR diff to the target branch
# This will fail if it does not merge cleanly
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"
git merge "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
flyctl launch --no-deploy --copy-config --name "$app" --region "$region" --org "$org"
echo $SECRETS | tr " " "\n" | flyctl secrets import --app "$app"
fi
# Deploy
flyctl deploy --app "$app" --regions "$region"
flyctl scale count 1 --app "$app" --yes
flyctl deploy --app "$app" --region "$region"
# Post a comment on the PR
app_url=$(flyctl status --app "$app" --json | jq -r .Hostname)

View File

@@ -1,22 +1,22 @@
name: Docusaurus swizzled component changes warning
name: Auto close
on:
pull_request:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
jobs:
docusaurus-swizzled-warning:
auto-close:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install action dependencies
run: cd .github/actions/docusaurus-swizzled-warning && npm ci
run: cd .github/actions/close-bot && npm ci
- uses: ./.github/actions/docusaurus-swizzled-warning
- uses: ./.github/actions/close-bot
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,25 +1,24 @@
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
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
with:
version: v0.9.1
- name: Set Git Short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v4
with:
context: .
push: false

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
environment: 'Review Apps'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- name: install jq

View File

@@ -1,73 +0,0 @@
name: Coveralls Code Coverage
on:
schedule:
- cron: '10 7 * * *'
# At 07:10, daily
workflow_dispatch:
jobs:
coveralls-code-coverage:
runs-on: ubuntu-latest
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:
# Even though we're currently deploying on Node 20, we run coverage test on Node 22
# to work around https://github.com/bcoe/v8-coverage/pull/2.
node-version: 22
env:
NPM_CONFIG_ENGINE_STRICT: 'false'
- name: Migrate DB
run: npm run migrate up
env:
POSTGRES_URL: postgresql://postgres:postgres@localhost:5432/ci_test
shell: bash
- name: Coverage for Main Tests
run: npm run coverage:test
env:
GH_TOKEN: '${{ secrets.GH_PAT }}'
POSTGRES_URL: postgresql://postgres:postgres@localhost:5432/ci_test
shell: bash
- name: Coverage for Service Tests
run: npm run coverage:test:services
continue-on-error: true
env:
RETRY_COUNT: 3
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 }}'
REDDIT_CLIENT_ID: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_ID }}'
REDDIT_CLIENT_SECRET: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_SECRET }}'
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 }}'
YOUTUBE_API_KEY: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
shell: bash
- name: Coveralls GitHub Action
uses: coverallsapp/github-action@v2

View File

@@ -24,7 +24,7 @@ jobs:
run: echo "::set-output name=date::$(date --rfc-3339=date)"
- name: Checkout branch "master"
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
ref: 'master'
@@ -35,39 +35,37 @@ jobs:
tag: server-${{ steps.date.outputs.date }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
with:
platforms: linux/amd64,linux/arm64
version: v0.9.1
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push snapshot release to DockerHub
uses: docker/build-push-action@v6
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
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
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push snapshot release to GHCR
uses: docker/build-push-action@v6
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/badges/shields:server-${{ steps.date.outputs.date }}
build-args: |
version=server-${{ steps.date.outputs.date }}

View File

@@ -1,72 +0,0 @@
name: Run Daily Tests
on:
schedule:
- cron: '45 3 * * *'
# At 03:45, daily
workflow_dispatch:
jobs:
daily-tests:
runs-on: ubuntu-latest
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: Core tests
if: always()
uses: ./.github/actions/core-tests
- name: Package tests
if: always()
uses: ./.github/actions/package-tests
- name: Integration Tests (with PAT)
if: always()
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GH_PAT }}'
- name: Run Service tests
run: npm run test:services -- --reporter json --reporter-option 'output=reports/service-tests.json'
if: always()
env:
RETRY_COUNT: 3
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 }}'
REDDIT_CLIENT_ID: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_ID }}'
REDDIT_CLIENT_SECRET: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_SECRET }}'
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 }}'
YOUTUBE_API_KEY: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
- name: Write Service Tests Markdown Summary
if: always()
run: |
echo '# Services' >> $GITHUB_STEP_SUMMARY
node scripts/mocha2md.js Report reports/service-tests.json >> $GITHUB_STEP_SUMMARY

View File

@@ -14,12 +14,12 @@ jobs:
if: github.actor != 'dependabot[bot]' && github.actor != 'repo-ranger[bot]'
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: Danger
run: npm run danger ci

View File

@@ -12,14 +12,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: Build
run: npm run build-docs

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
environment: 'Review Apps'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: superfly/flyctl-actions/setup-flyctl@master
@@ -35,11 +35,9 @@ jobs:
LIBRARIESIO_TOKENS=${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}
OBS_USER=${{ secrets.SERVICETESTS_OBS_USER }}
OBS_PASS=${{ secrets.SERVICETESTS_OBS_PASS }}
PEPY_KEY=${{ secrets.SERVICETESTS_PEPY_KEY }}
REDDIT_CLIENT_ID=${{ secrets.SERVICETESTS_REDDIT_CLIENT_ID }}
REDDIT_CLIENT_SECRET=${{ secrets.SERVICETESTS_REDDIT_CLIENT_SECRET }}
SL_INSIGHT_API_TOKEN=${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}
SL_INSIGHT_USER_UUID=${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}
TWITCH_CLIENT_ID=${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}
TWITCH_CLIENT_SECRET=${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}
WHEELMAP_TOKEN=${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}
YOUTUBE_API_KEY=${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Draft Release
uses: ./.github/actions/draft-release

View File

@@ -8,6 +8,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@v3

View File

@@ -12,13 +12,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
with:
version: v0.9.1
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -27,8 +29,7 @@ jobs:
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Build and push to DockerHub
id: docker_build_push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v4
with:
context: .
push: true
@@ -36,18 +37,15 @@ jobs:
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
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push to GHCR
uses: docker/build-push-action@v6
uses: docker/build-push-action@v4
with:
context: .
push: true

View File

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

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Cache Cypress binary
id: cache-cypress
uses: actions/cache@v4
uses: actions/cache@v3
env:
cache-name: cache-cypress
with:
@@ -26,24 +26,27 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
cypress: true
- name: Frontend build
run: GATSBY_BASE_URL=http://localhost:8080 npm run build
- name: Run tests
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: npm run e2e
run: npm run e2e-on-build
- name: Archive videos
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: videos
path: cypress/videos
- name: Archive screenshots
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: screenshots
path: cypress/screenshots

26
.github/workflows/test-frontend.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Frontend
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Frontend tests
uses: ./.github/actions/frontend-tests
- name: Frontend build
run: npm run build

View File

@@ -1,4 +1,4 @@
name: Integration@node 22
name: Integration@node 17
on:
pull_request:
types: [opened, reopened, synchronize]
@@ -8,7 +8,7 @@ on:
- 'dependabot/**'
jobs:
test-integration-22:
test-integration-17:
runs-on: ubuntu-latest
env:
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
@@ -30,12 +30,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 22
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'

View File

@@ -30,12 +30,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: Integration Tests (with PAT)
if: ${{ env.PAT_EXISTS == 'true' }}

View File

@@ -12,12 +12,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: ESLint
if: always()

View File

@@ -1,4 +1,4 @@
name: Main@node 22
name: Main@node 17
on:
pull_request:
types: [opened, reopened, synchronize]
@@ -8,16 +8,16 @@ on:
- 'dependabot/**'
jobs:
test-main-22:
test-main-17:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 22
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'

View File

@@ -17,12 +17,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: Core tests
uses: ./.github/actions/core-tests

View File

@@ -16,20 +16,25 @@ jobs:
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
engine-strict: 'false'
- node: '18'
- node: '20'
- node: '22'
engine-strict: 'true'
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install Node JS ${{ inputs.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
env:
CYPRESS_INSTALL_BINARY: 0
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
run: |
cd badge-maker
npm install

View File

@@ -13,27 +13,20 @@ jobs:
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
npm: '^9'
engine-strict: 'false'
- node: '18'
npm: '^9'
engine-strict: 'false'
- node: '20'
npm: '^10'
engine-strict: 'true'
- node: '22'
npm: '^11'
- node: '18'
engine-strict: 'false'
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ matrix.node }}
npm-version: ${{ matrix.npm }}
env:
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}

View File

@@ -1,23 +1,20 @@
name: Services@node 22
name: Services@node 17
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services-22:
test-services-17:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 22
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'
@@ -29,13 +26,11 @@ jobs:
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
reddit-client-id: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_ID }}'
reddit-client-secret: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_SECRET }}'
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)

View File

@@ -2,9 +2,6 @@ name: Services
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services:
@@ -12,12 +9,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: Service tests (triggered from local branch)
if: github.event.pull_request.head.repo.full_name == github.repository
@@ -27,13 +24,11 @@ jobs:
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
reddit-client-id: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_ID }}'
reddit-client-secret: '${{ secrets.SERVICETESTS_REDDIT_CLIENT_SECRET }}'
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)

View File

@@ -14,18 +14,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 16
- name: Check for new GitHub API version
run: node scripts/update-github-api.js
- name: Create Pull Request if config has changed
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v5
with:
token: '${{ secrets.GITHUB_TOKEN }}'
commit-message: Update GitHub API Version

17
.gitignore vendored
View File

@@ -50,6 +50,9 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
@@ -89,6 +92,10 @@ typings/
# Temporary build artifacts.
/build
.next
badge-examples.json
supported-features.json
service-definitions.yml
frontend/categories/*.yaml
# Local runtime configuration.
@@ -97,6 +104,11 @@ frontend/categories/*.yaml
# Template for the local runtime configuration.
!/config/local*.template.yml
# Gatsby
/frontend/.cache
/frontend/public
/public
# Cypress
/cypress/videos/
/cypress/screenshots/
@@ -109,8 +121,3 @@ flamegraph.html
# config file for node-pg-migrate
migrations-config.json
# Frontend/Docusaurus
frontend/.docusaurus
frontend/.cache-loader
/public

5
.mocharc-frontend.yml Normal file
View File

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

10
.nycrc-frontend.json Normal file
View File

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

View File

@@ -9,8 +9,10 @@
"**/test-helpers.js",
"**/*-test-helpers.js",
"**/*-fixtures.js",
"**/mocha-*.js",
"**/*.test-d.ts",
"dangerfile.js",
"gatsby-*.js",
"core/service-test-runner",
"core/got-test-client.js",
"services/**/*.tester.js",
@@ -21,9 +23,6 @@
"coverage",
"build",
".github",
"**/public/",
"cypress",
"frontend",
"migrations"
"**/public/"
]
}

View File

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

View File

@@ -4,330 +4,17 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2025-03-02
- time out long running requests more aggressively [#10833](https://github.com/badges/shields/issues/10833)
- Dependency updates
## server-2025-02-02
- Mark Stubs-only packages with [PypiTypes] badge [#10864](https://github.com/badges/shields/issues/10864)
- fix badge style when logo only [#10794](https://github.com/badges/shields/issues/10794)
- pass matching mime type to xmldom; test [dynamicxml] [#10830](https://github.com/badges/shields/issues/10830)
- allow [chromewebstore] size to contain decimal point [#10812](https://github.com/badges/shields/issues/10812)
- Add auth support to [Reddit] badges [#10790](https://github.com/badges/shields/issues/10790)
- Fixed mixed up Code climate endpoints [#10813](https://github.com/badges/shields/issues/10813)
- feat: add terraform registry providers and modules downloads [#10793](https://github.com/badges/shields/issues/10793)
- Renew [Mastodon] docs and improve parameter handling [#10789](https://github.com/badges/shields/issues/10789)
- Support [Matrix] summary endpoint [#10782](https://github.com/badges/shields/issues/10782)
- use metric() in [coderabbit] badge [#10779](https://github.com/badges/shields/issues/10779)
- cache matrix badges for 4 hours [#10778](https://github.com/badges/shields/issues/10778)
- Dependency updates
## server-2025-01-01
- Add [PypiTypes] badge [#10774](https://github.com/badges/shields/issues/10774)
- feat(endpoint-badge): add logoSize support [#10132](https://github.com/badges/shields/issues/10132)
- fix auto-sized logo sizes [#10764](https://github.com/badges/shields/issues/10764)
- Add [Coderabbit] PR Stats service and tests [#10749](https://github.com/badges/shields/issues/10749)
- add [PUB] downloads badge [#10745](https://github.com/badges/shields/issues/10745)
- Add [GitLab] Top Language Badge [#10750](https://github.com/badges/shields/issues/10750)
- provide a non-repository scoped version of [githubcodesearch] [#10733](https://github.com/badges/shields/issues/10733)
- [ReproducibleCentral] add Reproducible Central in Dependencies [#10705](https://github.com/badges/shields/issues/10705)
- Add ability to format bytes as metric or IEC; affects [bundlejs bundlephobia ChromeWebStoreSize CratesSize DockerSize GithubRepoSize GithubCodeSize GithubSize NpmUnpackedSize SpigetDownloadSize steam VisualStudioAppCenterReleasesSize whatpulse] [#10547](https://github.com/badges/shields/issues/10547)
- Dependency updates
## server-2024-12-01
- add [WingetVersion] Badge [#10245](https://github.com/badges/shields/issues/10245)
- Fix broken URL for pingpong.one [#10655](https://github.com/badges/shields/issues/10655)
- [npm] - Last update badge added [#10641](https://github.com/badges/shields/issues/10641)
- reduce overhead of NPM Last Update badge; test [npm] [#10666](https://github.com/badges/shields/issues/10666)
- Add YouTube-specific privacy notes [#10646](https://github.com/badges/shields/issues/10646)
- Dependency updates
## server-2024-11-02
- cleanly handle null or undefined result from jsonpath-plus [#10645](https://github.com/badges/shields/issues/10645)
- add content security policy header to SVG responses [#10642](https://github.com/badges/shields/issues/10642)
- [Scoop] Added scoop-license badge. [#10627](https://github.com/badges/shields/issues/10627)
- [Chromewebstore] Extension size & last updated [#10613](https://github.com/badges/shields/issues/10613)
- Deprecate HackageDeps service [#10618](https://github.com/badges/shields/issues/10618)
- Add [CratesUserDownloads] service and tester [#10619](https://github.com/badges/shields/issues/10619)
- [Snapcraft] - Added snapcraft last update badge [#10610](https://github.com/badges/shields/issues/10610)
- [GitHubHacktoberfest] 2024 support [#10612](https://github.com/badges/shields/issues/10612)
- add [homebrew] cask download badge [#10595](https://github.com/badges/shields/issues/10595)
- remove prefix v for commit hash version [#10597](https://github.com/badges/shields/issues/10597)
- [Maven] Added badge for Maven-Cenral last-update (#10301) [#10585](https://github.com/badges/shields/issues/10585)
- [DynamicXml] parse doc as html if served with text/html content type [#10607](https://github.com/badges/shields/issues/10607)
- Revert "Use old.stats.jenkins.io for JSON data (#10522)" [#10537](https://github.com/badges/shields/issues/10537)
- catch queries that cause TypeError [#10556](https://github.com/badges/shields/issues/10556)
- Dependency updates
## server-2024-09-25
This release includes an important security fix. See
- https://github.com/badges/shields/security/advisories/GHSA-rxvx-x284-4445
- https://github.com/badges/shields/issues/10553
for more details
- [dynamicjson dynamicyaml dynamictoml] switch to jsonpath-plus [#10551](https://github.com/badges/shields/issues/10551)
- [Snapcraft] license [#10520](https://github.com/badges/shields/issues/10520)
- deprecate [wheelmap] service [#10538](https://github.com/badges/shields/issues/10538)
- Use old.stats.jenkins.io for JSON data [#10522](https://github.com/badges/shields/issues/10522)
- catch xml ParseError [#10516](https://github.com/badges/shields/issues/10516)
- migrate [MozillaObservatory] to /scan endpoint [#10491](https://github.com/badges/shields/issues/10491)
- fix incorrect codecov config link [#10511](https://github.com/badges/shields/issues/10511)
- [OSSLifecycle OSSLifecycleRedirect] Add file_url param to pull from non-github sources [#10489](https://github.com/badges/shields/issues/10489)
- perf: improve logoSize performance [#10488](https://github.com/badges/shields/issues/10488)
- perf: faster `resetIconPosition` avoiding to parse path twice [#10497](https://github.com/badges/shields/issues/10497)
- perf: limit logoSize precision to 3 [#10521](https://github.com/badges/shields/issues/10521)
- Dependency updates
## server-2024-09-02
- Publish linux/amd64 docker images for snapshot builds [#10476](https://github.com/badges/shields/issues/10476)
- Fix Gitea not having credentials/authorizedOrigins in Docker environments [#10486](https://github.com/badges/shields/issues/10486)
- fix typo in pepy downloads [#10475](https://github.com/badges/shields/issues/10475)
- ignore a couple of docusaurus warnings [#10469](https://github.com/badges/shields/issues/10469)
- Use Ecologi API to power Treeware badges [#10467](https://github.com/badges/shields/issues/10467)
- move go version badge to platform support category [#10444](https://github.com/badges/shields/issues/10444)
- [Crates] Implement Dependents Badge [#10438](https://github.com/badges/shields/issues/10438)
- [Crates] Added crate size badge [#10421](https://github.com/badges/shields/issues/10421)
- Dependency updates
## server-2024-08-01
- send Cross-Origin-Resource-Policy header on all responses [#10420](https://github.com/badges/shields/issues/10420)
- migrate [MozillaObservatory] to new API [#10402](https://github.com/badges/shields/issues/10402)
- use metric() for [discord] and [revolt] badges [#10406](https://github.com/badges/shields/issues/10406)
- Cache text only static badges for longer [#10403](https://github.com/badges/shields/issues/10403)
- Fix [FreeCodeCampPoints] not found handling [#10377](https://github.com/badges/shields/issues/10377)
- Fix [Gitea] not found message [#10373](https://github.com/badges/shields/issues/10373)
- Deprecate [Bountysource] service [#10371](https://github.com/badges/shields/issues/10371)
- Sunset Shields custom logos [#10347](https://github.com/badges/shields/issues/10347)
- Use ellipsis when many versions returned for [ModrinthGameVersions] [#10350](https://github.com/badges/shields/issues/10350)
- deprecate [tokei] service [#9581](https://github.com/badges/shields/issues/9581)
- Add CF-Ray header value to Sentry errors if available [#10339](https://github.com/badges/shields/issues/10339)
- Use XML for Chocolatey, affects [Chocolatey Resharper PowershellGallery] [#10344](https://github.com/badges/shields/issues/10344)
- include github contributors badge in docs site [#10337](https://github.com/badges/shields/issues/10337)
- Dependency updates
## server-2024-07-01
- Add [AUR] Popularity Badge [#10304](https://github.com/badges/shields/issues/10304)
- fix npm badges when `maintainers` not in response [#10286](https://github.com/badges/shields/issues/10286)
- Expose `logoBase64` and `links` in badge-maker NPM package [#10283](https://github.com/badges/shields/issues/10283)
- Remove `logoPosition` [#10284](https://github.com/badges/shields/issues/10284)
- [MBIN] Add subscribers badge [#10270](https://github.com/badges/shields/issues/10270)
- Add [Docker] support for loong64 arch [#10241](https://github.com/badges/shields/issues/10241)
- Add puppetforge quality score badges [#10201](https://github.com/badges/shields/issues/10201)
- Dependency updates
## server-2024-06-01
- Remove namedLogo from defaultBadgeData of non-social badges [#10195](https://github.com/badges/shields/issues/10195)
- Update number of badges served each month [#10197](https://github.com/badges/shields/issues/10197)
- Delete old deprecated services [#10196](https://github.com/badges/shields/issues/10196)
- handle [BitbucketPipelines] responses with missing result key [#10163](https://github.com/badges/shields/issues/10163)
- Update description of GitHub commit status badge [#10198](https://github.com/badges/shields/issues/10198)
- chore: fix spelling of GitHub in badge descriptions [#10199](https://github.com/badges/shields/issues/10199)
- Add [GithubCheckRuns] service [#7759](https://github.com/badges/shields/issues/7759)
- feat: add Revolt badge [#10093](https://github.com/badges/shields/issues/10093)
- ensure color is string before calling toLowerCase() [#10129](https://github.com/badges/shields/issues/10129)
- instruct dependabot to monitor composite actions [#10139](https://github.com/badges/shields/issues/10139)
- run tests on node 22 [#10127](https://github.com/badges/shields/issues/10127)
- tweaks to libraries.io token pooling code [#10074](https://github.com/badges/shields/issues/10074)
- fix [pypi] status badge when package has no 'Development Status' classifier [#10107](https://github.com/badges/shields/issues/10107)
- clarify yml paths in server-secrets docs [#10106](https://github.com/badges/shields/issues/10106)
- Update region flag name in flyctl deploy command [#10134](https://github.com/badges/shields/issues/10134)
- Dependency updates
## server-2024-05-01
- [Hexpm] Fix badges for pre-release only versions [#10112](https://github.com/badges/shields/issues/10112)
- feat(logos): support auto-sizing mode [#9191](https://github.com/badges/shields/issues/9191) [#10110](https://github.com/badges/shields/issues/10110) [#10125](https://github.com/badges/shields/issues/10125)
- support setting pypiBaseUrl by environment variables and queryParameters; affects [pypi] [#10044](https://github.com/badges/shields/issues/10044)
- Add 0BSD license to licenseTypes and [PypiLicense] [#10092](https://github.com/badges/shields/issues/10092)
- Update Mastodon profile URL [#10082](https://github.com/badges/shields/issues/10082)
- [GitHubGoMod] Ignore comment after version (fixes #10079) [#10080](https://github.com/badges/shields/issues/10080)
- Perf: Librariesio repo dependencies [#10062](https://github.com/badges/shields/issues/10062)
- [Chocolatey Nuget] Fix "not found" error for chocolatey badge [#10060](https://github.com/badges/shields/issues/10060)
- Dependency updates
## server-2024-04-01
- improve performance of [GithubLastCommit] [GitlabLastCommit] [GiteaLastCommit] [#10046](https://github.com/badges/shields/issues/10046)
- [BitbucketLastCommit] Add Bitbucket last commit [#10043](https://github.com/badges/shields/issues/10043)
- [GithubLastCommit] [GitlabLastCommit] [GiteaLastCommit] Support file path for last commit [#10041](https://github.com/badges/shields/issues/10041)
- upgrade to docusaurus 3 [#9820](https://github.com/badges/shields/issues/9820)
- redirect [npm] /dt to /d18m [#10033](https://github.com/badges/shields/issues/10033)
- Add [JSR] version service [#10030](https://github.com/badges/shields/issues/10030)
- Add [snapcraft] version badge [#9976](https://github.com/badges/shields/issues/9976)
- Dependency updates
## server-2024-03-01
- feat(gitea): add last commit badge [#9995](https://github.com/badges/shields/issues/9995)
- [GithubCreatedAt] Add Created At Badge for Github [#9981](https://github.com/badges/shields/issues/9981)
- Added custom bucket url support for [Scoop] [#9984](https://github.com/badges/shields/issues/9984)
- [NpmUnpackedSize] Unpacked Size Badge [#9954](https://github.com/badges/shields/issues/9954)
- [Website] Render `status: down` badge if website is unresponsive [#9966](https://github.com/badges/shields/issues/9966)
- deprecate TAS [#9932](https://github.com/badges/shields/issues/9932)
- [GITEA] add forks, stars, issues and pr badges [#9923](https://github.com/badges/shields/issues/9923)
- tolerate missing short_version in [visualstudioappcenter] [#9951](https://github.com/badges/shields/issues/9951)
- [Crates] Only use non-yanked crate versions (ready for merge) [#9949](https://github.com/badges/shields/issues/9949)
- Dependency updates
## server-2024-02-01
- feat: added up_message and down_message to [uptimerobotstatus] [#9662](https://github.com/badges/shields/issues/9662)
- Add [Hangar] Badges [#9800](https://github.com/badges/shields/issues/9800)
- sort categories by title (except core) [#9888](https://github.com/badges/shields/issues/9888)
- Add Support for [Nostr] Followers [#9870](https://github.com/badges/shields/issues/9870)
- [thunderstore] replace experimental API usage with newly available v1 API [#9886](https://github.com/badges/shields/issues/9886)
- Update [Gitea] defaults to gitea.com [#9872](https://github.com/badges/shields/issues/9872)
- [crates] MSRV Badge [#9871](https://github.com/badges/shields/issues/9871)
- Add [galaxytoolshed] Version [#8249](https://github.com/badges/shields/issues/8249)
- fix default style docs for social badges [#9869](https://github.com/badges/shields/issues/9869)
- Dependency updates
## 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)
- fix [sourceforge] downloads [#9102](https://github.com/badges/shields/issues/9102)
- [SourceForge] Added badges for SourceForge [#9078](https://github.com/badges/shields/issues/9078)
- add fly.io PR review apps [#9069](https://github.com/badges/shields/issues/9069)
- remove redis token persistence backend [#9065](https://github.com/badges/shields/issues/9065)
- remove pull_request:edited trigger from most workflows [#9072](https://github.com/badges/shields/issues/9072)
- crates: Use `?include=` to reduce crates.io backend load [#9081](https://github.com/badges/shields/issues/9081)
- Export OpenAPI definitions from service examples; affects [dynamic endpoint static] [#8966](https://github.com/badges/shields/issues/8966)
- fix markdown summary if there are pending tests [#9068](https://github.com/badges/shields/issues/9068)
- Update the hint of simple-icons [#9054](https://github.com/badges/shields/issues/9054)
- Dependency updates
## server-2023-04-02

View File

@@ -8,7 +8,10 @@ financial contributions, issues, and pull requests!
### Financial contributions
We welcome financial contributions in full transparency on our
[open collective](https://opencollective.com/shields).
[open collective](https://opencollective.com/shields). Anyone can file an
expense. If the expense makes sense for the development of the community, it
will be "merged" into the ledger of our open collective by the core
contributors and the person who filed the expense will be reimbursed.
### Contributing code
@@ -74,20 +77,11 @@ don't see it, feel free to [open a new issue][open an issue].
[open an issue]: https://github.com/badges/shields/issues/new/choose
### Requesting new logos
We consume logos via [the SimpleIcons project][simple-icons github], and
encourage you to contribute logos there. Please review their
[guidance][simple-icons contributing] before doing so.
[simple-icons github]: https://github.com/simple-icons/simple-icons
[simple-icons contributing]: https://github.com/simple-icons/simple-icons/blob/develop/CONTRIBUTING.md
### Spreading the word
Feel free to star the repository. This will help increase the visibility of the project, therefore attracting more users and contributors to Shields!
We're also asking for [donations](https://opencollective.com/shields) from developers who use and love Shields, please spread the word!
We're also asking for [one-time \$10 donations](https://opencollective.com/shields) from developers who use and love Shields, please spread the word!
## Getting help
@@ -160,6 +154,10 @@ To run the integration tests:
There is a [High-level code walkthrough](doc/code-walkthrough.md) describing the layout of the project.
### 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!

View File

@@ -1,6 +1,4 @@
FROM node:20-alpine AS builder
RUN npm install -g "npm@^10"
FROM node:16-alpine AS Builder
RUN mkdir -p /usr/src/app
RUN mkdir /usr/src/app/private
@@ -10,19 +8,18 @@ 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@>=8"
# 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 \
&& npm prune --omit=dev --force \
&& rm -rf node_modules/.cache \
&& rm -rf frontend package-lock.json
RUN npm run build
RUN npm prune --production
RUN npm cache clean --force
# Use multi-stage build to reduce size
FROM node:20-alpine
FROM node:16-alpine
ARG version=dev
ENV DOCKER_SHIELDS_VERSION=$version
@@ -30,11 +27,11 @@ LABEL version=$version
LABEL fly.version=$version
# Run the server using production configs.
ENV NODE_ENV=production
ENV NODE_ENV production
WORKDIR /usr/src/app
COPY --from=builder --chown=0:0 /usr/src/app /usr/src/app
COPY --from=Builder --chown=0:0 /usr/src/app /usr/src/app
CMD ["node", "server"]
CMD node server
EXPOSE 80 443

View File

@@ -3,23 +3,28 @@
height="130">
</p>
<p align="center">
<a href="https://shields.io/community#backers" alt="Backers on Open Collective">
<a href="https://github.com/badges/shields/graphs/contributors" alt="Contributors">
<img src="https://img.shields.io/github/contributors/badges/shields" /></a>
<a href="#backers" alt="Backers on Open Collective">
<img src="https://img.shields.io/opencollective/backers/shields" /></a>
<a href="https://shields.io/community#sponsors" alt="Sponsors on Open Collective">
<a href="#sponsors" alt="Sponsors on Open Collective">
<img src="https://img.shields.io/opencollective/sponsors/shields" /></a>
<a href="https://github.com/badges/shields/pulse" alt="Activity">
<img src="https://img.shields.io/github/commit-activity/m/badges/shields" /></a>
<a href="https://github.com/badges/shields/discussions" alt="Discussions">
<img src="https://img.shields.io/github/discussions/badges/shields" /></a>
<a href="https://github.com/badges/shields/actions/workflows/daily-tests.yml">
<img src="https://img.shields.io/github/actions/workflow/status/badges/shields/daily-tests.yml?label=daily%20tests"
alt="Daily Tests Status"></a>
<a href="https://circleci.com/gh/badges/shields/tree/master">
<img src="https://img.shields.io/circleci/project/github/badges/shields/master" alt="build status"></a>
<a href="https://circleci.com/gh/badges/daily-tests">
<img src="https://img.shields.io/circleci/project/github/badges/daily-tests?label=service%20tests"
alt="service-test status"></a>
<a href="https://coveralls.io/github/badges/shields">
<img src="https://img.shields.io/coveralls/github/badges/shields"
alt="Code Coverage"></a>
alt="coverage"></a>
<a href="https://discord.gg/HjJCwm5">
<img src="https://img.shields.io/discord/308323056592486420?logo=discord&logoColor=white"
alt="Chat on Discord"></a>
<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,
@@ -27,7 +32,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 1.6 billion images and is used by some of the
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.
@@ -60,12 +65,12 @@ This repo hosts:
- amount of [Liberapay](https://liberapay.com/) donations per week: ![receives](https://img.shields.io/badge/receives-2.00%20USD%2Fweek-yellow)
- Python package downloads: ![downloads](https://img.shields.io/badge/downloads-13k%2Fmonth-brightgreen)
- Chrome Web Store extension rating: ![rating](https://img.shields.io/badge/rating-★★★★☆-brightgreen)
- Uptime Robot uptime percentage: ![uptime](https://img.shields.io/badge/uptime-100%25-brightgreen)
- [Uptime Robot](https://uptimerobot.com) percentage: ![uptime](https://img.shields.io/badge/uptime-100%25-brightgreen)
[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]: https://shields.io/#your-badge
### Quickstart
@@ -87,19 +92,14 @@ You can read a [tutorial on how to add a badge][tutorial].
[![GitHub issues by-label](https://img.shields.io/github/issues/badges/shields/good%20first%20issue)](https://github.com/badges/shields/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
If you intend on reporting or contributing a fix related to security vulnerabilities, please first refer to our [security policy][security].
[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
## Development
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/badges/shields?quickstart=1)
1. Install Node 20 or later. You can use the [package manager][] of your choice.
Tests need to pass in Node 20 and 22.
1. Install Node 16 or later. You can use the [package manager][] of your choice.
Tests need to pass in Node 16 and 17.
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,9 +107,9 @@ 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 prestart` or manually restart the server.
either run `npm run defs` or manually restart the server.
To debug a badge from the command line, run `npm run badge -- /npm/v/nock`.
It also works with full URLs like
@@ -133,7 +133,7 @@ snapshots, and `SNAPSHOT_UPDATE=1 npm run test:package` to update them.
The server can be configured to use [Sentry][] ([configuration][sentry configuration]) and [Prometheus][] ([configuration][prometheus configuration]).
Our [full test suite][full test suite] as well as [code coverage][code coverage] are run on a daily basis.
Daily tests, including a full run of the service tests and overall code coverage, are run via [badges/daily-tests][daily-tests].
[package manager]: https://nodejs.org/en/download/package-manager/
[gitpod]: https://www.gitpod.io/
@@ -142,11 +142,10 @@ Our [full test suite][full test suite] as well as [code coverage][code coverage]
[prometheus configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#prometheus
[sentry]: https://sentry.io/
[sentry configuration]: https://github.com/badges/shields/blob/master/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
[vs code]: https://code.visualstudio.com/
[full test suite]: https://github.com/badges/shields/actions/workflows/daily-tests.yml
[code coverage]: https://coveralls.io/github/badges/shields
## Hosting your own server
@@ -200,25 +199,34 @@ You can read more about [the project's inception][thread],
Maintainers:
- [calebcartwright](https://github.com/calebcartwright) (core team)
- [chris48s](https://github.com/chris48s) (core team)
- [Daniel15](https://github.com/Daniel15) (core team)
- [paulmelnikow](https://github.com/paulmelnikow) (core team)
- [platan](https://github.com/platan) (core team)
- [PyvesB](https://github.com/PyvesB) (core team)
- [RedSparr0w](https://github.com/RedSparr0w) (core team)
Operations:
- [calebcartwright](https://github.com/calebcartwright)
- [chris48s](https://github.com/chris48s)
- [jNullj](https://github.com/jnullj)
- [paulmelnikow](https://github.com/paulmelnikow)
- [PyvesB](https://github.com/PyvesB)
Alumni:
- [Daniel15](https://github.com/Daniel15)
- [espadrine](https://github.com/espadrine)
- [olivierlacan](https://github.com/olivierlacan)
- [platan](https://github.com/platan)
- [RedSparr0w](https://github.com/RedSparr0w)
## License
All assets and code are under the [CC0 LICENSE](LICENSE) and in the public
domain unless specified otherwise.
The assets in `logo/` are trademarks of their respective companies and are
under their terms and license.
## Community
Thanks to the people and companies who donate money, services or time to keep the project running. [https://shields.io/community](https://shields.io/community)

View File

@@ -23,7 +23,3 @@ Report security bugs in third-party modules to the person or team maintaining th
We aim to patch confirmed vulnerabilities within 90 days or less, disclosing the details of those vulnerabilities when a patch is published. We ask that you refrain from sharing your report with others while we work on our patch.
We may want to coordinate an advisory with you to be published simultaneously with the patch, but you are also welcome to self-disclose after 90 days if you prefer. We will never publish information about you or our communications with you without your permission.
## Bounties
Everyone who works on shields is an unpaid volunteer. That includes the core team, contributors and people who report security vulnerabilities. This means we are unable to offer bug or security bounties.

56
app.json Normal file
View File

@@ -0,0 +1,56 @@
{
"name": "Shields",
"description": "Concise, consistent, and legible badges in SVG and raster format.",
"keywords": ["badge", "github", "svg", "status"],
"website": "https://shields.io/",
"repository": "https://github.com/badges/shields",
"logo": "https://shields.io/favicon.png",
"env": {
"CYPRESS_INSTALL_BINARY": {
"description": "Disable the cypress binary installation",
"value": "0",
"required": false
},
"HUSKY_SKIP_INSTALL": {
"description": "Skip the husky git hook setup",
"value": "1",
"required": false
},
"WHEELMAP_TOKEN": {
"description": "Configure the token to be used for the Wheelmap service.",
"required": false
},
"GH_TOKEN": {
"description": "Configure the token to be used for the GitHub services.",
"required": false
},
"TWITCH_CLIENT_ID": {
"description": "Configure the client id to be used for the Twitch service.",
"required": false
},
"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": {
"web": {
"quantity": 1,
"size": "free"
}
}
}

View File

@@ -1,24 +1,8 @@
# Changelog
## Unreleased
## 4.0.0 [WIP]
- Switching to using `href`s instead of the old `xlink:href` syntax
## 4.1.0
### Features
- Add `idSuffix` param. This can be used to ensure every element id within the SVG is unique
## 4.0.0
### Breaking Changes
- Drop compatibility with Node < 16
### Features
- Add `links` and `logoBase64` params
- Drop compatibility with Node < 14
## 3.3.1
@@ -291,7 +275,7 @@ badge.loadFont('/path/to/Verdana.ttf', err => {
{ text: ['build', 'passed'], colorscheme: 'green', template: 'flat' },
(svg, err) => {
// svg is a string containing your badge
},
}
)
})
```

View File

@@ -16,7 +16,7 @@ npm install badge-maker
```sh
npm install -g badge-maker
badge build passed :brightgreen > mybadge.svg
badge build passed :green > mybadge.svg
```
### As a library
@@ -37,7 +37,7 @@ import { makeBadge, ValidationError } from 'badge-maker'
const format = {
label: 'build',
message: 'passed',
color: 'brightgreen',
color: 'green',
}
const svg = makeBadge(format)
@@ -67,17 +67,10 @@ The format is the following:
message: 'passed', // (Required) Badge message
labelColor: '#555', // (Optional) Label color
color: '#4c1', // (Optional) Message color
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iOCIgZmlsbD0iI2IxY2U1NiIvPjxwYXRoIGQ9Ik04IDBoMjR2NjRIOGMtNC40MzIgMC04LTMuNTY4LTgtOFY4YzAtNC40MzIgMy41NjgtOCA4LTh6IiBmaWxsPSIjNWQ1ZDVkIi8+PC9zdmc+', // (Optional) Any custom logo can be passed in a URL parameter by base64 encoding
links: ['https://example.com', 'https://example.com'], // (Optional) Links array of maximum two links
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
// Each offers a different visual design.
style: 'flat',
// (Optional) A string with only letters, numbers, -, and _. This can be used
// to ensure every element id within the SVG is unique and prevent CSS
// cross-contamination when the SVG badge is rendered inline in HTML pages.
idSuffix: 'dd'
}
```

View File

@@ -4,9 +4,6 @@ interface Format {
labelColor?: string
color?: string
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social'
logoBase64?: string
links?: Array<string>
idSuffix?: string
}
export declare class ValidationError extends Error {}

View File

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

View File

@@ -1,16 +1,13 @@
'use strict'
import path from 'path'
import { fileURLToPath } from 'url'
import { spawn } from 'child-process-promise'
import { expect, use } from 'chai'
import sinonChai from 'sinon-chai'
use(sinonChai)
const dirName = path.dirname(fileURLToPath(import.meta.url))
const path = require('path')
const { spawn } = require('child-process-promise')
const { expect, use } = require('chai')
use(require('chai-string'))
use(require('sinon-chai'))
function runCli(args) {
return spawn('node', [path.join(dirName, 'badge-cli.js'), ...args], {
return spawn('node', [path.join(__dirname, 'badge-cli.js'), ...args], {
capture: ['stdout'],
})
}
@@ -18,7 +15,7 @@ function runCli(args) {
describe('The CLI', function () {
it('should provide a help message', async function () {
const { stdout } = await runCli([])
expect(stdout.startsWith('Usage')).to.be.true
expect(stdout).to.startWith('Usage')
})
it('should produce default badges', async function () {

View File

@@ -62,14 +62,14 @@ function getLogoElement({ logo, horizPadding, badgeHeight, logoWidth }) {
y: 0.5 * (badgeHeight - logoHeight),
width: logoWidth,
height: logoHeight,
href: logo,
'xlink:href': logo,
},
})
}
function renderBadge(
{ links, leftWidth, rightWidth, height, accessibleText },
content,
content
) {
const width = leftWidth + rightWidth
const leftLink = links[0]
@@ -83,12 +83,13 @@ function renderBadge(
? new XmlElement({
name: 'a',
content,
attrs: { target: '_blank', href: leftLink },
attrs: { target: '_blank', 'xlink:href': leftLink },
})
: new ElementList({ content })
const svgAttrs = {
xmlns: 'http://www.w3.org/2000/svg',
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
width,
height,
}
@@ -127,7 +128,6 @@ class Badge {
logoPadding,
color = '#4c1',
labelColor,
idSuffix = '',
}) {
const horizPadding = 5
const hasLogo = !!logo
@@ -159,7 +159,7 @@ class Badge {
}
let rightWidth = messageWidth + 2 * horizPadding
if (hasLogo && !hasLabel) {
rightWidth += totalLogoWidth + (message.length ? horizPadding - 1 : 0)
rightWidth += totalLogoWidth + horizPadding - 1
}
const width = leftWidth + rightWidth
@@ -178,7 +178,6 @@ class Badge {
this.label = label
this.message = message
this.accessibleText = accessibleText
this.idSuffix = idSuffix
this.logoElement = getLogoElement({
logo,
@@ -243,7 +242,7 @@ class Badge {
return new XmlElement({
name: 'a',
content: [rect, shadow, text],
attrs: { target: '_blank', href: link },
attrs: { target: '_blank', 'xlink:href': link },
})
}
@@ -287,7 +286,7 @@ class Badge {
},
}),
],
attrs: { id: `r${this.idSuffix}` },
attrs: { id: 'r' },
})
}
@@ -314,7 +313,7 @@ class Badge {
attrs: {
width: this.width,
height: this.constructor.height,
fill: `url(#s${this.idSuffix})`,
fill: 'url(#s)',
},
})
const content = withGradient
@@ -380,14 +379,14 @@ class Plastic extends Badge {
attrs: { offset: 1, 'stop-color': '#000', 'stop-opacity': '.5' },
}),
],
attrs: { id: `s${this.idSuffix}`, x2: 0, y2: '100%' },
attrs: { id: 's', x2: 0, y2: '100%' },
})
const clipPath = this.getClipPathElement(4)
const backgroundGroup = this.getBackgroundGroupElement({
withGradient: true,
attrs: { 'clip-path': `url(#r${this.idSuffix})` },
attrs: { 'clip-path': 'url(#r)' },
})
return renderBadge(
@@ -398,7 +397,7 @@ class Plastic extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement],
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -429,14 +428,14 @@ class Flat extends Badge {
attrs: { offset: 1, 'stop-opacity': '.1' },
}),
],
attrs: { id: `s${this.idSuffix}`, x2: 0, y2: '100%' },
attrs: { id: 's', x2: 0, y2: '100%' },
})
const clipPath = this.getClipPathElement(3)
const backgroundGroup = this.getBackgroundGroupElement({
withGradient: true,
attrs: { 'clip-path': `url(#r${this.idSuffix})` },
attrs: { 'clip-path': 'url(#r)' },
})
return renderBadge(
@@ -447,7 +446,7 @@ class Flat extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement],
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -479,7 +478,7 @@ class FlatSquare extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[backgroundGroup, this.foregroundGroupElement],
[backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -493,7 +492,6 @@ function social({
logoPadding,
color = '#4c1',
labelColor = '#555',
idSuffix = '',
}) {
// Social label is styled with a leading capital. Convert to caps here so
// width can be measured using the correct characters.
@@ -567,9 +565,9 @@ function social({
const rect = new XmlElement({
name: 'rect',
attrs: {
id: `llink${idSuffix}`,
id: 'llink',
stroke: '#d5d5d5',
fill: `url(#a${idSuffix})`,
fill: 'url(#a)',
x: '.5',
y: '.5',
width: labelRectWidth,
@@ -604,7 +602,7 @@ function social({
? new XmlElement({
name: 'a',
content: [shadow, text, rect],
attrs: { target: '_blank', href: leftLink },
attrs: { target: '_blank', 'xlink:href': leftLink },
})
: new ElementList({ content: [rect, shadow, text] })
}
@@ -642,7 +640,7 @@ function social({
name: 'text',
content: [message],
attrs: {
id: `rlink${idSuffix}`,
id: 'rlink',
x: messageTextX,
y: 140,
transform: FONT_SCALE_DOWN_VALUE,
@@ -654,7 +652,7 @@ function social({
? new XmlElement({
name: 'a',
content: [rect, shadow, text],
attrs: { target: '_blank', href: rightLink },
attrs: { target: '_blank', 'xlink:href': rightLink },
})
: new ElementList({ content: [shadow, text] })
}
@@ -662,7 +660,7 @@ function social({
const style = new XmlElement({
name: 'style',
content: [
`a:hover #llink${idSuffix}{fill:url(#b${idSuffix});stroke:#ccc}a:hover #rlink${idSuffix}{fill:#4183c4}`,
'a:hover #llink{fill:url(#b);stroke:#ccc}a:hover #rlink{fill:#4183c4}',
],
})
const gradients = new ElementList({
@@ -683,7 +681,7 @@ function social({
attrs: { offset: 1, 'stop-opacity': '.1' },
}),
],
attrs: { id: `a${idSuffix}`, x2: 0, y2: '100%' },
attrs: { id: 'a', x2: 0, y2: '100%' },
}),
new XmlElement({
name: 'linearGradient',
@@ -697,7 +695,7 @@ function social({
attrs: { offset: 1, 'stop-opacity': '.1' },
}),
],
attrs: { id: `b${idSuffix}`, x2: 0, y2: '100%' },
attrs: { id: 'b', x2: 0, y2: '100%' },
}),
],
})
@@ -750,7 +748,7 @@ function social({
accessibleText,
height: externalHeight,
},
[style, gradients, backgroundGroup, logoElement, foregroundGroup],
[style, gradients, backgroundGroup, logoElement, foregroundGroup]
)
}
@@ -803,13 +801,11 @@ function forTheBadge({
// there is no label. When `needsLabelRect` is true, render a label rect and a
// message rect; when false, only a message rect.
const hasLabel = Boolean(label.length)
const noText = !hasLabel && !message
const needsLabelRect = hasLabel || (logo && labelColor)
const gutter = noText ? LOGO_TEXT_GUTTER - LOGO_MARGIN : LOGO_TEXT_GUTTER
let logoMinX, labelTextMinX
if (logo) {
logoMinX = LOGO_MARGIN
labelTextMinX = logoMinX + logoWidth + gutter
labelTextMinX = logoMinX + logoWidth + LOGO_TEXT_GUTTER
} else {
labelTextMinX = TEXT_MARGIN
}
@@ -824,8 +820,9 @@ function forTheBadge({
messageRectWidth = 2 * TEXT_MARGIN + messageTextWidth
} else {
if (logo) {
messageTextMinX = TEXT_MARGIN + logoWidth + gutter
messageRectWidth = 2 * TEXT_MARGIN + logoWidth + gutter + messageTextWidth
messageTextMinX = TEXT_MARGIN + logoWidth + LOGO_TEXT_GUTTER
messageRectWidth =
2 * TEXT_MARGIN + logoWidth + LOGO_TEXT_GUTTER + messageTextWidth
} else {
messageTextMinX = TEXT_MARGIN
messageRectWidth = 2 * TEXT_MARGIN + messageTextWidth
@@ -868,7 +865,7 @@ function forTheBadge({
content: [rect, text],
attrs: {
target: '_blank',
href: leftLink,
'xlink:href': leftLink,
},
})
} else {
@@ -907,7 +904,7 @@ function forTheBadge({
content: [rect, text],
attrs: {
target: '_blank',
href: rightLink,
'xlink:href': rightLink,
},
})
} else {
@@ -981,7 +978,7 @@ function forTheBadge({
accessibleText: createAccessibleText({ label, message }),
height: BADGE_HEIGHT,
},
[backgroundGroup, foregroundGroup],
[backgroundGroup, foregroundGroup]
)
}

View File

@@ -49,7 +49,7 @@ function normalizeColor(color) {
} else if (color in aliases) {
return aliases[color]
} else if (isHexColor(color)) {
return `#${color.toString().toLowerCase()}`
return `#${color.toLowerCase()}`
} else if (isCSSColor(color)) {
return color.toLowerCase()
} else {

View File

@@ -27,8 +27,6 @@ test(normalizeColor, () => {
given('blue').expect('blue')
given('4c1').expect('#4c1')
given('f00f00').expect('#f00f00')
given('111111').expect('#111111')
given(111111).expect('#111111')
given('ABC123').expect('#abc123')
given('#ccc').expect('#ccc')
given('#fffe').expect('#fffe')
@@ -77,7 +75,7 @@ test(toSvgColor, () => {
given('papayawhip').expect('papayawhip')
given('purple').expect('purple')
forCases([given(''), given(undefined), given('not-a-color')]).expect(
undefined,
undefined
)
given('lightgray').expect('#9f9f9f')
given('informational').expect('#007ec6')

View File

@@ -1,5 +0,0 @@
const DEFAULT_LOGO_HEIGHT = 14
module.exports = {
DEFAULT_LOGO_HEIGHT,
}

View File

@@ -16,30 +16,13 @@ function _validate(format) {
throw new ValidationError('Field `message` is required')
}
const stringFields = ['labelColor', 'color', 'message', 'label', 'logoBase64']
const stringFields = ['labelColor', 'color', 'message', 'label']
stringFields.forEach(function (field) {
if (field in format && typeof format[field] !== 'string') {
throw new ValidationError(`Field \`${field}\` must be of type string`)
}
})
if ('links' in format) {
if (!Array.isArray(format.links)) {
throw new ValidationError('Field `links` must be an array of strings')
} else {
if (format.links.length > 2) {
throw new ValidationError(
'Field `links` must not have more than 2 elements',
)
}
format.links.forEach(function (field) {
if (typeof field !== 'string') {
throw new ValidationError('Field `links` must be an array of strings')
}
})
}
}
const styleValues = [
'plastic',
'flat',
@@ -49,37 +32,21 @@ function _validate(format) {
]
if ('style' in format && !styleValues.includes(format.style)) {
throw new ValidationError(
`Field \`style\` must be one of (${styleValues.toString()})`,
)
}
if ('idSuffix' in format && !/^[a-zA-Z0-9\-_]*$/.test(format.idSuffix)) {
throw new ValidationError(
'Field `idSuffix` must contain only numbers, letters, -, and _',
`Field \`style\` must be one of (${styleValues.toString()})`
)
}
}
function _clean(format) {
const expectedKeys = [
'label',
'message',
'labelColor',
'color',
'style',
'logoBase64',
'links',
'idSuffix',
]
const expectedKeys = ['label', 'message', 'labelColor', 'color', 'style']
const cleaned = {}
Object.keys(format).forEach(key => {
if (format[key] != null && key === 'logoBase64') {
cleaned.logo = format[key]
} else if (format[key] != null && expectedKeys.includes(key)) {
if (format[key] != null && expectedKeys.includes(key)) {
cleaned[key] = format[key]
} else {
throw new ValidationError(
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`,
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
)
}
})
@@ -98,10 +65,7 @@ function _clean(format) {
* @param {string} format.message (Required) Badge message (e.g: 'passing')
* @param {string} format.labelColor (Optional) Label color
* @param {string} format.color (Optional) Message color
* @param {string} format.style (Optional) Visual style (e.g: 'flat')
* @param {string} format.logoBase64 (Optional) Logo data URL
* @param {Array} format.links (Optional) Links array (e.g: ['https://example.com', 'https://example.com'])
* @param {string} format.idSuffix (Optional) Suffix for IDs, e.g. 1, 2, and 3 for three invocations that will be used on the same page.
* @param {string} format.style (Optional) Visual style e.g: 'flat'
* @returns {string} Badge in SVG format
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
*/

View File

@@ -1,7 +1,7 @@
'use strict'
import { expect } from 'chai'
import { makeBadge, ValidationError } from './index.js'
const { expect } = require('chai')
const { makeBadge, ValidationError } = require('.')
describe('makeBadge function', function () {
it('should produce badge with valid input', async function () {
@@ -10,12 +10,12 @@ describe('makeBadge function', function () {
makeBadge({
label: 'build',
message: 'passed',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
message: 'passed',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
@@ -23,23 +23,8 @@ describe('makeBadge function', function () {
message: 'passed',
color: 'green',
style: 'flat',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
label: 'build',
message: 'passed',
color: 'green',
style: 'flat',
labelColor: 'blue',
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
links: ['https://example.com', 'https://example.com'],
}),
)
.to.satisfy(isSvg)
// explicitly make an assertion about logoBase64
// this param is not a straight passthrough
.and.to.include('data:image/svg+xml;base64,PHN2ZyB4bWxu')
})
it('should throw a ValidationError with invalid inputs', function () {
@@ -47,65 +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', logoBase64: 7 }),
).to.throw(ValidationError, 'Field `logoBase64` must be of type string')
expect(() =>
makeBadge({ label: 'build', message: 'passed', links: 'test' }),
).to.throw(ValidationError, 'Field `links` must be an array of strings')
expect(() =>
makeBadge({ label: 'build', message: 'passed', links: [1] }),
).to.throw(ValidationError, 'Field `links` must be an array of strings')
expect(() =>
makeBadge({ label: 'build', message: 'passed', links: ['1', '2', '3'] }),
).to.throw(
ValidationError,
'Field `links` must not have more than 2 elements',
)
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)',
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', idSuffix: '\\' }),
).to.throw(
ValidationError,
'Field `idSuffix` must contain only numbers, letters, -, and _',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
})
})

View File

@@ -3,7 +3,6 @@
const { normalizeColor, toSvgColor } = require('./color')
const badgeRenderers = require('./badge-renderers')
const { stripXmlWhitespace } = require('./xml')
const { DEFAULT_LOGO_HEIGHT } = require('./constants')
/*
note: makeBadge() is fairly thinly wrapped so if we are making changes here
@@ -17,10 +16,9 @@ module.exports = function makeBadge({
color,
labelColor,
logo,
logoSize,
logoPosition,
logoWidth,
links = ['', ''],
idSuffix,
}) {
// String coercion and whitespace removal.
label = `${label}`.trim()
@@ -39,7 +37,6 @@ module.exports = function makeBadge({
link: links,
name: label,
value: message,
idSuffix,
})
}
@@ -48,7 +45,7 @@ module.exports = function makeBadge({
throw new Error(`Unknown badge style: '${style}'`)
}
logoWidth = +logoWidth || (logo ? DEFAULT_LOGO_HEIGHT : 0)
logoWidth = +logoWidth || (logo ? 14 : 0)
return stripXmlWhitespace(
render({
@@ -56,12 +53,11 @@ module.exports = function makeBadge({
message,
links,
logo,
logoPosition,
logoWidth,
logoSize,
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),
idSuffix,
}),
})
)
}

View File

@@ -1,13 +1,13 @@
'use strict'
import { test, given, forCases } from 'sazerac'
import { expect } from 'chai'
import snapshot from 'snap-shot-it'
import prettier from 'prettier'
import makeBadge from './make-badge.js'
const { test, given, forCases } = require('sazerac')
const { expect } = require('chai')
const snapshot = require('snap-shot-it')
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 +17,7 @@ function testColor(color = '', colorAttr = 'color') {
message: 'Bob',
[colorAttr]: color,
format: 'json',
}),
})
).color
}
@@ -67,7 +67,7 @@ describe('The badge generator', function () {
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus'),
given('cactus')
).expect(undefined)
})
})
@@ -87,8 +87,8 @@ describe('The badge generator', function () {
.and.to.include('grown')
})
it('should match snapshot', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshot', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -138,14 +138,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 +155,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,20 +167,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
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',
@@ -189,8 +177,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',
@@ -200,8 +188,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',
@@ -212,8 +200,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',
@@ -224,8 +212,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -235,8 +223,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -248,8 +236,8 @@ describe('The badge generator', function () {
})
describe('"flat-square" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -259,8 +247,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',
@@ -271,21 +259,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
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',
@@ -294,8 +269,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',
@@ -305,8 +280,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',
@@ -317,8 +292,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',
@@ -329,8 +304,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -340,8 +315,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -353,8 +328,8 @@ describe('The badge generator', function () {
})
describe('"plastic" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -364,8 +339,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',
@@ -376,21 +351,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'plastic',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
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',
@@ -399,8 +361,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',
@@ -410,8 +372,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',
@@ -422,8 +384,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',
@@ -434,8 +396,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -445,8 +407,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -466,7 +428,7 @@ describe('The badge generator', function () {
message: 1999,
format: 'svg',
style: 'for-the-badge',
}),
})
)
.to.include('1998')
.and.to.include('1999')
@@ -479,14 +441,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',
@@ -496,8 +458,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',
@@ -508,21 +470,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
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',
@@ -531,8 +480,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',
@@ -542,8 +491,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',
@@ -554,8 +503,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',
@@ -566,8 +515,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -577,8 +526,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -597,7 +546,7 @@ describe('The badge generator', function () {
message: 'some-value',
format: 'svg',
style: 'social',
}),
})
)
.to.include('Some-key')
.and.to.include('some-value')
@@ -611,14 +560,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',
@@ -628,8 +577,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',
@@ -640,21 +589,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'social',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
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',
@@ -663,8 +599,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',
@@ -674,8 +610,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',
@@ -686,8 +622,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',
@@ -700,8 +636,8 @@ describe('The badge generator', function () {
})
describe('badges with logos should always produce the same badge', function () {
it('default badge with logo', async function () {
await expectBadgeToMatchSnapshot({
it('badge with logo', function () {
expectBadgeToMatchSnapshot({
label: 'label',
message: 'message',
format: 'svg',
@@ -709,56 +645,4 @@ describe('The badge generator', function () {
})
})
})
describe('badges with logo-only should always produce the same badge', function () {
it('flat badge, logo-only', async function () {
await expectBadgeToMatchSnapshot({
label: '',
message: '',
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
style: 'flat',
})
})
it('flat-square badge, logo-only', async function () {
await expectBadgeToMatchSnapshot({
label: '',
message: '',
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
style: 'flat-square',
})
})
it('for-the-badge badge, logo-only', async function () {
await expectBadgeToMatchSnapshot({
label: '',
message: '',
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
style: 'for-the-badge',
})
})
it('social badge, logo-only', async function () {
await expectBadgeToMatchSnapshot({
label: '',
message: '',
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
style: 'social',
})
})
it('plastic badge, logo-only', async function () {
await expectBadgeToMatchSnapshot({
label: '',
message: '',
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
style: 'plastic',
})
})
})
})

View File

@@ -35,7 +35,7 @@ class XmlElement {
* 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
* string and XmlElement objects. If content is `[]` or ommitted the
* element will be rendered as a self-closing element.
* @param {object} [attrs.attrs={}]
* Object representing the tag's attributes as name/value pairs
@@ -66,7 +66,7 @@ class XmlElement {
})
.join(' ')
return stripXmlWhitespace(
`<${this.name}${attrsStr}>${content}</${this.name}>`,
`<${this.name}${attrsStr}>${content}</${this.name}>`
)
}
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
@@ -88,7 +88,7 @@ class ElementList {
typeof el.render === 'function'
? acc + el.render()
: acc + escapeXml(el),
'',
''
)
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "badge-maker",
"version": "4.1.0",
"version": "3.3.1",
"description": "Shields.io badge library",
"keywords": [
"GitHub",
@@ -26,7 +26,8 @@
"badge": "lib/badge-cli.js"
},
"engines": {
"node": ">=16"
"node": ">= 14",
"npm": ">= 6"
},
"collective": {
"type": "opencollective",

View File

@@ -35,8 +35,6 @@ public:
authorizedOrigins: 'BITBUCKET_SERVER_ORIGINS'
drone:
authorizedOrigins: 'DRONE_ORIGINS'
gitea:
authorizedOrigins: 'GITEA_ORIGINS'
github:
baseUri: 'GITHUB_URL'
debug:
@@ -54,8 +52,6 @@ public:
authorizedOrigins: 'NPM_ORIGINS'
obs:
authorizedOrigins: 'OBS_ORIGINS'
pypi:
baseUri: 'PYPI_URL'
sonar:
authorizedOrigins: 'SONAR_ORIGINS'
teamcity:
@@ -81,12 +77,8 @@ private:
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'
gitea_token: 'GITEA_TOKEN'
gh_client_id: 'GH_CLIENT_ID'
gh_client_secret: 'GH_CLIENT_SECRET'
gh_token: 'GH_TOKEN'
@@ -102,11 +94,7 @@ private:
obs_user: 'OBS_USER'
obs_pass: 'OBS_PASS'
redis_url: 'REDIS_URL'
opencollective_token: 'OPENCOLLECTIVE_TOKEN'
pepy_key: 'PEPY_KEY'
postgres_url: 'POSTGRES_URL'
reddit_client_id: 'REDDIT_CLIENT_ID'
reddit_client_secret: 'REDDIT_CLIENT_SECRET'
sentry_dsn: 'SENTRY_DSN'
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'
sl_insight_apiToken: 'SL_INSIGHT_API_TOKEN'
@@ -116,6 +104,7 @@ private:
teamcity_pass: 'TEAMCITY_PASS'
twitch_client_id: 'TWITCH_CLIENT_ID'
twitch_client_secret: 'TWITCH_CLIENT_SECRET'
wheelmap_token: 'WHEELMAP_TOKEN'
influx_username: 'INFLUX_USERNAME'
influx_password: 'INFLUX_PASSWORD'
weblate_api_key: 'WEBLATE_API_KEY'

View File

@@ -22,8 +22,6 @@ public:
restApiVersion: '2022-11-28'
obs:
authorizedOrigins: 'https://api.opensuse.org'
pypi:
baseUri: 'https://pypi.org'
weblate:
authorizedOrigins: 'https://hosted.weblate.org'
trace: false

View File

@@ -1,12 +1,9 @@
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: ...
reddit_client_id: ...
reddit_client_secret: ...
sentry_dsn: ...
shields_secret: ...
sl_insight_userUuid: ...

View File

@@ -4,13 +4,10 @@ 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: '...'
reddit_client_id: '...'
reddit_client_secret: '...'
twitch_client_id: '...'
twitch_client_secret: '...'
weblate_api_key: '...'

View File

@@ -22,4 +22,4 @@ public:
rasterUrl: 'https://raster.shields.io'
userAgentBase: 'Shields.io'
requireCloudflare: true
requestTimeoutSeconds: 8
requestTimeoutSeconds: 20

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

@@ -0,0 +1,94 @@
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 encodeField(s: string): string
export function staticBadgeUrl({
baseUrl,
label,
message,
labelColor,
color,
style,
namedLogo,
format,
links,
}: {
baseUrl?: string
label: string
message: string
labelColor?: string
color?: string
style?: string
namedLogo?: string
format?: string
links?: string[]
}): string
export function queryStringStaticBadgeUrl({
baseUrl,
label,
message,
color,
labelColor,
style,
namedLogo,
logoColor,
logoWidth,
logoPosition,
format,
}: {
baseUrl?: string
label: string
message: string
color?: string
labelColor?: string
style?: string
namedLogo?: string
logoColor?: string
logoWidth?: number
logoPosition?: number
format?: string
}): string
export function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format,
}: {
baseUrl?: string
datatype: string
label: string
dataUrl: string
query: string
prefix: string
suffix: string
color?: string
style?: string
format?: string
}): string
export function rasterRedirectUrl(
{ rasterUrl }: { rasterUrl: string },
badgeUrl: string
): string

View File

@@ -1,5 +1,119 @@
// Avoid "Attempted import error: 'URL' is not exported from 'url'" in frontend.
import url from 'url'
import queryString from 'query-string'
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 encodeField(s) {
return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__'))
}
function staticBadgeUrl({
baseUrl = '',
label,
message,
labelColor,
color = 'lightgray',
style,
namedLogo,
format = '',
links = [],
}) {
const path = [label, message, color].map(encodeField).join('-')
const outQueryString = queryString.stringify({
labelColor,
style,
logo: namedLogo,
link: links,
})
const outExt = format.length ? `.${format}` : ''
const suffix = outQueryString ? `?${outQueryString}` : ''
return `${baseUrl}/badge/${path}${outExt}${suffix}`
}
function queryStringStaticBadgeUrl({
baseUrl = '',
label,
message,
color,
labelColor,
style,
namedLogo,
logoColor,
logoWidth,
logoPosition,
format = '',
}) {
// schemaVersion could be a parameter if we iterate on it,
// for now it's hardcoded to the only supported version.
const schemaVersion = '1'
const suffix = `?${queryString.stringify({
label,
message,
color,
labelColor,
style,
logo: namedLogo,
logoColor,
logoWidth,
logoPosition,
})}`
const outExt = format.length ? `.${format}` : ''
return `${baseUrl}/static/v${schemaVersion}${outExt}${suffix}`
}
function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format = '',
}) {
const outExt = format.length ? `.${format}` : ''
const queryParams = {
label,
url: dataUrl,
query,
style,
}
if (color) {
queryParams.color = color
}
if (prefix) {
queryParams.prefix = prefix
}
if (suffix) {
queryParams.suffix = suffix
}
const outQueryString = queryString.stringify(queryParams)
return `${baseUrl}/badge/dynamic/${datatype}${outExt}?${outQueryString}`
}
function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
// Ensure we're always using the `rasterUrl` by using just the path from
@@ -10,4 +124,11 @@ function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
return result
}
export { rasterRedirectUrl }
export {
badgeUrlFromPath,
encodeField,
staticBadgeUrl,
queryStringStaticBadgeUrl,
dynamicBadgeUrl,
rasterRedirectUrl,
}

View File

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

View File

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

View File

@@ -1,13 +1,5 @@
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'
let jwtCache = Object.create(null)
import { InvalidParameter } from './errors.js'
class AuthHelper {
constructor(
@@ -19,7 +11,7 @@ class AuthHelper {
isRequired = false,
defaultToEmptyStringForUser = false,
},
config,
config
) {
if (!userKey && !passKey) {
throw Error('Expected userKey or passKey to be set')
@@ -95,7 +87,7 @@ class AuthHelper {
}
}
isAllowedOrigin(url) {
shouldAuthenticateRequest({ url, options = {} }) {
let parsed
try {
parsed = new URL(url)
@@ -105,11 +97,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 &&
@@ -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,21 +170,15 @@ 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)
)
)
}
@@ -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 }
export { AuthHelper }

View File

@@ -1,45 +1,19 @@
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'
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}`
}
import { AuthHelper } from './auth-helper.js'
import { InvalidParameter } from './errors.js'
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 +25,8 @@ describe('AuthHelper', function () {
passKey: 'myci_pass',
authorizedOrigins: true,
},
{ private: {} },
),
{ private: {} }
)
).to.throw(Error, 'Expected authorizedOrigins to be an array of origins')
})
})
@@ -61,7 +35,7 @@ describe('AuthHelper', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
{ private: privateConfig },
{ private: privateConfig }
).isValid
}
test(validate, () => {
@@ -69,20 +43,20 @@ describe('AuthHelper', function () {
// Fully configured user + pass.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
// Fully configured user or pass.
given(
{ userKey: 'myci_user', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ passKey: 'myci_pass', isRequired: true },
{ myci_pass: 'abc123' },
{ myci_pass: 'abc123' }
),
given({ userKey: 'myci_user' }, { myci_user: 'admin' }),
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }),
@@ -96,16 +70,16 @@ describe('AuthHelper', function () {
// Partly configured.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
// Missing required config.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{},
{}
),
given({ userKey: 'myci_user', isRequired: true }, {}),
given({ passKey: 'myci_pass', isRequired: true }, {}),
@@ -117,18 +91,18 @@ 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' })
given({ userKey: 'myci_user' }, { myci_user: 'admin' }).expect({
@@ -140,11 +114,11 @@ describe('AuthHelper', function () {
password: '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',
@@ -194,7 +168,7 @@ describe('AuthHelper', function () {
expect(() =>
authHelper.enforceStrictSsl({
options: { https: { rejectUnauthorized: false } },
}),
})
).to.throw(InvalidParameter)
})
})
@@ -218,7 +192,7 @@ describe('AuthHelper', function () {
expect(() =>
authHelper.enforceStrictSsl({
options: { https: { rejectUnauthorized: false } },
}),
})
).not.to.throw()
})
})
@@ -344,7 +318,7 @@ describe('AuthHelper', function () {
},
},
private: { myci_user: 'admin', myci_pass: 'abc123' },
},
}
)
const withBasicAuth = requestOptions =>
authHelper.withBasicAuth(requestOptions)
@@ -402,157 +376,8 @@ describe('AuthHelper', function () {
withBasicAuth({
url: 'https://myci.test/api',
options: { https: { rejectUnauthorized: 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()
})
})
})
})

View File

@@ -20,7 +20,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) {
@@ -44,16 +44,8 @@ class BaseGraphqlService extends BaseService {
* 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
@@ -70,8 +62,6 @@ class BaseGraphqlService extends BaseService {
variables = {},
options = {},
httpErrorMessages = {},
systemErrors = {},
logErrors = [429],
transformJson = data => data,
transformErrors = defaultTransformErrors,
}) {
@@ -84,9 +74,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 +83,7 @@ class BaseGraphqlService extends BaseService {
throw exception
} else {
throw Error(
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`,
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`
)
}
}

View File

@@ -35,23 +35,23 @@ describe('BaseGraphqlService', function () {
Promise.resolve({
buffer: '{"some": "json"}',
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).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',
},
}
)
})
@@ -74,17 +74,17 @@ describe('BaseGraphqlService', function () {
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).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 },
},
}
)
})
})
@@ -98,8 +98,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -113,8 +113,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -130,8 +130,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -149,8 +149,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -188,8 +188,8 @@ describe('BaseGraphqlService', function () {
expect(
await WithErrorHandler.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -14,7 +14,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) {
@@ -30,29 +30,14 @@ class BaseJsonService extends BaseService {
* @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.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
*/
async _requestJson({
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
logErrors = [429],
}) {
async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
const mergedOptions = {
...{ headers: { Accept: 'application/json' } },
...options,
@@ -60,9 +45,7 @@ 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)

View File

@@ -28,21 +28,21 @@ describe('BaseJsonService', function () {
Promise.resolve({
buffer: '{"some": "json"}',
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.json',
{
headers: { Accept: 'application/json' },
},
}
)
})
@@ -60,7 +60,7 @@ describe('BaseJsonService', function () {
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -69,7 +69,7 @@ describe('BaseJsonService', function () {
headers: { Accept: 'application/json' },
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -83,8 +83,8 @@ describe('BaseJsonService', function () {
expect(
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -98,8 +98,8 @@ describe('BaseJsonService', function () {
expect(
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -115,8 +115,8 @@ describe('BaseJsonService', function () {
expect(
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -33,25 +33,21 @@ 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.
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
badgeData.format = format
let maxAge = 24 * 3600 // 1 day
if (!queryParams.logo && !badgeData.isError) {
maxAge = 5 * 24 * 3600 // 5 days
}
setCacheHeadersForStaticResource(ask.res, maxAge)
setCacheHeadersForStaticResource(ask.res)
const svg = makeBadge(badgeData)
makeSend(format, ask.res, end)(svg)

View File

@@ -53,18 +53,10 @@ class BaseSvgScrapingService extends BaseService {
* @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.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
*/
@@ -73,9 +65,7 @@ class BaseSvgScrapingService extends BaseService {
valueMatcher,
url,
options = {},
httpErrors = {},
systemErrors = {},
logErrors = [429],
errorMessages = {},
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
const mergedOptions = {
@@ -85,9 +75,7 @@ class BaseSvgScrapingService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
logErrors,
errorMessages,
})
logTrace(emojic.dart, 'Response SVG', buffer)
const data = {

View File

@@ -28,7 +28,7 @@ describe('BaseSvgScrapingService', function () {
describe('valueFromSvgBadge', function () {
it('should find the correct value', function () {
expect(BaseSvgScrapingService.valueFromSvgBadge(exampleSvg)).to.equal(
exampleMessage,
exampleMessage
)
})
})
@@ -40,21 +40,21 @@ describe('BaseSvgScrapingService', function () {
Promise.resolve({
buffer: exampleSvg,
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher with the expected header', async function () {
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.svg',
{
headers: { Accept: 'image/svg+xml' },
},
}
)
})
@@ -75,7 +75,7 @@ describe('BaseSvgScrapingService', function () {
await WithCustomOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -84,7 +84,7 @@ describe('BaseSvgScrapingService', function () {
method: 'POST',
headers: { Accept: 'image/svg+xml' },
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -98,8 +98,8 @@ describe('BaseSvgScrapingService', function () {
expect(
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: exampleMessage,
})
@@ -124,8 +124,8 @@ describe('BaseSvgScrapingService', function () {
expect(
await WithValueMatcher.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'a different message',
})
@@ -139,8 +139,8 @@ describe('BaseSvgScrapingService', function () {
expect(
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -1,86 +0,0 @@
/**
* @module
*/
import emojic from 'emojic'
import { parse } from 'smol-toml'
import BaseService from './base.js'
import { InvalidResponse } from './errors.js'
import trace from './trace.js'
/**
* Services which query a TOML endpoint should extend BaseTomlService
*
* @abstract
*/
class BaseTomlService extends BaseService {
/**
* Request data from an upstream API serving TOML,
* parse it and validate against a schema
*
* @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
* 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
*/
async _requestToml({
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
logErrors = [429],
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
const mergedOptions = {
...{
headers: {
Accept:
// the official header should be application/toml - see https://toml.io/en/v1.0.0#mime-type
// but as this is not registered here https://www.iana.org/assignments/media-types/media-types.xhtml
// some apps use other mime-type like application/x-toml, text/plain etc....
'text/x-toml, text/toml, application/x-toml, application/toml, text/plain',
},
},
...options,
}
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
logErrors,
})
let parsed
try {
parsed = parse(buffer.toString())
} catch (err) {
logTrace(emojic.dart, 'Response TOML (unparseable)', buffer)
throw new InvalidResponse({
prettyMessage: 'unparseable toml response',
underlyingError: err,
})
}
logTrace(emojic.dart, 'Response TOML (before validation)', parsed, {
deep: true,
})
return this.constructor._validate(parsed, schema)
}
}
export default BaseTomlService

View File

@@ -1,150 +0,0 @@
import Joi from 'joi'
import { expect } from 'chai'
import sinon from 'sinon'
import BaseTomlService from './base-toml.js'
const dummySchema = Joi.object({
requiredString: Joi.string().required(),
}).required()
class DummyTomlService extends BaseTomlService {
static category = 'cat'
static route = { base: 'foo' }
async handle() {
const { requiredString } = await this._requestToml({
schema: dummySchema,
url: 'http://example.com/foo.toml',
})
return { message: requiredString }
}
}
const expectedToml = `
# example toml
requiredString = "some-string"
`
const invalidSchemaToml = `
# example toml - legal toml syntax but invalid schema
unexpectedKey = "some-string"
`
const invalidTomlSyntax = `
# example illegal toml syntax that can't be parsed
missing= "space"
colonsCantBeUsed: 42
missing "assignment"
`
describe('BaseTomlService', function () {
describe('Making requests', function () {
let requestFetcher
beforeEach(function () {
requestFetcher = sinon.stub().returns(
Promise.resolve({
buffer: expectedToml,
res: { statusCode: 200 },
}),
)
})
it('invokes _requestFetcher', async function () {
await DummyTomlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.toml',
{
headers: {
Accept:
'text/x-toml, text/toml, application/x-toml, application/toml, text/plain',
},
},
)
})
it('forwards options to _requestFetcher', async function () {
class WithOptions extends DummyTomlService {
async handle() {
const { requiredString } = await this._requestToml({
schema: dummySchema,
url: 'http://example.com/foo.toml',
options: { method: 'POST', searchParams: { queryParam: 123 } },
})
return { message: requiredString }
}
}
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.toml',
{
headers: {
Accept:
'text/x-toml, text/toml, application/x-toml, application/toml, text/plain',
},
method: 'POST',
searchParams: { queryParam: 123 },
},
)
})
})
describe('Making badges', function () {
it('handles valid toml responses', async function () {
const requestFetcher = async () => ({
buffer: expectedToml,
res: { statusCode: 200 },
})
expect(
await DummyTomlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
).to.deep.equal({
message: 'some-string',
})
})
it('handles toml responses which do not match the schema', async function () {
const requestFetcher = async () => ({
buffer: invalidSchemaToml,
res: { statusCode: 200 },
})
expect(
await DummyTomlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
).to.deep.equal({
isError: true,
color: 'lightgray',
message: 'invalid response data',
})
})
it('handles unparseable toml responses', async function () {
const requestFetcher = async () => ({
buffer: invalidTomlSyntax,
res: { statusCode: 200 },
})
expect(
await DummyTomlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
).to.deep.equal({
isError: true,
color: 'lightgray',
message: 'unparseable toml response',
})
})
})
})

View File

@@ -24,18 +24,10 @@ class BaseXmlService extends BaseService {
* @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.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).
* @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
* [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
* @returns {object} Parsed response
@@ -46,9 +38,7 @@ class BaseXmlService extends BaseService {
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
logErrors = [429],
errorMessages = {},
parserOptions = {},
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -59,9 +49,7 @@ class BaseXmlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
logErrors,
errorMessages,
})
const validateResult = XMLValidator.validate(buffer)
if (validateResult !== true) {

View File

@@ -28,21 +28,21 @@ describe('BaseXmlService', function () {
Promise.resolve({
buffer: '<requiredString>some-string</requiredString>',
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.xml',
{
headers: { Accept: 'application/xml, text/xml' },
},
}
)
})
@@ -62,7 +62,7 @@ describe('BaseXmlService', function () {
await WithCustomOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -71,7 +71,7 @@ describe('BaseXmlService', function () {
headers: { Accept: 'application/xml, text/xml' },
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -85,8 +85,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -112,8 +112,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlServiceWithParserOption.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string with trailing whitespace ',
})
@@ -127,8 +127,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -144,8 +144,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -23,18 +23,10 @@ class BaseYamlService extends BaseService {
* @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.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).
* @param {object} [attrs.encoding='utf8'] Character encoding
* @returns {object} Parsed response
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
@@ -43,9 +35,7 @@ class BaseYamlService extends BaseService {
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
logErrors = [429],
errorMessages = {},
encoding = 'utf8',
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -61,9 +51,7 @@ class BaseYamlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
httpErrors,
systemErrors,
logErrors,
errorMessages,
})
let parsed
try {

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