Compare commits
1 Commits
server-202
...
missing-sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d60bca325f |
@@ -18,6 +18,10 @@ labels: 'keep-service-tests-green'
|
||||
|
||||
<!-- Indicate whether or not the live badge is working. -->
|
||||
|
||||
:link: **CircleCI link**
|
||||
|
||||
<!-- Provide a link to the failing test in CircleCI. -->
|
||||
|
||||
:lady_beetle: **Stack trace**
|
||||
|
||||
```
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/3_Badge_request.yml
vendored
4
.github/ISSUE_TEMPLATE/3_Badge_request.yml
vendored
@@ -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
12
.github/actions/close-bot/action.yml
vendored
Normal 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
67
.github/actions/close-bot/helpers.js
vendored
Normal 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
38
.github/actions/close-bot/index.js
vendored
Normal 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
431
.github/actions/close-bot/package-lock.json
generated
vendored
Normal 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
16
.github/actions/close-bot/package.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
name: 'docusaurus-theme-openapi swizzled component changes warning'
|
||||
description: 'Check for changes in docusaurus-theme-openapi components which are swizzled and prints out a warning'
|
||||
branding:
|
||||
icon: 'alert-triangle'
|
||||
color: 'yellow'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'index.js'
|
||||
@@ -1,105 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Returns info about all files changed in a PR (max 3000 results)
|
||||
*
|
||||
* @param {object} client hydrated octokit ready to use for GitHub Actions
|
||||
* @param {string} owner repo owner
|
||||
* @param {string} repo repo name
|
||||
* @param {number} pullNumber pull request number
|
||||
* @returns {object[]} array of object that describe pr changed files - see https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files
|
||||
*/
|
||||
async function getAllFilesForPullRequest(client, owner, repo, pullNumber) {
|
||||
const perPage = 100 // Max number of items per page
|
||||
let page = 1 // Start with the first page
|
||||
let allFiles = []
|
||||
while (true) {
|
||||
const response = await client.rest.pulls.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber,
|
||||
per_page: perPage,
|
||||
page,
|
||||
})
|
||||
|
||||
if (response.data.length === 0) {
|
||||
// Break the loop if no more results
|
||||
break
|
||||
}
|
||||
|
||||
allFiles = allFiles.concat(response.data)
|
||||
page++ // Move to the next page
|
||||
}
|
||||
return allFiles
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of files changed betwen two tags for a github repo
|
||||
*
|
||||
* @param {object} client hydrated octokit ready to use for GitHub Actions
|
||||
* @param {string} owner repo owner
|
||||
* @param {string} repo repo name
|
||||
* @param {string} baseTag base tag
|
||||
* @param {string} headTag head tag
|
||||
* @returns {string[]} Array listing all changed files betwen the base tag and the head tag
|
||||
*/
|
||||
async function getChangedFilesBetweenTags(
|
||||
client,
|
||||
owner,
|
||||
repo,
|
||||
baseTag,
|
||||
headTag,
|
||||
) {
|
||||
const response = await client.rest.repos.compareCommits({
|
||||
owner,
|
||||
repo,
|
||||
base: baseTag,
|
||||
head: headTag,
|
||||
})
|
||||
|
||||
return response.data.files.map(file => file.filename)
|
||||
}
|
||||
|
||||
function findKeyEndingWith(obj, ending) {
|
||||
for (const key in obj) {
|
||||
if (key.endsWith(ending)) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get large (>1MB) JSON file from git repo on at ref as a json object
|
||||
*
|
||||
* @param {object} client Hydrated octokit ready to use for GitHub Actions
|
||||
* @param {string} owner Repo owner
|
||||
* @param {string} repo Repo name
|
||||
* @param {string} path Path of the file in repo relative to root directory
|
||||
* @param {string} ref Git refrence (commit, branch, tag)
|
||||
* @returns {string[]} Array listing all changed files betwen the base tag and the head tag
|
||||
*/
|
||||
async function getLargeJsonAtRef(client, owner, repo, path, ref) {
|
||||
const fileSha = (
|
||||
await client.rest.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
path,
|
||||
ref,
|
||||
})
|
||||
).data.sha
|
||||
const fileBlob = (
|
||||
await client.rest.git.getBlob({
|
||||
owner,
|
||||
repo,
|
||||
file_sha: fileSha,
|
||||
})
|
||||
).data.content
|
||||
return JSON.parse(Buffer.from(fileBlob, 'base64').toString())
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAllFilesForPullRequest,
|
||||
getChangedFilesBetweenTags,
|
||||
findKeyEndingWith,
|
||||
getLargeJsonAtRef,
|
||||
}
|
||||
148
.github/actions/docusaurus-swizzled-warning/index.js
vendored
148
.github/actions/docusaurus-swizzled-warning/index.js
vendored
@@ -1,148 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const core = require('@actions/core')
|
||||
const github = require('@actions/github')
|
||||
const {
|
||||
getAllFilesForPullRequest,
|
||||
getChangedFilesBetweenTags,
|
||||
findKeyEndingWith,
|
||||
getLargeJsonAtRef,
|
||||
} = require('./helpers')
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const token = core.getInput('github-token', { required: true })
|
||||
|
||||
const { pull_request: pr } = github.context.payload
|
||||
if (!pr) {
|
||||
throw new Error('Event payload missing `pull_request`')
|
||||
}
|
||||
|
||||
const client = github.getOctokit(token)
|
||||
const packageName = 'docusaurus-theme-openapi'
|
||||
const packageParentName = 'docusaurus-preset-openapi'
|
||||
const overideComponents = ['Curl', 'Response']
|
||||
const messageTemplate = `<table><thead><tr><th colspan="2">
|
||||
⚠️ This PR contains changes to components of ${packageName} we've overridden
|
||||
</th></tr>
|
||||
<tr><th colspan="2">
|
||||
We need to watch out for changes to the ${overideComponents.join(
|
||||
', ',
|
||||
)} components
|
||||
</th></tr></thead>
|
||||
`
|
||||
|
||||
if (
|
||||
!['dependabot[bot]', 'dependabot-preview[bot]'].includes(pr.user.login)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const files = await getAllFilesForPullRequest(
|
||||
client,
|
||||
github.context.repo.owner,
|
||||
github.context.repo.repo,
|
||||
pr.number,
|
||||
)
|
||||
|
||||
const file = files.filter(f => f.filename === 'package-lock.json')[0]
|
||||
if (file === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const prCommitRefForFile = file.contents_url.split('ref=')[1]
|
||||
const pkgLockNewJson = await getLargeJsonAtRef(
|
||||
client,
|
||||
github.context.repo.owner,
|
||||
github.context.repo.repo,
|
||||
file.filename,
|
||||
prCommitRefForFile,
|
||||
)
|
||||
const pkgLockOldJson = await getLargeJsonAtRef(
|
||||
client,
|
||||
github.context.repo.owner,
|
||||
github.context.repo.repo,
|
||||
file.filename,
|
||||
'master',
|
||||
)
|
||||
|
||||
const oldVesionModuleKey = findKeyEndingWith(
|
||||
pkgLockOldJson.packages,
|
||||
`node_modules/${packageName}`,
|
||||
)
|
||||
const newVesionModuleKey = findKeyEndingWith(
|
||||
pkgLockNewJson.packages,
|
||||
`node_modules/${packageName}`,
|
||||
)
|
||||
let oldVersion = pkgLockOldJson.packages[oldVesionModuleKey].version
|
||||
let newVersion = pkgLockNewJson.packages[newVesionModuleKey].version
|
||||
|
||||
const oldVesionModuleKeyParent = findKeyEndingWith(
|
||||
pkgLockOldJson.packages,
|
||||
`node_modules/${packageParentName}`,
|
||||
)
|
||||
const newVesionModuleKeyParent = findKeyEndingWith(
|
||||
pkgLockNewJson.packages,
|
||||
`node_modules/${packageParentName}`,
|
||||
)
|
||||
const oldVersionParent =
|
||||
pkgLockOldJson.packages[oldVesionModuleKeyParent].dependencies[
|
||||
packageName
|
||||
].substring(1)
|
||||
const newVersionParent =
|
||||
pkgLockNewJson.packages[newVesionModuleKeyParent].dependencies[
|
||||
packageName
|
||||
].substring(1)
|
||||
|
||||
// if parent dependency is higher version then existing
|
||||
// npm install will retrive the newer version from the parent dependency
|
||||
if (oldVersionParent > oldVersion) {
|
||||
oldVersion = oldVersionParent
|
||||
}
|
||||
if (newVersionParent > newVersion) {
|
||||
newVersion = newVersionParent
|
||||
}
|
||||
core.info(`oldVersion=${oldVersion}`)
|
||||
core.info(`newVersion=${newVersion}`)
|
||||
|
||||
if (newVersion !== oldVersion) {
|
||||
const pkgChangedFiles = await getChangedFilesBetweenTags(
|
||||
client,
|
||||
'cloud-annotations',
|
||||
'docusaurus-openapi',
|
||||
`v${oldVersion}`,
|
||||
`v${newVersion}`,
|
||||
)
|
||||
const changedComponents = overideComponents.filter(
|
||||
componenet =>
|
||||
pkgChangedFiles.filter(
|
||||
path =>
|
||||
path.includes('docusaurus-theme-openapi/src/theme') &&
|
||||
path.includes(componenet),
|
||||
).length > 0,
|
||||
)
|
||||
const versionReport = `<tbody><tr><td> Old version </td><td> ${oldVersion} </td></tr>
|
||||
<tr><td> New version </td><td> ${newVersion} </td></tr>
|
||||
`
|
||||
const changedComponentsReport = `<tr><td> Overide components changed </td><td> ${changedComponents.join(
|
||||
', ',
|
||||
)} </td></tr></tbody></table>
|
||||
`
|
||||
const body = messageTemplate + versionReport + changedComponentsReport
|
||||
await client.rest.issues.createComment({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body,
|
||||
})
|
||||
|
||||
core.info('Found changes and posted comment, done.')
|
||||
return
|
||||
}
|
||||
|
||||
core.info('No changes found, done.')
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
237
.github/actions/docusaurus-swizzled-warning/package-lock.json
generated
vendored
237
.github/actions/docusaurus-swizzled-warning/package-lock.json
generated
vendored
@@ -1,237 +0,0 @@
|
||||
{
|
||||
"name": "docusaurus-swizzled-warning",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "docusaurus-swizzled-warning",
|
||||
"version": "0.0.0",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
|
||||
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
|
||||
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.2.0",
|
||||
"@octokit/core": "^5.0.1",
|
||||
"@octokit/plugin-paginate-rest": "^9.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz",
|
||||
"integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz",
|
||||
"integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.1.tgz",
|
||||
"integrity": "sha512-lyeeeZyESFo+ffI801SaBKmCfsvarO+dgV8/0gD8u1d87clbEdWsP5yC+dSj3zLhb2eIf5SJrn6vDz9AheETHw==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.0.0",
|
||||
"@octokit/request": "^8.0.2",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.1.tgz",
|
||||
"integrity": "sha512-hRlOKAovtINHQPYHZlfyFwaM8OyetxeoC81lAkBy34uLb8exrZB50SQdeW3EROqiY9G9yxQTpp5OHTV54QD+vA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz",
|
||||
"integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^8.0.1",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz",
|
||||
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.0.0.tgz",
|
||||
"integrity": "sha512-oIJzCpttmBTlEhBmRvb+b9rlnGpmFgDtZ0bB6nq39qIod6A5DP+7RkVLMOixIgRCYSHDTeayWqmiJ2SZ6xgfdw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.0.1.tgz",
|
||||
"integrity": "sha512-fgS6HPkPvJiz8CCliewLyym9qAx0RZ/LKh3sATaPfM41y/O2wQ4Z9MrdYeGPVh04wYmHFmWiGlKPC7jWVtZXQA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz",
|
||||
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz",
|
||||
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz",
|
||||
"integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.28.4",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
|
||||
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "docusaurus-swizzled-warning",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "jNullj",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
}
|
||||
2
.github/actions/draft-release/Dockerfile
vendored
2
.github/actions/draft-release/Dockerfile
vendored
@@ -1,4 +1,4 @@
|
||||
FROM node:20-bullseye
|
||||
FROM node:12-buster
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y jq
|
||||
|
||||
9
.github/actions/draft-release/entrypoint.sh
vendored
9
.github/actions/draft-release/entrypoint.sh
vendored
@@ -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))
|
||||
|
||||
5
.github/actions/service-tests/action.yml
vendored
5
.github/actions/service-tests/action.yml
vendored
@@ -16,10 +16,6 @@ inputs:
|
||||
description: 'The SERVICETESTS_OBS_PASS secret'
|
||||
required: false
|
||||
default: ''
|
||||
pepy-key:
|
||||
description: 'The SERVICETESTS_PEPY_KEY secret'
|
||||
required: false
|
||||
default: ''
|
||||
sl-insight-user-uuid:
|
||||
description: 'The SERVICETESTS_SL_INSIGHT_USER_UUID secret'
|
||||
required: false
|
||||
@@ -70,7 +66,6 @@ runs:
|
||||
LIBRARIESIO_TOKENS: '${{ inputs.librariesio-tokens }}'
|
||||
OBS_USER: '${{ inputs.obs-user }}'
|
||||
OBS_PASS: '${{ inputs.obs-pass }}'
|
||||
PEPY_KEY: '${{ inputs.pepy-key }}'
|
||||
SL_INSIGHT_USER_UUID: '${{ inputs.sl-insight-user-uuid }}'
|
||||
SL_INSIGHT_API_TOKEN: '${{ inputs.sl-insight-api-token }}'
|
||||
TWITCH_CLIENT_ID: '${{ inputs.twitch-client-id }}'
|
||||
|
||||
12
.github/actions/setup/action.yml
vendored
12
.github/actions/setup/action.yml
vendored
@@ -2,12 +2,8 @@ name: 'Set up project'
|
||||
description: 'Set up project'
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'Version Spec of the node version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
required: true
|
||||
npm-version:
|
||||
description: 'Version Spec of the npm version to use. Examples: 9.x, 10.2.3, >=10.1.0.'
|
||||
required: false
|
||||
default: '^10'
|
||||
cypress:
|
||||
description: 'Install Cypress binary (boolean)'
|
||||
type: boolean
|
||||
@@ -19,14 +15,10 @@ runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
|
||||
- name: Install NPM ${{ inputs.npm-version }}
|
||||
run: npm install -g npm@${{ inputs.npm-version }}
|
||||
shell: bash
|
||||
|
||||
- name: Install dependencies
|
||||
if: ${{ inputs.cypress == 'false' }}
|
||||
env:
|
||||
|
||||
46
.github/dependabot.yml
vendored
46
.github/dependabot.yml
vendored
@@ -16,20 +16,10 @@ updates:
|
||||
# https://caniuse.com/js-regexp-lookbehind
|
||||
- dependency-name: 'decamelize'
|
||||
- dependency-name: 'humanize-string'
|
||||
groups:
|
||||
# All official @docusaurus/* packages should have the exact same version as @docusaurus/core.
|
||||
# From https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups:
|
||||
# "You cannot apply a single grouping set of rules to both version updates and security
|
||||
# updates [...] you must define two, separately named, grouping sets of rules"
|
||||
# See https://github.com/badges/shields/issues/10242 for more information.
|
||||
docusaurus-version-updates:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- '@docusaurus/*'
|
||||
docusaurus-security-updates:
|
||||
applies-to: security-updates
|
||||
patterns:
|
||||
- '@docusaurus/*'
|
||||
|
||||
# https://github.com/badges/shields/pull/7288#issuecomment-974699240
|
||||
- dependency-name: '@types/node'
|
||||
|
||||
# badge-maker package dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/badge-maker'
|
||||
@@ -40,28 +30,20 @@ updates:
|
||||
open-pull-requests-limit: 99
|
||||
rebase-strategy: disabled
|
||||
|
||||
# GH actions
|
||||
- package-ecosystem: 'github-actions'
|
||||
# all composite actions must be individually listed here
|
||||
# https://github.com/dependabot/dependabot-core/issues/6704
|
||||
directories:
|
||||
- '/'
|
||||
- '/.github/actions/core-tests'
|
||||
- '/.github/actions/integration-tests'
|
||||
- '/.github/actions/package-tests'
|
||||
- '/.github/actions/service-tests'
|
||||
- '/.github/actions/setup'
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 99
|
||||
rebase-strategy: disabled
|
||||
|
||||
# docusaurus-swizzled-warning package dependencies
|
||||
# close-bot package dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: '/.github/actions/docusaurus-swizzled-warning'
|
||||
directory: '/.github/actions/close-bot'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
rebase-strategy: disabled
|
||||
|
||||
# GH actions
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 99
|
||||
rebase-strategy: disabled
|
||||
|
||||
10
.github/scripts/deploy-review-app.sh
vendored
10
.github/scripts/deploy-review-app.sh
vendored
@@ -10,21 +10,21 @@ org="shields-io"
|
||||
# This will fail if $PR_NUMBER is not a valid PR
|
||||
pr_json=$(curl --fail "https://api.github.com/repos/badges/shields/pulls/$PR_NUMBER")
|
||||
|
||||
# Checkout the PR branch
|
||||
# Attempt to apply the PR diff to the target branch
|
||||
# This will fail if it does not merge cleanly
|
||||
git config user.name "actions[bot]"
|
||||
git config user.email "actions@users.noreply.github.com"
|
||||
git fetch origin "pull/$PR_NUMBER/head:pr-$PR_NUMBER"
|
||||
git checkout "pr-$PR_NUMBER"
|
||||
git merge "pr-$PR_NUMBER"
|
||||
|
||||
# If the app does not already exist, create it
|
||||
if ! flyctl status --app "$app"; then
|
||||
flyctl launch --no-deploy --copy-config --name "$app" --region "$region" --org "$org" --dockerfile ./Dockerfile
|
||||
flyctl launch --no-deploy --copy-config --name "$app" --region "$region" --org "$org"
|
||||
echo $SECRETS | tr " " "\n" | flyctl secrets import --app "$app"
|
||||
fi
|
||||
|
||||
# Deploy
|
||||
flyctl deploy --app "$app" --regions "$region"
|
||||
flyctl scale count 1 --app "$app" --yes
|
||||
flyctl deploy --app "$app" --region "$region"
|
||||
|
||||
# Post a comment on the PR
|
||||
app_url=$(flyctl status --app "$app" --json | jq -r .Hostname)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
name: Docusaurus swizzled component changes warning
|
||||
name: Auto close
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
docusaurus-swizzled-warning:
|
||||
auto-close:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install action dependencies
|
||||
run: cd .github/actions/docusaurus-swizzled-warning && npm ci
|
||||
run: cd .github/actions/close-bot && npm ci
|
||||
|
||||
- uses: ./.github/actions/docusaurus-swizzled-warning
|
||||
- uses: ./.github/actions/close-bot
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
11
.github/workflows/build-docker-image.yml
vendored
11
.github/workflows/build-docker-image.yml
vendored
@@ -1,25 +1,24 @@
|
||||
name: Build Docker Image
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'gh-readonly-queue/**'
|
||||
|
||||
jobs:
|
||||
build-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
||||
2
.github/workflows/cleanup-review-apps.yml
vendored
2
.github/workflows/cleanup-review-apps.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: 'Review Apps'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
|
||||
- name: install jq
|
||||
|
||||
72
.github/workflows/coveralls-code-coverage.yml
vendored
72
.github/workflows/coveralls-code-coverage.yml
vendored
@@ -1,72 +0,0 @@
|
||||
name: Coveralls Code Coverage
|
||||
on:
|
||||
schedule:
|
||||
- cron: '10 7 * * *'
|
||||
# At 07:10, daily
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
coveralls-code-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: ci_test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
# Even though we're currently deploying on Node 20, we run coverage test on Node 22
|
||||
# to work around https://github.com/bcoe/v8-coverage/pull/2.
|
||||
node-version: 22
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Migrate DB
|
||||
run: npm run migrate up
|
||||
env:
|
||||
POSTGRES_URL: postgresql://postgres:postgres@localhost:5432/ci_test
|
||||
shell: bash
|
||||
|
||||
- name: Coverage for Main Tests
|
||||
run: npm run coverage:test
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GH_PAT }}'
|
||||
POSTGRES_URL: postgresql://postgres:postgres@localhost:5432/ci_test
|
||||
shell: bash
|
||||
|
||||
- name: Coverage for Service Tests
|
||||
run: npm run coverage:test:services
|
||||
continue-on-error: true
|
||||
env:
|
||||
RETRY_COUNT: 3
|
||||
GH_TOKEN: '${{ secrets.GH_PAT }}'
|
||||
LIBRARIESIO_TOKENS: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
OBS_USER: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
OBS_PASS: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
PEPY_KEY: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
SL_INSIGHT_USER_UUID: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
SL_INSIGHT_API_TOKEN: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
TWITCH_CLIENT_ID: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
TWITCH_CLIENT_SECRET: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
|
||||
WHEELMAP_TOKEN: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
|
||||
YOUTUBE_API_KEY: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
|
||||
shell: bash
|
||||
|
||||
- name: Coveralls GitHub Action
|
||||
uses: coverallsapp/github-action@v2
|
||||
14
.github/workflows/create-release.yml
vendored
14
.github/workflows/create-release.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
run: echo "::set-output name=date::$(date --rfc-3339=date)"
|
||||
|
||||
- name: Checkout branch "master"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: 'master'
|
||||
|
||||
@@ -35,16 +35,18 @@ jobs:
|
||||
tag: server-${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to DockerHub
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -53,14 +55,14 @@ jobs:
|
||||
version=server-${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to GHCR
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
71
.github/workflows/daily-tests.yml
vendored
71
.github/workflows/daily-tests.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: Run Daily Tests
|
||||
on:
|
||||
schedule:
|
||||
- cron: '45 3 * * *'
|
||||
# At 03:45, daily
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
daily-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: ci_test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Core tests
|
||||
if: always()
|
||||
uses: ./.github/actions/core-tests
|
||||
|
||||
- name: Package tests
|
||||
if: always()
|
||||
uses: ./.github/actions/package-tests
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: always()
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
|
||||
- name: Run Service tests
|
||||
run: npm run test:services -- --reporter json --reporter-option 'output=reports/service-tests.json'
|
||||
if: always()
|
||||
env:
|
||||
RETRY_COUNT: 3
|
||||
GH_TOKEN: '${{ secrets.GH_PAT }}'
|
||||
LIBRARIESIO_TOKENS: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
OBS_USER: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
OBS_PASS: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
PEPY_KEY: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
SL_INSIGHT_USER_UUID: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
SL_INSIGHT_API_TOKEN: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
TWITCH_CLIENT_ID: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
TWITCH_CLIENT_SECRET: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
|
||||
WHEELMAP_TOKEN: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
|
||||
YOUTUBE_API_KEY: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
|
||||
|
||||
- name: Write Service Tests Markdown Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo '# Services' >> $GITHUB_STEP_SUMMARY
|
||||
node scripts/mocha2md.js Report reports/service-tests.json >> $GITHUB_STEP_SUMMARY
|
||||
4
.github/workflows/danger.yml
vendored
4
.github/workflows/danger.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
if: github.actor != 'dependabot[bot]' && github.actor != 'repo-ranger[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: Danger
|
||||
run: npm run danger ci
|
||||
|
||||
4
.github/workflows/deploy-docs.yml
vendored
4
.github/workflows/deploy-docs.yml
vendored
@@ -12,14 +12,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: Build
|
||||
run: npm run build-docs
|
||||
|
||||
3
.github/workflows/deploy-review-app.yml
vendored
3
.github/workflows/deploy-review-app.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: 'Review Apps'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
@@ -35,7 +35,6 @@ jobs:
|
||||
LIBRARIESIO_TOKENS=${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}
|
||||
OBS_USER=${{ secrets.SERVICETESTS_OBS_USER }}
|
||||
OBS_PASS=${{ secrets.SERVICETESTS_OBS_PASS }}
|
||||
PEPY_KEY=${{ secrets.SERVICETESTS_PEPY_KEY }}
|
||||
SL_INSIGHT_API_TOKEN=${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}
|
||||
SL_INSIGHT_USER_UUID=${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}
|
||||
TWITCH_CLIENT_ID=${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}
|
||||
|
||||
2
.github/workflows/draft-release.yml
vendored
2
.github/workflows/draft-release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Draft Release
|
||||
uses: ./.github/actions/draft-release
|
||||
|
||||
@@ -8,6 +8,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
uses: actions/dependency-review-action@v3
|
||||
|
||||
18
.github/workflows/publish-docker-next.yml
vendored
18
.github/workflows/publish-docker-next.yml
vendored
@@ -12,13 +12,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -27,8 +29,7 @@ jobs:
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push to DockerHub
|
||||
id: docker_build_push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -36,18 +37,15 @@ jobs:
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
|
||||
- name: Output Image Digest
|
||||
run: echo ${{ steps.docker_build_push.outputs.digest }} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to GHCR
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
69
.github/workflows/test-bug-run-badge.yml
vendored
69
.github/workflows/test-bug-run-badge.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: Test new bug report badge
|
||||
run-name: Test bug report on issue ${{ github.event.issue.number }}
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
extract-bug-badge-url:
|
||||
if: ${{ contains(github.event.issue.labels.*.name, 'question') }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
runBadgeTest: ${{ steps.testCondition.outputs.runNext }}
|
||||
link: ${{ steps.testCondition.outputs.link }}
|
||||
steps:
|
||||
- name: Test badge test run conditions
|
||||
id: testCondition
|
||||
env:
|
||||
ISSUE_BODY: '${{ github.event.issue.body }}'
|
||||
run: |
|
||||
product=$(echo "$ISSUE_BODY" | grep -A2 "Are you experiencing an issue with.*" | tail -n 1)
|
||||
link=$(echo "$ISSUE_BODY" | grep -A2 "Link to the badge.*" | tail -n 1)
|
||||
|
||||
if [[ "$product" == "shields.io" && "$link" == "https://img.shields.io"* ]]; then
|
||||
echo "runNext=true" >> "$GITHUB_OUTPUT"
|
||||
echo "link=$link" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Conditions not met. Skipping the workflow..."
|
||||
echo "runNext=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
run-bug-badge-url-test:
|
||||
needs: extract-bug-badge-url
|
||||
if: needs.extract-bug-badge-url.outputs.runBadgeTest == 'true'
|
||||
permissions:
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
cypress: false
|
||||
|
||||
- name: Output debug info
|
||||
env:
|
||||
TEST_BADGE_LINK: '${{ needs.extract-bug-badge-url.outputs.link }}'
|
||||
run: npm run badge $TEST_BADGE_LINK
|
||||
|
||||
- name: Add Comment to Issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueNumber = context.issue.number;
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const runId = context.runId;
|
||||
const jobUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`;
|
||||
const issueComment = `
|
||||
Badge tested using \`npm run badge ${{ needs.extract-bug-badge-url.outputs.link }}\`
|
||||
Output is available [here](${jobUrl})
|
||||
`;
|
||||
github.rest.issues.createComment({
|
||||
issue_number: issueNumber,
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
body: issueComment
|
||||
});
|
||||
10
.github/workflows/test-e2e.yml
vendored
10
.github/workflows/test-e2e.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache Cypress binary
|
||||
id: cache-cypress
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cypress
|
||||
with:
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
cypress: true
|
||||
|
||||
- name: Run tests
|
||||
@@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
- name: Archive videos
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: videos
|
||||
path: cypress/videos
|
||||
|
||||
- name: Archive screenshots
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: screenshots
|
||||
path: cypress/screenshots
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Integration@node 22
|
||||
name: Integration@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration-22:
|
||||
test-integration-17:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
|
||||
@@ -30,12 +30,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
4
.github/workflows/test-integration.yml
vendored
4
.github/workflows/test-integration.yml
vendored
@@ -30,12 +30,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: ${{ env.PAT_EXISTS == 'true' }}
|
||||
|
||||
4
.github/workflows/test-lint.yml
vendored
4
.github/workflows/test-lint.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: ESLint
|
||||
if: always()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Main@node 22
|
||||
name: Main@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
@@ -8,16 +8,16 @@ on:
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main-22:
|
||||
test-main-17:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
4
.github/workflows/test-main.yml
vendored
4
.github/workflows/test-main.yml
vendored
@@ -17,12 +17,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
|
||||
13
.github/workflows/test-package-cli.yml
vendored
13
.github/workflows/test-package-cli.yml
vendored
@@ -16,20 +16,25 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- node: '14'
|
||||
engine-strict: 'false'
|
||||
- node: '16'
|
||||
engine-strict: 'false'
|
||||
- node: '18'
|
||||
- node: '20'
|
||||
- node: '22'
|
||||
engine-strict: 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
run: |
|
||||
cd badge-maker
|
||||
npm install
|
||||
|
||||
15
.github/workflows/test-package-lib.yml
vendored
15
.github/workflows/test-package-lib.yml
vendored
@@ -13,27 +13,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- node: '14'
|
||||
engine-strict: 'false'
|
||||
- node: '16'
|
||||
npm: '^9'
|
||||
engine-strict: 'false'
|
||||
- node: '18'
|
||||
npm: '^9'
|
||||
engine-strict: 'false'
|
||||
- node: '20'
|
||||
npm: '^10'
|
||||
engine-strict: 'true'
|
||||
- node: '22'
|
||||
npm: '^10'
|
||||
- node: '18'
|
||||
engine-strict: 'false'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
npm-version: ${{ matrix.npm }}
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
name: Services@node 22
|
||||
name: Services@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches:
|
||||
- 'gh-readonly-queue/**'
|
||||
|
||||
jobs:
|
||||
test-services-22:
|
||||
test-services-17:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
@@ -29,7 +26,6 @@ jobs:
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
8
.github/workflows/test-services.yml
vendored
8
.github/workflows/test-services.yml
vendored
@@ -2,9 +2,6 @@ name: Services
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches:
|
||||
- 'gh-readonly-queue/**'
|
||||
|
||||
jobs:
|
||||
test-services:
|
||||
@@ -12,12 +9,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: Service tests (triggered from local branch)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
@@ -27,7 +24,6 @@ jobs:
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
|
||||
6
.github/workflows/update-github-api.yml
vendored
6
.github/workflows/update-github-api.yml
vendored
@@ -14,18 +14,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 16
|
||||
|
||||
- name: Check for new GitHub API version
|
||||
run: node scripts/update-github-api.js
|
||||
|
||||
- name: Create Pull Request if config has changed
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
commit-message: Update GitHub API Version
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,6 +50,9 @@ lib-cov
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
"**/test-helpers.js",
|
||||
"**/*-test-helpers.js",
|
||||
"**/*-fixtures.js",
|
||||
"**/mocha-*.js",
|
||||
"**/*.test-d.ts",
|
||||
"dangerfile.js",
|
||||
"gatsby-*.js",
|
||||
"core/service-test-runner",
|
||||
"core/got-test-client.js",
|
||||
"services/**/*.tester.js",
|
||||
@@ -21,9 +23,6 @@
|
||||
"coverage",
|
||||
"build",
|
||||
".github",
|
||||
"**/public/",
|
||||
"cypress",
|
||||
"frontend",
|
||||
"migrations"
|
||||
"**/public/"
|
||||
]
|
||||
}
|
||||
232
CHANGELOG.md
232
CHANGELOG.md
@@ -4,236 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2024-09-01
|
||||
|
||||
- Revert "Add Platform linux/arm64 to Docker Build (#10441)" [#10477](https://github.com/badges/shields/issues/10477)
|
||||
- fix typo in pepy downloads [#10475](https://github.com/badges/shields/issues/10475)
|
||||
- Add Platform linux/arm64 to Docker Build [#10441](https://github.com/badges/shields/issues/10441)
|
||||
- ignore a couple of docusaurus warnings [#10469](https://github.com/badges/shields/issues/10469)
|
||||
- Use Ecologi API to power Treeware badges [#10467](https://github.com/badges/shields/issues/10467)
|
||||
- un-pin buildx version [#10453](https://github.com/badges/shields/issues/10453)
|
||||
- move go version badge to platform support category [#10444](https://github.com/badges/shields/issues/10444)
|
||||
- remove libraries we don't need in docker build [#10443](https://github.com/badges/shields/issues/10443)
|
||||
- [Crates] Implement Dependents Badge [#10438](https://github.com/badges/shields/issues/10438)
|
||||
- [Crates] Added crate size badge [#10421](https://github.com/badges/shields/issues/10421)
|
||||
- Fix branch in [Bitrise] tests [#10439](https://github.com/badges/shields/issues/10439)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-08-01
|
||||
|
||||
- send Cross-Origin-Resource-Policy header on all responses [#10420](https://github.com/badges/shields/issues/10420)
|
||||
- migrate [MozillaObservatory] to new API [#10402](https://github.com/badges/shields/issues/10402)
|
||||
- use metric() for [discord] and [revolt] badges [#10406](https://github.com/badges/shields/issues/10406)
|
||||
- Cache text only static badges for longer [#10403](https://github.com/badges/shields/issues/10403)
|
||||
- Fix [FreeCodeCampPoints] not found handling [#10377](https://github.com/badges/shields/issues/10377)
|
||||
- Fix [Gitea] not found message [#10373](https://github.com/badges/shields/issues/10373)
|
||||
- Deprecate [Bountysource] service [#10371](https://github.com/badges/shields/issues/10371)
|
||||
- Sunset Shields custom logos [#10347](https://github.com/badges/shields/issues/10347)
|
||||
- Use ellipsis when many versions returned for [ModrinthGameVersions] [#10350](https://github.com/badges/shields/issues/10350)
|
||||
- deprecate [tokei] service [#9581](https://github.com/badges/shields/issues/9581)
|
||||
- Add CF-Ray header value to Sentry errors if available [#10339](https://github.com/badges/shields/issues/10339)
|
||||
- Use XML for Chocolatey, affects [Chocolatey Resharper PowershellGallery] [#10344](https://github.com/badges/shields/issues/10344)
|
||||
- include github contributors badge in docs site [#10337](https://github.com/badges/shields/issues/10337)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-07-01
|
||||
|
||||
- Add [AUR] Popularity Badge [#10304](https://github.com/badges/shields/issues/10304)
|
||||
- fix npm badges when `maintainers` not in response [#10286](https://github.com/badges/shields/issues/10286)
|
||||
- Expose `logoBase64` and `links` in badge-maker NPM package [#10283](https://github.com/badges/shields/issues/10283)
|
||||
- Remove `logoPosition` [#10284](https://github.com/badges/shields/issues/10284)
|
||||
- [MBIN] Add subscribers badge [#10270](https://github.com/badges/shields/issues/10270)
|
||||
- Add [Docker] support for loong64 arch [#10241](https://github.com/badges/shields/issues/10241)
|
||||
- Add puppetforge quality score badges [#10201](https://github.com/badges/shields/issues/10201)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-06-01
|
||||
|
||||
- Remove namedLogo from defaultBadgeData of non-social badges [#10195](https://github.com/badges/shields/issues/10195)
|
||||
- Update number of badges served each month [#10197](https://github.com/badges/shields/issues/10197)
|
||||
- Delete old deprecated services [#10196](https://github.com/badges/shields/issues/10196)
|
||||
- handle [BitbucketPipelines] responses with missing result key [#10163](https://github.com/badges/shields/issues/10163)
|
||||
- Update description of GitHub commit status badge [#10198](https://github.com/badges/shields/issues/10198)
|
||||
- chore: fix spelling of GitHub in badge descriptions [#10199](https://github.com/badges/shields/issues/10199)
|
||||
- Add [GithubCheckRuns] service [#7759](https://github.com/badges/shields/issues/7759)
|
||||
- feat: add Revolt badge [#10093](https://github.com/badges/shields/issues/10093)
|
||||
- ensure color is string before calling toLowerCase() [#10129](https://github.com/badges/shields/issues/10129)
|
||||
- instruct dependabot to monitor composite actions [#10139](https://github.com/badges/shields/issues/10139)
|
||||
- run tests on node 22 [#10127](https://github.com/badges/shields/issues/10127)
|
||||
- tweaks to libraries.io token pooling code [#10074](https://github.com/badges/shields/issues/10074)
|
||||
- fix [pypi] status badge when package has no 'Development Status' classifier [#10107](https://github.com/badges/shields/issues/10107)
|
||||
- clarify yml paths in server-secrets docs [#10106](https://github.com/badges/shields/issues/10106)
|
||||
- Update region flag name in flyctl deploy command [#10134](https://github.com/badges/shields/issues/10134)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-05-01
|
||||
|
||||
- [Hexpm] Fix badges for pre-release only versions [#10112](https://github.com/badges/shields/issues/10112)
|
||||
- feat(logos): support auto-sizing mode [#9191](https://github.com/badges/shields/issues/9191) [#10110](https://github.com/badges/shields/issues/10110) [#10125](https://github.com/badges/shields/issues/10125)
|
||||
- support setting pypiBaseUrl by environment variables and queryParameters; affects [pypi] [#10044](https://github.com/badges/shields/issues/10044)
|
||||
- Add 0BSD license to licenseTypes and [PypiLicense] [#10092](https://github.com/badges/shields/issues/10092)
|
||||
- Update Mastodon profile URL [#10082](https://github.com/badges/shields/issues/10082)
|
||||
- [GitHubGoMod] Ignore comment after version (fixes #10079) [#10080](https://github.com/badges/shields/issues/10080)
|
||||
- Perf: Librariesio repo dependencies [#10062](https://github.com/badges/shields/issues/10062)
|
||||
- [Chocolatey Nuget] Fix "not found" error for chocolatey badge [#10060](https://github.com/badges/shields/issues/10060)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-04-01
|
||||
|
||||
- improve performance of [GithubLastCommit] [GitlabLastCommit] [GiteaLastCommit] [#10046](https://github.com/badges/shields/issues/10046)
|
||||
- [BitbucketLastCommit] Add Bitbucket last commit [#10043](https://github.com/badges/shields/issues/10043)
|
||||
- [GithubLastCommit] [GitlabLastCommit] [GiteaLastCommit] Support file path for last commit [#10041](https://github.com/badges/shields/issues/10041)
|
||||
- upgrade to docusaurus 3 [#9820](https://github.com/badges/shields/issues/9820)
|
||||
- redirect [npm] /dt to /d18m [#10033](https://github.com/badges/shields/issues/10033)
|
||||
- Add [JSR] version service [#10030](https://github.com/badges/shields/issues/10030)
|
||||
- Add [snapcraft] version badge [#9976](https://github.com/badges/shields/issues/9976)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-03-01
|
||||
|
||||
- feat(gitea): add last commit badge [#9995](https://github.com/badges/shields/issues/9995)
|
||||
- [GithubCreatedAt] Add Created At Badge for Github [#9981](https://github.com/badges/shields/issues/9981)
|
||||
- Added custom bucket url support for [Scoop] [#9984](https://github.com/badges/shields/issues/9984)
|
||||
- [NpmUnpackedSize] Unpacked Size Badge [#9954](https://github.com/badges/shields/issues/9954)
|
||||
- [Website] Render `status: down` badge if website is unresponsive [#9966](https://github.com/badges/shields/issues/9966)
|
||||
- deprecate TAS [#9932](https://github.com/badges/shields/issues/9932)
|
||||
- [GITEA] add forks, stars, issues and pr badges [#9923](https://github.com/badges/shields/issues/9923)
|
||||
- tolerate missing short_version in [visualstudioappcenter] [#9951](https://github.com/badges/shields/issues/9951)
|
||||
- [Crates] Only use non-yanked crate versions (ready for merge) [#9949](https://github.com/badges/shields/issues/9949)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-02-01
|
||||
|
||||
- feat: added up_message and down_message to [uptimerobotstatus] [#9662](https://github.com/badges/shields/issues/9662)
|
||||
- Add [Hangar] Badges [#9800](https://github.com/badges/shields/issues/9800)
|
||||
- sort categories by title (except core) [#9888](https://github.com/badges/shields/issues/9888)
|
||||
- Add Support for [Nostr] Followers [#9870](https://github.com/badges/shields/issues/9870)
|
||||
- [thunderstore] replace experimental API usage with newly available v1 API [#9886](https://github.com/badges/shields/issues/9886)
|
||||
- Update [Gitea] defaults to gitea.com [#9872](https://github.com/badges/shields/issues/9872)
|
||||
- [crates] MSRV Badge [#9871](https://github.com/badges/shields/issues/9871)
|
||||
- Add [galaxytoolshed] Version [#8249](https://github.com/badges/shields/issues/8249)
|
||||
- fix default style docs for social badges [#9869](https://github.com/badges/shields/issues/9869)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-01-01
|
||||
|
||||
The most important changes in this release for users hosting their own instance are:
|
||||
|
||||
The shields docker image is now based on node 20:
|
||||
|
||||
- deploy on node 20 [#9799](https://github.com/badges/shields/issues/9799)
|
||||
|
||||
It is now possible to use [authentication for DockerHub](https://github.com/badges/shields/blob/master/doc/server-secrets.md#dockerhub) to allow higher API rate limit or access to private repos:
|
||||
|
||||
- call [docker] with auth [#9803](https://github.com/badges/shields/issues/9803)
|
||||
|
||||
### New Badges
|
||||
|
||||
- [Thunderstore] Add Thunderstore Badges [#9782](https://github.com/badges/shields/issues/9782)
|
||||
- Add [Raycast] Badge [#9801](https://github.com/badges/shields/issues/9801)
|
||||
- [GITEA] add new gitea service (release/languages) [#9781](https://github.com/badges/shields/issues/9781)
|
||||
- Add [NpmStatDownloads] Badge [#9783](https://github.com/badges/shields/issues/9783)
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
- improve documentation for [dynamicxml] service [#9798](https://github.com/badges/shields/issues/9798)
|
||||
- add description to interval enums [#9854](https://github.com/badges/shields/issues/9854)
|
||||
- convert 'style' param to enum [#9853](https://github.com/badges/shields/issues/9853)
|
||||
- Ensure social category badges are rendered with social style and logo; affects [gitlab keybase lemmy modrinth thunderstore twitch] gist github reddit [#9859](https://github.com/badges/shields/issues/9859)
|
||||
|
||||
### Fixes
|
||||
|
||||
- [pub] Use official version endpoint for pub-service [#9802](https://github.com/badges/shields/issues/9802)
|
||||
- cache weblate badges for longer [#9786](https://github.com/badges/shields/issues/9786)
|
||||
- [Discourse] Update schema keys to use plural form (`topic_count` -> `topics_count`) [#9778](https://github.com/badges/shields/issues/9778)
|
||||
- cache some badges for longer [#9785](https://github.com/badges/shields/issues/9785)
|
||||
- increase page size for github release badge by semver [#9818](https://github.com/badges/shields/issues/9818)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-12-04
|
||||
|
||||
- move from @renovate/pep440 to @renovatebot/pep440 [#9614](https://github.com/badges/shields/issues/9614)
|
||||
- deprecate/fix [ansible] galaxy services [#9648](https://github.com/badges/shields/issues/9648)
|
||||
- call [pepy] with auth [#9748](https://github.com/badges/shields/issues/9748)
|
||||
- add meaningful descriptions including keywords [#9715](https://github.com/badges/shields/issues/9715)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-11-01
|
||||
|
||||
- fix greasyfork 404 bug [#9632](https://github.com/badges/shields/issues/9632)
|
||||
- Hacktoberfest 2023 support - resolves #9636 [#9637](https://github.com/badges/shields/issues/9637)
|
||||
- switch to fixed OpenCollective images [#9615](https://github.com/badges/shields/issues/9615)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-10-02
|
||||
|
||||
- add python package total downloads from [pepy] badge [#9564](https://github.com/badges/shields/issues/9564)
|
||||
- deprecate [redmine] plugin rating badges [#9568](https://github.com/badges/shields/issues/9568)
|
||||
- fix [bower] version badge [#9567](https://github.com/badges/shields/issues/9567)
|
||||
- Add [PythonVersionFromToml] shield [#9516](https://github.com/badges/shields/issues/9516)
|
||||
- Add [dub] score badge service [#9549](https://github.com/badges/shields/issues/9549)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-09-04
|
||||
|
||||
- Fix [testspace] badges [#9525](https://github.com/badges/shields/issues/9525)
|
||||
- fix rSt code example [#9528](https://github.com/badges/shields/issues/9528)
|
||||
- Add dynamic TOML support via [DynamicToml] Service [#9517](https://github.com/badges/shields/issues/9517)
|
||||
- cache [pypi] downloads for longer [#9522](https://github.com/badges/shields/issues/9522)
|
||||
- [twitter] --> x [#9496](https://github.com/badges/shields/issues/9496)
|
||||
- [bundlejs] add badge for the npm package size [#9055](https://github.com/badges/shields/issues/9055)
|
||||
- Switch [OpenCollective] badges to use GraphQL and auth [#9387](https://github.com/badges/shields/issues/9387)
|
||||
- [Pulsar] Add Pulsar Badges for Stargazers & Downloads [#8767](https://github.com/badges/shields/issues/8767)
|
||||
- Add [CurseForge] badges [#9252](https://github.com/badges/shields/issues/9252)
|
||||
- deploy on node 18 [#9385](https://github.com/badges/shields/issues/9385)
|
||||
- allow calling [github] without auth [#9427](https://github.com/badges/shields/issues/9427)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-08-01
|
||||
|
||||
- Convert `examples` arrays to `openApi` objects (part 1) [#9320](https://github.com/badges/shields/issues/9320)
|
||||
- Migrate from docs.rs' builds API to status API [#9422](https://github.com/badges/shields/issues/9422)
|
||||
- [OpenVSX] Fix OpenVSX API call for unversioned package URLs [#9408](https://github.com/badges/shields/issues/9408)
|
||||
- Add support for [Lemmy] [#9368](https://github.com/badges/shields/issues/9368)
|
||||
- upgrade to npm 9 [#9323](https://github.com/badges/shields/issues/9323)
|
||||
- Go back to default YouTube cache [#9372](https://github.com/badges/shields/issues/9372)
|
||||
- Add [GitHubDiscussionsSearch] and GitHubRepoDiscussionsSearch service [#9340](https://github.com/badges/shields/issues/9340)
|
||||
- Allow user to filter github tags and releases [#9193](https://github.com/badges/shields/issues/9193)
|
||||
- don't URL encode slash in [githubactionsworkflow] badge [#9322](https://github.com/badges/shields/issues/9322)
|
||||
- add a bit of border to select boxes [#9348](https://github.com/badges/shields/issues/9348)
|
||||
- deprecate [snyk] badges [#9349](https://github.com/badges/shields/issues/9349)
|
||||
- increase max-age on [docker] badges, again [#9350](https://github.com/badges/shields/issues/9350) [#9369](https://github.com/badges/shields/issues/9369)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-07-02
|
||||
|
||||
By far the most significant change in this release is the long-awaited launch of the re-designed frontend:
|
||||
|
||||
- migrate frontend to docusaurus [#9014](https://github.com/badges/shields/issues/9014)
|
||||
- fix a load of spacing issues in frontend content [#9281](https://github.com/badges/shields/issues/9281)
|
||||
- set a sensible meta description [#9283](https://github.com/badges/shields/issues/9283)
|
||||
- chore(frontend): open homepage feature links in new tab [#9300](https://github.com/badges/shields/issues/9300)
|
||||
- adapt opencollective images to theme background [#9298](https://github.com/badges/shields/issues/9298)
|
||||
- temp fix: wrap code examples tabs in narrow browser windows [#9302](https://github.com/badges/shields/issues/9302)
|
||||
- add a bit of border to text boxes [#9324](https://github.com/badges/shields/issues/9324)
|
||||
|
||||
Other changes in this release:
|
||||
|
||||
- cache [dockerpulls] badges for an hour [#9343](https://github.com/badges/shields/issues/9343)
|
||||
- Mention YouTube API services and link to Google Privacy Policy [#9339](https://github.com/badges/shields/issues/9339)
|
||||
- allow negative timestamps in relative [date] badge [#9321](https://github.com/badges/shields/issues/9321)
|
||||
- upgrade to graphql 16 [#9290](https://github.com/badges/shields/issues/9290)
|
||||
- remove obsolete travis .org examples [#9284](https://github.com/badges/shields/issues/9284)
|
||||
- increase max age on reddit badges [#9282](https://github.com/badges/shields/issues/9282)
|
||||
- feat: Add author filter option for [GithubCommitActivity] [#9251](https://github.com/badges/shields/issues/9251)
|
||||
- Fix: [GithubCommitActivity] invalid branch error handling [#9258](https://github.com/badges/shields/issues/9258)
|
||||
- Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] [#9233](https://github.com/badges/shields/issues/9233)
|
||||
- Delete old deprecated services [#9254](https://github.com/badges/shields/issues/9254)
|
||||
- feat: add 'canceled' status to netlify deploy badge [#9240](https://github.com/badges/shields/issues/9240)
|
||||
- increase default cache on youtube badges [#9238](https://github.com/badges/shields/issues/9238)
|
||||
- embiggen youtube cache, again [#9250](https://github.com/badges/shields/issues/9250)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-06-01
|
||||
|
||||
- feat: Add total commits to [GitHubCommitActivity] [#9196](https://github.com/badges/shields/issues/9196)
|
||||
@@ -244,7 +14,7 @@ Other changes in this release:
|
||||
|
||||
## server-2023-05-01
|
||||
|
||||
**Removal:** For users who need to maintain a Github Token pool, storage has been provided via the `RedisTokenPersistence` and `REDIS_URL` settings. This feature was deprecated in `server-2023-03-01`. As of this release, the `RedisTokenPersistence` backend is now removed. If you are using this feature, you will need to migrate to using the `SQLTokenPersistence` backend for storage and provide a postgres connection string via the `POSTGRES_URL` setting. [#8922](https://github.com/badges/shields/issues/8922)
|
||||
** Removal:** For users who need to maintain a Github Token pool, storage has been provided via the `RedisTokenPersistence` and `REDIS_URL` settings. This feature was deprecated in `server-2023-03-01`. As of this release, the `RedisTokenPersistence` backend is now removed. If you are using this feature, you will need to migrate to using the `SQLTokenPersistence` backend for storage and provide a postgres connection string via the `POSTGRES_URL` setting. [#8922](https://github.com/badges/shields/issues/8922)
|
||||
|
||||
- fail to start server if there are duplicate service names [#9099](https://github.com/badges/shields/issues/9099)
|
||||
- [SourceForge] Added badges for SourceForge [#9078](https://github.com/badges/shields/issues/9078) [#9102](https://github.com/badges/shields/issues/9102)
|
||||
|
||||
@@ -77,15 +77,6 @@ don't see it, feel free to [open a new issue][open an issue].
|
||||
|
||||
[open an issue]: https://github.com/badges/shields/issues/new/choose
|
||||
|
||||
### Requesting new logos
|
||||
|
||||
We consume logos via [the SimpleIcons project][simple-icons github], and
|
||||
encourage you to contribute logos there. Please review their
|
||||
[guidance][simple-icons contributing] before doing so.
|
||||
|
||||
[simple-icons github]: https://github.com/simple-icons/simple-icons
|
||||
[simple-icons contributing]: https://github.com/simple-icons/simple-icons/blob/develop/CONTRIBUTING.md
|
||||
|
||||
### Spreading the word
|
||||
|
||||
Feel free to star the repository. This will help increase the visibility of the project, therefore attracting more users and contributors to Shields!
|
||||
@@ -163,6 +154,10 @@ To run the integration tests:
|
||||
|
||||
There is a [High-level code walkthrough](doc/code-walkthrough.md) describing the layout of the project.
|
||||
|
||||
### Logos
|
||||
|
||||
We have [documentation for logo usage](doc/logos.md) which includes [contribution guidance](doc/logos.md#contributing-logos)
|
||||
|
||||
## Pull Requests
|
||||
|
||||
All code changes are incorporated via pull requests, and pull requests are always squashed into a single commit on merging. Therefore there's no requirement to squash commits within your PR, but feel free to squash or restructure the commits on your PR branch if you think it will be helpful. PRs with well structured commits are always easier to review!
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:16-alpine AS Builder
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
RUN mkdir /usr/src/app/private
|
||||
@@ -8,17 +8,18 @@ COPY package.json package-lock.json /usr/src/app/
|
||||
# Without the badge-maker package.json and CLI script in place, `npm ci` will fail.
|
||||
COPY badge-maker /usr/src/app/badge-maker/
|
||||
|
||||
RUN npm install -g "npm@^9.0.0"
|
||||
RUN apk add python3 make g++
|
||||
RUN npm install -g "npm@>=8"
|
||||
# We need dev deps to build the front end. We don't need Cypress, though.
|
||||
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci
|
||||
|
||||
COPY . /usr/src/app
|
||||
RUN npm run build
|
||||
RUN npm prune --omit=dev
|
||||
RUN npm prune --production
|
||||
RUN npm cache clean --force
|
||||
|
||||
# Use multi-stage build to reduce size
|
||||
FROM node:20-alpine
|
||||
FROM node:16-alpine
|
||||
|
||||
ARG version=dev
|
||||
ENV DOCKER_SHIELDS_VERSION=$version
|
||||
@@ -26,11 +27,11 @@ LABEL version=$version
|
||||
LABEL fly.version=$version
|
||||
|
||||
# Run the server using production configs.
|
||||
ENV NODE_ENV=production
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --from=builder --chown=0:0 /usr/src/app /usr/src/app
|
||||
COPY --from=Builder --chown=0:0 /usr/src/app /usr/src/app
|
||||
|
||||
CMD ["node", "server"]
|
||||
CMD node server
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
46
README.md
46
README.md
@@ -3,23 +3,28 @@
|
||||
height="130">
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://shields.io/community#backers" alt="Backers on Open Collective">
|
||||
<a href="https://github.com/badges/shields/graphs/contributors" alt="Contributors">
|
||||
<img src="https://img.shields.io/github/contributors/badges/shields" /></a>
|
||||
<a href="#backers" alt="Backers on Open Collective">
|
||||
<img src="https://img.shields.io/opencollective/backers/shields" /></a>
|
||||
<a href="https://shields.io/community#sponsors" alt="Sponsors on Open Collective">
|
||||
<a href="#sponsors" alt="Sponsors on Open Collective">
|
||||
<img src="https://img.shields.io/opencollective/sponsors/shields" /></a>
|
||||
<a href="https://github.com/badges/shields/pulse" alt="Activity">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/badges/shields" /></a>
|
||||
<a href="https://github.com/badges/shields/discussions" alt="Discussions">
|
||||
<img src="https://img.shields.io/github/discussions/badges/shields" /></a>
|
||||
<a href="https://github.com/badges/shields/actions/workflows/daily-tests.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/badges/shields/daily-tests.yml?label=daily%20tests"
|
||||
alt="Daily Tests Status"></a>
|
||||
<a href="https://circleci.com/gh/badges/shields/tree/master">
|
||||
<img src="https://img.shields.io/circleci/project/github/badges/shields/master" alt="build status"></a>
|
||||
<a href="https://circleci.com/gh/badges/daily-tests">
|
||||
<img src="https://img.shields.io/circleci/project/github/badges/daily-tests?label=service%20tests"
|
||||
alt="service-test status"></a>
|
||||
<a href="https://coveralls.io/github/badges/shields">
|
||||
<img src="https://img.shields.io/coveralls/github/badges/shields"
|
||||
alt="Code Coverage"></a>
|
||||
alt="coverage"></a>
|
||||
<a href="https://discord.gg/HjJCwm5">
|
||||
<img src="https://img.shields.io/discord/308323056592486420?logo=discord&logoColor=white"
|
||||
alt="Chat on Discord"></a>
|
||||
<img src="https://img.shields.io/discord/308323056592486420?logo=discord"
|
||||
alt="chat on Discord"></a>
|
||||
<a href="https://twitter.com/intent/follow?screen_name=shields_io">
|
||||
<img src="https://img.shields.io/twitter/follow/shields_io?style=social&logo=twitter"
|
||||
alt="follow on Twitter"></a>
|
||||
</p>
|
||||
|
||||
This is home to [Shields.io][shields.io], a service for concise, consistent,
|
||||
@@ -27,7 +32,7 @@ and legible badges in SVG and raster format, which can easily be included in
|
||||
GitHub readmes or any other web page. The service supports dozens of
|
||||
continuous integration services, package registries, distributions, app
|
||||
stores, social networks, code coverage services, and code analysis services.
|
||||
Every month it serves over 1.6 billion images and is used by some of the
|
||||
Every month it serves over 870 million images and is used by some of the
|
||||
world's most popular open-source projects, [VS Code][vscode], [Vue.js][vue]
|
||||
and [Bootstrap][bootstrap] to name a few.
|
||||
|
||||
@@ -65,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
|
||||
|
||||
@@ -87,17 +92,14 @@ You can read a [tutorial on how to add a badge][tutorial].
|
||||
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
|
||||
If you intend on reporting or contributing a fix related to security vulnerabilities, please first refer to our [security policy][security].
|
||||
|
||||
[service-tests]: https://github.com/badges/shields/blob/master/doc/service-tests.md
|
||||
[tutorial]: https://github.com/badges/shields/blob/master/doc/TUTORIAL.md
|
||||
[contributing]: https://github.com/badges/shields/blob/master/CONTRIBUTING.md
|
||||
[security]: https://github.com/badges/shields/blob/master/SECURITY.md
|
||||
|
||||
## Development
|
||||
|
||||
1. Install Node 20 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 20 and 22.
|
||||
1. Install Node 16 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 16 and 17.
|
||||
2. Clone this repository.
|
||||
3. Run `npm ci` to install the dependencies.
|
||||
4. Run `npm start` to start the badge server and the frontend dev server.
|
||||
@@ -107,7 +109,7 @@ When server source files change, the badge server should automatically restart
|
||||
itself (using [nodemon][]). When the frontend files change, the frontend dev
|
||||
server (`docusaurus start`) should also automatically reload. However the badge
|
||||
definitions are built only before the server first starts. To regenerate those,
|
||||
either run `npm run prestart` or manually restart the server.
|
||||
either run `npm run defs` or manually restart the server.
|
||||
|
||||
To debug a badge from the command line, run `npm run badge -- /npm/v/nock`.
|
||||
It also works with full URLs like
|
||||
@@ -131,7 +133,7 @@ snapshots, and `SNAPSHOT_UPDATE=1 npm run test:package` to update them.
|
||||
|
||||
The server can be configured to use [Sentry][] ([configuration][sentry configuration]) and [Prometheus][] ([configuration][prometheus configuration]).
|
||||
|
||||
Our [full test suite][full test suite] as well as [code coverage][code coverage] are run on a daily basis.
|
||||
Daily tests, including a full run of the service tests and overall code coverage, are run via [badges/daily-tests][daily-tests].
|
||||
|
||||
[package manager]: https://nodejs.org/en/download/package-manager/
|
||||
[gitpod]: https://www.gitpod.io/
|
||||
@@ -140,11 +142,10 @@ Our [full test suite][full test suite] as well as [code coverage][code coverage]
|
||||
[prometheus configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#prometheus
|
||||
[sentry]: https://sentry.io/
|
||||
[sentry configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#sentry
|
||||
[daily-tests]: https://github.com/badges/daily-tests
|
||||
[nodemon]: https://nodemon.io/
|
||||
[nodemon debug]: https://github.com/Microsoft/vscode-recipes/tree/master/nodemon
|
||||
[vs code]: https://code.visualstudio.com/
|
||||
[full test suite]: https://github.com/badges/shields/actions/workflows/daily-tests.yml
|
||||
[code coverage]: https://coveralls.io/github/badges/shields
|
||||
|
||||
## Hosting your own server
|
||||
|
||||
@@ -223,6 +224,9 @@ Alumni:
|
||||
All assets and code are under the [CC0 LICENSE](LICENSE) and in the public
|
||||
domain unless specified otherwise.
|
||||
|
||||
The assets in `logo/` are trademarks of their respective companies and are
|
||||
under their terms and license.
|
||||
|
||||
## Community
|
||||
|
||||
Thanks to the people and companies who donate money, services or time to keep the project running. [https://shields.io/community](https://shields.io/community)
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## 4.0.0
|
||||
## 4.0.0 [WIP]
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Drop compatibility with Node < 16
|
||||
|
||||
### Features
|
||||
|
||||
- Add `links` and `logoBase64` params
|
||||
- Drop compatibility with Node < 14
|
||||
|
||||
## 3.3.1
|
||||
|
||||
@@ -281,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
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@ npm install badge-maker
|
||||
|
||||
```sh
|
||||
npm install -g badge-maker
|
||||
badge build passed :brightgreen > mybadge.svg
|
||||
badge build passed :green > mybadge.svg
|
||||
```
|
||||
|
||||
### As a library
|
||||
@@ -37,7 +37,7 @@ import { makeBadge, ValidationError } from 'badge-maker'
|
||||
const format = {
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
color: 'brightgreen',
|
||||
color: 'green',
|
||||
}
|
||||
|
||||
const svg = makeBadge(format)
|
||||
@@ -67,8 +67,6 @@ The format is the following:
|
||||
message: 'passed', // (Required) Badge message
|
||||
labelColor: '#555', // (Optional) Label color
|
||||
color: '#4c1', // (Optional) Message color
|
||||
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iOCIgZmlsbD0iI2IxY2U1NiIvPjxwYXRoIGQ9Ik04IDBoMjR2NjRIOGMtNC40MzIgMC04LTMuNTY4LTgtOFY4YzAtNC40MzIgMy41NjgtOCA4LTh6IiBmaWxsPSIjNWQ1ZDVkIi8+PC9zdmc+' // (Optional) Any custom logo can be passed in a URL parameter by base64 encoding
|
||||
links: ['https://example.com', 'https://example.com'], // (Optional) Links array of maximum two links
|
||||
|
||||
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
|
||||
// Each offers a different visual design.
|
||||
|
||||
2
badge-maker/index.d.ts
vendored
2
badge-maker/index.d.ts
vendored
@@ -4,8 +4,6 @@ interface Format {
|
||||
labelColor?: string
|
||||
color?: string
|
||||
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social'
|
||||
logoBase64?: string
|
||||
links?: Array<string>
|
||||
}
|
||||
|
||||
export declare class ValidationError extends Error {}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ function normalizeColor(color) {
|
||||
} else if (color in aliases) {
|
||||
return aliases[color]
|
||||
} else if (isHexColor(color)) {
|
||||
return `#${color.toString().toLowerCase()}`
|
||||
return `#${color.toLowerCase()}`
|
||||
} else if (isCSSColor(color)) {
|
||||
return color.toLowerCase()
|
||||
} else {
|
||||
|
||||
@@ -27,8 +27,6 @@ test(normalizeColor, () => {
|
||||
given('blue').expect('blue')
|
||||
given('4c1').expect('#4c1')
|
||||
given('f00f00').expect('#f00f00')
|
||||
given('111111').expect('#111111')
|
||||
given(111111).expect('#111111')
|
||||
given('ABC123').expect('#abc123')
|
||||
given('#ccc').expect('#ccc')
|
||||
given('#fffe').expect('#fffe')
|
||||
@@ -77,7 +75,7 @@ test(toSvgColor, () => {
|
||||
given('papayawhip').expect('papayawhip')
|
||||
given('purple').expect('purple')
|
||||
forCases([given(''), given(undefined), given('not-a-color')]).expect(
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
given('lightgray').expect('#9f9f9f')
|
||||
given('informational').expect('#007ec6')
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
const DEFAULT_LOGO_HEIGHT = 14
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_LOGO_HEIGHT,
|
||||
}
|
||||
@@ -16,30 +16,13 @@ function _validate(format) {
|
||||
throw new ValidationError('Field `message` is required')
|
||||
}
|
||||
|
||||
const stringFields = ['labelColor', 'color', 'message', 'label', 'logoBase64']
|
||||
const stringFields = ['labelColor', 'color', 'message', 'label']
|
||||
stringFields.forEach(function (field) {
|
||||
if (field in format && typeof format[field] !== 'string') {
|
||||
throw new ValidationError(`Field \`${field}\` must be of type string`)
|
||||
}
|
||||
})
|
||||
|
||||
if ('links' in format) {
|
||||
if (!Array.isArray(format.links)) {
|
||||
throw new ValidationError('Field `links` must be an array of strings')
|
||||
} else {
|
||||
if (format.links.length > 2) {
|
||||
throw new ValidationError(
|
||||
'Field `links` must not have more than 2 elements',
|
||||
)
|
||||
}
|
||||
format.links.forEach(function (field) {
|
||||
if (typeof field !== 'string') {
|
||||
throw new ValidationError('Field `links` must be an array of strings')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const styleValues = [
|
||||
'plastic',
|
||||
'flat',
|
||||
@@ -49,31 +32,21 @@ 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()})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function _clean(format) {
|
||||
const expectedKeys = [
|
||||
'label',
|
||||
'message',
|
||||
'labelColor',
|
||||
'color',
|
||||
'style',
|
||||
'logoBase64',
|
||||
'links',
|
||||
]
|
||||
const expectedKeys = ['label', 'message', 'labelColor', 'color', 'style']
|
||||
|
||||
const cleaned = {}
|
||||
Object.keys(format).forEach(key => {
|
||||
if (format[key] != null && key === 'logoBase64') {
|
||||
cleaned.logo = format[key]
|
||||
} else if (format[key] != null && expectedKeys.includes(key)) {
|
||||
if (format[key] != null && expectedKeys.includes(key)) {
|
||||
cleaned[key] = format[key]
|
||||
} else {
|
||||
throw new ValidationError(
|
||||
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`,
|
||||
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -92,9 +65,7 @@ function _clean(format) {
|
||||
* @param {string} format.message (Required) Badge message (e.g: 'passing')
|
||||
* @param {string} format.labelColor (Optional) Label color
|
||||
* @param {string} format.color (Optional) Message color
|
||||
* @param {string} format.style (Optional) Visual style (e.g: 'flat')
|
||||
* @param {string} format.logoBase64 (Optional) Logo data URL
|
||||
* @param {Array} format.links (Optional) Links array (e.g: ['https://example.com', 'https://example.com'])
|
||||
* @param {string} format.style (Optional) Visual style e.g: 'flat'
|
||||
* @returns {string} Badge in SVG format
|
||||
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
|
||||
*/
|
||||
|
||||
@@ -10,12 +10,12 @@ describe('makeBadge function', function () {
|
||||
makeBadge({
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
}),
|
||||
})
|
||||
).to.satisfy(isSvg)
|
||||
expect(
|
||||
makeBadge({
|
||||
message: 'passed',
|
||||
}),
|
||||
})
|
||||
).to.satisfy(isSvg)
|
||||
expect(
|
||||
makeBadge({
|
||||
@@ -23,23 +23,8 @@ describe('makeBadge function', function () {
|
||||
message: 'passed',
|
||||
color: 'green',
|
||||
style: 'flat',
|
||||
}),
|
||||
})
|
||||
).to.satisfy(isSvg)
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
color: 'green',
|
||||
style: 'flat',
|
||||
labelColor: 'blue',
|
||||
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
links: ['https://example.com', 'https://example.com'],
|
||||
}),
|
||||
)
|
||||
.to.satisfy(isSvg)
|
||||
// explicitly make an assertion about logoBase64
|
||||
// this param is not a straight passthrough
|
||||
.and.to.include('data:image/svg+xml;base64,PHN2ZyB4bWxu')
|
||||
})
|
||||
|
||||
it('should throw a ValidationError with invalid inputs', function () {
|
||||
@@ -47,59 +32,44 @@ describe('makeBadge function', function () {
|
||||
console.log(x)
|
||||
expect(() => makeBadge(x)).to.throw(
|
||||
ValidationError,
|
||||
'makeBadge takes an argument of type object',
|
||||
'makeBadge takes an argument of type object'
|
||||
)
|
||||
})
|
||||
expect(() => makeBadge({})).to.throw(
|
||||
ValidationError,
|
||||
'Field `message` is required',
|
||||
'Field `message` is required'
|
||||
)
|
||||
expect(() => makeBadge({ label: 'build' })).to.throw(
|
||||
ValidationError,
|
||||
'Field `message` is required',
|
||||
'Field `message` is required'
|
||||
)
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', labelColor: 7 }),
|
||||
makeBadge({ label: 'build', message: 'passed', labelColor: 7 })
|
||||
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', logoBase64: 7 }),
|
||||
).to.throw(ValidationError, 'Field `logoBase64` must be of type string')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', links: 'test' }),
|
||||
).to.throw(ValidationError, 'Field `links` must be an array of strings')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', links: [1] }),
|
||||
).to.throw(ValidationError, 'Field `links` must be an array of strings')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', links: ['1', '2', '3'] }),
|
||||
).to.throw(
|
||||
ValidationError,
|
||||
'Field `links` must not have more than 2 elements',
|
||||
)
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', format: 'png' }),
|
||||
makeBadge({ label: 'build', message: 'passed', format: 'png' })
|
||||
).to.throw(ValidationError, "Unexpected field 'format'")
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', template: 'flat' }),
|
||||
makeBadge({ label: 'build', message: 'passed', template: 'flat' })
|
||||
).to.throw(ValidationError, "Unexpected field 'template'")
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', foo: 'bar' }),
|
||||
makeBadge({ label: 'build', message: 'passed', foo: 'bar' })
|
||||
).to.throw(ValidationError, "Unexpected field 'foo'")
|
||||
expect(() =>
|
||||
makeBadge({
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
style: 'something else',
|
||||
}),
|
||||
})
|
||||
).to.throw(
|
||||
ValidationError,
|
||||
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
|
||||
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
|
||||
)
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', style: 'popout' }),
|
||||
makeBadge({ label: 'build', message: 'passed', style: 'popout' })
|
||||
).to.throw(
|
||||
ValidationError,
|
||||
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
|
||||
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
const { normalizeColor, toSvgColor } = require('./color')
|
||||
const badgeRenderers = require('./badge-renderers')
|
||||
const { stripXmlWhitespace } = require('./xml')
|
||||
const { DEFAULT_LOGO_HEIGHT } = require('./constants')
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
@@ -17,7 +16,7 @@ module.exports = function makeBadge({
|
||||
color,
|
||||
labelColor,
|
||||
logo,
|
||||
logoSize,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
links = ['', ''],
|
||||
}) {
|
||||
@@ -46,7 +45,7 @@ module.exports = function makeBadge({
|
||||
throw new Error(`Unknown badge style: '${style}'`)
|
||||
}
|
||||
|
||||
logoWidth = +logoWidth || (logo ? DEFAULT_LOGO_HEIGHT : 0)
|
||||
logoWidth = +logoWidth || (logo ? 14 : 0)
|
||||
|
||||
return stripXmlWhitespace(
|
||||
render({
|
||||
@@ -54,11 +53,11 @@ module.exports = function makeBadge({
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoSize,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -35,7 +35,7 @@ class XmlElement {
|
||||
* Name of the XML tag
|
||||
* @param {Array.<string|module:badge-maker/lib/xml~XmlElement>} [attrs.content=[]]
|
||||
* Array of objects to render inside the tag. content may contain a mix of
|
||||
* string and XmlElement objects. If content is `[]` or omitted the
|
||||
* string and XmlElement objects. If content is `[]` or ommitted the
|
||||
* element will be rendered as a self-closing element.
|
||||
* @param {object} [attrs.attrs={}]
|
||||
* Object representing the tag's attributes as name/value pairs
|
||||
@@ -66,7 +66,7 @@ class XmlElement {
|
||||
})
|
||||
.join(' ')
|
||||
return stripXmlWhitespace(
|
||||
`<${this.name}${attrsStr}>${content}</${this.name}>`,
|
||||
`<${this.name}${attrsStr}>${content}</${this.name}>`
|
||||
)
|
||||
}
|
||||
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
|
||||
@@ -88,7 +88,7 @@ class ElementList {
|
||||
typeof el.render === 'function'
|
||||
? acc + el.render()
|
||||
: acc + escapeXml(el),
|
||||
'',
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "4.0.0",
|
||||
"version": "3.3.1",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
@@ -26,7 +26,8 @@
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">= 14",
|
||||
"npm": ">= 6"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -52,8 +52,6 @@ public:
|
||||
authorizedOrigins: 'NPM_ORIGINS'
|
||||
obs:
|
||||
authorizedOrigins: 'OBS_ORIGINS'
|
||||
pypi:
|
||||
baseUri: 'PYPI_URL'
|
||||
sonar:
|
||||
authorizedOrigins: 'SONAR_ORIGINS'
|
||||
teamcity:
|
||||
@@ -79,10 +77,7 @@ private:
|
||||
bitbucket_password: 'BITBUCKET_PASS'
|
||||
bitbucket_server_username: 'BITBUCKET_SERVER_USER'
|
||||
bitbucket_server_password: 'BITBUCKET_SERVER_PASS'
|
||||
curseforge_api_key: 'CURSEFORGE_API_KEY'
|
||||
discord_bot_token: 'DISCORD_BOT_TOKEN'
|
||||
dockerhub_username: 'DOCKERHUB_USER'
|
||||
dockerhub_pat: 'DOCKERHUB_PAT'
|
||||
drone_token: 'DRONE_TOKEN'
|
||||
gh_client_id: 'GH_CLIENT_ID'
|
||||
gh_client_secret: 'GH_CLIENT_SECRET'
|
||||
@@ -99,8 +94,6 @@ private:
|
||||
obs_user: 'OBS_USER'
|
||||
obs_pass: 'OBS_PASS'
|
||||
redis_url: 'REDIS_URL'
|
||||
opencollective_token: 'OPENCOLLECTIVE_TOKEN'
|
||||
pepy_key: 'PEPY_KEY'
|
||||
postgres_url: 'POSTGRES_URL'
|
||||
sentry_dsn: 'SENTRY_DSN'
|
||||
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'
|
||||
|
||||
@@ -22,8 +22,6 @@ public:
|
||||
restApiVersion: '2022-11-28'
|
||||
obs:
|
||||
authorizedOrigins: 'https://api.opensuse.org'
|
||||
pypi:
|
||||
baseUri: 'https://pypi.org'
|
||||
weblate:
|
||||
authorizedOrigins: 'https://hosted.weblate.org'
|
||||
trace: false
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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: '...'
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { URL } from 'url'
|
||||
import dayjs from 'dayjs'
|
||||
import Joi from 'joi'
|
||||
import checkErrorResponse from './check-error-response.js'
|
||||
import { InvalidParameter, InvalidResponse } from './errors.js'
|
||||
import { fetch } from './got.js'
|
||||
import { parseJson } from './json.js'
|
||||
import validate from './validate.js'
|
||||
|
||||
let jwtCache = Object.create(null)
|
||||
import { InvalidParameter } from './errors.js'
|
||||
|
||||
class AuthHelper {
|
||||
constructor(
|
||||
@@ -19,7 +11,7 @@ class AuthHelper {
|
||||
isRequired = false,
|
||||
defaultToEmptyStringForUser = false,
|
||||
},
|
||||
config,
|
||||
config
|
||||
) {
|
||||
if (!userKey && !passKey) {
|
||||
throw Error('Expected userKey or passKey to be set')
|
||||
@@ -95,7 +87,7 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
isAllowedOrigin(url) {
|
||||
shouldAuthenticateRequest({ url, options = {} }) {
|
||||
let parsed
|
||||
try {
|
||||
parsed = new URL(url)
|
||||
@@ -105,11 +97,7 @@ class AuthHelper {
|
||||
|
||||
const { protocol, host } = parsed
|
||||
const origin = `${protocol}//${host}`
|
||||
return this._authorizedOrigins.includes(origin)
|
||||
}
|
||||
|
||||
shouldAuthenticateRequest({ url, options = {} }) {
|
||||
const originViolation = !this.isAllowedOrigin(url)
|
||||
const originViolation = !this._authorizedOrigins.includes(origin)
|
||||
|
||||
const strictSslCheckViolation =
|
||||
this._requireStrictSslToAuthenticate &&
|
||||
@@ -154,7 +142,7 @@ class AuthHelper {
|
||||
|
||||
withBasicAuth(requestParams) {
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeAuth(requestParams, this._basicAuth),
|
||||
this.constructor._mergeAuth(requestParams, this._basicAuth)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,11 +153,6 @@ class AuthHelper {
|
||||
: undefined
|
||||
}
|
||||
|
||||
_apiKeyHeader(apiKeyHeader) {
|
||||
const { _pass: pass } = this
|
||||
return this.isConfigured ? { [apiKeyHeader]: pass } : undefined
|
||||
}
|
||||
|
||||
static _mergeHeaders(requestParams, headers) {
|
||||
const {
|
||||
options: { headers: existingHeaders, ...restOptions } = {},
|
||||
@@ -187,21 +170,15 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
withApiKeyHeader(requestParams, header = 'x-api-key') {
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(requestParams, this._apiKeyHeader(header)),
|
||||
)
|
||||
}
|
||||
|
||||
withBearerAuthHeader(
|
||||
requestParams,
|
||||
bearerKey = 'Bearer', // lgtm [js/hardcoded-credentials]
|
||||
bearerKey = 'Bearer' // lgtm [js/hardcoded-credentials]
|
||||
) {
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(
|
||||
requestParams,
|
||||
this._bearerAuthHeader(bearerKey),
|
||||
),
|
||||
this._bearerAuthHeader(bearerKey)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -227,106 +204,9 @@ class AuthHelper {
|
||||
this.constructor._mergeQueryParams(requestParams, {
|
||||
...(userKey ? { [userKey]: this._user } : undefined),
|
||||
...(passKey ? { [passKey]: this._pass } : undefined),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
static _getJwtExpiry(token, max = dayjs().add(1, 'hours').unix()) {
|
||||
// get the expiry timestamp for this JWT (capped at a max length)
|
||||
const parts = token.split('.')
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'invalid response data from auth endpoint',
|
||||
})
|
||||
}
|
||||
|
||||
const json = validate(
|
||||
{
|
||||
ErrorClass: InvalidResponse,
|
||||
prettyErrorMessage: 'invalid response data from auth endpoint',
|
||||
},
|
||||
parseJson(Buffer.from(parts[1], 'base64').toString()),
|
||||
Joi.object({ exp: Joi.number().required() }).required(),
|
||||
)
|
||||
|
||||
return Math.min(json.exp, max)
|
||||
}
|
||||
|
||||
static _isJwtValid(expiry) {
|
||||
// we consider the token valid if the expiry
|
||||
// datetime is later than (now + 1 minute)
|
||||
return dayjs.unix(expiry).isAfter(dayjs().add(1, 'minutes'))
|
||||
}
|
||||
|
||||
async _getJwt(loginEndpoint) {
|
||||
const { _user: username, _pass: password } = this
|
||||
|
||||
// attempt to get JWT from cache
|
||||
if (
|
||||
jwtCache?.[loginEndpoint]?.[username]?.token &&
|
||||
jwtCache?.[loginEndpoint]?.[username]?.expiry &&
|
||||
this.constructor._isJwtValid(jwtCache[loginEndpoint][username].expiry)
|
||||
) {
|
||||
// cache hit
|
||||
return jwtCache[loginEndpoint][username].token
|
||||
}
|
||||
|
||||
// cache miss - request a new JWT
|
||||
const originViolation = !this.isAllowedOrigin(loginEndpoint)
|
||||
if (originViolation) {
|
||||
throw new InvalidParameter({
|
||||
prettyMessage: 'requested origin not authorized',
|
||||
})
|
||||
}
|
||||
|
||||
const { buffer } = await checkErrorResponse({})(
|
||||
await fetch(loginEndpoint, {
|
||||
method: 'POST',
|
||||
form: { username, password },
|
||||
}),
|
||||
)
|
||||
|
||||
const json = validate(
|
||||
{
|
||||
ErrorClass: InvalidResponse,
|
||||
prettyErrorMessage: 'invalid response data from auth endpoint',
|
||||
},
|
||||
parseJson(buffer),
|
||||
Joi.object({ token: Joi.string().required() }).required(),
|
||||
)
|
||||
|
||||
const token = json.token
|
||||
const expiry = this.constructor._getJwtExpiry(token)
|
||||
|
||||
// store in the cache
|
||||
if (!(loginEndpoint in jwtCache)) {
|
||||
jwtCache[loginEndpoint] = {}
|
||||
}
|
||||
jwtCache[loginEndpoint][username] = { token, expiry }
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
async _getJwtAuthHeader(loginEndpoint) {
|
||||
if (!this.isConfigured) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const token = await this._getJwt(loginEndpoint)
|
||||
return { Authorization: `Bearer ${token}` }
|
||||
}
|
||||
|
||||
async withJwtAuth(requestParams, loginEndpoint) {
|
||||
const authHeader = await this._getJwtAuthHeader(loginEndpoint)
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(requestParams, authHeader),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function clearJwtCache() {
|
||||
jwtCache = Object.create(null)
|
||||
}
|
||||
|
||||
export { AuthHelper, clearJwtCache }
|
||||
export { AuthHelper }
|
||||
|
||||
@@ -1,45 +1,19 @@
|
||||
import dayjs from 'dayjs'
|
||||
import nock from 'nock'
|
||||
import { expect } from 'chai'
|
||||
import { test, given, forCases } from 'sazerac'
|
||||
import { AuthHelper, clearJwtCache } from './auth-helper.js'
|
||||
import { InvalidParameter, InvalidResponse } from './errors.js'
|
||||
|
||||
function base64UrlEncode(input) {
|
||||
const base64 = btoa(JSON.stringify(input))
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
||||
}
|
||||
|
||||
function getMockJwt(extras) {
|
||||
// this function returns a mock JWT that contains enough
|
||||
// for a unit test but ignores important aspects e.g: signing
|
||||
|
||||
const header = {
|
||||
alg: 'HS256',
|
||||
typ: 'JWT',
|
||||
}
|
||||
const payload = {
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
...extras,
|
||||
}
|
||||
|
||||
const encodedHeader = base64UrlEncode(header)
|
||||
const encodedPayload = base64UrlEncode(payload)
|
||||
return `${encodedHeader}.${encodedPayload}`
|
||||
}
|
||||
import { AuthHelper } from './auth-helper.js'
|
||||
import { InvalidParameter } from './errors.js'
|
||||
|
||||
describe('AuthHelper', function () {
|
||||
describe('constructor checks', function () {
|
||||
it('throws without userKey or passKey', function () {
|
||||
expect(() => new AuthHelper({}, {})).to.throw(
|
||||
Error,
|
||||
'Expected userKey or passKey to be set',
|
||||
'Expected userKey or passKey to be set'
|
||||
)
|
||||
})
|
||||
it('throws without serviceKey or authorizedOrigins', function () {
|
||||
expect(
|
||||
() =>
|
||||
new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {}),
|
||||
() => new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {})
|
||||
).to.throw(Error, 'Expected authorizedOrigins or serviceKey to be set')
|
||||
})
|
||||
it('throws when authorizedOrigins is not an array', function () {
|
||||
@@ -51,8 +25,8 @@ describe('AuthHelper', function () {
|
||||
passKey: 'myci_pass',
|
||||
authorizedOrigins: true,
|
||||
},
|
||||
{ private: {} },
|
||||
),
|
||||
{ private: {} }
|
||||
)
|
||||
).to.throw(Error, 'Expected authorizedOrigins to be an array of origins')
|
||||
})
|
||||
})
|
||||
@@ -61,7 +35,7 @@ describe('AuthHelper', function () {
|
||||
function validate(config, privateConfig) {
|
||||
return new AuthHelper(
|
||||
{ authorizedOrigins: ['https://example.test'], ...config },
|
||||
{ private: privateConfig },
|
||||
{ private: privateConfig }
|
||||
).isValid
|
||||
}
|
||||
test(validate, () => {
|
||||
@@ -69,20 +43,20 @@ describe('AuthHelper', function () {
|
||||
// Fully configured user + pass.
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' }
|
||||
),
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' }
|
||||
),
|
||||
// Fully configured user or pass.
|
||||
given(
|
||||
{ userKey: 'myci_user', isRequired: true },
|
||||
{ myci_user: 'admin' },
|
||||
{ myci_user: 'admin' }
|
||||
),
|
||||
given(
|
||||
{ passKey: 'myci_pass', isRequired: true },
|
||||
{ myci_pass: 'abc123' },
|
||||
{ myci_pass: 'abc123' }
|
||||
),
|
||||
given({ userKey: 'myci_user' }, { myci_user: 'admin' }),
|
||||
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }),
|
||||
@@ -96,16 +70,16 @@ describe('AuthHelper', function () {
|
||||
// Partly configured.
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
|
||||
{ myci_user: 'admin' },
|
||||
{ myci_user: 'admin' }
|
||||
),
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass' },
|
||||
{ myci_user: 'admin' },
|
||||
{ myci_user: 'admin' }
|
||||
),
|
||||
// Missing required config.
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
|
||||
{},
|
||||
{}
|
||||
),
|
||||
given({ userKey: 'myci_user', isRequired: true }, {}),
|
||||
given({ passKey: 'myci_pass', isRequired: true }, {}),
|
||||
@@ -117,18 +91,18 @@ describe('AuthHelper', function () {
|
||||
function validate(config, privateConfig) {
|
||||
return new AuthHelper(
|
||||
{ authorizedOrigins: ['https://example.test'], ...config },
|
||||
{ private: privateConfig },
|
||||
{ private: privateConfig }
|
||||
)._basicAuth
|
||||
}
|
||||
test(validate, () => {
|
||||
forCases([
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' }
|
||||
),
|
||||
given(
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' }
|
||||
),
|
||||
]).expect({ username: 'admin', password: 'abc123' })
|
||||
given({ userKey: 'myci_user' }, { myci_user: 'admin' }).expect({
|
||||
@@ -140,11 +114,11 @@ describe('AuthHelper', function () {
|
||||
password: 'abc123',
|
||||
})
|
||||
given({ userKey: 'myci_user', passKey: 'myci_pass' }, {}).expect(
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
given(
|
||||
{ passKey: 'myci_pass', defaultToEmptyStringForUser: true },
|
||||
{ myci_pass: 'abc123' },
|
||||
{ myci_pass: 'abc123' }
|
||||
).expect({
|
||||
username: '',
|
||||
password: 'abc123',
|
||||
@@ -194,7 +168,7 @@ describe('AuthHelper', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
}),
|
||||
})
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
@@ -218,7 +192,7 @@ describe('AuthHelper', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
}),
|
||||
})
|
||||
).not.to.throw()
|
||||
})
|
||||
})
|
||||
@@ -344,7 +318,7 @@ describe('AuthHelper', function () {
|
||||
},
|
||||
},
|
||||
private: { myci_user: 'admin', myci_pass: 'abc123' },
|
||||
},
|
||||
}
|
||||
)
|
||||
const withBasicAuth = requestOptions =>
|
||||
authHelper.withBasicAuth(requestOptions)
|
||||
@@ -402,157 +376,8 @@ describe('AuthHelper', function () {
|
||||
withBasicAuth({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
}),
|
||||
})
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
|
||||
context('JTW Auth', function () {
|
||||
describe('_isJwtValid', function () {
|
||||
test(AuthHelper._isJwtValid, () => {
|
||||
given(dayjs().add(1, 'month').unix()).expect(true)
|
||||
given(dayjs().add(2, 'minutes').unix()).expect(true)
|
||||
given(dayjs().add(30, 'seconds').unix()).expect(false)
|
||||
given(dayjs().unix()).expect(false)
|
||||
given(dayjs().subtract(1, 'seconds').unix()).expect(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_getJwtExpiry', function () {
|
||||
it('extracts expiry from valid JWT', function () {
|
||||
const nowPlus30Mins = dayjs().add(30, 'minutes').unix()
|
||||
expect(
|
||||
AuthHelper._getJwtExpiry(getMockJwt({ exp: nowPlus30Mins })),
|
||||
).to.equal(nowPlus30Mins)
|
||||
})
|
||||
|
||||
it('caps expiry at max', function () {
|
||||
const nowPlus1Hour = dayjs().add(1, 'hours').unix()
|
||||
const nowPlus2Hours = dayjs().add(2, 'hours').unix()
|
||||
expect(
|
||||
AuthHelper._getJwtExpiry(getMockJwt({ exp: nowPlus2Hours })),
|
||||
).to.equal(nowPlus1Hour)
|
||||
})
|
||||
|
||||
it('throws if JWT does not contain exp', function () {
|
||||
expect(() => {
|
||||
AuthHelper._getJwtExpiry(getMockJwt({}))
|
||||
}).to.throw(InvalidResponse)
|
||||
})
|
||||
|
||||
it('throws if JWT is invalid', function () {
|
||||
expect(() => {
|
||||
AuthHelper._getJwtExpiry('abc')
|
||||
}).to.throw(InvalidResponse)
|
||||
})
|
||||
})
|
||||
|
||||
describe('withJwtAuth', function () {
|
||||
const authHelper = new AuthHelper(
|
||||
{
|
||||
userKey: 'jwt_user',
|
||||
passKey: 'jwt_pass',
|
||||
authorizedOrigins: ['https://example.com'],
|
||||
isRequired: false,
|
||||
},
|
||||
{ private: { jwt_user: 'fred', jwt_pass: 'abc123' } },
|
||||
)
|
||||
|
||||
beforeEach(function () {
|
||||
clearJwtCache()
|
||||
})
|
||||
|
||||
it('should use cached response if valid', async function () {
|
||||
// the expiry is far enough in the future that the token
|
||||
// will still be valid on the second hit
|
||||
const mockToken = getMockJwt({ exp: dayjs().add(1, 'hours').unix() })
|
||||
|
||||
// .times(1) ensures if we try to make a second call to this endpoint,
|
||||
// we will throw `Nock: No match for request`
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken })
|
||||
const params1 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params1).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
// second time round, we'll get the same response again
|
||||
// but this time served from cache
|
||||
const params2 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(params2).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
nock.cleanAll()
|
||||
})
|
||||
|
||||
it('should not use cached response if expired', async function () {
|
||||
// this time we define a token expiry is close enough
|
||||
// that the token will not be valid on the second call
|
||||
const mockToken1 = getMockJwt({
|
||||
exp: dayjs().add(20, 'seconds').unix(),
|
||||
})
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken1 })
|
||||
const params1 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params1).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken1}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
// second time round we make another network request
|
||||
const mockToken2 = getMockJwt({
|
||||
exp: dayjs().add(20, 'seconds').unix(),
|
||||
})
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken2 })
|
||||
const params2 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params2).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken2}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
nock.cleanAll()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ class BaseGraphqlService extends BaseService {
|
||||
/**
|
||||
* Parse data from JSON endpoint
|
||||
*
|
||||
* @param {string} buffer JSON response from upstream API
|
||||
* @param {string} buffer JSON repsonse from upstream API
|
||||
* @returns {object} Parsed response
|
||||
*/
|
||||
_parseJson(buffer) {
|
||||
@@ -50,10 +50,8 @@ class BaseGraphqlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {Function} [attrs.transformJson=data => data] Function which takes the raw json and transforms it before
|
||||
* further processing. In case of multiple query in a single graphql call and few of them
|
||||
* further procesing. In case of multiple query in a single graphql call and few of them
|
||||
* throw error, partial data might be used ignoring the error.
|
||||
* @param {Function} [attrs.transformErrors=defaultTransformErrors]
|
||||
* Function which takes an errors object from a GraphQL
|
||||
@@ -71,7 +69,6 @@ class BaseGraphqlService extends BaseService {
|
||||
options = {},
|
||||
httpErrorMessages = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
transformJson = data => data,
|
||||
transformErrors = defaultTransformErrors,
|
||||
}) {
|
||||
@@ -86,7 +83,6 @@ class BaseGraphqlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors: httpErrorMessages,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
const json = transformJson(this._parseJson(buffer))
|
||||
if (json.errors) {
|
||||
@@ -95,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}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -14,7 +14,7 @@ class BaseJsonService extends BaseService {
|
||||
/**
|
||||
* Parse data from JSON endpoint
|
||||
*
|
||||
* @param {string} buffer JSON response from upstream API
|
||||
* @param {string} buffer JSON repsonse from upstream API
|
||||
* @returns {object} Parsed response
|
||||
*/
|
||||
_parseJson(buffer) {
|
||||
@@ -40,8 +40,6 @@ class BaseJsonService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
*/
|
||||
@@ -51,7 +49,6 @@ class BaseJsonService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const mergedOptions = {
|
||||
...{ headers: { Accept: 'application/json' } },
|
||||
@@ -62,7 +59,6 @@ class BaseJsonService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
const json = this._parseJson(buffer)
|
||||
return this.constructor._validate(json, schema)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -33,25 +33,21 @@ export default class BaseStaticService extends BaseService {
|
||||
{},
|
||||
serviceConfig,
|
||||
namedParams,
|
||||
queryParams,
|
||||
queryParams
|
||||
)
|
||||
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
serviceData,
|
||||
this.defaultBadgeData,
|
||||
this,
|
||||
this
|
||||
)
|
||||
|
||||
// The final capture group is the extension.
|
||||
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
|
||||
badgeData.format = format
|
||||
|
||||
let maxAge = 24 * 3600 // 1 day
|
||||
if (!queryParams.logo && !badgeData.isError) {
|
||||
maxAge = 5 * 24 * 3600 // 5 days
|
||||
}
|
||||
setCacheHeadersForStaticResource(ask.res, maxAge)
|
||||
setCacheHeadersForStaticResource(ask.res)
|
||||
|
||||
const svg = makeBadge(badgeData)
|
||||
makeSend(format, ask.res, end)(svg)
|
||||
|
||||
@@ -63,8 +63,6 @@ class BaseSvgScrapingService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
*/
|
||||
@@ -75,7 +73,6 @@ class BaseSvgScrapingService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
const mergedOptions = {
|
||||
@@ -87,7 +84,6 @@ class BaseSvgScrapingService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
logTrace(emojic.dart, 'Response SVG', buffer)
|
||||
const data = {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
import emojic from 'emojic'
|
||||
import { parse } from 'smol-toml'
|
||||
import BaseService from './base.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
import trace from './trace.js'
|
||||
|
||||
/**
|
||||
* Services which query a TOML endpoint should extend BaseTomlService
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
class BaseTomlService extends BaseService {
|
||||
/**
|
||||
* Request data from an upstream API serving TOML,
|
||||
* parse it and validate against a schema
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.httpErrors={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
|
||||
* and an object of params to pass when we construct an Inaccessible exception object
|
||||
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
*/
|
||||
async _requestToml({
|
||||
schema,
|
||||
url,
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
const mergedOptions = {
|
||||
...{
|
||||
headers: {
|
||||
Accept:
|
||||
// the official header should be application/toml - see https://toml.io/en/v1.0.0#mime-type
|
||||
// but as this is not registered here https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
// some apps use other mime-type like application/x-toml, text/plain etc....
|
||||
'text/x-toml, text/toml, application/x-toml, application/toml, text/plain',
|
||||
},
|
||||
},
|
||||
...options,
|
||||
}
|
||||
const { buffer } = await this._request({
|
||||
url,
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
let parsed
|
||||
try {
|
||||
parsed = parse(buffer.toString())
|
||||
} catch (err) {
|
||||
logTrace(emojic.dart, 'Response TOML (unparseable)', buffer)
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'unparseable toml response',
|
||||
underlyingError: err,
|
||||
})
|
||||
}
|
||||
logTrace(emojic.dart, 'Response TOML (before validation)', parsed, {
|
||||
deep: true,
|
||||
})
|
||||
return this.constructor._validate(parsed, schema)
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseTomlService
|
||||
@@ -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',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -34,8 +34,6 @@ class BaseXmlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
|
||||
* [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
|
||||
* @returns {object} Parsed response
|
||||
@@ -48,7 +46,6 @@ class BaseXmlService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
parserOptions = {},
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
@@ -61,7 +58,6 @@ class BaseXmlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
const validateResult = XMLValidator.validate(buffer)
|
||||
if (validateResult !== true) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -33,8 +33,6 @@ class BaseYamlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {object} [attrs.encoding='utf8'] Character encoding
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
@@ -45,7 +43,6 @@ class BaseYamlService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
encoding = 'utf8',
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
@@ -63,7 +60,6 @@ class BaseYamlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
let parsed
|
||||
try {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
} from './errors.js'
|
||||
import { validateExample, transformExample } from './examples.js'
|
||||
import { fetch } from './got.js'
|
||||
import { getEnum } from './openapi.js'
|
||||
import {
|
||||
makeFullUrl,
|
||||
assertValidRoute,
|
||||
@@ -44,7 +44,7 @@ const optionalStringWhenNamedLogoPresent = Joi.alternatives().conditional(
|
||||
{
|
||||
is: Joi.string().required(),
|
||||
then: Joi.string(),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
|
||||
@@ -66,6 +66,7 @@ const serviceDataSchema = Joi.object({
|
||||
logoSvg: Joi.string(),
|
||||
logoColor: optionalStringWhenNamedLogoPresent,
|
||||
logoWidth: optionalNumberWhenAnyLogoPresent,
|
||||
logoPosition: optionalNumberWhenAnyLogoPresent,
|
||||
cacheSeconds: Joi.number().integer().min(0),
|
||||
style: Joi.string(),
|
||||
})
|
||||
@@ -101,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.
|
||||
@@ -142,14 +123,31 @@ class BaseService {
|
||||
static auth = undefined
|
||||
|
||||
/**
|
||||
* An OpenAPI Paths Object describing this service's
|
||||
* Array of Example objects describing example URLs for this service.
|
||||
* These should use the format specified in `route`,
|
||||
* and can be used to demonstrate how to use badges for this service.
|
||||
*
|
||||
* The preferred way to specify an example is with `namedParams` which are
|
||||
* substituted into the service's compiled route pattern. The rendered badge
|
||||
* is specified with `staticPreview`.
|
||||
*
|
||||
* For services which use a route `format`, the `pattern` can be specified as
|
||||
* part of the example.
|
||||
*
|
||||
* @see {@link module:core/base-service/base~Example}
|
||||
* @abstract
|
||||
* @type {module:core/base-service/base~Example[]}
|
||||
*/
|
||||
static examples = []
|
||||
|
||||
/**
|
||||
* Optional: an OpenAPI Paths Object describing this service's
|
||||
* route or routes in OpenAPI format.
|
||||
*
|
||||
* @abstract
|
||||
* @see https://swagger.io/specification/#paths-object
|
||||
* @type {module:core/base-service/service-definitions~openApiSchema}
|
||||
* @abstract
|
||||
*/
|
||||
static openApi = {}
|
||||
static openApi = undefined
|
||||
|
||||
static get _cacheLength() {
|
||||
const cacheLengths = {
|
||||
@@ -185,22 +183,12 @@ class BaseService {
|
||||
Joi.assert(
|
||||
this.defaultBadgeData,
|
||||
defaultBadgeDataSchema,
|
||||
`Default badge data for ${this.name}`,
|
||||
`Default badge data for ${this.name}`
|
||||
)
|
||||
|
||||
// ensure openApi spec matches route
|
||||
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}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
this.examples.forEach((example, index) =>
|
||||
validateExample(example, index, this)
|
||||
)
|
||||
}
|
||||
|
||||
static getDefinition() {
|
||||
@@ -208,6 +196,10 @@ class BaseService {
|
||||
const { base, format, pattern } = this.route
|
||||
const queryParams = getQueryParamNames(this.route)
|
||||
|
||||
const examples = this.examples.map((example, index) =>
|
||||
transformExample(example, index, this)
|
||||
)
|
||||
|
||||
let route
|
||||
if (pattern) {
|
||||
route = { pattern: makeFullUrl(base, pattern), queryParams }
|
||||
@@ -217,7 +209,7 @@ class BaseService {
|
||||
route = undefined
|
||||
}
|
||||
|
||||
const result = { category, name, isDeprecated, route, openApi }
|
||||
const result = { category, name, isDeprecated, route, examples, openApi }
|
||||
|
||||
assertValidServiceDefinition(result, `getDefinition() for ${this.name}`)
|
||||
|
||||
@@ -226,7 +218,7 @@ class BaseService {
|
||||
|
||||
constructor(
|
||||
{ requestFetcher, authHelper, metricHelper },
|
||||
{ handleInternalErrors },
|
||||
{ handleInternalErrors }
|
||||
) {
|
||||
this._requestFetcher = requestFetcher
|
||||
this.authHelper = authHelper
|
||||
@@ -234,13 +226,7 @@ class BaseService {
|
||||
this._metricHelper = metricHelper
|
||||
}
|
||||
|
||||
async _request({
|
||||
url,
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
async _request({ url, options = {}, httpErrors = {}, systemErrors = {} }) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
let logUrl = url
|
||||
const logOptions = Object.assign({}, options)
|
||||
@@ -248,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
|
||||
@@ -258,16 +244,16 @@ 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)
|
||||
return checkErrorResponse(httpErrors, logErrors)({ buffer, res })
|
||||
return checkErrorResponse(httpErrors)({ buffer, res })
|
||||
}
|
||||
|
||||
static enabledMetrics = []
|
||||
@@ -293,7 +279,7 @@ class BaseService {
|
||||
prettyErrorMessage = 'invalid response data',
|
||||
includeKeys = false,
|
||||
allowAndStripUnknownKeys = true,
|
||||
} = {},
|
||||
} = {}
|
||||
) {
|
||||
return validate(
|
||||
{
|
||||
@@ -305,7 +291,7 @@ class BaseService {
|
||||
allowAndStripUnknownKeys,
|
||||
},
|
||||
data,
|
||||
schema,
|
||||
schema
|
||||
)
|
||||
}
|
||||
|
||||
@@ -361,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
|
||||
@@ -379,7 +365,7 @@ class BaseService {
|
||||
'unhandledError',
|
||||
emojic.boom,
|
||||
'Unhandled internal error',
|
||||
error,
|
||||
error
|
||||
)
|
||||
throw error
|
||||
}
|
||||
@@ -389,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)
|
||||
@@ -423,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
|
||||
@@ -443,7 +429,7 @@ class BaseService {
|
||||
try {
|
||||
serviceData = await serviceInstance.handle(
|
||||
namedParams,
|
||||
transformedQueryParams,
|
||||
transformedQueryParams
|
||||
)
|
||||
serviceInstance._validateServiceData(serviceData)
|
||||
} catch (error) {
|
||||
@@ -468,7 +454,7 @@ class BaseService {
|
||||
librariesIoApiProvider,
|
||||
metricInstance,
|
||||
},
|
||||
serviceConfig,
|
||||
serviceConfig
|
||||
) {
|
||||
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
@@ -496,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(/^\./, '')
|
||||
@@ -512,7 +498,7 @@ class BaseService {
|
||||
metricHandle.noteResponseSent()
|
||||
},
|
||||
cacheLength: this._cacheLength,
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -568,11 +554,9 @@ class BaseService {
|
||||
* receives numeric can use `Joi.string()`. A boolean
|
||||
* parameter should use `Joi.equal('')` and will receive an
|
||||
* empty string on e.g. `?compact_message` and undefined
|
||||
* when the parameter is absent. In the OpenApi definitions,
|
||||
* this type of param should be documented as
|
||||
* queryParam({
|
||||
* name: 'compact_message', schema: { type: 'boolean' }, example: null
|
||||
* })
|
||||
* when the parameter is absent. (Note that in,
|
||||
* `examples.queryParams` boolean query params should be given
|
||||
* `null` values.)
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -587,4 +571,30 @@ class BaseService {
|
||||
* configured credentials are present.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Example
|
||||
* @property {string} title
|
||||
* Descriptive text that will be shown next to the badge. The default
|
||||
* is to use the service class name, which probably is not what you want.
|
||||
* @property {object} namedParams
|
||||
* An object containing the values of named parameters to
|
||||
* substitute into the compiled route pattern.
|
||||
* @property {object} queryParams
|
||||
* An object containing query parameters to include in the
|
||||
* example URLs. For alphanumeric query parameters, specify a string value.
|
||||
* For boolean query parameters, specify `null`.
|
||||
* @property {string} pattern
|
||||
* The route pattern to compile. Defaults to `this.route.pattern`.
|
||||
* @property {object} staticPreview
|
||||
* A rendered badge of the sort returned by `handle()` or
|
||||
* `render()`: an object containing `message` and optional `label` and
|
||||
* `color`. This is usually generated by invoking `this.render()` with some
|
||||
* explicit props.
|
||||
* @property {string[]} keywords
|
||||
* Additional keywords, other than words in the title. This helps
|
||||
* users locate relevant badges.
|
||||
* @property {string} documentation
|
||||
* An HTML string that is included in the badge popup.
|
||||
*/
|
||||
|
||||
export default BaseService
|
||||
|
||||
@@ -4,7 +4,6 @@ import sinon from 'sinon'
|
||||
import prometheus from 'prom-client'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import PrometheusMetrics from '../server/prometheus-metrics.js'
|
||||
import { pathParam, queryParam } from './openapi.js'
|
||||
import trace from './trace.js'
|
||||
import {
|
||||
NotFound,
|
||||
@@ -32,17 +31,14 @@ class DummyService extends BaseService {
|
||||
static category = 'other'
|
||||
static route = { base: 'foo', pattern: ':namedParamA', queryParamSchema }
|
||||
|
||||
static openApi = {
|
||||
'/foo/{namedParamA}': {
|
||||
get: {
|
||||
summary: 'Dummy Service',
|
||||
parameters: [
|
||||
pathParam({ name: 'namedParamA', example: 'foo' }),
|
||||
queryParam({ name: 'queryParamA', example: 'bar' }),
|
||||
],
|
||||
},
|
||||
static examples = [
|
||||
{
|
||||
pattern: ':world',
|
||||
namedParams: { world: 'World' },
|
||||
staticPreview: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
|
||||
keywords: ['hello'],
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'cat', namedLogo: 'appveyor' }
|
||||
|
||||
@@ -76,8 +72,8 @@ describe('BaseService', function () {
|
||||
{},
|
||||
defaultConfig,
|
||||
{ namedParamA: 'bar.bar.bar' },
|
||||
{ queryParamA: '!' },
|
||||
),
|
||||
{ queryParamA: '!' }
|
||||
)
|
||||
).to.deep.equal({
|
||||
message: 'Hello namedParamA: bar.bar.bar with queryParamA: !',
|
||||
})
|
||||
@@ -89,8 +85,8 @@ describe('BaseService', function () {
|
||||
{},
|
||||
defaultConfig,
|
||||
{ namedParamA: 'bar.bar.bar' },
|
||||
{ queryParamA: ['foo', 'bar'] },
|
||||
),
|
||||
{ queryParamA: ['foo', 'bar'] }
|
||||
)
|
||||
).to.deep.equal({
|
||||
color: 'red',
|
||||
isError: true,
|
||||
@@ -101,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$/
|
||||
)
|
||||
})
|
||||
|
||||
@@ -116,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$/
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -139,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: '!' }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -175,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({
|
||||
@@ -198,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) {
|
||||
@@ -216,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) {
|
||||
@@ -237,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',
|
||||
@@ -255,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',
|
||||
@@ -270,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',
|
||||
@@ -285,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',
|
||||
@@ -300,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',
|
||||
@@ -315,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',
|
||||
@@ -326,7 +322,7 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
describe('ScoutCamp integration', function () {
|
||||
// TODO Strangely, without the useless escape the regexes do not match in Node 12.
|
||||
// TODO Strangly, without the useless escape the regexes do not match in Node 12.
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const expectedRouteRegex = /^\/foo(?:\/([^\/#\?]+?))(|\.svg|\.json)$/
|
||||
|
||||
@@ -340,7 +336,7 @@ describe('BaseService', function () {
|
||||
mockHandleRequest = sinon.spy()
|
||||
DummyService.register(
|
||||
{ camp: mockCamp, handleRequest: mockHandleRequest },
|
||||
defaultConfig,
|
||||
defaultConfig
|
||||
)
|
||||
})
|
||||
|
||||
@@ -377,7 +373,7 @@ describe('BaseService', function () {
|
||||
namedLogo: undefined,
|
||||
logo: undefined,
|
||||
logoWidth: undefined,
|
||||
logoSize: undefined,
|
||||
logoPosition: undefined,
|
||||
links: [],
|
||||
labelColor: undefined,
|
||||
cacheLengthSeconds: undefined,
|
||||
@@ -387,7 +383,7 @@ describe('BaseService', function () {
|
||||
|
||||
describe('getDefinition', function () {
|
||||
it('returns the expected result', function () {
|
||||
const { category, name, isDeprecated, route, openApi } =
|
||||
const { category, name, isDeprecated, route, examples } =
|
||||
DummyService.getDefinition()
|
||||
expect({
|
||||
category,
|
||||
@@ -404,7 +400,7 @@ describe('BaseService', function () {
|
||||
},
|
||||
})
|
||||
// The in-depth tests for examples reside in examples.spec.js
|
||||
expect(Object.keys(openApi)).to.have.lengthOf(1)
|
||||
expect(examples).to.have.lengthOf(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -417,8 +413,8 @@ describe('BaseService', function () {
|
||||
expect(() =>
|
||||
DummyService._validate(
|
||||
{ requiredString: ['this', "shouldn't", 'work'] },
|
||||
dummySchema,
|
||||
),
|
||||
dummySchema
|
||||
)
|
||||
)
|
||||
.to.throw()
|
||||
.instanceof(InvalidResponse)
|
||||
@@ -440,7 +436,7 @@ describe('BaseService', function () {
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher },
|
||||
defaultConfig,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
const url = 'some-url'
|
||||
@@ -457,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
|
||||
)
|
||||
})
|
||||
|
||||
@@ -475,7 +471,7 @@ describe('BaseService', function () {
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher },
|
||||
defaultConfig,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
try {
|
||||
@@ -508,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'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -533,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 = {
|
||||
@@ -570,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' })
|
||||
})
|
||||
|
||||
@@ -588,8 +583,8 @@ describe('BaseService', function () {
|
||||
},
|
||||
{
|
||||
namedParamA: 'bar.bar.bar',
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
).to.deep.equal({
|
||||
color: 'lightgray',
|
||||
isError: true,
|
||||
@@ -597,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',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,7 +41,7 @@ function coalesceCacheLength({
|
||||
const cacheLength = coalesce(
|
||||
serviceOverrideCacheLengthSeconds,
|
||||
serviceDefaultCacheLengthSeconds,
|
||||
defaultCacheLengthSeconds,
|
||||
defaultCacheLengthSeconds
|
||||
)
|
||||
|
||||
// Overrides can apply _more_ caching, but not less. Query param overriding
|
||||
@@ -90,11 +90,8 @@ function setCacheHeaders({
|
||||
setHeadersForCacheLength(res, cacheLengthSeconds)
|
||||
}
|
||||
|
||||
function setCacheHeadersForStaticResource(
|
||||
res,
|
||||
maxAge = 24 * 3600, // 1 day
|
||||
) {
|
||||
const staticCacheControlHeader = `max-age=${maxAge}, s-maxage=${maxAge}`
|
||||
const staticCacheControlHeader = `max-age=${24 * 3600}, s-maxage=${24 * 3600}` // 1 day.
|
||||
function setCacheHeadersForStaticResource(res) {
|
||||
res.setHeader('Cache-Control', staticCacheControlHeader)
|
||||
res.setHeader('Last-Modified', serverStartTimeGMTString)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import log from '../server/log.js'
|
||||
import { NotFound, InvalidResponse, Inaccessible } from './errors.js'
|
||||
|
||||
const defaultErrorMessages = {
|
||||
@@ -6,9 +5,7 @@ const defaultErrorMessages = {
|
||||
429: 'rate limited by upstream service',
|
||||
}
|
||||
|
||||
const headersToInclude = ['cf-ray']
|
||||
|
||||
export default function checkErrorResponse(httpErrors = {}, logErrors = [429]) {
|
||||
export default function checkErrorResponse(httpErrors = {}) {
|
||||
return async function ({ buffer, res }) {
|
||||
let error
|
||||
httpErrors = { ...defaultErrorMessages, ...httpErrors }
|
||||
@@ -16,7 +13,7 @@ export default function checkErrorResponse(httpErrors = {}, logErrors = [429]) {
|
||||
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) {
|
||||
@@ -28,21 +25,6 @@ export default function checkErrorResponse(httpErrors = {}, logErrors = [429]) {
|
||||
error = new InvalidResponse(props)
|
||||
}
|
||||
}
|
||||
|
||||
if (logErrors.includes(res.statusCode)) {
|
||||
const tags = {}
|
||||
for (const headerKey of headersToInclude) {
|
||||
const headerValue = res.headers[headerKey]
|
||||
if (headerValue) {
|
||||
tags[`header-${headerKey}`] = headerValue
|
||||
}
|
||||
}
|
||||
log.error(
|
||||
new Error(`${res.statusCode} calling ${res.requestUrl.origin}`),
|
||||
tags,
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
error.response = res
|
||||
error.buffer = buffer
|
||||
|
||||
@@ -47,11 +47,7 @@ describe('async error handler', function () {
|
||||
|
||||
context('when status is 429', function () {
|
||||
const buffer = Buffer.from('some stuff')
|
||||
const res = {
|
||||
statusCode: 429,
|
||||
headers: { 'some-key': 'some-value' },
|
||||
requestUrl: new URL('https://example.com/'),
|
||||
}
|
||||
const res = { statusCode: 429 }
|
||||
|
||||
it('throws InvalidResponse', async function () {
|
||||
try {
|
||||
@@ -60,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)
|
||||
@@ -76,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"
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -94,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)
|
||||
@@ -110,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')
|
||||
}
|
||||
@@ -126,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)
|
||||
@@ -142,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')
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ import {
|
||||
decodeDataUrlFromQueryParam,
|
||||
prepareNamedLogo,
|
||||
} from '../../lib/logos.js'
|
||||
import { svg2base64, getIconSize } from '../../lib/svg-helpers.js'
|
||||
import { DEFAULT_LOGO_HEIGHT } from '../../badge-maker/lib/constants.js'
|
||||
import { svg2base64 } from '../../lib/svg-helpers.js'
|
||||
import coalesce from './coalesce.js'
|
||||
import toArray from './to-array.js'
|
||||
|
||||
@@ -17,12 +16,15 @@ import toArray from './to-array.js'
|
||||
//
|
||||
// Logos are resolved in this manner:
|
||||
//
|
||||
// 1. When `?logo=` contains a simple-icons logo or contains a base64-encoded
|
||||
// SVG, that logo is used. When a `&logoColor=` is specified, that color is
|
||||
// used (except for the base64-encoded logos). Otherwise the default color
|
||||
// is used. The appearance of the logo can be customized using `logoWidth`,
|
||||
// When `?logo=` is specified, any logo-related parameters specified
|
||||
// dynamically by the service, or by default in the service, are ignored.
|
||||
// 1. When `?logo=` contains a named logo or the name of one of the Shields
|
||||
// logos or contains base64-encoded SVG, that logo is used. When a
|
||||
// `&logoColor=` is specified, that color is used (except for the
|
||||
// base64-encoded logos). Otherwise the default color is used. If the color
|
||||
// is specified for a multicolor Shield logo, the named logo will be used and
|
||||
// colored. The appearance of the logo can be customized using `logoWidth`,
|
||||
// and in the case of the popout badge, `logoPosition`. When `?logo=` is
|
||||
// specified, any logo-related parameters specified dynamically by the
|
||||
// service, or by default in the service, are ignored.
|
||||
// 2. The second precedence is the dynamic logo returned by a service. This is
|
||||
// used only by the Endpoint badge. The `logoColor` can be overridden by the
|
||||
// query string.
|
||||
@@ -34,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
|
||||
@@ -53,7 +55,7 @@ export default function coalesceBadge(
|
||||
} = overrides
|
||||
let {
|
||||
logoWidth: overrideLogoWidth,
|
||||
logoSize: overrideLogoSize,
|
||||
logoPosition: overrideLogoPosition,
|
||||
color: overrideColor,
|
||||
labelColor: overrideLabelColor,
|
||||
} = overrides
|
||||
@@ -74,6 +76,7 @@ export default function coalesceBadge(
|
||||
overrideLabelColor = `${overrideLabelColor}`
|
||||
}
|
||||
overrideLogoWidth = +overrideLogoWidth || undefined
|
||||
overrideLogoPosition = +overrideLogoPosition || undefined
|
||||
|
||||
const {
|
||||
isError,
|
||||
@@ -84,8 +87,8 @@ export default function coalesceBadge(
|
||||
logoSvg: serviceLogoSvg,
|
||||
namedLogo: serviceNamedLogo,
|
||||
logoColor: serviceLogoColor,
|
||||
logoSize: serviceLogoSize,
|
||||
logoWidth: serviceLogoWidth,
|
||||
logoPosition: serviceLogoPosition,
|
||||
link: serviceLink,
|
||||
cacheSeconds: serviceCacheSeconds,
|
||||
style: serviceStyle,
|
||||
@@ -116,7 +119,7 @@ export default function coalesceBadge(
|
||||
style = 'flat'
|
||||
}
|
||||
|
||||
let namedLogo, namedLogoColor, logoSize, logoWidth, logoSvgBase64
|
||||
let namedLogo, namedLogoColor, logoWidth, logoPosition, logoSvgBase64
|
||||
if (overrideLogo) {
|
||||
// `?logo=` could be a named logo or encoded svg.
|
||||
const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrideLogo)
|
||||
@@ -130,32 +133,25 @@ export default function coalesceBadge(
|
||||
}
|
||||
// If the logo has been overridden it does not make sense to inherit the
|
||||
// original width or position.
|
||||
logoSize = overrideLogoSize
|
||||
logoWidth = overrideLogoWidth
|
||||
logoPosition = overrideLogoPosition
|
||||
} else {
|
||||
if (serviceLogoSvg) {
|
||||
logoSvgBase64 = svg2base64(serviceLogoSvg)
|
||||
} else {
|
||||
namedLogo = coalesce(
|
||||
serviceNamedLogo,
|
||||
style === 'social' ? defaultNamedLogo : undefined,
|
||||
style === 'social' ? defaultNamedLogo : undefined
|
||||
)
|
||||
namedLogoColor = coalesce(overrideLogoColor, serviceLogoColor)
|
||||
}
|
||||
logoSize = coalesce(overrideLogoSize, serviceLogoSize)
|
||||
logoWidth = coalesce(overrideLogoWidth, serviceLogoWidth)
|
||||
logoPosition = coalesce(overrideLogoPosition, serviceLogoPosition)
|
||||
}
|
||||
if (namedLogo) {
|
||||
const iconSize = getIconSize(String(namedLogo).toLowerCase())
|
||||
|
||||
if (!logoWidth && iconSize && logoSize === 'auto') {
|
||||
logoWidth = (iconSize.width / iconSize.height) * DEFAULT_LOGO_HEIGHT
|
||||
}
|
||||
|
||||
logoSvgBase64 = prepareNamedLogo({
|
||||
name: namedLogo,
|
||||
color: namedLogoColor,
|
||||
size: logoSize,
|
||||
style,
|
||||
})
|
||||
}
|
||||
@@ -170,19 +166,19 @@ 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,
|
||||
logo: logoSvgBase64,
|
||||
logoWidth,
|
||||
logoSize,
|
||||
logoPosition,
|
||||
links: toArray(overrideLink || serviceLink),
|
||||
cacheLengthSeconds: coalesce(serviceCacheSeconds, defaultCacheSeconds),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { getSimpleIcon } from '../../lib/logos.js'
|
||||
import { getShieldsIcon, getSimpleIcon } from '../../lib/logos.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
|
||||
describe('coalesceBadge', function () {
|
||||
@@ -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' })
|
||||
})
|
||||
})
|
||||
@@ -138,9 +138,9 @@ describe('coalesceBadge', function () {
|
||||
})
|
||||
|
||||
it('when a social badge, uses the default named logo', function () {
|
||||
// .not.be.empty for confidence that nothing has changed with `getSimpleIcon()`.
|
||||
// .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,65 +149,93 @@ describe('coalesceBadge', function () {
|
||||
namedLogo: 'npm',
|
||||
})
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).logo).to.equal(
|
||||
getSimpleIcon({ name: 'npm' }),
|
||||
getShieldsIcon({ name: 'npm' })
|
||||
).and.not.to.be.empty
|
||||
})
|
||||
|
||||
it('applies the named logo with color', function () {
|
||||
it('applies the named monochrome logo with color', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'dependabot', logoColor: 'blue' }, {})
|
||||
.logo,
|
||||
).to.equal(getSimpleIcon({ name: 'dependabot', color: 'blue' })).and.not
|
||||
.logo
|
||||
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
|
||||
.to.be.empty
|
||||
})
|
||||
|
||||
it('applies the named multicolored logo with color', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoColor: 'blue' }, {}).logo
|
||||
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.to.be
|
||||
.empty
|
||||
})
|
||||
|
||||
it('overrides the logo', function () {
|
||||
expect(
|
||||
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo,
|
||||
).to.equal(getSimpleIcon({ name: 'npm' })).and.not.be.empty
|
||||
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo
|
||||
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it('overrides the logo with a color', function () {
|
||||
it('overrides the monochrome logo with a color', function () {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: 'dependabot', logoColor: 'blue' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{},
|
||||
).logo,
|
||||
).to.equal(getSimpleIcon({ name: 'dependabot', color: 'blue' })).and.not
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
|
||||
.be.empty
|
||||
})
|
||||
|
||||
it("when the logo is overridden, it ignores the service's logo color and width", function () {
|
||||
it('overrides multicolored logo with a color', function () {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: 'npm', logoColor: 'blue' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it("when the logo is overridden, it ignores the service's logo color, position, and width", function () {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: 'npm' },
|
||||
{
|
||||
namedLogo: 'appveyor',
|
||||
logoColor: 'red',
|
||||
logoPosition: -3,
|
||||
logoWidth: 100,
|
||||
},
|
||||
{},
|
||||
).logo,
|
||||
).to.equal(getSimpleIcon({ name: 'npm' })).and.not.be.empty
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it("overrides the service logo's color", function () {
|
||||
it("overrides the service monochome logo's color", function () {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logoColor: 'blue' },
|
||||
{ namedLogo: 'dependabot', logoColor: 'red' },
|
||||
{},
|
||||
).logo,
|
||||
).to.equal(getSimpleIcon({ name: 'dependabot', color: 'blue' })).and.not
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
|
||||
.be.empty
|
||||
})
|
||||
|
||||
it("overrides the service multicolored logo's color", function () {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logoColor: 'blue' },
|
||||
{ namedLogo: 'npm', logoColor: 'red' },
|
||||
{}
|
||||
).logo
|
||||
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
|
||||
})
|
||||
|
||||
// https://github.com/badges/shields/issues/2998
|
||||
it('overrides logoSvg', function () {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
expect(coalesceBadge({ logo: 'npm' }, { logoSvg }, {}).logo).to.equal(
|
||||
getSimpleIcon({ name: 'npm' }),
|
||||
getShieldsIcon({ name: 'npm' })
|
||||
).and.not.be.empty
|
||||
})
|
||||
})
|
||||
@@ -216,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 })
|
||||
})
|
||||
|
||||
@@ -226,26 +254,12 @@ describe('coalesceBadge', function () {
|
||||
coalesceBadge(
|
||||
{ logo: logoSvg, logoColor: 'brightgreen' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{},
|
||||
),
|
||||
{}
|
||||
)
|
||||
).to.include({ logo: logoSvg })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo size', function () {
|
||||
it('overrides the logoSize', function () {
|
||||
expect(coalesceBadge({ logoSize: 'auto' }, {}, {})).to.include({
|
||||
logoSize: 'auto',
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the logo size', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoSize: 'auto' }, {}),
|
||||
).to.include({ logoSize: 'auto' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo width', function () {
|
||||
it('overrides the logoWidth', function () {
|
||||
expect(coalesceBadge({ logoWidth: 20 }, {}, {})).to.include({
|
||||
@@ -255,11 +269,25 @@ describe('coalesceBadge', function () {
|
||||
|
||||
it('applies the logo width', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {}),
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {})
|
||||
).to.include({ logoWidth: 275 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo position', function () {
|
||||
it('overrides the logoPosition', function () {
|
||||
expect(coalesceBadge({ logoPosition: -10 }, {}, {})).to.include({
|
||||
logoPosition: -10,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the logo position', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {})
|
||||
).to.include({ logoPosition: -10 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Links', function () {
|
||||
it('overrides the links', function () {
|
||||
expect(
|
||||
@@ -268,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'])
|
||||
})
|
||||
})
|
||||
@@ -300,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 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import coalesce from './coalesce.js'
|
||||
// https://github.com/royriojas/coalescy for these tests!
|
||||
|
||||
describe('coalesce', function () {
|
||||
test(coalesce, () => {
|
||||
test(coalesce, function () {
|
||||
given().expect(undefined)
|
||||
given(null, []).expect([])
|
||||
given(null, [], {}).expect([])
|
||||
|
||||
@@ -10,15 +10,17 @@ const attrSchema = Joi.object({
|
||||
name: Joi.string(),
|
||||
label: Joi.string(),
|
||||
category: isValidCategory,
|
||||
// The content of examples is validated later, via `transformExamples()`.
|
||||
examples: Joi.array().default([]),
|
||||
message: Joi.string(),
|
||||
dateAdded: Joi.date().required(),
|
||||
}).required()
|
||||
|
||||
function deprecatedService(attrs) {
|
||||
const { route, name, label, category, message } = Joi.attempt(
|
||||
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 {
|
||||
@@ -31,6 +33,7 @@ function deprecatedService(attrs) {
|
||||
static category = category
|
||||
static isDeprecated = true
|
||||
static route = route
|
||||
static examples = examples
|
||||
static defaultBadgeData = { label }
|
||||
|
||||
async handle() {
|
||||
|
||||
@@ -36,6 +36,16 @@ describe('DeprecatedService', function () {
|
||||
expect(service.category).to.equal(category)
|
||||
})
|
||||
|
||||
it('sets specified examples', function () {
|
||||
const examples = [
|
||||
{
|
||||
title: 'Not sure we would have examples',
|
||||
},
|
||||
]
|
||||
const service = deprecatedService({ ...commonAttrs, examples })
|
||||
expect(service.examples).to.deep.equal(examples)
|
||||
})
|
||||
|
||||
it('uses default deprecation message when no message specified', async function () {
|
||||
const service = deprecatedService({ ...commonAttrs })
|
||||
expect(await service.invoke()).to.deep.equal({
|
||||
|
||||
155
core/base-service/examples.js
Normal file
155
core/base-service/examples.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import Joi from 'joi'
|
||||
import { pathToRegexp, compile } from 'path-to-regexp'
|
||||
import categories from '../../services/categories.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
import { makeFullUrl } from './route.js'
|
||||
|
||||
const optionalObjectOfKeyValues = Joi.object().pattern(
|
||||
/./,
|
||||
Joi.string().allow(null)
|
||||
)
|
||||
|
||||
const schema = Joi.object({
|
||||
// This should be:
|
||||
// title: Joi.string().required(),
|
||||
title: Joi.string(),
|
||||
namedParams: optionalObjectOfKeyValues.required(),
|
||||
queryParams: optionalObjectOfKeyValues.default({}),
|
||||
pattern: Joi.string(),
|
||||
staticPreview: Joi.object({
|
||||
label: Joi.string(),
|
||||
message: Joi.alternatives()
|
||||
.try(Joi.string().allow('').required(), Joi.number())
|
||||
.required(),
|
||||
color: Joi.string(),
|
||||
style: Joi.string(),
|
||||
}).required(),
|
||||
keywords: Joi.array().items(Joi.string()).default([]),
|
||||
documentation: Joi.string(), // Valid HTML.
|
||||
}).required()
|
||||
|
||||
function validateExample(example, index, ServiceClass) {
|
||||
const result = Joi.attempt(
|
||||
example,
|
||||
schema,
|
||||
`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`
|
||||
)
|
||||
}
|
||||
if (pattern === ServiceClass.route.pattern) {
|
||||
throw new Error(
|
||||
`Example for ${ServiceClass.name} at index ${index} declares a redundant pattern which should be removed`
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure we can build the full URL using these patterns.
|
||||
try {
|
||||
compile(pattern || ServiceClass.route.pattern, {
|
||||
encode: encodeURIComponent,
|
||||
})(namedParams)
|
||||
} catch (e) {
|
||||
throw Error(
|
||||
`In example for ${
|
||||
ServiceClass.name
|
||||
} at index ${index}, ${e.message.toLowerCase()}`
|
||||
)
|
||||
}
|
||||
// Make sure there are no extra keys.
|
||||
let keys = []
|
||||
pathToRegexp(pattern || ServiceClass.route.pattern, keys, {
|
||||
strict: true,
|
||||
sensitive: true,
|
||||
})
|
||||
keys = keys.map(({ name }) => name)
|
||||
const extraKeys = Object.keys(namedParams).filter(k => !keys.includes(k))
|
||||
if (extraKeys.length) {
|
||||
throw Error(
|
||||
`In example for ${
|
||||
ServiceClass.name
|
||||
} at index ${index}, namedParams contains unknown keys: ${extraKeys.join(
|
||||
', '
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
if (example.keywords) {
|
||||
// Make sure the keywords are at least two characters long.
|
||||
const tinyKeywords = example.keywords.filter(k => k.length < 2)
|
||||
if (tinyKeywords.length) {
|
||||
throw Error(
|
||||
`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())
|
||||
)
|
||||
if (redundantKeywords.length) {
|
||||
throw Error(
|
||||
`In example for ${
|
||||
ServiceClass.name
|
||||
} at index ${index}, keywords contains words that are already in the title: ${redundantKeywords.join(
|
||||
', '
|
||||
)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function transformExample(inExample, index, ServiceClass) {
|
||||
const {
|
||||
// We should get rid of this transform, since the class name is never what
|
||||
// we want to see.
|
||||
title = ServiceClass.name,
|
||||
namedParams,
|
||||
queryParams,
|
||||
pattern,
|
||||
staticPreview,
|
||||
keywords,
|
||||
documentation,
|
||||
} = validateExample(inExample, index, ServiceClass)
|
||||
|
||||
const { label, message, color, style, namedLogo } = coalesceBadge(
|
||||
{},
|
||||
staticPreview,
|
||||
ServiceClass.defaultBadgeData,
|
||||
ServiceClass
|
||||
)
|
||||
|
||||
const category = categories.find(c => c.id === ServiceClass.category)
|
||||
return {
|
||||
title,
|
||||
example: {
|
||||
pattern: makeFullUrl(
|
||||
ServiceClass.route.base,
|
||||
pattern || ServiceClass.route.pattern
|
||||
),
|
||||
namedParams,
|
||||
queryParams,
|
||||
},
|
||||
preview: {
|
||||
label,
|
||||
message: `${message}`,
|
||||
color,
|
||||
style: style === 'flat' ? undefined : style,
|
||||
namedLogo,
|
||||
},
|
||||
keywords: category ? keywords.concat(category.keywords) : keywords,
|
||||
documentation: documentation ? { __html: documentation } : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export { validateExample, transformExample }
|
||||
167
core/base-service/examples.spec.js
Normal file
167
core/base-service/examples.spec.js
Normal file
@@ -0,0 +1,167 @@
|
||||
import { expect } from 'chai'
|
||||
import { test, given } from 'sazerac'
|
||||
import { validateExample, transformExample } from './examples.js'
|
||||
|
||||
describe('validateExample function', function () {
|
||||
it('passes valid examples', function () {
|
||||
const validExamples = [
|
||||
{
|
||||
title: 'Package manager versioning badge',
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
namedParams: { package: 'mypackage' },
|
||||
keywords: ['semver', 'management'],
|
||||
},
|
||||
]
|
||||
|
||||
validExamples.forEach(example => {
|
||||
expect(() =>
|
||||
validateExample(example, 0, { route: {}, name: 'mockService' })
|
||||
).not.to.throw(Error)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects invalid examples', function () {
|
||||
const invalidExamples = [
|
||||
{},
|
||||
{ staticPreview: { message: '123' } },
|
||||
{
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
namedParams: { package: 'mypackage' },
|
||||
exampleUrl: 'dt/mypackage',
|
||||
},
|
||||
{ staticPreview: { message: '123' }, pattern: 'dt/:package' },
|
||||
{
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
previewUrl: 'dt/mypackage',
|
||||
},
|
||||
{
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
exampleUrl: 'dt/mypackage',
|
||||
},
|
||||
{ previewUrl: 'dt/mypackage' },
|
||||
{
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
namedParams: { package: 'mypackage' },
|
||||
keywords: ['a'], // Keyword too short.
|
||||
},
|
||||
{
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
namedParams: { package: 'mypackage' },
|
||||
keywords: ['mockService'], // No title and keyword matching the class name.
|
||||
},
|
||||
{
|
||||
title: 'Package manager versioning badge',
|
||||
staticPreview: { message: '123' },
|
||||
pattern: 'dt/:package',
|
||||
namedParams: { package: 'mypackage' },
|
||||
keywords: ['version'], // Keyword included in title.
|
||||
},
|
||||
]
|
||||
|
||||
invalidExamples.forEach(example => {
|
||||
expect(() =>
|
||||
validateExample(example, 0, { route: {}, name: 'mockService' })
|
||||
).to.throw(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test(transformExample, function () {
|
||||
const ExampleService = {
|
||||
name: 'ExampleService',
|
||||
route: {
|
||||
base: 'some-service',
|
||||
pattern: ':interval/:packageName',
|
||||
},
|
||||
defaultBadgeData: {
|
||||
label: 'downloads',
|
||||
},
|
||||
category: 'platform-support',
|
||||
}
|
||||
|
||||
given(
|
||||
{
|
||||
pattern: 'dt/:packageName',
|
||||
namedParams: { packageName: 'express' },
|
||||
staticPreview: { message: '50k' },
|
||||
keywords: ['hello'],
|
||||
},
|
||||
0,
|
||||
ExampleService
|
||||
).expect({
|
||||
title: 'ExampleService',
|
||||
example: {
|
||||
pattern: '/some-service/dt/:packageName',
|
||||
namedParams: { packageName: 'express' },
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'downloads',
|
||||
message: '50k',
|
||||
color: 'lightgrey',
|
||||
namedLogo: undefined,
|
||||
style: undefined,
|
||||
},
|
||||
keywords: ['hello', 'platform'],
|
||||
documentation: undefined,
|
||||
})
|
||||
|
||||
given(
|
||||
{
|
||||
namedParams: { interval: 'dt', packageName: 'express' },
|
||||
staticPreview: { message: '50k' },
|
||||
keywords: ['hello'],
|
||||
},
|
||||
0,
|
||||
ExampleService
|
||||
).expect({
|
||||
title: 'ExampleService',
|
||||
example: {
|
||||
pattern: '/some-service/:interval/:packageName',
|
||||
namedParams: { interval: 'dt', packageName: 'express' },
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'downloads',
|
||||
message: '50k',
|
||||
color: 'lightgrey',
|
||||
namedLogo: undefined,
|
||||
style: undefined,
|
||||
},
|
||||
keywords: ['hello', 'platform'],
|
||||
documentation: undefined,
|
||||
})
|
||||
|
||||
given(
|
||||
{
|
||||
namedParams: { interval: 'dt', packageName: 'express' },
|
||||
queryParams: { registry_url: 'http://example.com/' },
|
||||
staticPreview: { message: '50k' },
|
||||
keywords: ['hello'],
|
||||
},
|
||||
0,
|
||||
ExampleService
|
||||
).expect({
|
||||
title: 'ExampleService',
|
||||
example: {
|
||||
pattern: '/some-service/:interval/:packageName',
|
||||
namedParams: { interval: 'dt', packageName: 'express' },
|
||||
queryParams: { registry_url: 'http://example.com/' },
|
||||
},
|
||||
preview: {
|
||||
label: 'downloads',
|
||||
message: '50k',
|
||||
color: 'lightgrey',
|
||||
namedLogo: undefined,
|
||||
style: undefined,
|
||||
},
|
||||
keywords: ['hello', 'platform'],
|
||||
documentation: undefined,
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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 }')
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const globalQueryParams = new Set([
|
||||
'link',
|
||||
'logo',
|
||||
'logoColor',
|
||||
'logoSize',
|
||||
'logoPosition',
|
||||
'logoWidth',
|
||||
'link',
|
||||
'colorA',
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user