Compare commits

..

1 Commits

Author SHA1 Message Date
Caleb Cartwright
d60bca325f fix: add some missing spaces in docs 2023-06-17 14:53:52 -05:00
712 changed files with 26648 additions and 8666 deletions

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

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'

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

@@ -0,0 +1,67 @@
'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/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: 'node16'
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,236 +0,0 @@
{
"name": "docusaurus-swizzled-warning",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "docusaurus-swizzled-warning",
"version": "0.0.0",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.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.1.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
"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": "12.11.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"dependencies": {
"@octokit/types": "^6.40.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"dependencies": {
"@octokit/types": "^6.39.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.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
"dependencies": {
"@octokit/openapi-types": "^12.11.0"
}
},
"node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
"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": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"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": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"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": "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.10.0",
"@actions/github": "^5.1.1"
}
}

View File

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

View File

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

View File

@@ -19,10 +19,6 @@ runs:
with:
node-version: ${{ inputs.node-version }}
- name: Install NPM 9
run: npm install -g npm@^9.0.0
shell: bash
- name: Install dependencies
if: ${{ inputs.cypress == 'false' }}
env:

View File

@@ -17,6 +17,9 @@ updates:
- dependency-name: 'decamelize'
- dependency-name: 'humanize-string'
# https://github.com/badges/shields/pull/7288#issuecomment-974699240
- dependency-name: '@types/node'
# badge-maker package dependencies
- package-ecosystem: npm
directory: '/badge-maker'
@@ -27,6 +30,16 @@ updates:
open-pull-requests-limit: 99
rebase-strategy: disabled
# close-bot package dependencies
- package-ecosystem: npm
directory: '/.github/actions/close-bot'
schedule:
interval: weekly
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
# GH actions
- package-ecosystem: 'github-actions'
directory: '/'
@@ -34,13 +47,3 @@ updates:
interval: weekly
open-pull-requests-limit: 99
rebase-strategy: disabled
# docusaurus-swizzled-warning package dependencies
- package-ecosystem: npm
directory: '/.github/actions/docusaurus-swizzled-warning'
schedule:
interval: weekly
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled

View File

@@ -10,11 +10,12 @@ 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

View File

@@ -1,13 +1,13 @@
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:
@@ -15,8 +15,8 @@ jobs:
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,9 +1,6 @@
name: Build Docker Image
on:
pull_request:
push:
branches:
- 'gh-readonly-queue/**'
jobs:
build-docker-image:

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: Danger
run: npm run danger ci

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: Build
run: npm run build-docs

View File

@@ -29,7 +29,6 @@ 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@v4
with:
context: .
@@ -38,9 +37,6 @@ 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@v2
with:

View File

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

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
cypress: true
- name: Run tests

View File

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

View File

@@ -35,7 +35,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: Integration Tests (with PAT)
if: ${{ env.PAT_EXISTS == 'true' }}

View File

@@ -17,7 +17,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: ESLint
if: always()

View File

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

View File

@@ -22,7 +22,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: Core tests
uses: ./.github/actions/core-tests

View File

@@ -16,9 +16,12 @@ jobs:
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
engine-strict: 'false'
- node: '18'
- node: '20'
engine-strict: 'true'
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -29,6 +32,9 @@ jobs:
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,11 +13,11 @@ jobs:
strategy:
matrix:
include:
- node: '16'
- node: '14'
engine-strict: 'false'
- node: '18'
- node: '16'
engine-strict: 'true'
- node: '20'
- node: '18'
engine-strict: 'false'
steps:
- name: Checkout

View File

@@ -1,13 +1,10 @@
name: Services@node 20
name: Services@node 17
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services-20:
test-services-17:
runs-on: ubuntu-latest
steps:
@@ -17,7 +14,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 20
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'

View File

@@ -2,9 +2,6 @@ name: Services
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services:
@@ -17,7 +14,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: Service tests (triggered from local branch)
if: github.event.pull_request.head.repo.full_name == github.repository

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 16
- name: Check for new GitHub API version
run: node scripts/update-github-api.js

View File

@@ -4,85 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2023-09-01
- migrate examples to openApi part 8; affects [ubuntu uptimerobot vaadin vcpkg visualstudiomarketplace wheelmap] [#9463](https://github.com/badges/shields/issues/9463)
- Add dynamic TOML support via [DynamicToml] Service [#9517](https://github.com/badges/shields/issues/9517)
- migrate `examples` to `openApi` part 11: enums; affects [codefactor conda depfu homebrew jsdelivr reddit sourceforge testspace vaadin github] [#9437](https://github.com/badges/shields/issues/9437)
- Revert "log an error to sentry if an upstream service responds with `429 Too Many Requests` (#9505)" [#9523](https://github.com/badges/shields/issues/9523)
- cache [pypi] downloads for longer [#9522](https://github.com/badges/shields/issues/9522)
- log an error to sentry if an upstream service responds with `429 Too Many Requests` [#9505](https://github.com/badges/shields/issues/9505)
- Docs: update TUTORIAL.md add BaseTomlService [#9518](https://github.com/badges/shields/issues/9518)
- Replace localhost with domain in example link. [#9506](https://github.com/badges/shields/issues/9506)
- [twitter] --> x [#9496](https://github.com/badges/shields/issues/9496)
- use defaultBadgeData as object instead of function in tutorial docs [#9502](https://github.com/badges/shields/issues/9502)
- add docstrings for website status service [#9495](https://github.com/badges/shields/issues/9495)
- migrate `examples` to `openApi` part5; affects [itunes jetbrains jitpack keybase lemmy luarocks maintenance openvsx] [#9431](https://github.com/badges/shields/issues/9431)
- migrate `examples` to `openApi` part 4; affects [ecologi elm flathub gem gitter GithubTotalDiscussions greasyfork hackage hackernews homebrew] [#9430](https://github.com/badges/shields/issues/9430)
- [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)
- migrate examples to openApi part 6; affects [opencollective opm ossf powershell pub pypi reddit repology] [#9462](https://github.com/badges/shields/issues/9462)
- devops: Add ci warning for swizzled docusaurus components [#9467](https://github.com/badges/shields/issues/9467)
- migrate `examples` to `openApi` part 3; affects [conan cookbook coverity cpan debian docker docsrs dub eclipse] [#9429](https://github.com/badges/shields/issues/9429)
- Add TOML support with BaseTomlService [#9438](https://github.com/badges/shields/issues/9438)
- [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)
- update npm prune command [#9470](https://github.com/badges/shields/issues/9470)
- remove obsolete entries from dependabot.yml [#9469](https://github.com/badges/shields/issues/9469)
- output DockerHub digest in publish-docker-next workflow [#9471](https://github.com/badges/shields/issues/9471)
- delete close-bot [#9468](https://github.com/badges/shields/issues/9468)
- Add docstrings for version service [#9411](https://github.com/badges/shields/issues/9411)
- deploy on node 18 [#9385](https://github.com/badges/shields/issues/9385)
- migrate `examples` to `openApi` part 2; affects [archlinux bitcomponents bountysource cdnjs chrome clearlydefined clojars cocoapods coincap] [#9428](https://github.com/badges/shields/issues/9428)
- allow calling [github] without auth [#9427](https://github.com/badges/shields/issues/9427)
- fix [github] service tests [#9425](https://github.com/badges/shields/issues/9425)
- 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)

View File

@@ -1,4 +1,4 @@
FROM node:18-alpine AS Builder
FROM node:16-alpine AS Builder
RUN mkdir -p /usr/src/app
RUN mkdir /usr/src/app/private
@@ -9,17 +9,17 @@ COPY package.json package-lock.json /usr/src/app/
COPY badge-maker /usr/src/app/badge-maker/
RUN apk add python3 make g++
RUN npm install -g "npm@^9.0.0"
RUN npm install -g "npm@>=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
RUN npm prune --omit=dev
RUN npm prune --production
RUN npm cache clean --force
# Use multi-stage build to reduce size
FROM node:18-alpine
FROM node:16-alpine
ARG version=dev
ENV DOCKER_SHIELDS_VERSION=$version

View File

@@ -70,7 +70,7 @@ This repo hosts:
[Make your own badges!][custom badges]
(Quick example: `https://img.shields.io/badge/left-right-f39f37`)
[custom badges]: https://img.shields.io/badges/static-badge
[custom badges]: https://shields.io/#your-badge
### Quickstart
@@ -98,8 +98,8 @@ You can read a [tutorial on how to add a badge][tutorial].
## Development
1. Install Node 18 or later. You can use the [package manager][] of your choice.
Tests need to pass in Node 18 and 20.
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,7 +23,7 @@ describe('makeBadge function', function () {
message: 'passed',
color: 'green',
style: 'flat',
}),
})
).to.satisfy(isSvg)
})
@@ -32,44 +32,44 @@ describe('makeBadge function', function () {
console.log(x)
expect(() => makeBadge(x)).to.throw(
ValidationError,
'makeBadge takes an argument of type object',
'makeBadge takes an argument of type object'
)
})
expect(() => makeBadge({})).to.throw(
ValidationError,
'Field `message` is required',
'Field `message` is required'
)
expect(() => makeBadge({ label: 'build' })).to.throw(
ValidationError,
'Field `message` is required',
'Field `message` is required'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', labelColor: 7 }),
makeBadge({ label: 'build', message: 'passed', labelColor: 7 })
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
expect(() =>
makeBadge({ label: 'build', message: 'passed', format: 'png' }),
makeBadge({ label: 'build', message: 'passed', format: 'png' })
).to.throw(ValidationError, "Unexpected field 'format'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', template: 'flat' }),
makeBadge({ label: 'build', message: 'passed', template: 'flat' })
).to.throw(ValidationError, "Unexpected field 'template'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', foo: 'bar' }),
makeBadge({ label: 'build', message: 'passed', foo: 'bar' })
).to.throw(ValidationError, "Unexpected field 'foo'")
expect(() =>
makeBadge({
label: 'build',
message: 'passed',
style: 'something else',
}),
})
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', style: 'popout' }),
makeBadge({ label: 'build', message: 'passed', style: 'popout' })
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
})
})

View File

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

View File

@@ -6,8 +6,8 @@ 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,8 +167,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -177,8 +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',
@@ -188,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',
@@ -200,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',
@@ -212,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',
@@ -223,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',
@@ -236,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',
@@ -247,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',
@@ -259,8 +259,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -269,8 +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',
@@ -280,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',
@@ -292,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',
@@ -304,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',
@@ -315,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',
@@ -328,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',
@@ -339,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',
@@ -351,8 +351,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -361,8 +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',
@@ -372,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',
@@ -384,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',
@@ -396,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',
@@ -407,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',
@@ -428,7 +428,7 @@ describe('The badge generator', function () {
message: 1999,
format: 'svg',
style: 'for-the-badge',
}),
})
)
.to.include('1998')
.and.to.include('1999')
@@ -441,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',
@@ -458,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',
@@ -470,8 +470,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -480,8 +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',
@@ -491,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',
@@ -503,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',
@@ -515,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',
@@ -526,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',
@@ -546,7 +546,7 @@ describe('The badge generator', function () {
message: 'some-value',
format: 'svg',
style: 'social',
}),
})
)
.to.include('Some-key')
.and.to.include('some-value')
@@ -560,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',
@@ -577,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',
@@ -589,8 +589,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -599,8 +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',
@@ -610,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',
@@ -622,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',
@@ -636,8 +636,8 @@ describe('The badge generator', function () {
})
describe('badges with logos should always produce the same badge', function () {
it('badge with logo', async function () {
await expectBadgeToMatchSnapshot({
it('badge with logo', function () {
expectBadgeToMatchSnapshot({
label: 'label',
message: 'message',
format: 'svg',

View File

@@ -66,7 +66,7 @@ class XmlElement {
})
.join(' ')
return stripXmlWhitespace(
`<${this.name}${attrsStr}>${content}</${this.name}>`,
`<${this.name}${attrsStr}>${content}</${this.name}>`
)
}
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
@@ -88,7 +88,7 @@ class ElementList {
typeof el.render === 'function'
? acc + el.render()
: acc + escapeXml(el),
'',
''
)
}
}

View File

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

View File

@@ -77,7 +77,6 @@ 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'
drone_token: 'DRONE_TOKEN'
gh_client_id: 'GH_CLIENT_ID'
@@ -95,7 +94,6 @@ private:
obs_user: 'OBS_USER'
obs_pass: 'OBS_PASS'
redis_url: 'REDIS_URL'
opencollective_token: 'OPENCOLLECTIVE_TOKEN'
postgres_url: 'POSTGRES_URL'
sentry_dsn: 'SENTRY_DSN'
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'

View File

@@ -1,6 +1,5 @@
private:
# These are the keys which are set on the production servers.
curseforge_api_key: ...
discord_bot_token: ...
gh_client_id: ...
gh_client_secret: ...

View File

@@ -4,7 +4,6 @@ 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: '...'

View File

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

View File

@@ -11,7 +11,7 @@ class AuthHelper {
isRequired = false,
defaultToEmptyStringForUser = false,
},
config,
config
) {
if (!userKey && !passKey) {
throw Error('Expected userKey or passKey to be set')
@@ -142,7 +142,7 @@ class AuthHelper {
withBasicAuth(requestParams) {
return this._withAnyAuth(requestParams, requestParams =>
this.constructor._mergeAuth(requestParams, this._basicAuth),
this.constructor._mergeAuth(requestParams, this._basicAuth)
)
}
@@ -153,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 } = {},
@@ -175,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)
)
)
}
@@ -215,7 +204,7 @@ class AuthHelper {
this.constructor._mergeQueryParams(requestParams, {
...(userKey ? { [userKey]: this._user } : undefined),
...(passKey ? { [passKey]: this._pass } : undefined),
}),
})
)
}
}

View File

@@ -8,13 +8,12 @@ describe('AuthHelper', function () {
it('throws without userKey or passKey', function () {
expect(() => new AuthHelper({}, {})).to.throw(
Error,
'Expected userKey or passKey to be set',
'Expected userKey or passKey to be set'
)
})
it('throws without serviceKey or authorizedOrigins', function () {
expect(
() =>
new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {}),
() => new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {})
).to.throw(Error, 'Expected authorizedOrigins or serviceKey to be set')
})
it('throws when authorizedOrigins is not an array', function () {
@@ -26,8 +25,8 @@ describe('AuthHelper', function () {
passKey: 'myci_pass',
authorizedOrigins: true,
},
{ private: {} },
),
{ private: {} }
)
).to.throw(Error, 'Expected authorizedOrigins to be an array of origins')
})
})
@@ -36,7 +35,7 @@ describe('AuthHelper', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
{ private: privateConfig },
{ private: privateConfig }
).isValid
}
test(validate, () => {
@@ -44,20 +43,20 @@ describe('AuthHelper', function () {
// Fully configured user + pass.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
// Fully configured user or pass.
given(
{ userKey: 'myci_user', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ passKey: 'myci_pass', isRequired: true },
{ myci_pass: 'abc123' },
{ myci_pass: 'abc123' }
),
given({ userKey: 'myci_user' }, { myci_user: 'admin' }),
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }),
@@ -71,16 +70,16 @@ describe('AuthHelper', function () {
// Partly configured.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
// Missing required config.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{},
{}
),
given({ userKey: 'myci_user', isRequired: true }, {}),
given({ passKey: 'myci_pass', isRequired: true }, {}),
@@ -92,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({
@@ -115,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',
@@ -169,7 +168,7 @@ describe('AuthHelper', function () {
expect(() =>
authHelper.enforceStrictSsl({
options: { https: { rejectUnauthorized: false } },
}),
})
).to.throw(InvalidParameter)
})
})
@@ -193,7 +192,7 @@ describe('AuthHelper', function () {
expect(() =>
authHelper.enforceStrictSsl({
options: { https: { rejectUnauthorized: false } },
}),
})
).not.to.throw()
})
})
@@ -319,7 +318,7 @@ describe('AuthHelper', function () {
},
},
private: { myci_user: 'admin', myci_pass: 'abc123' },
},
}
)
const withBasicAuth = requestOptions =>
authHelper.withBasicAuth(requestOptions)
@@ -377,7 +376,7 @@ describe('AuthHelper', function () {
withBasicAuth({
url: 'https://myci.test/api',
options: { https: { rejectUnauthorized: false } },
}),
})
).to.throw(InvalidParameter)
})
})

View File

@@ -91,7 +91,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

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

View File

@@ -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,82 +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
* @returns {object} Parsed response
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
*/
async _requestToml({
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
}) {
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,
})
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

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

@@ -44,14 +44,14 @@ describe('BaseYamlService', function () {
Promise.resolve({
buffer: expectedYaml,
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -61,7 +61,7 @@ describe('BaseYamlService', function () {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
},
}
)
})
@@ -79,7 +79,7 @@ describe('BaseYamlService', function () {
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -91,7 +91,7 @@ describe('BaseYamlService', function () {
},
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -105,8 +105,8 @@ describe('BaseYamlService', function () {
expect(
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -120,8 +120,8 @@ describe('BaseYamlService', function () {
expect(
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -137,8 +137,8 @@ describe('BaseYamlService', function () {
expect(
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -21,7 +21,6 @@ import {
} from './errors.js'
import { validateExample, transformExample } from './examples.js'
import { fetch } from './got.js'
import { getEnum } from './openapi.js'
import {
makeFullUrl,
assertValidRoute,
@@ -45,7 +44,7 @@ const optionalStringWhenNamedLogoPresent = Joi.alternatives().conditional(
{
is: Joi.string().required(),
then: Joi.string(),
},
}
)
const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
@@ -103,26 +102,6 @@ class BaseService {
throw new Error(`Route not defined for ${this.name}`)
}
/**
* Extract an array of allowed values from this service's route pattern
* for a given route parameter
*
* @param {string} param The name of a param in this service's route pattern
* @returns {string[]} Array of allowed values for this param
*/
static getEnum(param) {
if (!('pattern' in this.route)) {
throw new Error('getEnum() requires route to have a .pattern property')
}
const enumeration = getEnum(this.route.pattern, param)
if (!Array.isArray(enumeration)) {
throw new Error(
`Could not extract enum for param ${param} from pattern ${this.route.pattern}`,
)
}
return enumeration
}
/**
* Configuration for the authentication helper that prepares credentials
* for upstream requests.
@@ -204,28 +183,12 @@ class BaseService {
Joi.assert(
this.defaultBadgeData,
defaultBadgeDataSchema,
`Default badge data for ${this.name}`,
`Default badge data for ${this.name}`
)
this.examples.forEach((example, index) =>
validateExample(example, index, this),
validateExample(example, index, this)
)
// ensure openApi spec matches route
if (this.openApi) {
const preparedRoute = prepareRoute(this.route)
for (const [key, value] of Object.entries(this.openApi)) {
let example = key
for (const param of value.get.parameters) {
example = example.replace(`{${param.name}}`, param.example)
}
if (!example.match(preparedRoute.regex)) {
throw new Error(
`Inconsistent Open Api spec and Route found for service ${this.name}`,
)
}
}
}
}
static getDefinition() {
@@ -234,7 +197,7 @@ class BaseService {
const queryParams = getQueryParamNames(this.route)
const examples = this.examples.map((example, index) =>
transformExample(example, index, this),
transformExample(example, index, this)
)
let route
@@ -255,7 +218,7 @@ class BaseService {
constructor(
{ requestFetcher, authHelper, metricHelper },
{ handleInternalErrors },
{ handleInternalErrors }
) {
this._requestFetcher = requestFetcher
this.authHelper = authHelper
@@ -271,9 +234,9 @@ class BaseService {
const params = new URLSearchParams(
Object.fromEntries(
Object.entries(options.searchParams).filter(
([k, v]) => v !== undefined,
),
),
([k, v]) => v !== undefined
)
)
)
logUrl = `${url}?${params.toString()}`
delete logOptions.searchParams
@@ -281,12 +244,12 @@ class BaseService {
logTrace(
emojic.bowAndArrow,
'Request',
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`,
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`
)
const { res, buffer } = await this._requestFetcher(
url,
options,
systemErrors,
systemErrors
)
await this._meterResponse(res, buffer)
logTrace(emojic.dart, 'Response status code', res.statusCode)
@@ -316,7 +279,7 @@ class BaseService {
prettyErrorMessage = 'invalid response data',
includeKeys = false,
allowAndStripUnknownKeys = true,
} = {},
} = {}
) {
return validate(
{
@@ -328,7 +291,7 @@ class BaseService {
allowAndStripUnknownKeys,
},
data,
schema,
schema
)
}
@@ -384,7 +347,7 @@ class BaseService {
'unhandledError',
emojic.boom,
'Unhandled internal error',
error,
error
)
) {
// This is where we end up if an unhandled exception is thrown in
@@ -402,7 +365,7 @@ class BaseService {
'unhandledError',
emojic.boom,
'Unhandled internal error',
error,
error
)
throw error
}
@@ -412,7 +375,7 @@ class BaseService {
context = {},
config = {},
namedParams = {},
queryParams = {},
queryParams = {}
) {
trace.logTrace('inbound', emojic.womanCook, 'Service class', this.name)
trace.logTrace('inbound', emojic.ticket, 'Named params', namedParams)
@@ -446,13 +409,13 @@ class BaseService {
traceSuccessMessage: 'Query params after validation',
},
queryParams,
queryParamSchema,
queryParamSchema
)
trace.logTrace(
'inbound',
emojic.crayon,
'Query params after validation',
queryParams,
queryParams
)
} catch (error) {
serviceError = error
@@ -466,7 +429,7 @@ class BaseService {
try {
serviceData = await serviceInstance.handle(
namedParams,
transformedQueryParams,
transformedQueryParams
)
serviceInstance._validateServiceData(serviceData)
} catch (error) {
@@ -491,7 +454,7 @@ class BaseService {
librariesIoApiProvider,
metricInstance,
},
serviceConfig,
serviceConfig
) {
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
const { regex, captureNames } = prepareRoute(this.route)
@@ -519,14 +482,14 @@ class 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(/^\./, '')
@@ -535,7 +498,7 @@ class BaseService {
metricHandle.noteResponseSent()
},
cacheLength: this._cacheLength,
}),
})
)
}
}

View File

@@ -72,8 +72,8 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: '!' },
),
{ queryParamA: '!' }
)
).to.deep.equal({
message: 'Hello namedParamA: bar.bar.bar with queryParamA: !',
})
@@ -85,8 +85,8 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: ['foo', 'bar'] },
),
{ queryParamA: ['foo', 'bar'] }
)
).to.deep.equal({
color: 'red',
isError: true,
@@ -97,13 +97,13 @@ describe('BaseService', function () {
describe('Required overrides', function () {
it('Should throw if render() is not overridden', function () {
expect(() => BaseService.render()).to.throw(
/^render\(\) function not implemented for BaseService$/,
/^render\(\) function not implemented for BaseService$/
)
})
it('Should throw if route is not overridden', function () {
return expect(BaseService.invoke({}, {}, {})).to.be.rejectedWith(
/^Route not defined for BaseService$/,
/^Route not defined for BaseService$/
)
})
@@ -112,13 +112,13 @@ describe('BaseService', function () {
}
it('Should throw if handle() is not overridden', function () {
return expect(WithRoute.invoke({}, {}, {})).to.be.rejectedWith(
/^Handler not implemented for WithRoute$/,
/^Handler not implemented for WithRoute$/
)
})
it('Should throw if category is not overridden', function () {
expect(() => BaseService.category).to.throw(
/^Category not set for BaseService$/,
/^Category not set for BaseService$/
)
})
})
@@ -135,25 +135,25 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: '!' },
{ queryParamA: '!' }
)
expect(trace.logTrace).to.be.calledWithMatch(
'inbound',
sinon.match.string,
'Service class',
'DummyService',
'DummyService'
)
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Named params',
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Query params after validation',
{ queryParamA: '!' },
{ queryParamA: '!' }
)
})
})
@@ -171,7 +171,7 @@ describe('BaseService', function () {
const serviceData = await LinkService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect(serviceData).to.deep.equal({
@@ -194,7 +194,7 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect.fail('Expected to throw')
} catch (e) {
@@ -212,7 +212,7 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect.fail('Expected to throw')
} catch (e) {
@@ -233,8 +233,8 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: true },
{ namedParamA: 'bar.bar.bar' },
),
{ namedParamA: 'bar.bar.bar' }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -251,7 +251,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'red',
@@ -266,7 +266,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -281,7 +281,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -296,7 +296,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -311,7 +311,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'red',
@@ -336,7 +336,7 @@ describe('BaseService', function () {
mockHandleRequest = sinon.spy()
DummyService.register(
{ camp: mockCamp, handleRequest: mockHandleRequest },
defaultConfig,
defaultConfig
)
})
@@ -413,8 +413,8 @@ describe('BaseService', function () {
expect(() =>
DummyService._validate(
{ requiredString: ['this', "shouldn't", 'work'] },
dummySchema,
),
dummySchema
)
)
.to.throw()
.instanceof(InvalidResponse)
@@ -436,7 +436,7 @@ describe('BaseService', function () {
})
const serviceInstance = new DummyService(
{ requestFetcher },
defaultConfig,
defaultConfig
)
const url = 'some-url'
@@ -453,14 +453,14 @@ describe('BaseService', function () {
`${url}?param1=foobar\n${JSON.stringify(
{ headers: options.headers },
null,
2,
)}`,
2
)}`
)
expect(trace.logTrace).to.be.calledWithMatch(
'fetch',
sinon.match.string,
'Response status code',
200,
200
)
})
@@ -471,7 +471,7 @@ describe('BaseService', function () {
})
const serviceInstance = new DummyService(
{ requestFetcher },
defaultConfig,
defaultConfig
)
try {
@@ -504,17 +504,17 @@ describe('BaseService', function () {
const serviceInstance =
new DummyServiceWithServiceResponseSizeMetricEnabled(
{ requestFetcher, metricHelper },
defaultConfig,
defaultConfig
)
await serviceInstance._request({ url })
expect(await register.getSingleMetricAsString('service_response_bytes'))
.to.contain(
'service_response_bytes_bucket{le="65536",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 0\n',
'service_response_bytes_bucket{le="65536",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 0\n'
)
.and.to.contain(
'service_response_bytes_bucket{le="131072",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 1\n',
'service_response_bytes_bucket{le="131072",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 1\n'
)
})
@@ -529,17 +529,16 @@ describe('BaseService', function () {
})
const serviceInstance = new DummyService(
{ requestFetcher, metricHelper },
defaultConfig,
defaultConfig
)
await serviceInstance._request({ url })
expect(
await register.getSingleMetricAsString('service_response_bytes'),
await register.getSingleMetricAsString('service_response_bytes')
).to.not.contain('service_response_bytes_bucket')
})
})
describe('auth', function () {
class AuthService extends DummyService {
static auth = {
@@ -566,8 +565,8 @@ describe('BaseService', function () {
},
private: { myci_pass: 'abc123' },
},
{ namedParamA: 'bar.bar.bar' },
),
{ namedParamA: 'bar.bar.bar' }
)
).to.deep.equal({ message: 'The CI password is abc123' })
})
@@ -584,8 +583,8 @@ describe('BaseService', function () {
},
{
namedParamA: 'bar.bar.bar',
},
),
}
)
).to.deep.equal({
color: 'lightgray',
isError: true,
@@ -593,44 +592,4 @@ describe('BaseService', function () {
})
})
})
describe('getEnum', function () {
class EnumService extends DummyService {
static route = {
base: 'foo',
pattern: ':namedParamA/:namedParamB(this|that)',
queryParamSchema,
}
}
it('returns an array of allowed values', async function () {
expect(EnumService.getEnum('namedParamB')).to.deep.equal(['this', 'that'])
})
it('throws if param name is invalid', async function () {
expect(() => EnumService.getEnum('notAValidParam')).to.throw(
'Could not extract enum for param notAValidParam from pattern :namedParamA/:namedParamB(this|that)',
)
})
it('throws if param name is not an enum', async function () {
expect(() => EnumService.getEnum('namedParamA')).to.throw(
'Could not extract enum for param namedParamA from pattern :namedParamA/:namedParamB(this|that)',
)
})
it('throws if route does not have a pattern', async function () {
class FormatService extends DummyService {
static route = {
base: 'foo',
format: '([^/]+?)',
queryParamSchema,
}
}
expect(() => FormatService.getEnum('notAValidParam')).to.throw(
'getEnum() requires route to have a .pattern property',
)
})
})
})

View File

@@ -41,7 +41,7 @@ function coalesceCacheLength({
const cacheLength = coalesce(
serviceOverrideCacheLengthSeconds,
serviceDefaultCacheLengthSeconds,
defaultCacheLengthSeconds,
defaultCacheLengthSeconds
)
// Overrides can apply _more_ caching, but not less. Query param overriding

View File

@@ -125,7 +125,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'no-cache, no-store, must-revalidate',
'no-cache, no-store, must-revalidate'
)
})
@@ -141,7 +141,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'max-age=123, s-maxage=123',
'max-age=123, s-maxage=123'
)
})
@@ -156,7 +156,7 @@ describe('Cache header functions', function () {
it('sets the expected fields', function () {
const expectedFields = ['date', 'cache-control', 'expires']
expectedFields.forEach(field =>
expect(res._headers[field]).to.equal(undefined),
expect(res._headers[field]).to.equal(undefined)
)
setCacheHeaders({
@@ -169,7 +169,7 @@ describe('Cache header functions', function () {
expectedFields.forEach(field =>
expect(res._headers[field])
.to.be.a('string')
.and.have.lengthOf.at.least(1),
.and.have.lengthOf.at.least(1)
)
})
})
@@ -181,7 +181,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
`max-age=${24 * 3600}, s-maxage=${24 * 3600}`,
`max-age=${24 * 3600}, s-maxage=${24 * 3600}`
)
})
@@ -190,7 +190,7 @@ describe('Cache header functions', function () {
expect(new Date(lastModified)).to.be.withinTime(
// Within the last 60 seconds.
new Date(Date.now() - 60 * 1000),
new Date(),
new Date()
)
})
})
@@ -221,7 +221,7 @@ describe('Cache header functions', function () {
})
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(false)
})
},
}
)
context(
'when the If-Modified-Since header is after the process started',
@@ -233,7 +233,7 @@ describe('Cache header functions', function () {
})
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(true)
})
},
}
)
})
})

View File

@@ -13,7 +13,7 @@ export default function checkErrorResponse(httpErrors = {}) {
error = new NotFound({ prettyMessage: httpErrors[404] })
} else if (res.statusCode !== 200) {
const underlying = Error(
`Got status code ${res.statusCode} (expected 200)`,
`Got status code ${res.statusCode} (expected 200)`
)
const props = { underlyingError: underlying }
if (httpErrors[res.statusCode] !== undefined) {

View File

@@ -56,7 +56,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)',
'Invalid Response: Got status code 429 (expected 200)'
)
expect(e.prettyMessage).to.equal('rate limited by upstream service')
expect(e.response).to.equal(res)
@@ -72,10 +72,10 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)',
'Invalid Response: Got status code 429 (expected 200)'
)
expect(e.prettyMessage).to.equal(
"terribly sorry but that's one too many requests",
"terribly sorry but that's one too many requests"
)
}
})
@@ -90,7 +90,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 499 (expected 200)',
'Invalid Response: Got status code 499 (expected 200)'
)
expect(e.prettyMessage).to.equal('invalid')
expect(e.response).to.equal(res)
@@ -106,7 +106,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 403 (expected 200)',
'Invalid Response: Got status code 403 (expected 200)'
)
expect(e.prettyMessage).to.equal('access denied')
}
@@ -122,7 +122,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(Inaccessible)
expect(e.message).to.equal(
'Inaccessible: Got status code 503 (expected 200)',
'Inaccessible: Got status code 503 (expected 200)'
)
expect(e.prettyMessage).to.equal('inaccessible')
expect(e.response).to.equal(res)
@@ -138,7 +138,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(Inaccessible)
expect(e.message).to.equal(
'Inaccessible: Got status code 500 (expected 200)',
'Inaccessible: Got status code 500 (expected 200)'
)
expect(e.prettyMessage).to.equal('server overloaded')
}

View File

@@ -36,7 +36,7 @@ export default function coalesceBadge(
serviceData,
// These two parameters were kept separate to make tests clearer.
defaultBadgeData,
{ category, _cacheLength: defaultCacheSeconds } = {},
{ category, _cacheLength: defaultCacheSeconds } = {}
) {
// The "overrideX" naming is based on services that provide badge
// parameters themselves, which can be overridden by a query string
@@ -141,7 +141,7 @@ export default function coalesceBadge(
} else {
namedLogo = coalesce(
serviceNamedLogo,
style === 'social' ? defaultNamedLogo : undefined,
style === 'social' ? defaultNamedLogo : undefined
)
namedLogoColor = coalesce(overrideLogoColor, serviceLogoColor)
}
@@ -166,13 +166,13 @@ export default function coalesceBadge(
isError ? undefined : overrideColor,
serviceColor,
defaultColor,
'lightgrey',
'lightgrey'
),
labelColor: coalesce(
// In case of an error, disregard user's color override.
isError ? undefined : overrideLabelColor,
serviceLabelColor,
defaultLabelColor,
defaultLabelColor
),
style,
namedLogo,

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ describe('got wrapper', function () {
.reply(200, 'x'.repeat(101))
const sendRequest = _fetchFactory(100)
return expect(
sendRequest('https://www.google.com/foo/bar'),
sendRequest('https://www.google.com/foo/bar')
).to.be.rejectedWith(InvalidResponse, 'Maximum response size exceeded')
})
@@ -29,7 +29,7 @@ describe('got wrapper', function () {
nock('https://www.google.com').get('/foo/bar').replyWithError('oh no')
const sendRequest = _fetchFactory(1024)
return expect(
sendRequest('https://www.google.com/foo/bar'),
sendRequest('https://www.google.com/foo/bar')
).to.be.rejectedWith(Inaccessible, 'oh no')
})
@@ -38,10 +38,10 @@ describe('got wrapper', function () {
nock.disableNetConnect()
const sendRequest = _fetchFactory(1024)
return expect(
sendRequest('https://www.google.com/foo/bar'),
sendRequest('https://www.google.com/foo/bar')
).to.be.rejectedWith(
Inaccessible,
'Nock: Disallowed net connect for "www.google.com:443/foo/bar"',
'Nock: Disallowed net connect for "www.google.com:443/foo/bar"'
)
})
@@ -57,18 +57,18 @@ describe('got wrapper', function () {
prettyMessage: 'Oh no! A terrible thing has happened',
cacheSeconds: 10,
},
},
),
}
)
)
.to.be.rejectedWith(
Inaccessible,
"Inaccessible: Timeout awaiting 'request' for 1ms",
"Inaccessible: Timeout awaiting 'request' for 1ms"
)
// eslint-disable-next-line promise/prefer-await-to-then
.then(error => {
expect(error).to.have.property(
'prettyMessage',
'Oh no! A terrible thing has happened',
'Oh no! A terrible thing has happened'
)
expect(error).to.have.property('cacheSeconds', 10)
})

View File

@@ -9,16 +9,18 @@ describe('mergeQueries function', function () {
it('merges valid gql queries', function () {
expect(
print(
mergeQueries(gql`
query ($param: String!) {
foo(param: $param) {
bar
mergeQueries(
gql`
query ($param: String!) {
foo(param: $param) {
bar
}
}
}
`),
),
`
)
)
).to.equalIgnoreSpaces(
'query ($param: String!) { foo(param: $param) { bar } }',
'query ($param: String!) { foo(param: $param) { bar } }'
)
expect(
@@ -35,11 +37,11 @@ describe('mergeQueries function', function () {
query {
baz
}
`,
),
),
`
)
)
).to.equalIgnoreSpaces(
'query ($param: String!) { foo(param: $param) { bar } baz }',
'query ($param: String!) { foo(param: $param) { bar } baz }'
)
expect(
@@ -59,9 +61,9 @@ describe('mergeQueries function', function () {
query {
baz
}
`,
),
),
`
)
)
).to.equalIgnoreSpaces('{ foo bar baz }')
expect(
@@ -76,9 +78,9 @@ describe('mergeQueries function', function () {
{
bar
}
`,
),
),
`
)
)
).to.equalIgnoreSpaces('{ foo bar }')
})

View File

@@ -3,7 +3,6 @@ import BaseJsonService from './base-json.js'
import BaseGraphqlService from './base-graphql.js'
import BaseStaticService from './base-static.js'
import BaseSvgScrapingService from './base-svg-scraping.js'
import BaseTomlService from './base-toml.js'
import BaseXmlService from './base-xml.js'
import BaseYamlService from './base-yaml.js'
import deprecatedService from './deprecated-service.js'
@@ -16,7 +15,6 @@ import {
Deprecated,
ImproperlyConfigured,
} from './errors.js'
import { pathParam, pathParams, queryParam, queryParams } from './openapi.js'
export {
BaseService,
@@ -24,7 +22,6 @@ export {
BaseGraphqlService,
BaseStaticService,
BaseSvgScrapingService,
BaseTomlService,
BaseXmlService,
BaseYamlService,
deprecatedService,
@@ -35,8 +32,4 @@ export {
InvalidParameter,
ImproperlyConfigured,
Deprecated,
pathParam,
pathParams,
queryParam,
queryParams,
}

View File

@@ -104,7 +104,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
const badgeData = coalesceBadge(
filteredQueryParams,
{ label: 'vendor', message: 'unresponsive' },
{},
{}
)
const svg = makeBadge(badgeData)
const extension = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
@@ -126,7 +126,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
const svg = makeBadge(badgeData)
setCacheHeadersOnResponse(ask.res, badgeData.cacheLengthSeconds)
makeSend(format, ask.res, end)(svg)
},
}
)
// eslint-disable-next-line promise/prefer-await-to-then
if (result && result.catch) {

View File

@@ -18,7 +18,7 @@ function fakeHandler(queryParams, match, sendBadge, request) {
label: 'testing',
message: someValue,
},
{},
{}
)
sendBadge(format, badgeData)
}
@@ -35,7 +35,7 @@ function createFakeHandlerWithCacheLength(cacheLengthSeconds) {
{},
{
_cacheLength: cacheLengthSeconds,
},
}
)
sendBadge(format, badgeData)
}
@@ -66,7 +66,7 @@ describe('The request handler', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, { handler: fakeHandler }),
handleRequest(standardCacheHeaders, { handler: fakeHandler })
)
})
@@ -90,7 +90,7 @@ describe('The request handler', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, fakeHandler),
handleRequest(standardCacheHeaders, fakeHandler)
)
})
@@ -119,8 +119,8 @@ describe('The request handler', function () {
cacheHeaderConfig,
(queryParams, match, sendBadge, request) => {
fakeHandler(queryParams, match, sendBadge, request)
},
),
}
)
)
}
@@ -128,7 +128,7 @@ describe('The request handler', function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
const { headers } = await got(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(headers.date) + 900000,
+new Date(headers.date) + 900000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=900, s-maxage=900')
@@ -142,7 +142,7 @@ describe('The request handler', function () {
const { headers } = await got(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(headers.date) + 900000,
+new Date(headers.date) + 900000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=900, s-maxage=900')
@@ -158,10 +158,10 @@ describe('The request handler', function () {
queryParams,
match,
sendBadge,
request,
request
)
},
),
}
)
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
@@ -178,10 +178,10 @@ describe('The request handler', function () {
queryParams,
match,
sendBadge,
request,
request
)
},
),
}
)
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
@@ -191,10 +191,10 @@ describe('The request handler', function () {
it('should set the expires header to current time + cacheSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const { headers } = await got(
`${baseUrl}/testing/123.json?cacheSeconds=3600`,
`${baseUrl}/testing/123.json?cacheSeconds=3600`
)
const expectedExpiry = new Date(
+new Date(headers.date) + 3600000,
+new Date(headers.date) + 3600000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=3600, s-maxage=3600')
@@ -203,10 +203,10 @@ describe('The request handler', function () {
it('should ignore cacheSeconds when shorter than defaultCacheLengthSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 600 } })
const { headers } = await got(
`${baseUrl}/testing/123.json?cacheSeconds=300`,
`${baseUrl}/testing/123.json?cacheSeconds=300`
)
const expectedExpiry = new Date(
+new Date(headers.date) + 600000,
+new Date(headers.date) + 600000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=600, s-maxage=600')
@@ -217,7 +217,7 @@ describe('The request handler', function () {
const { headers } = await got(`${baseUrl}/testing/123.json`)
expect(headers.expires).to.equal(headers.date)
expect(headers['cache-control']).to.equal(
'no-cache, no-store, must-revalidate',
'no-cache, no-store, must-revalidate'
)
})
})
@@ -234,7 +234,7 @@ describe('The request handler', function () {
++handlerCallCount
fakeHandler(queryParams, match, sendBadge, request)
},
}),
})
)
})
@@ -242,7 +242,7 @@ describe('The request handler', function () {
await performTwoRequests(
baseUrl,
'/testing/123.svg?foo=1',
'/testing/123.svg?foo=2',
'/testing/123.svg?foo=2'
)
expect(handlerCallCount).to.equal(2)
})

View File

@@ -10,7 +10,7 @@ const serviceDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'services',
'services'
)
function toUnixPath(path) {
@@ -51,14 +51,14 @@ async function loadServiceClasses(servicePaths) {
const serviceClasses = []
for await (const servicePath of servicePaths) {
const currentServiceClasses = Object.values(
await import(`file://${servicePath}`),
await import(`file://${servicePath}`)
).flatMap(element =>
typeof element === 'object' ? Object.values(element) : element,
typeof element === 'object' ? Object.values(element) : element
)
if (currentServiceClasses.length === 0) {
throw new InvalidService(
`Expected ${servicePath} to export a service or a collection of services`,
`Expected ${servicePath} to export a service or a collection of services`
)
}
currentServiceClasses.forEach(serviceClass => {
@@ -71,7 +71,7 @@ async function loadServiceClasses(servicePaths) {
return serviceClasses.push(serviceClass)
}
throw new InvalidService(
`Expected ${servicePath} to export a service or a collection of services; one of them was ${serviceClass}`,
`Expected ${servicePath} to export a service or a collection of services; one of them was ${serviceClass}`
)
})
}
@@ -80,20 +80,8 @@ async function loadServiceClasses(servicePaths) {
serviceClasses.map(({ name }) => name),
{
message: 'Duplicate service names found',
},
)
const routeSummaries = []
serviceClasses.forEach(function (serviceClass) {
if (serviceClass.openApi) {
for (const route of Object.values(serviceClass.openApi)) {
routeSummaries.push(route.get.summary)
}
}
})
assertNamesUnique(routeSummaries, {
message: 'Duplicate route summary found',
})
)
return serviceClasses
}
@@ -114,8 +102,8 @@ async function collectDefinitions() {
async function loadTesters() {
return Promise.all(
getServicePaths('*.tester.js').map(
async path => await import(`file://${path}`),
),
async path => await import(`file://${path}`)
)
)
}

View File

@@ -12,56 +12,50 @@ chai.use(chaiAsPromised)
const { expect } = chai
const fixturesDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'loader-test-fixtures',
'loader-test-fixtures'
)
describe('loadServiceClasses function', function () {
it('throws if module exports empty', async function () {
await expect(
loadServiceClasses([
path.join(fixturesDir, 'empty-undefined.fixture.js'),
]),
loadServiceClasses([path.join(fixturesDir, 'empty-undefined.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([path.join(fixturesDir, 'empty-array.fixture.js')]),
loadServiceClasses([path.join(fixturesDir, 'empty-array.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([path.join(fixturesDir, 'empty-object.fixture.js')]),
loadServiceClasses([path.join(fixturesDir, 'empty-object.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'empty-no-export.fixture.js'),
]),
loadServiceClasses([path.join(fixturesDir, 'empty-no-export.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'valid-array.fixture.js'),
path.join(fixturesDir, 'valid-class.fixture.js'),
path.join(fixturesDir, 'empty-array.fixture.js'),
]),
])
).to.be.rejectedWith(InvalidService)
})
it('throws if module exports invalid', async function () {
await expect(
loadServiceClasses([
path.join(fixturesDir, 'invalid-no-base.fixture.js'),
]),
loadServiceClasses([path.join(fixturesDir, 'invalid-no-base.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'invalid-wrong-base.fixture.js'),
]),
])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([path.join(fixturesDir, 'invalid-mixed.fixture.js')]),
loadServiceClasses([path.join(fixturesDir, 'invalid-mixed.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'valid-array.fixture.js'),
path.join(fixturesDir, 'valid-class.fixture.js'),
path.join(fixturesDir, 'invalid-no-base.fixture.js'),
]),
])
).to.be.rejectedWith(InvalidService)
})
@@ -71,7 +65,7 @@ describe('loadServiceClasses function', function () {
path.join(fixturesDir, 'valid-array.fixture.js'),
path.join(fixturesDir, 'valid-object.fixture.js'),
path.join(fixturesDir, 'valid-class.fixture.js'),
]),
])
).to.eventually.have.length(5)
})
})

View File

@@ -1,9 +1,3 @@
/**
* Functions for publishing the shields.io URL schema as an OpenAPI Document
*
* @module
*/
const baseUrl = process.env.BASE_URL
const globalParamRefs = [
{ $ref: '#/components/parameters/style' },
@@ -164,7 +158,7 @@ function examples2openapi(examples) {
const parameters = [
...pathParams,
...Object.entries(queryParams).map(([paramName, exampleValue]) =>
param2openapi(pattern, paramName, exampleValue, 'query'),
param2openapi(pattern, paramName, exampleValue, 'query')
),
...globalParamRefs,
]
@@ -204,7 +198,7 @@ function services2openapi(services) {
if (service.openApi) {
// if the service declares its own OpenAPI definition, use that...
for (const [key, value] of Object.entries(
addGlobalProperties(service.openApi),
addGlobalProperties(service.openApi)
)) {
if (key in paths) {
throw new Error(`Conflicting route: ${key}`)
@@ -214,7 +208,7 @@ function services2openapi(services) {
} else {
// ...otherwise do our best to build one from examples[]
for (const [key, value] of Object.entries(
examples2openapi(service.examples),
examples2openapi(service.examples)
)) {
// allow conflicting routes for legacy examples
paths[key] = value
@@ -253,7 +247,7 @@ function category2openapi(category, services) {
in: 'query',
required: false,
description:
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository. <a href="/docs/logos">Further info</a>.',
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository.',
schema: {
type: 'string',
},
@@ -338,139 +332,4 @@ function category2openapi(category, services) {
return spec
}
/**
* Helper function for assembling an OpenAPI path parameter object
*
* @param {module:core/base-service/openapi~PathParamInput} param Input param
* @returns {module:core/base-service/openapi~OpenApiParam} OpenAPI Parameter Object
* @see https://swagger.io/specification/#parameter-object
*/
function pathParam({
name,
example,
schema = { type: 'string' },
description,
}) {
return { name, in: 'path', required: true, schema, example, description }
}
/**
* Helper function for assembling an array of OpenAPI path parameter objects
* The code
* ```
* const params = pathParams(
* { name: 'name1', example: 'example1' },
* { name: 'name2', example: 'example2' },
* )
* ```
* is equivilent to
* ```
* const params = [
* pathParam({ name: 'name1', example: 'example1' }),
* pathParam({ name: 'name2', example: 'example2' }),
* ]
* ```
*
* @param {...module:core/base-service/openapi~PathParamInput} params Input params
* @returns {Array.<module:core/base-service/openapi~OpenApiParam>} Array of OpenAPI Parameter Objects
* @see {@link module:core/base-service/openapi~pathParam}
*/
function pathParams(...params) {
return params.map(param => pathParam(param))
}
/**
* Helper function for assembling an OpenAPI query parameter object
*
* @param {module:core/base-service/openapi~QueryParamInput} param Input param
* @returns {module:core/base-service/openapi~OpenApiParam} OpenAPI Parameter Object
* @see https://swagger.io/specification/#parameter-object
*/
function queryParam({
name,
example,
schema = { type: 'string' },
required = false,
description,
}) {
const param = { name, in: 'query', required, schema, example, description }
if (example === null && schema.type === 'boolean') {
param.allowEmptyValue = true
}
return param
}
/**
* Helper function for assembling an array of OpenAPI query parameter objects
* The code
* ```
* const params = queryParams(
* { name: 'name1', example: 'example1' },
* { name: 'name2', example: 'example2' },
* )
* ```
* is equivilent to
* ```
* const params = [
* queryParam({ name: 'name1', example: 'example1' }),
* queryParams({ name: 'name2', example: 'example2' }),
* ]
* ```
*
* @param {...module:core/base-service/openapi~QueryParamInput} params Input params
* @returns {Array.<module:core/base-service/openapi~OpenApiParam>} Array of OpenAPI Parameter Objects
* @see {@link module:core/base-service/openapi~queryParam}
*/
function queryParams(...params) {
return params.map(param => queryParam(param))
}
/**
* @typedef {object} PathParamInput
* @property {string} name The name of the parameter. Parameter names are case sensitive
* @property {string} example Example of a valid value for this parameter
* @property {object} [schema={ type: 'string' }] Parameter schema.
* An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
* specifying the parameter type.
* Normally this should be omitted as all path parameters are strings.
* Use this when we also want to pass an enum of valid parameters
* to be presented as a drop-down in the frontend. e.g:
* `{'type': 'string', 'enum': ['github', 'bitbucket'}` (Optional)
* @property {string} description A brief description of the parameter (Optional)
*/
/**
* @typedef {object} QueryParamInput
* @property {string} name The name of the parameter. Parameter names are case sensitive
* @property {string|null} example Example of a valid value for this parameter
* @property {object} [schema={ type: 'string' }] Parameter schema.
* An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
* specifying the parameter type. This can normally be omitted.
* Query params are usually strings. (Optional)
* @property {boolean} [required=false] Determines whether this parameter is mandatory (Optional)
* @property {string} description A brief description of the parameter (Optional)
*/
/**
* OpenAPI Parameter Object
*
* @typedef {object} OpenApiParam
* @property {string} name The name of the parameter
* @property {string|null} example Example of a valid value for this parameter
* @property {('path'|'query')} in The location of the parameter
* @property {object} schema Parameter schema.
* An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
* specifying the parameter type.
* @property {boolean} required Determines whether this parameter is mandatory
* @property {string} description A brief description of the parameter
* @property {boolean} allowEmptyValue If true, allows the ability to pass an empty value to this parameter
*/
export {
category2openapi,
getEnum,
pathParam,
pathParams,
queryParam,
queryParams,
}
export { category2openapi }

View File

@@ -1,11 +1,5 @@
import chai from 'chai'
import {
category2openapi,
pathParam,
pathParams,
queryParam,
queryParams,
} from './openapi.js'
import { category2openapi } from './openapi.js'
import BaseJsonService from './base-json.js'
const { expect } = chai
@@ -98,7 +92,7 @@ const expected = {
in: 'query',
required: false,
description:
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository. <a href="/docs/logos">Further info</a>.',
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository.',
schema: { type: 'string' },
example: 'appveyor',
},
@@ -377,153 +371,8 @@ describe('category2openapi', function () {
category2openapi({ name: 'build' }, [
OpenApiService.getDefinition(),
LegacyService.getDefinition(),
]),
),
])
)
).to.deep.equal(expected)
})
})
describe('pathParam, pathParams', function () {
it('generates a pathParam with defaults', function () {
const input = { name: 'name', example: 'example' }
const expected = {
name: 'name',
in: 'path',
required: true,
schema: {
type: 'string',
},
example: 'example',
description: undefined,
}
expect(pathParam(input)).to.deep.equal(expected)
expect(pathParams(input)[0]).to.deep.equal(expected)
})
it('generates a pathParam with custom args', function () {
const input = {
name: 'name',
example: true,
schema: { type: 'boolean' },
description: 'long desc',
}
const expected = {
name: 'name',
in: 'path',
required: true,
schema: {
type: 'boolean',
},
example: true,
description: 'long desc',
}
expect(pathParam(input)).to.deep.equal(expected)
expect(pathParams(input)[0]).to.deep.equal(expected)
})
it('generates multiple pathParams', function () {
expect(
pathParams(
{ name: 'name1', example: 'example1' },
{ name: 'name2', example: 'example2' },
),
).to.deep.equal([
{
name: 'name1',
in: 'path',
required: true,
schema: {
type: 'string',
},
example: 'example1',
description: undefined,
},
{
name: 'name2',
in: 'path',
required: true,
schema: {
type: 'string',
},
example: 'example2',
description: undefined,
},
])
})
})
describe('queryParam, queryParams', function () {
it('generates a queryParam with defaults', function () {
const input = { name: 'name', example: 'example' }
const expected = {
name: 'name',
in: 'query',
required: false,
schema: { type: 'string' },
example: 'example',
description: undefined,
}
expect(queryParam(input)).to.deep.equal(expected)
expect(queryParams(input)[0]).to.deep.equal(expected)
})
it('generates queryParam with custom args', function () {
const input = {
name: 'name',
example: 'example',
required: true,
description: 'long desc',
}
const expected = {
name: 'name',
in: 'query',
required: true,
schema: { type: 'string' },
example: 'example',
description: 'long desc',
}
expect(queryParam(input)).to.deep.equal(expected)
expect(queryParams(input)[0]).to.deep.equal(expected)
})
it('generates a queryParam with boolean/null example', function () {
const input = { name: 'name', example: null, schema: { type: 'boolean' } }
const expected = {
name: 'name',
in: 'query',
required: false,
schema: { type: 'boolean' },
allowEmptyValue: true,
example: null,
description: undefined,
}
expect(queryParam(input)).to.deep.equal(expected)
expect(queryParams(input)[0]).to.deep.equal(expected)
})
it('generates multiple queryParams', function () {
expect(
queryParams(
{ name: 'name1', example: 'example1' },
{ name: 'name2', example: 'example2' },
),
).to.deep.equal([
{
name: 'name1',
in: 'query',
required: false,
schema: { type: 'string' },
example: 'example1',
description: undefined,
},
{
name: 'name2',
in: 'query',
required: false,
schema: { type: 'string' },
example: 'example2',
description: undefined,
},
])
})
})

View File

@@ -23,7 +23,7 @@ const attrSchema = Joi.object({
.required()
.error(
() =>
'"transformPath" must be a function that transforms named params to a new path',
'"transformPath" must be a function that transforms named params to a new path'
),
transformQueryParams: Joi.func().arity(1),
dateAdded: Joi.date().required(),
@@ -80,7 +80,7 @@ export default function redirector(attrs) {
'inbound',
emojic.arrowHeadingUp,
'Redirector',
route.base,
route.base
)
trace.logTrace('inbound', emojic.ticket, 'Named params', namedParams)
trace.logTrace('inbound', emojic.crayon, 'Query params', queryParams)

View File

@@ -27,7 +27,7 @@ describe('Redirector', function () {
redirector({
...attrs,
name: 'ShinyRedirect',
}).name,
}).name
).to.equal('ShinyRedirect')
})
@@ -41,7 +41,7 @@ describe('Redirector', function () {
it('throws the expected error when dateAdded is missing', function () {
expect(() =>
redirector({ route, category, transformPath }).validateDefinition(),
redirector({ route, category, transformPath }).validateDefinition()
).to.throw('"dateAdded" is required')
})
@@ -93,7 +93,7 @@ describe('Redirector', function () {
})
ServiceClass.register(
{ camp },
{ rasterUrl: 'http://raster.example.test' },
{ rasterUrl: 'http://raster.example.test' }
)
})
@@ -102,7 +102,7 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello-world.svg`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
@@ -114,12 +114,12 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello-world.png`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/new/service/hello-world.png',
'http://raster.example.test/new/service/hello-world.png'
)
})
@@ -128,12 +128,12 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello-world.svg?color=123&style=flat-square`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?color=123&style=flat-square',
'/new/service/hello-world.svg?color=123&style=flat-square'
)
})
@@ -142,12 +142,12 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello%0Dworld.svg?foobar=a%0Db`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello%0Dworld.svg?foobar=a%0Db',
'/new/service/hello%0Dworld.svg?foobar=a%0Db'
)
})
@@ -174,12 +174,12 @@ describe('Redirector', function () {
`${baseUrl}/another/old/service/token/abc123/hello-world.svg`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?token=abc123',
'/new/service/hello-world.svg?token=abc123'
)
})
@@ -188,12 +188,12 @@ describe('Redirector', function () {
`${baseUrl}/another/old/service/token/abc123/hello-world.svg?color=123&style=flat-square`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123',
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123'
)
})
@@ -202,12 +202,12 @@ describe('Redirector', function () {
`${baseUrl}/another/old/service/token/abc123/hello-world.svg?color=123&style=flat-square&token=def456`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123',
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123'
)
})
@@ -229,12 +229,12 @@ describe('Redirector', function () {
`${baseUrl}/override/service/token/abc123/hello-world.svg?style=flat-square&token=def456`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?style=flat-square&token=def456',
'/new/service/hello-world.svg?style=flat-square&token=def456'
)
})
})

View File

@@ -40,7 +40,7 @@ async function getCachedResource({
}
const { buffer } = await checkErrorResponse({})(
await requestFetcher(url, options),
await requestFetcher(url, options)
)
let reqData

View File

@@ -52,7 +52,7 @@ function namedParamsForMatch(captureNames = [], match, ServiceClass) {
if (captureNames.length !== captures.length) {
throw new Error(
`Service ${ServiceClass.name} declares incorrect number of named params ` +
`(expected ${captures.length}, got ${captureNames.length})`,
`(expected ${captures.length}, got ${captureNames.length})`
)
}

View File

@@ -86,9 +86,9 @@ describe('Route helpers', function () {
expect(() =>
namedParamsForMatch(captureNames, regex.exec('/foo/bar/baz.svg'), {
name: 'MyService',
}),
})
).to.throw(
'Service MyService declares incorrect number of named params (expected 2, got 1)',
'Service MyService declares incorrect number of named params (expected 2, got 1)'
)
})
@@ -96,14 +96,14 @@ describe('Route helpers', function () {
expect(
getQueryParamNames({
queryParamSchema: Joi.object({ foo: Joi.string() }).required(),
}),
})
).to.deep.equal(['foo'])
expect(
getQueryParamNames({
queryParamSchema: Joi.object({ foo: Joi.string() })
.rename('bar', 'foo', { ignoreUndefined: true, override: true })
.required(),
}),
})
).to.deep.equal(['foo', 'bar'])
})
})

View File

@@ -18,7 +18,7 @@ const serviceDefinition = Joi.object({
Joi.object({
format: Joi.string().required(),
queryParams: arrayOfStrings,
}),
})
),
examples: Joi.array()
.items(
@@ -40,7 +40,7 @@ const serviceDefinition = Joi.object({
documentation: Joi.object({
__html: Joi.string().required(), // Valid HTML.
}),
}),
})
)
.default([]),
openApi: Joi.object().pattern(
@@ -48,7 +48,7 @@ const serviceDefinition = Joi.object({
Joi.object({
get: Joi.object({
summary: Joi.string().required(),
description: Joi.string(),
description: Joi.string().required(),
parameters: Joi.array()
.items(
Joi.object({
@@ -56,23 +56,19 @@ const serviceDefinition = Joi.object({
description: Joi.string(),
in: Joi.string().valid('query', 'path').required(),
required: Joi.boolean().required(),
schema: Joi.object({
type: Joi.string().required(),
enum: Joi.array(),
}).required(),
allowEmptyValue: Joi.boolean(),
example: Joi.string().allow(null),
}),
schema: Joi.object({ type: Joi.string().required() }).required(),
example: Joi.string(),
})
)
.min(1)
.required(),
}).required(),
}).required(),
}).required()
),
}).required()
function assertValidServiceDefinition(service, message = undefined) {
Joi.assert(service, serviceDefinition, message)
function assertValidServiceDefinition(example, message = undefined) {
Joi.assert(example, serviceDefinition, message)
}
const serviceDefinitionExport = Joi.object({
@@ -83,7 +79,7 @@ const serviceDefinitionExport = Joi.object({
id: Joi.string().required(),
name: Joi.string().required(),
keywords: arrayOfStrings,
}),
})
)
.required(),
services: Joi.array().items(serviceDefinition).required(),

View File

@@ -12,7 +12,7 @@ function validate(
allowAndStripUnknownKeys = true,
},
data,
schema,
schema
) {
if (!schema || !Joi.isSchema(schema)) {
throw Error('A Joi schema is required')
@@ -28,7 +28,7 @@ function validate(
'validate',
emojic.womanShrugging,
traceErrorMessage,
error.message,
error.message
)
let prettyMessage = prettyErrorMessage

View File

@@ -49,7 +49,7 @@ describe('validate', function () {
sinon.match.string,
traceSuccessMessage,
{ requiredString: 'bar' },
{ deep: true },
{ deep: true }
)
})
})
@@ -60,13 +60,13 @@ describe('validate', function () {
validate(
options,
{ requiredString: ['this', "shouldn't", 'work'] },
schema,
schema
)
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidParameter)
expect(e.message).to.equal(
'Invalid Parameter: "requiredString" must be a string',
'Invalid Parameter: "requiredString" must be a string'
)
expect(e.prettyMessage).to.equal(prettyErrorMessage)
}
@@ -74,7 +74,7 @@ describe('validate', function () {
'validate',
sinon.match.string,
traceErrorMessage,
'"requiredString" must be a string',
'"requiredString" must be a string'
)
})
@@ -86,16 +86,16 @@ describe('validate', function () {
{
requiredString: ['this', "shouldn't", 'work'],
},
schema,
schema
)
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidParameter)
expect(e.message).to.equal(
'Invalid Parameter: "requiredString" must be a string',
'Invalid Parameter: "requiredString" must be a string'
)
expect(e.prettyMessage).to.equal(
`${prettyErrorMessage}: requiredString`,
`${prettyErrorMessage}: requiredString`
)
}
})
@@ -107,13 +107,13 @@ describe('validate', function () {
validate(
{ ...options, allowAndStripUnknownKeys: false, includeKeys: true },
{ requiredString: 'bar', extra: 'nonsense', more: 'bogus' },
schema,
schema
)
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidParameter)
expect(e.message).to.equal(
'Invalid Parameter: "extra" is not allowed. "more" is not allowed',
'Invalid Parameter: "extra" is not allowed. "more" is not allowed'
)
expect(e.prettyMessage).to.equal(`${prettyErrorMessage}: extra, more`)
}

View File

@@ -27,16 +27,14 @@ export default class InfluxMetrics {
response = await got.post(request)
} catch (error) {
log.error(
new Error(
`Cannot push metrics. Cause: ${error.name}: ${error.message}`,
),
new Error(`Cannot push metrics. Cause: ${error.name}: ${error.message}`)
)
}
if (response && response.statusCode >= 300) {
log.error(
new Error(
`Cannot push metrics. ${request.url} responded with status code ${response.statusCode}`,
),
`Cannot push metrics. ${request.url} responded with status code ${response.statusCode}`
)
)
}
}
@@ -44,7 +42,7 @@ export default class InfluxMetrics {
startPushingMetrics() {
this._intervalId = setInterval(
() => this.sendMetrics(),
this._config.intervalSeconds * 1000,
this._config.intervalSeconds * 1000
)
}

View File

@@ -47,7 +47,7 @@ describe('Influx metrics', function () {
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(await influxMetrics.metrics()).to.be.contain(
'instance=test-hostname',
'instance=test-hostname'
)
})
@@ -69,7 +69,7 @@ describe('Influx metrics', function () {
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(await influxMetrics.metrics()).to.be.contain(
'instance=test-hostname-alias',
'instance=test-hostname-alias'
)
})
})
@@ -96,7 +96,7 @@ describe('Influx metrics', function () {
.persist()
.post(
'/metrics',
'prometheus,application=shields,env=test-env,instance=instance2 counter1=11',
'prometheus,application=shields,env=test-env,instance=instance2 counter1=11'
)
.basicAuth({ user: 'metrics-username', pass: 'metrics-password' })
.reply(200)
@@ -117,7 +117,7 @@ describe('Influx metrics', function () {
await clock.tickAsync(10)
expect(scope.isDone()).to.be.equal(
true,
`pending mocks: ${scope.pendingMocks()}`,
`pending mocks: ${scope.pendingMocks()}`
)
})
})
@@ -150,9 +150,9 @@ describe('Influx metrics', function () {
.and(
sinon.match.has(
'message',
'Cannot push metrics. Cause: RequestError: Nock: Disallowed net connect for "shields-metrics.io:443/metrics"',
),
),
'Cannot push metrics. Cause: RequestError: Nock: Disallowed net connect for "shields-metrics.io:443/metrics"'
)
)
)
})
@@ -167,9 +167,9 @@ describe('Influx metrics', function () {
.and(
sinon.match.has(
'message',
'Cannot push metrics. https://shields-metrics.io/metrics responded with status code 400',
),
),
'Cannot push metrics. https://shields-metrics.io/metrics responded with status code 400'
)
)
)
})
})

View File

@@ -4,7 +4,7 @@ function promClientJsonToInfluxV2(metrics, extraLabels = {}) {
return metrics
.flatMap(metric => {
const valuesByLabels = groupBy(metric.values, value =>
JSON.stringify(Object.entries(value.labels).sort()),
JSON.stringify(Object.entries(value.labels).sort())
)
return Object.values(valuesByLabels).map(metricsWithSameLabel => {
const labels = Object.entries(metricsWithSameLabel[0].labels)

View File

@@ -95,7 +95,7 @@ describe('Metric format converters', function () {
prometheus,le=50 histogram1_bucket=2
prometheus,le=15 histogram1_bucket=2
prometheus,le=5 histogram1_bucket=1
prometheus histogram1_count=3,histogram1_sum=111`),
prometheus histogram1_count=3,histogram1_sum=111`)
)
})
@@ -118,7 +118,7 @@ prometheus histogram1_count=3,histogram1_sum=111`),
prometheus,le=50 histogram1_bucket=2
prometheus,le=15 histogram1_bucket=2
prometheus,le=5 histogram1_bucket=1
prometheus histogram1_count=3,histogram1_sum=111`),
prometheus histogram1_count=3,histogram1_sum=111`)
)
})
@@ -145,7 +145,7 @@ prometheus histogram1_count=3,histogram1_sum=111`),
sortLines(`prometheus,quantile=0.99 summary1=100
prometheus,quantile=0.9 summary1=100
prometheus,quantile=0.1 summary1=1
prometheus summary1_count=3,summary1_sum=111`),
prometheus summary1_count=3,summary1_sum=111`)
)
})
@@ -167,7 +167,7 @@ prometheus summary1_count=3,summary1_sum=111`),
sortLines(`prometheus,quantile=0.99 summary1=100
prometheus,quantile=0.9 summary1=100
prometheus,quantile=0.1 summary1=1
prometheus summary1_count=3,summary1_sum=111`),
prometheus summary1_count=3,summary1_sum=111`)
)
})
@@ -204,7 +204,7 @@ prometheus summary1_count=3,summary1_sum=111`),
})
expect(influx).to.be.equal(
'prometheus,env=production,instance=instance1 counter1=11',
'prometheus,env=production,instance=instance1 counter1=11'
)
})
})

View File

@@ -79,7 +79,7 @@ export default class PrometheusMetrics {
return this.counters.serviceResponseSize.labels(
category,
serviceFamily,
service,
service
)
}
}

View File

@@ -65,11 +65,11 @@ const publicConfigSchema = Joi.object({
bind: {
port: Joi.alternatives().try(
Joi.number().port(),
Joi.string().pattern(/^\\\\\.\\pipe\\.+$/),
Joi.string().pattern(/^\\\\\.\\pipe\\.+$/)
),
address: Joi.alternatives().try(
Joi.string().ip().required(),
Joi.string().hostname().required(),
Joi.string().hostname().required()
),
},
metrics: {
@@ -154,15 +154,14 @@ const publicConfigSchema = Joi.object({
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'public',
),
'public'
)
),
requireCloudflare: Joi.boolean().required(),
}).required()
const privateConfigSchema = Joi.object({
azure_devops_token: Joi.string(),
curseforge_api_key: Joi.string(),
discord_bot_token: Joi.string(),
drone_token: Joi.string(),
gh_client_id: Joi.string(),
@@ -184,7 +183,6 @@ const privateConfigSchema = Joi.object({
obs_user: Joi.string(),
obs_pass: Joi.string(),
redis_url: Joi.string().uri({ scheme: ['redis', 'rediss'] }),
opencollective_token: Joi.string(),
postgres_url: Joi.string().uri({ scheme: 'postgresql' }),
sentry_dsn: Joi.string(),
sl_insight_userUuid: Joi.string(),
@@ -238,7 +236,7 @@ class Server {
const publicConfig = Joi.attempt(config.public, publicConfigSchema)
const privateConfig = this.validatePrivateConfig(
config.private,
privateConfigSchema,
privateConfigSchema
)
// We want to require an username and a password for the influx metrics
// only if the influx metrics are enabled. The private config schema
@@ -247,7 +245,7 @@ class Server {
if (publicConfig.metrics.influx && publicConfig.metrics.influx.enabled) {
this.validatePrivateConfig(
config.private,
privateMetricsInfluxConfigSchema,
privateMetricsInfluxConfigSchema
)
}
this.config = {
@@ -272,7 +270,7 @@ class Server {
Object.assign({}, publicConfig.metrics.influx, {
username: privateConfig.influx_username,
password: privateConfig.influx_password,
}),
})
)
}
}
@@ -285,8 +283,8 @@ class Server {
const badPaths = e.details.map(({ path }) => path)
throw Error(
`Private configuration is invalid. Check these paths: ${badPaths.join(
',',
)}`,
','
)}`
)
}
}
@@ -352,35 +350,32 @@ class Server {
makeSend(
'svg',
request.res,
end,
end
)(
makeBadge({
label: '410',
message: `${format} no longer available`,
color: 'lightgray',
format: 'svg',
}),
})
)
})
if (!rasterUrl) {
camp.route(
/^\/((?!img|assets\/)).*\.png$/,
(query, match, end, request) => {
makeSend(
'svg',
request.res,
end,
)(
makeBadge({
label: '404',
message: 'raster badges not available',
color: 'lightgray',
format: 'svg',
}),
)
},
)
camp.route(/^\/((?!img\/)).*\.png$/, (query, match, end, request) => {
makeSend(
'svg',
request.res,
end
)(
makeBadge({
label: '404',
message: 'raster badges not available',
color: 'lightgray',
format: 'svg',
})
)
})
}
camp.notfound(/(\.svg|\.json|)$/, (query, match, end, request) => {
@@ -390,14 +385,14 @@ class Server {
makeSend(
format,
request.res,
end,
end
)(
makeBadge({
label: '404',
message: 'badge not found',
color: 'red',
format,
}),
})
)
})
}
@@ -417,21 +412,18 @@ class Server {
if (rasterUrl) {
// Redirect to the raster server for raster versions of modern badges.
camp.route(
/^\/((?!img|assets\/)).*\.png$/,
(queryParams, match, end, ask) => {
ask.res.statusCode = 301
ask.res.setHeader(
'Location',
rasterRedirectUrl({ rasterUrl }, ask.req.url),
)
camp.route(/^\/((?!img\/)).*\.png$/, (queryParams, match, end, ask) => {
ask.res.statusCode = 301
ask.res.setHeader(
'Location',
rasterRedirectUrl({ rasterUrl }, ask.req.url)
)
const cacheDuration = (30 * 24 * 3600) | 0 // 30 days.
ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`)
const cacheDuration = (30 * 24 * 3600) | 0 // 30 days.
ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`)
ask.res.end()
},
)
ask.res.end()
})
}
if (redirectUrl) {
@@ -467,8 +459,8 @@ class Server {
rasterUrl: config.public.rasterUrl,
private: config.private,
public: config.public,
},
),
}
)
)
}

View File

@@ -19,7 +19,7 @@ describe('The server', function () {
public: {
documentRoot: path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'test-public',
'test-public'
),
},
})
@@ -66,7 +66,7 @@ describe('The server', function () {
it('should return cors header for the request', async function () {
const { statusCode, headers } = await got(
`${baseUrl}badge/foo-bar-blue.svg`,
`${baseUrl}badge/foo-bar-blue.svg`
)
expect(statusCode).to.equal(200)
expect(headers['access-control-allow-origin']).to.equal('*')
@@ -77,11 +77,11 @@ describe('The server', function () {
`${baseUrl}:fruit-apple-green.png`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/:fruit-apple-green.png',
'http://raster.example.test/:fruit-apple-green.png'
)
})
@@ -90,11 +90,11 @@ describe('The server', function () {
`${baseUrl}badge/foo-bar-blue.png`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/badge/foo-bar-blue.png',
'http://raster.example.test/badge/foo-bar-blue.png'
)
})
@@ -105,7 +105,7 @@ describe('The server', function () {
it('should produce SVG badges with expected headers', async function () {
const { statusCode, headers } = await got(
`${baseUrl}:fruit-apple-green.svg`,
`${baseUrl}:fruit-apple-green.svg`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('image/svg+xml;charset=utf-8')
@@ -119,7 +119,7 @@ describe('The server', function () {
it('should produce JSON badges with expected headers', async function () {
const { statusCode, body, headers } = await got(
`${baseUrl}:fruit-apple-green.json`,
`${baseUrl}:fruit-apple-green.json`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('application/json')
@@ -137,7 +137,7 @@ describe('The server', function () {
// https://github.com/badges/shields/pull/1319
it('should not crash with a numeric logo', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?logo=1`,
`${baseUrl}:fruit-apple-green.svg?logo=1`
)
expect(statusCode).to.equal(200)
expect(body)
@@ -148,7 +148,7 @@ describe('The server', function () {
it('should not crash with a numeric link', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=1`,
`${baseUrl}:fruit-apple-green.svg?link=1`
)
expect(statusCode).to.equal(200)
expect(body)
@@ -159,7 +159,7 @@ describe('The server', function () {
it('should not crash with a boolean link', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=true`,
`${baseUrl}:fruit-apple-green.svg?link=true`
)
expect(statusCode).to.equal(200)
expect(body)
@@ -173,7 +173,7 @@ describe('The server', function () {
`${baseUrl}this/is/not/a/badge.svg`,
{
throwHttpErrors: false,
},
}
)
expect(statusCode).to.equal(404)
expect(body)
@@ -187,7 +187,7 @@ describe('The server', function () {
`${baseUrl}this/is/most/definitely/not/a/badge.js`,
{
throwHttpErrors: false,
},
}
)
expect(statusCode).to.equal(404)
expect(body)
@@ -211,7 +211,7 @@ describe('The server', function () {
`${baseUrl}badge/foo-bar-blue.jpg`,
{
throwHttpErrors: false,
},
}
)
// TODO It would be nice if this were 404 or 410.
expect(statusCode).to.equal(200)
@@ -236,7 +236,7 @@ describe('The server', function () {
await server.start()
const { statusCode, body } = await got(
`${server.baseUrl}badge/foo-bar-blue.svg`,
`${server.baseUrl}badge/foo-bar-blue.svg`
)
expect(statusCode).to.be.equal(200)
@@ -365,7 +365,7 @@ describe('The server', function () {
it('should require url when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.url
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.url" is required',
'"metrics.influx.url" is required'
)
})
@@ -378,21 +378,21 @@ describe('The server', function () {
it('should require timeoutMilliseconds when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.timeoutMilliseconds
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.timeoutMilliseconds" is required',
'"metrics.influx.timeoutMilliseconds" is required'
)
})
it('should require intervalSeconds when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.intervalSeconds
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.intervalSeconds" is required',
'"metrics.influx.intervalSeconds" is required'
)
})
it('should require instanceIdFrom when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.instanceIdFrom
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.instanceIdFrom" is required',
'"metrics.influx.instanceIdFrom" is required'
)
})
@@ -400,7 +400,7 @@ describe('The server', function () {
customConfig.public.metrics.influx.instanceIdFrom = 'env-var'
delete customConfig.public.metrics.influx.instanceIdEnvVarName
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.instanceIdEnvVarName" is required',
'"metrics.influx.instanceIdEnvVarName" is required'
)
})
@@ -422,7 +422,7 @@ describe('The server', function () {
it('should require envLabel when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.envLabel
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.envLabel" is required',
'"metrics.influx.envLabel" is required'
)
})
@@ -439,14 +439,14 @@ describe('The server', function () {
it('should require username when influx configuration is enabled', function () {
delete customConfig.private.influx_username
expect(() => new Server(customConfig)).to.throw(
'Private configuration is invalid. Check these paths: influx_username',
'Private configuration is invalid. Check these paths: influx_username'
)
})
it('should require password when influx configuration is enabled', function () {
delete customConfig.private.influx_password
expect(() => new Server(customConfig)).to.throw(
'Private configuration is invalid. Check these paths: influx_password',
'Private configuration is invalid. Check these paths: influx_password'
)
})
@@ -505,7 +505,7 @@ describe('The server', function () {
})
.post(
'/metrics',
/prometheus,application=shields,category=static,env=localhost-env,family=static-badge,instance=test-instance,service=static_badge service_requests_total=1\n/,
/prometheus,application=shields,category=static,env=localhost-env,family=static-badge,instance=test-instance,service=static_badge service_requests_total=1\n/
)
.basicAuth({ user: 'influx-username', pass: 'influx-password' })
.reply(200)
@@ -515,7 +515,7 @@ describe('The server', function () {
expect(scope.isDone()).to.be.equal(
true,
`pending mocks: ${scope.pendingMocks()}`,
`pending mocks: ${scope.pendingMocks()}`
)
})
})

View File

@@ -121,8 +121,8 @@ if (typeof onlyServices === 'undefined' || onlyServices.includes('*****')) {
} else {
console.info(
`Running tests for ${onlyServices.length} services: ${onlyServices.join(
', ',
)}.\n`,
', '
)}.\n`
)
runner.only(onlyServices)
}

View File

@@ -23,7 +23,7 @@ async function createServiceTester() {
const ServiceClass = Object.values(await import(servicePath))[0]
if (!(ServiceClass.prototype instanceof BaseService)) {
throw Error(
`${servicePath} does not export a single service. Invoke new ServiceTester() directly.`,
`${servicePath} does not export a single service. Invoke new ServiceTester() directly.`
)
}
return ServiceTester.forServiceClass(ServiceClass)

View File

@@ -91,11 +91,11 @@ const factory = superclass =>
Joi.attempt(
json[name],
Joi.string().regex(expected),
`${name} mismatch:`,
`${name} mismatch:`
)
} else {
throw new Error(
"'expected' must be a string, a number, a regex, an array or a Joi schema",
"'expected' must be a string, a number, a regex, an array or a Joi schema"
)
}
}

View File

@@ -25,7 +25,7 @@ class Runner {
*/
async prepare() {
this.testers = (await loadTesters()).flatMap(testerModule =>
Object.values(testerModule),
Object.values(testerModule)
)
this.testers.forEach(tester => {
tester.beforeEach = () => {

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