Compare commits
5 Commits
server-202
...
github-oau
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6e31d7f32 | ||
|
|
3aadb79325 | ||
|
|
b8412fd80b | ||
|
|
345188e34b | ||
|
|
a92dc72ff5 |
@@ -1,13 +1,74 @@
|
||||
version: 2
|
||||
|
||||
main_steps: &main_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm ci
|
||||
environment:
|
||||
# https://docs.cypress.io/guides/getting-started/installing-cypress.html#Skipping-installation
|
||||
# We don't need to install the Cypress binary in jobs that aren't actually running Cypress.
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
- run:
|
||||
name: Linter
|
||||
when: always
|
||||
command: npm run lint
|
||||
|
||||
- run:
|
||||
name: Core tests
|
||||
when: always
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/core/results.xml
|
||||
command: npm run test:core
|
||||
|
||||
- run:
|
||||
name: Entrypoint tests
|
||||
when: always
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/entrypoint/results.xml
|
||||
command: npm run test:entrypoint
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
- run:
|
||||
name: 'Prettier check (quick fix: `npm run prettier`)'
|
||||
when: always
|
||||
command: npm run prettier:check
|
||||
|
||||
integration_steps: &integration_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
- run:
|
||||
name: Integration tests
|
||||
when: always
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/integration/results.xml
|
||||
command: npm run test:integration
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
services_steps: &services_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -25,10 +86,84 @@ services_steps: &services_steps
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
package_steps: &package_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install node and npm
|
||||
command: |
|
||||
set +e
|
||||
export NVM_DIR="/opt/circleci/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
nvm install v14
|
||||
nvm use v14
|
||||
npm install -g npm
|
||||
|
||||
# Run the package tests on each currently supported node version. See:
|
||||
# https://github.com/badges/shields/blob/master/badge-maker/README.md#node-version-support
|
||||
# https://nodejs.org/en/about/releases/
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v12/results.xml
|
||||
NODE_VERSION: v12
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 12
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v14/results.xml
|
||||
NODE_VERSION: v14
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 14
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v16/results.xml
|
||||
NODE_VERSION: v16
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 16
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
jobs:
|
||||
main:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
main@node-16:
|
||||
docker:
|
||||
- image: circleci/node:16
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
integration:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
integration@node-16:
|
||||
docker:
|
||||
- image: circleci/node:16
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
danger:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: circleci/node:14
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
@@ -48,15 +183,13 @@ jobs:
|
||||
|
||||
frontend:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
|
||||
- image: circleci/node:14
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -84,24 +217,26 @@ jobs:
|
||||
when: always
|
||||
command: npm run build
|
||||
|
||||
package:
|
||||
machine: true
|
||||
|
||||
<<: *package_steps
|
||||
|
||||
services:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
services@node-17:
|
||||
services@node-16:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
- image: circleci/node:16
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
e2e:
|
||||
docker:
|
||||
- image: cypress/base:16.14.0
|
||||
|
||||
- image: cypress/base:14.16.0
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
@@ -112,8 +247,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
command: npm ci
|
||||
|
||||
- run:
|
||||
name: Frontend build
|
||||
@@ -147,17 +281,33 @@ workflows:
|
||||
|
||||
on-commit:
|
||||
jobs:
|
||||
- main:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- main@node-16:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- integration@node-16:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- frontend:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- package:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- services:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- services@node-17:
|
||||
- services@node-16:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
@@ -180,6 +330,12 @@ workflows:
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: gh-pages
|
||||
# - main:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# - main@node-latest:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# - frontend:
|
||||
# requires:
|
||||
# - npm-install
|
||||
|
||||
@@ -144,8 +144,6 @@ rules:
|
||||
func-style: ['error', 'declaration', { 'allowArrowFunctions': true }]
|
||||
new-cap: ['error', { 'capIsNew': true }]
|
||||
import/order: ['error', { 'newlines-between': 'never' }]
|
||||
quotes:
|
||||
['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }]
|
||||
|
||||
# Account for destructuring responses from upstream services,
|
||||
# many of which do not follow camelcase
|
||||
|
||||
@@ -22,7 +22,7 @@ labels: 'keep-service-tests-green'
|
||||
|
||||
<!-- Provide a link to the failing test in CircleCI. -->
|
||||
|
||||
:lady_beetle: **Stack trace**
|
||||
:beetle: **Stack trace**
|
||||
|
||||
```
|
||||
<!-- Provide the complete stack trace from the CircleCI test summary. -->
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/3_Badge_request.md
vendored
2
.github/ISSUE_TEMPLATE/3_Badge_request.md
vendored
@@ -21,7 +21,7 @@ A clear and concise description of the new badge.
|
||||
Where can we get the data from?
|
||||
|
||||
- Is there a public API?
|
||||
- Does the API require an API key?
|
||||
- Does the API requires an API key?
|
||||
- Link to the API documentation.
|
||||
-->
|
||||
|
||||
|
||||
6
.github/actions/close-bot/helpers.js
vendored
6
.github/actions/close-bot/helpers.js
vendored
@@ -56,12 +56,6 @@ function isPointlessVersionBump(body) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
2
.github/actions/close-bot/index.js
vendored
2
.github/actions/close-bot/index.js
vendored
@@ -27,7 +27,7 @@ async function run() {
|
||||
state: 'closed',
|
||||
})
|
||||
|
||||
core.debug('Done.')
|
||||
core.debug(`Done.`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
309
.github/actions/close-bot/package-lock.json
generated
vendored
309
.github/actions/close-bot/package-lock.json
generated
vendored
@@ -9,54 +9,50 @@
|
||||
"version": "0.0.0",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1"
|
||||
"@actions/core": "^1.5.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz",
|
||||
"integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@octokit/core": "^3.4.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.13.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
"tunnel": "0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
|
||||
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request": "^5.4.12",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
@@ -64,9 +60,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz",
|
||||
"integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
@@ -74,37 +70,37 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.2.tgz",
|
||||
"integrity": "sha512-WmsIR1OzOr/3IqfG9JIczI8gMJUMzzyx5j0XXQ4YihHtKlQc+u35VpVoOXhlKAlaBntvry1WpAzPl/a+s3n89Q==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/request": "^5.3.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
|
||||
"integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA=="
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.0.0.tgz",
|
||||
"integrity": "sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
|
||||
"integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz",
|
||||
"integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.34.0"
|
||||
"@octokit/types": "^6.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz",
|
||||
"integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.1.1.tgz",
|
||||
"integrity": "sha512-u4zy0rVA8darm/AYsIeWkRalhQR99qPL1D/EXHejV2yaECMdHfxXiTXtba8NMBSajOJe8+C9g+EqMKSvysx0dg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.34.0",
|
||||
"@octokit/types": "^6.14.1",
|
||||
"deprecation": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -112,22 +108,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"version": "5.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz",
|
||||
"integrity": "sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"@octokit/request-error": "^2.0.0",
|
||||
"@octokit/types": "^6.7.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-fetch": "^2.6.1",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz",
|
||||
"integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
@@ -135,17 +131,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
|
||||
"integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.14.2.tgz",
|
||||
"integrity": "sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^11.2.0"
|
||||
"@octokit/openapi-types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz",
|
||||
"integrity": "sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw=="
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
@@ -161,22 +157,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
@@ -187,11 +172,6 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
@@ -205,28 +185,6 @@
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@@ -235,49 +193,45 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
|
||||
},
|
||||
"@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz",
|
||||
"integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@octokit/core": "^3.4.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.13.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"requires": {
|
||||
"tunnel": "^0.0.6"
|
||||
"tunnel": "0.0.6"
|
||||
}
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
|
||||
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==",
|
||||
"requires": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request": "^5.4.12",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
@@ -285,9 +239,9 @@
|
||||
}
|
||||
},
|
||||
"@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz",
|
||||
"integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
@@ -295,54 +249,54 @@
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.2.tgz",
|
||||
"integrity": "sha512-WmsIR1OzOr/3IqfG9JIczI8gMJUMzzyx5j0XXQ4YihHtKlQc+u35VpVoOXhlKAlaBntvry1WpAzPl/a+s3n89Q==",
|
||||
"requires": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/request": "^5.3.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
|
||||
"integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA=="
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.0.0.tgz",
|
||||
"integrity": "sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw=="
|
||||
},
|
||||
"@octokit/plugin-paginate-rest": {
|
||||
"version": "2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
|
||||
"integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz",
|
||||
"integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.34.0"
|
||||
"@octokit/types": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz",
|
||||
"integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.1.1.tgz",
|
||||
"integrity": "sha512-u4zy0rVA8darm/AYsIeWkRalhQR99qPL1D/EXHejV2yaECMdHfxXiTXtba8NMBSajOJe8+C9g+EqMKSvysx0dg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.34.0",
|
||||
"@octokit/types": "^6.14.1",
|
||||
"deprecation": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"version": "5.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz",
|
||||
"integrity": "sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==",
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"@octokit/request-error": "^2.0.0",
|
||||
"@octokit/types": "^6.7.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-fetch": "^2.6.1",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz",
|
||||
"integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
@@ -350,17 +304,17 @@
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "6.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
|
||||
"integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.14.2.tgz",
|
||||
"integrity": "sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA==",
|
||||
"requires": {
|
||||
"@octokit/openapi-types": "^11.2.0"
|
||||
"@octokit/openapi-types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz",
|
||||
"integrity": "sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw=="
|
||||
},
|
||||
"deprecation": {
|
||||
"version": "2.3.1",
|
||||
@@ -373,12 +327,9 @@
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
@@ -388,11 +339,6 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
@@ -403,25 +349,6 @@
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
4
.github/actions/close-bot/package.json
vendored
4
.github/actions/close-bot/package.json
vendored
@@ -10,7 +10,7 @@
|
||||
"author": "chris48s",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1"
|
||||
"@actions/core": "^1.5.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
21
.github/actions/core-tests/action.yml
vendored
21
.github/actions/core-tests/action.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: 'Core tests'
|
||||
description: 'Run core and entrypoint tests'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Core tests
|
||||
if: always()
|
||||
run: npm run test:core -- --reporter json --reporter-option 'output=reports/core.json'
|
||||
shell: bash
|
||||
|
||||
- name: Entrypoint tests
|
||||
if: always()
|
||||
run: npm run test:entrypoint -- --reporter json --reporter-option 'output=reports/entrypoint.json'
|
||||
shell: bash
|
||||
|
||||
- name: Write Markdown Summary
|
||||
if: always()
|
||||
run: |
|
||||
node scripts/mocha2md.js Core reports/core.json >> $GITHUB_STEP_SUMMARY
|
||||
node scripts/mocha2md.js Entrypoint reports/entrypoint.json >> $GITHUB_STEP_SUMMARY
|
||||
shell: bash
|
||||
20
.github/actions/integration-tests/action.yml
vendored
20
.github/actions/integration-tests/action.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 'Integration tests'
|
||||
description: 'Run integration tests'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Integration Tests
|
||||
if: always()
|
||||
run: npm run test:integration -- --reporter json --reporter-option 'output=reports/integration-tests.json'
|
||||
env:
|
||||
GH_TOKEN: '${{ inputs.github-token }}'
|
||||
shell: bash
|
||||
|
||||
- name: Write Markdown Summary
|
||||
if: always()
|
||||
run: node scripts/mocha2md.js Integration reports/integration-tests.json >> $GITHUB_STEP_SUMMARY
|
||||
shell: bash
|
||||
26
.github/actions/package-tests/action.yml
vendored
26
.github/actions/package-tests/action.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: 'Package tests'
|
||||
description: 'Run package tests and check types'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Tests
|
||||
if: always()
|
||||
run: npm run test:package -- --reporter json --reporter-option 'output=reports/package-tests.json'
|
||||
shell: bash
|
||||
|
||||
- name: Type Checks
|
||||
if: always()
|
||||
run: |
|
||||
set -o pipefail
|
||||
npm run check-types:package 2>&1 | tee reports/package-types.txt
|
||||
shell: bash
|
||||
|
||||
- name: Write Markdown Summary
|
||||
if: always()
|
||||
run: |
|
||||
node scripts/mocha2md.js 'Package Tests' reports/package-tests.json >> $GITHUB_STEP_SUMMARY
|
||||
echo '# Package Types' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
cat reports/package-types.txt >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
shell: bash
|
||||
25
.github/actions/setup/action.yml
vendored
25
.github/actions/setup/action.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: 'Set up project'
|
||||
description: 'Set up project'
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
required: true
|
||||
cypress:
|
||||
description: 'Install Cypress binary: 0 or 1'
|
||||
# https://docs.cypress.io/guides/getting-started/installing-cypress.html#Skipping-installation
|
||||
# We don't need to install the Cypress binary in jobs that aren't actually running Cypress.
|
||||
required: false
|
||||
default: 0
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: ${{ inputs.cypress }}
|
||||
run: npm ci
|
||||
shell: bash
|
||||
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
@@ -8,16 +8,6 @@ updates:
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
ignore:
|
||||
# https://github.com/badges/shields/issues/7324
|
||||
# https://github.com/badges/shields/issues/7447
|
||||
# we're stuck with these versions until Safari is compatible with lookbehind regex syntax
|
||||
# https://caniuse.com/js-regexp-lookbehind
|
||||
- dependency-name: 'decamelize'
|
||||
- dependency-name: 'humanize-string'
|
||||
|
||||
# https://github.com/badges/shields/pull/7288#issuecomment-974699240
|
||||
- dependency-name: '@types/node'
|
||||
|
||||
# badge-maker package dependencies
|
||||
- package-ecosystem: npm
|
||||
@@ -36,8 +26,3 @@ updates:
|
||||
day: friday
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 99
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 99
|
||||
|
||||
8
.github/workflows/auto-close.yml
vendored
8
.github/workflows/auto-close.yml
vendored
@@ -1,18 +1,16 @@
|
||||
name: Auto close
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
on: pull_request_target
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-close:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install action dependencies
|
||||
run: cd .github/actions/close-bot && npm ci
|
||||
|
||||
13
.github/workflows/build-docker-image.yml
vendored
13
.github/workflows/build-docker-image.yml
vendored
@@ -3,23 +3,18 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build-docker-image:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: shieldsio/shields:pr-validation
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
|
||||
13
.github/workflows/create-release.yml
vendored
13
.github/workflows/create-release.yml
vendored
@@ -4,9 +4,6 @@ on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
if: |
|
||||
@@ -23,7 +20,7 @@ jobs:
|
||||
run: echo "::set-output name=date::$(date --rfc-3339=date)"
|
||||
|
||||
- name: Checkout branch "master"
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: 'master'
|
||||
|
||||
@@ -34,19 +31,17 @@ jobs:
|
||||
tag: server-${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to DockerHub
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: shieldsio/shields:server-${{ steps.date.outputs.date }}
|
||||
build-args: |
|
||||
version=server-${{ steps.date.outputs.date }}
|
||||
|
||||
17
.github/workflows/deploy-docs.yml
vendored
17
.github/workflows/deploy-docs.yml
vendored
@@ -3,16 +3,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -22,8 +18,9 @@ jobs:
|
||||
npm run build-docs
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: api-docs
|
||||
clean: true
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: api-docs
|
||||
CLEAN: true
|
||||
|
||||
8
.github/workflows/draft-release.yml
vendored
8
.github/workflows/draft-release.yml
vendored
@@ -5,16 +5,12 @@ on:
|
||||
# At 01:00 on the first day of every month
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
draft-release:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Draft Release
|
||||
uses: ./.github/actions/draft-release
|
||||
|
||||
13
.github/workflows/enforce-dependency-review.yml
vendored
13
.github/workflows/enforce-dependency-review.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: 'Dependency Review'
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
enforce-dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v2
|
||||
15
.github/workflows/publish-docker-next.yml
vendored
15
.github/workflows/publish-docker-next.yml
vendored
@@ -5,29 +5,24 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
publish-docker-next:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: shieldsio/shields:next
|
||||
build-args: |
|
||||
version=${{ env.SHORT_SHA }}
|
||||
|
||||
48
.github/workflows/test-integration-17.yml
vendored
48
.github/workflows/test-integration-17.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: Integration@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration-17:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: ${{ env.PAT_EXISTS == 'true' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
|
||||
- name: Integration Tests (with workflow token)
|
||||
if: ${{ env.PAT_EXISTS == 'false' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
46
.github/workflows/test-integration.yml
vendored
46
.github/workflows/test-integration.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Integration
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: ${{ env.PAT_EXISTS == 'true' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
|
||||
- name: Integration Tests (with workflow token)
|
||||
if: ${{ env.PAT_EXISTS == 'false' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
28
.github/workflows/test-lint.yml
vendored
28
.github/workflows/test-lint.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: ESLint
|
||||
if: always()
|
||||
run: npm run lint
|
||||
|
||||
- name: 'Prettier check (quick fix: `npm run prettier`)'
|
||||
if: always()
|
||||
run: npm run prettier:check
|
||||
25
.github/workflows/test-main-17.yml
vendored
25
.github/workflows/test-main-17.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Main@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main-17:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
28
.github/workflows/test-main.yml
vendored
28
.github/workflows/test-main.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Main
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['ubuntu-latest', 'windows-latest']
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
46
.github/workflows/test-package-cli.yml
vendored
46
.github/workflows/test-package-cli.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Package CLI
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
# Smoke test (render a badge with the CLI) with only the package
|
||||
# dependencies installed.
|
||||
|
||||
jobs:
|
||||
test-package-cli:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- node: '14'
|
||||
engine-strict: 'false'
|
||||
- node: '16'
|
||||
engine-strict: 'false'
|
||||
- node: '18'
|
||||
engine-strict: 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
run: |
|
||||
cd badge-maker
|
||||
npm install
|
||||
npm link
|
||||
|
||||
- name: Render a badge with the CLI
|
||||
run: |
|
||||
cd badge-maker
|
||||
badge cactus grown :green @flat
|
||||
34
.github/workflows/test-package-lib.yml
vendored
34
.github/workflows/test-package-lib.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Package Library
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-package-lib:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- node: '14'
|
||||
engine-strict: 'false'
|
||||
- node: '16'
|
||||
engine-strict: 'true'
|
||||
- node: '18'
|
||||
engine-strict: 'false'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
|
||||
- name: Package tests
|
||||
uses: ./.github/actions/package-tests
|
||||
208
CHANGELOG.md
208
CHANGELOG.md
@@ -4,214 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2022-11-01
|
||||
|
||||
- [Ansible] Add collection badge [#8578](https://github.com/badges/shields/issues/8578)
|
||||
- [VisualStudioMarketplace] Add support to prerelease extensions version (Issue #8207) [#8561](https://github.com/badges/shields/issues/8561)
|
||||
- feat: add [GitlabLastCommit] service [#8508](https://github.com/badges/shields/issues/8508)
|
||||
- fix [swagger] service tests (allow 0 items in array) [#8564](https://github.com/badges/shields/issues/8564)
|
||||
- fix codecov badge for non-default branch [#8565](https://github.com/badges/shields/issues/8565)
|
||||
- Add [GitHubLastCommit] by committer badge [#8537](https://github.com/badges/shields/issues/8537)
|
||||
- [GitHubReleaseDate] - published_at field [#8543](https://github.com/badges/shields/issues/8543)
|
||||
- Fix [Testspace] with new "untested" value in case_counts array [#8544](https://github.com/badges/shields/issues/8544)
|
||||
- fix: Support WAITING status for GitHub deployments [#8521](https://github.com/badges/shields/issues/8521)
|
||||
- [Whatpulse] badge for a user and for a team [#8466](https://github.com/badges/shields/issues/8466)
|
||||
- deprecate [pkgreview] service [#8499](https://github.com/badges/shields/issues/8499)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-10-08
|
||||
|
||||
- deprecate [criterion] service [#8501](https://github.com/badges/shields/issues/8501)
|
||||
- fix formatRelativeDate error handling; run [date] [#8497](https://github.com/badges/shields/issues/8497)
|
||||
- allow/validate bitbucket_username / bitbucket_password in private config schema [#8472](https://github.com/badges/shields/issues/8472)
|
||||
- fix [pub] points badge test and example [#8498](https://github.com/badges/shields/issues/8498)
|
||||
- feat: add [GitlabLanguageCount] service [#8377](https://github.com/badges/shields/issues/8377)
|
||||
- [GitHubGistStars] add GitHub Gist Stars [#8471](https://github.com/badges/shields/issues/8471)
|
||||
- fix display/search of CII badge examples [#8473](https://github.com/badges/shields/issues/8473)
|
||||
- feat: add 2022 support to GitHub Hacktoberfest [#8468](https://github.com/badges/shields/issues/8468)
|
||||
- fix [GitLabCoverage] subgroup bug [#8401](https://github.com/badges/shields/issues/8401)
|
||||
- implement ruby gems-specific version sort/color functions [#8434](https://github.com/badges/shields/issues/8434)
|
||||
- Add `rc` to pre-release identifiers [#8435](https://github.com/badges/shields/issues/8435)
|
||||
- add [GitHub] Number of commits between branches/tags/commits [#8394](https://github.com/badges/shields/issues/8394)
|
||||
- add [Packagist] dependency version [#8371](https://github.com/badges/shields/issues/8371)
|
||||
- fix Docker build status invalid response data bug [#8392](https://github.com/badges/shields/issues/8392)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-09-04
|
||||
|
||||
- fix frontend compile for users running on Windows [#8350](https://github.com/badges/shields/issues/8350)
|
||||
- [DockerSize] Docker image size multi arch [#8290](https://github.com/badges/shields/issues/8290)
|
||||
- upgrade gatsby [#8334](https://github.com/badges/shields/issues/8334)
|
||||
- Custom domains for [JitPack] artifacts [#8333](https://github.com/badges/shields/issues/8333)
|
||||
- fix [dockerstars] service [#8316](https://github.com/badges/shields/issues/8316)
|
||||
- [BountySource] Fix: Broken Badge generation for decimal activity values [#8315](https://github.com/badges/shields/issues/8315)
|
||||
- feat: add [gitlabmergerequests] service [#8166](https://github.com/badges/shields/issues/8166)
|
||||
- Fix terminology for [ROS] version service [#8292](https://github.com/badges/shields/issues/8292)
|
||||
- feat: add [GitlabStars] service [#8209](https://github.com/badges/shields/issues/8209)
|
||||
- Fix invalid `rst` format when `alt` or `target` is present [#8275](https://github.com/badges/shields/issues/8275)
|
||||
- [GithubGistLastCommit] GitHub gist last commit [#8272](https://github.com/badges/shields/issues/8272)
|
||||
- [GitHub] GitHub file size for a specific branch [#8262](https://github.com/badges/shields/issues/8262)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-08-01
|
||||
|
||||
- [pypi] Add Framework Version Badges support [#8261](https://github.com/badges/shields/issues/8261)
|
||||
- feat: add [GitlabForks] server [#8208](https://github.com/badges/shields/issues/8208)
|
||||
- Update PyPI api according to https://warehouse.pypa.io/api-reference/json.html [#8251](https://github.com/badges/shields/issues/8251)
|
||||
- Add [galaxytoolshed] Activity [#8164](https://github.com/badges/shields/issues/8164)
|
||||
- [greasyfork] Add Greasy Fork rating badges [#8087](https://github.com/badges/shields/issues/8087)
|
||||
- refactor(deps): Replace moment with dayjs [#8192](https://github.com/badges/shields/issues/8192)
|
||||
- add spaces round pipe in [conda] badge [#8189](https://github.com/badges/shields/issues/8189)
|
||||
- Add [ROS] version service [#8169](https://github.com/badges/shields/issues/8169)
|
||||
- feat: add [gitlabissues] service [#8108](https://github.com/badges/shields/issues/8108)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-07-03
|
||||
|
||||
- Add [galaxytoolshed] services [#8114](https://github.com/badges/shields/issues/8114)
|
||||
- fix [gitlab] auth [#8145](https://github.com/badges/shields/issues/8145) [#8162](https://github.com/badges/shields/issues/8162)
|
||||
- increase cache length on AUR version badge, run [AUR] [#8110](https://github.com/badges/shields/issues/8110)
|
||||
- Use GraphQL to fix GitHub file count badges [github] [#8112](https://github.com/badges/shields/issues/8112)
|
||||
- feat: add [gitlab] contributors service [#8084](https://github.com/badges/shields/issues/8084)
|
||||
- [greasyfork] Add Greasy Fork service badges [#8080](https://github.com/badges/shields/issues/8080)
|
||||
- Add [gitlablicense] services [#8024](https://github.com/badges/shields/issues/8024)
|
||||
- [Spack] Package Manager: Update Domain [#8046](https://github.com/badges/shields/issues/8046)
|
||||
- switch [jitpack] to use latestOk endpoint [#8041](https://github.com/badges/shields/issues/8041)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-06-01
|
||||
|
||||
- Update GitLab logo (2022) [#7984](https://github.com/badges/shields/issues/7984)
|
||||
- [GitHub] Added milestone property to GitHub issue details service [#7864](https://github.com/badges/shields/issues/7864)
|
||||
- [Spack] Package Manager: Update Endpoint [#7957](https://github.com/badges/shields/issues/7957)
|
||||
- Update Chocolatey API endpoint URL [#7952](https://github.com/badges/shields/issues/7952)
|
||||
- [Flathub]Add downloads badge [#7724](https://github.com/badges/shields/issues/7724)
|
||||
- replace the outdated Telegram logo with the newest [#7831](https://github.com/badges/shields/issues/7831)
|
||||
- add [PUB] points badge [#7918](https://github.com/badges/shields/issues/7918)
|
||||
- add [PUB] popularity badge [#7920](https://github.com/badges/shields/issues/7920)
|
||||
- add [PUB] likes badge [#7916](https://github.com/badges/shields/issues/7916)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-05-03
|
||||
|
||||
- [OSSFScorecard] Create scorecard badge service [#7687](https://github.com/badges/shields/issues/7687)
|
||||
- Stringify [githublanguagecount] message [#7881](https://github.com/badges/shields/issues/7881)
|
||||
- Stringify and trim whitespace from a few services [#7880](https://github.com/badges/shields/issues/7880)
|
||||
- add labels to Dockerfile [#7862](https://github.com/badges/shields/issues/7862)
|
||||
- handle missing 'fly-client-ip' [#7814](https://github.com/badges/shields/issues/7814)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-04-03
|
||||
|
||||
- Breaking change: This release updates ioredis from v4 to v5.
|
||||
If you are using redis for GitHub token pooling, redis connection strings of the form
|
||||
`redis://junkusername:authpassword@example.com:1234` will need to be updated to
|
||||
`redis://:authpassword@example.com:1234`. See the
|
||||
[ioredis upgrade guide](https://github.com/luin/ioredis/wiki/Upgrading-from-v4-to-v5)
|
||||
for further details.
|
||||
- fix installation issue on npm >= 8.5.5 [#7809](https://github.com/badges/shields/issues/7809)
|
||||
- two fixes for [packagist] schemas [#7782](https://github.com/badges/shields/issues/7782)
|
||||
- allow requireCloudflare setting to work when hosted on fly.io [#7781](https://github.com/badges/shields/issues/7781)
|
||||
- fix [pypi] badges when package has null license [#7761](https://github.com/badges/shields/issues/7761)
|
||||
- Add a [pub] publisher badge [#7715](https://github.com/badges/shields/issues/7715)
|
||||
- Switch Steam file size badge to informational color [#7722](https://github.com/badges/shields/issues/7722)
|
||||
- Make W3C and Youtube documentation links clickable [#7721](https://github.com/badges/shields/issues/7721)
|
||||
- Improve Wercker examples [#7720](https://github.com/badges/shields/issues/7720)
|
||||
- Improve Cirrus CI examples [#7719](https://github.com/badges/shields/issues/7719)
|
||||
- Support [CodeClimate] responses with multiple data items [#7716](https://github.com/badges/shields/issues/7716)
|
||||
- Delete [TeamCityCoverage] and [BowerVersion] redirectors [#7718](https://github.com/badges/shields/issues/7718)
|
||||
- Deprecate [Shippable] service [#7717](https://github.com/badges/shields/issues/7717)
|
||||
- fix: restore version comparison updates from #4173 [#4254](https://github.com/badges/shields/issues/4254)
|
||||
- [piwheels], filter out versions with no files [#7696](https://github.com/badges/shields/issues/7696)
|
||||
- set a longer cacheLength on [librariesio] badges [#7692](https://github.com/badges/shields/issues/7692)
|
||||
- improve python version formatting [#7682](https://github.com/badges/shields/issues/7682)
|
||||
- Clarify GitHub All Contributors badge [#7690](https://github.com/badges/shields/issues/7690)
|
||||
- Support [HexPM] packages with no stable release [#7685](https://github.com/badges/shields/issues/7685)
|
||||
- Add Test at Scale Badge [#7612](https://github.com/badges/shields/issues/7612)
|
||||
- [packagist] api v2 support [#7681](https://github.com/badges/shields/issues/7681)
|
||||
- Add [piwheels] version badge [#7656](https://github.com/badges/shields/issues/7656)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-03-01
|
||||
|
||||
- Add [Conan] version service (#7460)
|
||||
- remove suspended [github] tokens from the pool [#7654](https://github.com/badges/shields/issues/7654)
|
||||
- generate links without trailing : if port not set [#7655](https://github.com/badges/shields/issues/7655)
|
||||
- Use the latest build status when checking docs.rs [#7613](https://github.com/badges/shields/issues/7613)
|
||||
- Remove no download handling and add API warning to [Wordpress] badges [#7606](https://github.com/badges/shields/issues/7606)
|
||||
- set a higher default cacheLength on rating/star category [#7587](https://github.com/badges/shields/issues/7587)
|
||||
- Update [amo] to use v4 API, set custom `cacheLength`s [#7586](https://github.com/badges/shields/issues/7586)
|
||||
- fix(amo): include trailing slash in API call [#7585](https://github.com/badges/shields/issues/7585)
|
||||
- fix docker image user agent [#7582](https://github.com/badges/shields/issues/7582)
|
||||
- Delete deprecated Codetally and continuousphp services [#7572](https://github.com/badges/shields/issues/7572)
|
||||
- Deprecate [Requires] service [#7571](https://github.com/badges/shields/issues/7571)
|
||||
- [AUR] Fix RPC URL [#7570](https://github.com/badges/shields/issues/7570)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-02-01
|
||||
|
||||
- [Depfu] Add support for Gitlab [#7475](https://github.com/badges/shields/issues/7475)
|
||||
- replace label in hn-user-karma with U/ [#7500](https://github.com/badges/shields/issues/7500)
|
||||
- Support [Feedz] response with multiple pages without items [#7476](https://github.com/badges/shields/issues/7476)
|
||||
- revert decamelize and humanize-string to old versions [#7449](https://github.com/badges/shields/issues/7449)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-01-01
|
||||
|
||||
- minor [reddit] improvements [#7436](https://github.com/badges/shields/issues/7436)
|
||||
- [HackerNews] Show User Karma [#7411](https://github.com/badges/shields/issues/7411)
|
||||
- [YouTube] Drop support for removed dislikes [#7410](https://github.com/badges/shields/issues/7410)
|
||||
- change closed GitHub issue color to purple [#7374](https://github.com/badges/shields/issues/7374)
|
||||
- restore cors header injection from #4171 [#4255](https://github.com/badges/shields/issues/4255)
|
||||
- [GithubPackageJson] Get version from monorepo subfolder package.json [#7350](https://github.com/badges/shields/issues/7350)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-12-01
|
||||
|
||||
- Send better user-agent values [#7309](https://github.com/badges/shields/issues/7309)
|
||||
Self-hosting users now send a user agent which indicates the server version and starts `shields (self-hosted)/` by default.
|
||||
This can be configured using the env var `USER_AGENT_BASE`
|
||||
- upgrade to node 16 [#7271](https://github.com/badges/shields/issues/7271)
|
||||
- feat: deprecate dependabot badges [#7274](https://github.com/badges/shields/issues/7274)
|
||||
- fix: npmversion tagged service test [#7269](https://github.com/badges/shields/issues/7269)
|
||||
- feat: create new Test Results category [#7218](https://github.com/badges/shields/issues/7218)
|
||||
- Migration from Request to Got for all HTTP requests is completed in this release
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-11-04
|
||||
|
||||
- migrate regularUpdate() from request-->got [#7215](https://github.com/badges/shields/issues/7215)
|
||||
- migrate github badges to use got instead of request; affects [github librariesio] [#7212](https://github.com/badges/shields/issues/7212)
|
||||
- deprecate David badges [#7197](https://github.com/badges/shields/issues/7197)
|
||||
- fix: ensure libraries.io header values are processed numerically [#7196](https://github.com/badges/shields/issues/7196)
|
||||
- Add authentication for Libraries.io-based badges, run [Libraries Bower] [#7080](https://github.com/badges/shields/issues/7080)
|
||||
- fixes and tests for pipenv helpers [#7194](https://github.com/badges/shields/issues/7194)
|
||||
- add GitLab Release badge, run all [GitLab] [#7021](https://github.com/badges/shields/issues/7021)
|
||||
- set content-length header on badge responses [#7179](https://github.com/badges/shields/issues/7179)
|
||||
- fix [github] release/tag/download schema [#7170](https://github.com/badges/shields/issues/7170)
|
||||
- Supported nested groups on [GitLabPipeline] badge [#7159](https://github.com/badges/shields/issues/7159)
|
||||
- Support nested groups on [GitLabTag] badge [#7158](https://github.com/badges/shields/issues/7158)
|
||||
- Fixing incorrect JetBrains Plugin rating values for [JetBrainsRating] [#7140](https://github.com/badges/shields/issues/7140)
|
||||
- support using release or tag name in [GitHub] Release version badge [#7075](https://github.com/badges/shields/issues/7075)
|
||||
- feat: support branches in sonar badges [#7065](https://github.com/badges/shields/issues/7065)
|
||||
- Add [Modrinth] total downloads badge [#7132](https://github.com/badges/shields/issues/7132)
|
||||
- remove [github] admin routes [#7105](https://github.com/badges/shields/issues/7105)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-10-04
|
||||
|
||||
- feat: add 2021 support to GitHub Hacktoberfest [#7086](https://github.com/badges/shields/issues/7086)
|
||||
- Add [ClearlyDefined] service [#6944](https://github.com/badges/shields/issues/6944)
|
||||
- handle null licenses in crates.io response schema, run [crates] [#7074](https://github.com/badges/shields/issues/7074)
|
||||
- [OBS] add Open Build Service service-badge [#6993](https://github.com/badges/shields/issues/6993)
|
||||
- Correction of badges url in self-hosting configuration with a custom port. Issue 7025 [#7036](https://github.com/badges/shields/issues/7036)
|
||||
- fix: support gitlab token via env var [#7023](https://github.com/badges/shields/issues/7023)
|
||||
- Add API-based support for [GitLab] badges, add new GitLab Tag badge [#6988](https://github.com/badges/shields/issues/6988)
|
||||
- [freecodecamp]: allow + symbol in username [#7016](https://github.com/badges/shields/issues/7016)
|
||||
- Rename Riot to Element in Matrix badge help [#6996](https://github.com/badges/shields/issues/6996)
|
||||
- Fixed Reddit Negative Karma Issue [#6992](https://github.com/badges/shields/issues/6992)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-09-01
|
||||
|
||||
- use multi-stage build to reduce size of docker images [#6938](https://github.com/badges/shields/issues/6938)
|
||||
|
||||
@@ -134,7 +134,7 @@ Prettier before a commit by default.
|
||||
When adding or changing a service [please write tests][service-tests], and ensure the [title of your Pull Requests follows the required conventions](#running-service-tests-in-pull-requests) to ensure your tests are executed.
|
||||
When changing other code, please add unit tests.
|
||||
|
||||
To run the integration tests, you must have Redis installed and in your PATH.
|
||||
To run the integration tests, you must have redis installed and in your PATH.
|
||||
Use `brew install redis`, `yum install redis`, etc. The test runner will
|
||||
start the server automatically.
|
||||
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:16-alpine AS Builder
|
||||
FROM node:14-alpine AS Builder
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
RUN mkdir /usr/src/app/private
|
||||
@@ -8,8 +8,7 @@ COPY package.json package-lock.json /usr/src/app/
|
||||
# Without the badge-maker package.json and CLI script in place, `npm ci` will fail.
|
||||
COPY badge-maker /usr/src/app/badge-maker/
|
||||
|
||||
RUN apk add python3 make g++
|
||||
RUN npm install -g "npm@>=8"
|
||||
RUN npm install -g "npm@>=7"
|
||||
# We need dev deps to build the front end. We don't need Cypress, though.
|
||||
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci
|
||||
|
||||
@@ -19,13 +18,7 @@ RUN npm prune --production
|
||||
RUN npm cache clean --force
|
||||
|
||||
# Use multi-stage build to reduce size
|
||||
FROM node:16-alpine
|
||||
|
||||
ARG version=dev
|
||||
ENV DOCKER_SHIELDS_VERSION=$version
|
||||
LABEL version=$version
|
||||
LABEL fly.version=$version
|
||||
|
||||
FROM node:14-alpine
|
||||
# Run the server using production configs.
|
||||
ENV NODE_ENV production
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ and legible badges in SVG and raster format, which can easily be included in
|
||||
GitHub readmes or any other web page. The service supports dozens of
|
||||
continuous integration services, package registries, distributions, app
|
||||
stores, social networks, code coverage services, and code analysis services.
|
||||
Every month it serves over 870 million images and is used by some of the
|
||||
Every month it serves over 770 million images and is used by some of the
|
||||
world's most popular open-source projects, [VS Code][vscode], [Vue.js][vue]
|
||||
and [Bootstrap][bootstrap] to name a few.
|
||||
|
||||
@@ -101,8 +101,8 @@ You can read a [tutorial on how to add a badge][tutorial].
|
||||
|
||||
## Development
|
||||
|
||||
1. Install Node 16 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 16 and 17.
|
||||
1. Install Node 14 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 14 and 16.
|
||||
2. Clone this repository.
|
||||
3. Run `npm ci` to install the dependencies.
|
||||
4. Run `npm start` to start the badge server and the frontend dev server.
|
||||
|
||||
@@ -7,10 +7,10 @@ Please follow this guidance when reporting security issues affecting:
|
||||
- [Shields.io](https://shields.io)
|
||||
- [Raster.shields.io](https://raster.shields.io)
|
||||
- Self-hosted Shields instances
|
||||
- The [squint](https://github.com/badges/squint) raster proxy
|
||||
- The [svg-to-image-proxy](https://www.npmjs.com/package/svg-to-image-proxy) NPM package
|
||||
- The [badge-maker](https://www.npmjs.com/package/badge-maker) NPM package
|
||||
|
||||
The [gh-badges](https://www.npmjs.com/package/gh-badges) and [svg-to-image-proxy](https://www.npmjs.com/package/svg-to-image-proxy) NPM packages are now deprecated and will no longer receive fixes for bugs or security issues.
|
||||
The [gh-badges](https://www.npmjs.com/package/gh-badges) NPM package is now deprecated and will no longer receive fixes for bugs or security issues.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
10
app.json
10
app.json
@@ -35,16 +35,6 @@
|
||||
"WEBLATE_API_KEY": {
|
||||
"description": "Configure the API key to be used for the Weblate service.",
|
||||
"required": false
|
||||
},
|
||||
"METRICS_INFLUX_ENABLED": {
|
||||
"description": "Disable influx metrics",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"REQUIRE_CLOUDFLARE": {
|
||||
"description": "Allow direct traffic",
|
||||
"value": "false",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"formation": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 4.0.0 [WIP]
|
||||
|
||||
- Drop compatibility with Node < 14
|
||||
- Drop compatibility with Node 10
|
||||
|
||||
## 3.3.1
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class XmlElement {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.name
|
||||
* Name of the XML tag
|
||||
* @param {Array.<string|module:badge-maker/lib/xml~XmlElement>} [attrs.content=[]]
|
||||
* @param {Array.<string|module:badge-maker/lib/xml-element~XmlElement>} [attrs.content=[]]
|
||||
* Array of objects to render inside the tag. content may contain a mix of
|
||||
* string and XmlElement objects. If content is `[]` or ommitted the
|
||||
* element will be rendered as a self-closing element.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14",
|
||||
"node": ">= 12",
|
||||
"npm": ">= 6"
|
||||
},
|
||||
"collective": {
|
||||
|
||||
@@ -50,8 +50,6 @@ public:
|
||||
authorizedOrigins: 'NEXUS_ORIGINS'
|
||||
npm:
|
||||
authorizedOrigins: 'NPM_ORIGINS'
|
||||
obs:
|
||||
authorizedOrigins: 'OBS_ORIGINS'
|
||||
sonar:
|
||||
authorizedOrigins: 'SONAR_ORIGINS'
|
||||
teamcity:
|
||||
@@ -64,7 +62,6 @@ public:
|
||||
defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS'
|
||||
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
userAgentBase: 'USER_AGENT_BASE'
|
||||
|
||||
requestTimeoutSeconds: 'REQUEST_TIMEOUT_SECONDS'
|
||||
requestTimeoutMaxAgeSeconds: 'REQUEST_TIMEOUT_MAX_AGE_SECONDS'
|
||||
@@ -87,14 +84,12 @@ private:
|
||||
jenkins_pass: 'JENKINS_PASS'
|
||||
jira_user: 'JIRA_USER'
|
||||
jira_pass: 'JIRA_PASS'
|
||||
librariesio_tokens: 'LIBRARIESIO_TOKENS'
|
||||
nexus_user: 'NEXUS_USER'
|
||||
nexus_pass: 'NEXUS_PASS'
|
||||
npm_token: 'NPM_TOKEN'
|
||||
obs_user: 'OBS_USER'
|
||||
obs_pass: 'OBS_PASS'
|
||||
redis_url: 'REDIS_URL'
|
||||
sentry_dsn: 'SENTRY_DSN'
|
||||
shields_secret: 'SHIELDS_SECRET'
|
||||
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'
|
||||
sl_insight_apiToken: 'SL_INSIGHT_API_TOKEN'
|
||||
sonarqube_token: 'SONARQUBE_TOKEN'
|
||||
|
||||
@@ -22,8 +22,6 @@ public:
|
||||
debug:
|
||||
enabled: false
|
||||
intervalSeconds: 200
|
||||
obs:
|
||||
authorizedOrigins: 'https://api.opensuse.org'
|
||||
weblate:
|
||||
authorizedOrigins: 'https://hosted.weblate.org'
|
||||
trace: false
|
||||
@@ -34,7 +32,6 @@ public:
|
||||
handleInternalErrors: true
|
||||
|
||||
fetchLimit: '10MB'
|
||||
userAgentBase: 'shields (self-hosted)'
|
||||
|
||||
requestTimeoutSeconds: 120
|
||||
requestTimeoutMaxAgeSeconds: 30
|
||||
|
||||
@@ -6,8 +6,6 @@ private:
|
||||
# preferable for self hosting.
|
||||
gh_token: '...'
|
||||
gitlab_token: '...'
|
||||
obs_user: '...'
|
||||
obs_pass: '...'
|
||||
twitch_client_id: '...'
|
||||
twitch_client_secret: '...'
|
||||
weblate_api_key: '...'
|
||||
|
||||
@@ -6,20 +6,13 @@ public:
|
||||
enabled: true
|
||||
url: https://metrics.shields.io/telegraf
|
||||
instanceIdFrom: env-var
|
||||
instanceIdEnvVarName: FLY_ALLOC_ID
|
||||
instanceIdEnvVarName: HEROKU_DYNO_ID
|
||||
envLabel: shields-production
|
||||
|
||||
ssl:
|
||||
isSecure: false
|
||||
isSecure: true
|
||||
|
||||
cors:
|
||||
allowedOrigin: ['http://shields.io', 'https://shields.io']
|
||||
|
||||
services:
|
||||
gitlab:
|
||||
authorizedOrigins: 'https://gitlab.com'
|
||||
|
||||
rasterUrl: 'https://raster.shields.io'
|
||||
userAgentBase: 'Shields.io'
|
||||
requireCloudflare: true
|
||||
requestTimeoutSeconds: 20
|
||||
|
||||
@@ -74,7 +74,7 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
static _isInsecureSslRequest({ options = {} }) {
|
||||
const strictSSL = options?.https?.rejectUnauthorized ?? true
|
||||
const { strictSSL = true } = options
|
||||
return strictSSL !== true
|
||||
}
|
||||
|
||||
@@ -107,10 +107,8 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
get _basicAuth() {
|
||||
const { _user: username, _pass: password } = this
|
||||
return this.isConfigured
|
||||
? { username: username || '', password: password || '' }
|
||||
: undefined
|
||||
const { _user: user, _pass: pass } = this
|
||||
return this.isConfigured ? { user, pass } : undefined
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -133,7 +131,7 @@ class AuthHelper {
|
||||
const { options, ...rest } = requestParams
|
||||
return {
|
||||
options: {
|
||||
...auth,
|
||||
auth,
|
||||
...options,
|
||||
},
|
||||
...rest,
|
||||
@@ -183,13 +181,11 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
static _mergeQueryParams(requestParams, query) {
|
||||
const {
|
||||
options: { searchParams: existingQuery, ...restOptions } = {},
|
||||
...rest
|
||||
} = requestParams
|
||||
const { options: { qs: existingQuery, ...restOptions } = {}, ...rest } =
|
||||
requestParams
|
||||
return {
|
||||
options: {
|
||||
searchParams: {
|
||||
qs: {
|
||||
...existingQuery,
|
||||
...query,
|
||||
},
|
||||
|
||||
@@ -104,14 +104,14 @@ describe('AuthHelper', function () {
|
||||
{ userKey: 'myci_user', passKey: 'myci_pass' },
|
||||
{ myci_user: 'admin', myci_pass: 'abc123' }
|
||||
),
|
||||
]).expect({ username: 'admin', password: 'abc123' })
|
||||
]).expect({ user: 'admin', pass: 'abc123' })
|
||||
given({ userKey: 'myci_user' }, { myci_user: 'admin' }).expect({
|
||||
username: 'admin',
|
||||
password: '',
|
||||
user: 'admin',
|
||||
pass: undefined,
|
||||
})
|
||||
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }).expect({
|
||||
username: '',
|
||||
password: 'abc123',
|
||||
user: undefined,
|
||||
pass: 'abc123',
|
||||
})
|
||||
given({ userKey: 'myci_user', passKey: 'myci_pass' }, {}).expect(
|
||||
undefined
|
||||
@@ -120,8 +120,8 @@ describe('AuthHelper', function () {
|
||||
{ passKey: 'myci_pass', defaultToEmptyStringForUser: true },
|
||||
{ myci_pass: 'abc123' }
|
||||
).expect({
|
||||
username: '',
|
||||
password: 'abc123',
|
||||
user: '',
|
||||
pass: 'abc123',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -131,18 +131,15 @@ describe('AuthHelper', function () {
|
||||
forCases([
|
||||
given({ url: 'http://example.test' }),
|
||||
given({ url: 'http://example.test', options: {} }),
|
||||
given({ url: 'http://example.test', options: { strictSSL: true } }),
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: true } },
|
||||
}),
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: undefined } },
|
||||
options: { strictSSL: undefined },
|
||||
}),
|
||||
]).expect(false)
|
||||
given({
|
||||
url: 'http://example.test',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(true)
|
||||
})
|
||||
})
|
||||
@@ -166,9 +163,7 @@ describe('AuthHelper', function () {
|
||||
})
|
||||
it('throws for insecure requests', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
})
|
||||
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
@@ -190,9 +185,7 @@ describe('AuthHelper', function () {
|
||||
})
|
||||
it('does not throw for insecure requests', function () {
|
||||
expect(() =>
|
||||
authHelper.enforceStrictSsl({
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
})
|
||||
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
|
||||
).not.to.throw()
|
||||
})
|
||||
})
|
||||
@@ -227,7 +220,7 @@ describe('AuthHelper', function () {
|
||||
test(shouldAuthenticateRequest, () => {
|
||||
given({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(false)
|
||||
})
|
||||
})
|
||||
@@ -265,7 +258,7 @@ describe('AuthHelper', function () {
|
||||
test(shouldAuthenticateRequest, () => {
|
||||
given({
|
||||
url: 'https://myci.test',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
}).expect(true)
|
||||
})
|
||||
})
|
||||
@@ -330,8 +323,7 @@ describe('AuthHelper', function () {
|
||||
}).expect({
|
||||
url: 'https://myci.test/api',
|
||||
options: {
|
||||
username: 'admin',
|
||||
password: 'abc123',
|
||||
auth: { user: 'admin', pass: 'abc123' },
|
||||
},
|
||||
})
|
||||
given({
|
||||
@@ -343,8 +335,7 @@ describe('AuthHelper', function () {
|
||||
url: 'https://myci.test/api',
|
||||
options: {
|
||||
headers: { Accept: 'application/json' },
|
||||
username: 'admin',
|
||||
password: 'abc123',
|
||||
auth: { user: 'admin', pass: 'abc123' },
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -375,7 +366,7 @@ describe('AuthHelper', function () {
|
||||
expect(() =>
|
||||
withBasicAuth({
|
||||
url: 'https://myci.test/api',
|
||||
options: { https: { rejectUnauthorized: false } },
|
||||
options: { strictSSL: false },
|
||||
})
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
|
||||
@@ -38,8 +38,8 @@ class BaseGraphqlService extends BaseService {
|
||||
* representing the query clause of GraphQL POST body
|
||||
* e.g. gql`{ query { ... } }`
|
||||
* @param {object} attrs.variables Variables clause of GraphQL POST body
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.httpErrorMessages={}] Key-value map of HTTP status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
@@ -53,7 +53,7 @@ class BaseGraphqlService extends BaseService {
|
||||
* The default is to return the first entry of the `errors` array as
|
||||
* an InvalidResponse.
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestGraphql({
|
||||
schema,
|
||||
|
||||
@@ -29,9 +29,9 @@ class DummyGraphqlService extends BaseGraphqlService {
|
||||
|
||||
describe('BaseGraphqlService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: '{"some": "json"}',
|
||||
res: { statusCode: 200 },
|
||||
@@ -39,13 +39,13 @@ describe('BaseGraphqlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/graphql',
|
||||
{
|
||||
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
|
||||
@@ -55,7 +55,7 @@ describe('BaseGraphqlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithOptions extends DummyGraphqlService {
|
||||
async handle() {
|
||||
const { value } = await this._requestGraphql({
|
||||
@@ -66,24 +66,24 @@ describe('BaseGraphqlService', function () {
|
||||
requiredString
|
||||
}
|
||||
`,
|
||||
options: { searchParams: { queryParam: 123 } },
|
||||
options: { qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: value }
|
||||
}
|
||||
}
|
||||
|
||||
await WithOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/graphql',
|
||||
{
|
||||
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
|
||||
headers: { Accept: 'application/json' },
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -91,13 +91,13 @@ describe('BaseGraphqlService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"requiredString": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -106,13 +106,13 @@ describe('BaseGraphqlService', function () {
|
||||
})
|
||||
|
||||
it('handles json responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"unexpectedKey": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -123,13 +123,13 @@ describe('BaseGraphqlService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not json',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -142,13 +142,13 @@ describe('BaseGraphqlService', function () {
|
||||
|
||||
describe('Error handling', function () {
|
||||
it('handles generic error', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{ "errors": [ { "message": "oh noes!!" } ] }',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyGraphqlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -181,13 +181,13 @@ describe('BaseGraphqlService', function () {
|
||||
}
|
||||
}
|
||||
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{ "errors": [ { "message": "oh noes!!" } ] }',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await WithErrorHandler.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -28,14 +28,14 @@ class BaseJsonService extends BaseService {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
|
||||
const mergedOptions = {
|
||||
|
||||
@@ -22,9 +22,9 @@ class DummyJsonService extends BaseJsonService {
|
||||
|
||||
describe('BaseJsonService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: '{"some": "json"}',
|
||||
res: { statusCode: 200 },
|
||||
@@ -32,13 +32,13 @@ describe('BaseJsonService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.json',
|
||||
{
|
||||
headers: { Accept: 'application/json' },
|
||||
@@ -46,29 +46,29 @@ describe('BaseJsonService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithOptions extends DummyJsonService {
|
||||
async handle() {
|
||||
const { value } = await this._requestJson({
|
||||
schema: dummySchema,
|
||||
url: 'http://example.com/foo.json',
|
||||
options: { method: 'POST', searchParams: { queryParam: 123 } },
|
||||
options: { method: 'POST', qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: value }
|
||||
}
|
||||
}
|
||||
|
||||
await WithOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.json',
|
||||
{
|
||||
headers: { Accept: 'application/json' },
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -76,13 +76,13 @@ describe('BaseJsonService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"requiredString": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -91,13 +91,13 @@ describe('BaseJsonService', function () {
|
||||
})
|
||||
|
||||
it('handles json responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '{"unexpectedKey": "some-string"}',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -108,13 +108,13 @@ describe('BaseJsonService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable json responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not json',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyJsonService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -51,14 +51,14 @@ class BaseSvgScrapingService extends BaseService {
|
||||
* @param {RegExp} attrs.valueMatcher
|
||||
* RegExp to match the value we want to parse from the SVG
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestSvg({
|
||||
schema,
|
||||
|
||||
@@ -34,9 +34,9 @@ describe('BaseSvgScrapingService', function () {
|
||||
})
|
||||
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: exampleSvg,
|
||||
res: { statusCode: 200 },
|
||||
@@ -44,13 +44,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher with the expected header', async function () {
|
||||
it('invokes _sendAndCacheRequest with the expected header', async function () {
|
||||
await DummySvgScrapingService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.svg',
|
||||
{
|
||||
headers: { Accept: 'image/svg+xml' },
|
||||
@@ -58,7 +58,7 @@ describe('BaseSvgScrapingService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithCustomOptions extends DummySvgScrapingService {
|
||||
async handle() {
|
||||
const { message } = await this._requestSvg({
|
||||
@@ -66,7 +66,7 @@ describe('BaseSvgScrapingService', function () {
|
||||
url: 'http://example.com/foo.svg',
|
||||
options: {
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
},
|
||||
})
|
||||
return { message }
|
||||
@@ -74,16 +74,16 @@ describe('BaseSvgScrapingService', function () {
|
||||
}
|
||||
|
||||
await WithCustomOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.svg',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { Accept: 'image/svg+xml' },
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -91,13 +91,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid svg responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: exampleSvg,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummySvgScrapingService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -117,13 +117,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
})
|
||||
}
|
||||
}
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '<desc>a different message</desc>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await WithValueMatcher.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -132,13 +132,13 @@ describe('BaseSvgScrapingService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable svg responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not svg yo',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummySvgScrapingService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// See available emoji at http://emoji.muan.co/
|
||||
import emojic from 'emojic'
|
||||
import { XMLParser, XMLValidator } from 'fast-xml-parser'
|
||||
import fastXmlParser from 'fast-xml-parser'
|
||||
import BaseService from './base.js'
|
||||
import trace from './trace.js'
|
||||
import { InvalidResponse } from './errors.js'
|
||||
@@ -22,8 +22,8 @@ class BaseXmlService extends BaseService {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
@@ -31,7 +31,7 @@ class BaseXmlService extends BaseService {
|
||||
* @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
|
||||
* [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
* @see https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json
|
||||
*/
|
||||
async _requestXml({
|
||||
@@ -51,15 +51,14 @@ class BaseXmlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
errorMessages,
|
||||
})
|
||||
const validateResult = XMLValidator.validate(buffer)
|
||||
const validateResult = fastXmlParser.validate(buffer)
|
||||
if (validateResult !== true) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'unparseable xml response',
|
||||
underlyingError: validateResult.err,
|
||||
})
|
||||
}
|
||||
const parser = new XMLParser(parserOptions)
|
||||
const xml = parser.parse(buffer)
|
||||
const xml = fastXmlParser.parse(buffer, parserOptions)
|
||||
logTrace(emojic.dart, 'Response XML (before validation)', xml, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
@@ -22,9 +22,9 @@ class DummyXmlService extends BaseXmlService {
|
||||
|
||||
describe('BaseXmlService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: '<requiredString>some-string</requiredString>',
|
||||
res: { statusCode: 200 },
|
||||
@@ -32,13 +32,13 @@ describe('BaseXmlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.xml',
|
||||
{
|
||||
headers: { Accept: 'application/xml, text/xml' },
|
||||
@@ -46,7 +46,7 @@ describe('BaseXmlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithCustomOptions extends BaseXmlService {
|
||||
static route = {}
|
||||
|
||||
@@ -54,23 +54,23 @@ describe('BaseXmlService', function () {
|
||||
const { requiredString } = await this._requestXml({
|
||||
schema: dummySchema,
|
||||
url: 'http://example.com/foo.xml',
|
||||
options: { method: 'POST', searchParams: { queryParam: 123 } },
|
||||
options: { method: 'POST', qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: requiredString }
|
||||
}
|
||||
}
|
||||
|
||||
await WithCustomOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.xml',
|
||||
{
|
||||
headers: { Accept: 'application/xml, text/xml' },
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -78,13 +78,13 @@ describe('BaseXmlService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid xml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '<requiredString>some-string</requiredString>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -104,14 +104,14 @@ describe('BaseXmlService', function () {
|
||||
return { message: requiredString }
|
||||
}
|
||||
}
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer:
|
||||
'<requiredString>some-string with trailing whitespace </requiredString>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlServiceWithParserOption.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -120,13 +120,13 @@ describe('BaseXmlService', function () {
|
||||
})
|
||||
|
||||
it('handles xml responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '<unexpectedAttribute>some-string</unexpectedAttribute>',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -137,13 +137,13 @@ describe('BaseXmlService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable xml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'not xml',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyXmlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -21,15 +21,15 @@ class BaseYamlService extends BaseService {
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {Joi} attrs.schema Joi schema to validate the response against
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {object} [attrs.options={}] Options to pass to got. See
|
||||
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
|
||||
* @param {object} [attrs.options={}] Options to pass to request. See
|
||||
* [documentation](https://github.com/request/request#requestoptions-callback)
|
||||
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
|
||||
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
||||
* This can be used to extend or override the
|
||||
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
|
||||
* @param {object} [attrs.encoding='utf8'] Character encoding
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
* @see https://github.com/request/request#requestoptions-callback
|
||||
*/
|
||||
async _requestYaml({
|
||||
schema,
|
||||
|
||||
@@ -38,9 +38,9 @@ foo: baz
|
||||
|
||||
describe('BaseYamlService', function () {
|
||||
describe('Making requests', function () {
|
||||
let requestFetcher
|
||||
let sendAndCacheRequest
|
||||
beforeEach(function () {
|
||||
requestFetcher = sinon.stub().returns(
|
||||
sendAndCacheRequest = sinon.stub().returns(
|
||||
Promise.resolve({
|
||||
buffer: expectedYaml,
|
||||
res: { statusCode: 200 },
|
||||
@@ -48,13 +48,13 @@ describe('BaseYamlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('invokes _requestFetcher', async function () {
|
||||
it('invokes _sendAndCacheRequest', async function () {
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.yaml',
|
||||
{
|
||||
headers: {
|
||||
@@ -65,24 +65,24 @@ describe('BaseYamlService', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards options to _requestFetcher', async function () {
|
||||
it('forwards options to _sendAndCacheRequest', async function () {
|
||||
class WithOptions extends DummyYamlService {
|
||||
async handle() {
|
||||
const { requiredString } = await this._requestYaml({
|
||||
schema: dummySchema,
|
||||
url: 'http://example.com/foo.yaml',
|
||||
options: { method: 'POST', searchParams: { queryParam: 123 } },
|
||||
options: { method: 'POST', qs: { queryParam: 123 } },
|
||||
})
|
||||
return { message: requiredString }
|
||||
}
|
||||
}
|
||||
|
||||
await WithOptions.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
|
||||
expect(requestFetcher).to.have.been.calledOnceWith(
|
||||
expect(sendAndCacheRequest).to.have.been.calledOnceWith(
|
||||
'http://example.com/foo.yaml',
|
||||
{
|
||||
headers: {
|
||||
@@ -90,7 +90,7 @@ describe('BaseYamlService', function () {
|
||||
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
|
||||
},
|
||||
method: 'POST',
|
||||
searchParams: { queryParam: 123 },
|
||||
qs: { queryParam: 123 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -98,13 +98,13 @@ describe('BaseYamlService', function () {
|
||||
|
||||
describe('Making badges', function () {
|
||||
it('handles valid yaml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: expectedYaml,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -113,13 +113,13 @@ describe('BaseYamlService', function () {
|
||||
})
|
||||
|
||||
it('handles yaml responses which do not match the schema', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: unexpectedYaml,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
@@ -130,13 +130,13 @@ describe('BaseYamlService', function () {
|
||||
})
|
||||
|
||||
it('handles unparseable yaml responses', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: invalidYaml,
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
expect(
|
||||
await DummyYamlService.invoke(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
{ handleInternalErrors: false }
|
||||
)
|
||||
).to.deep.equal({
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
Deprecated,
|
||||
} from './errors.js'
|
||||
import { validateExample, transformExample } from './examples.js'
|
||||
import { fetch } from './got.js'
|
||||
import { fetchFactory } from './got.js'
|
||||
import {
|
||||
makeFullUrl,
|
||||
assertValidRoute,
|
||||
@@ -108,14 +108,11 @@ class BaseService {
|
||||
*
|
||||
* See also the config schema in `./server.js` and `doc/server-secrets.md`.
|
||||
*
|
||||
* To use the configured auth in the handler or fetch method, wrap the
|
||||
* _request() input params in a call to one of:
|
||||
* - this.authHelper.withBasicAuth()
|
||||
* - this.authHelper.withBearerAuthHeader()
|
||||
* - this.authHelper.withQueryStringAuth()
|
||||
*
|
||||
* For example:
|
||||
* this._request(this.authHelper.withBasicAuth({ url, schema, options }))
|
||||
* To use the configured auth in the handler or fetch method, pass the
|
||||
* credentials to the request. For example:
|
||||
* - `{ options: { auth: this.authHelper.basicAuth } }`
|
||||
* - `{ options: { headers: this.authHelper.bearerAuthHeader } }`
|
||||
* - `{ options: { qs: { token: this.authHelper._pass } } }`
|
||||
*
|
||||
* @abstract
|
||||
* @type {module:core/base-service/base~Auth}
|
||||
@@ -147,7 +144,6 @@ class BaseService {
|
||||
version: 300,
|
||||
debug: 60,
|
||||
downloads: 900,
|
||||
rating: 900,
|
||||
social: 900,
|
||||
}
|
||||
return cacheLengths[this.category]
|
||||
@@ -208,10 +204,10 @@ class BaseService {
|
||||
}
|
||||
|
||||
constructor(
|
||||
{ requestFetcher, authHelper, metricHelper },
|
||||
{ sendAndCacheRequest, authHelper, metricHelper },
|
||||
{ handleInternalErrors }
|
||||
) {
|
||||
this._requestFetcher = requestFetcher
|
||||
this._requestFetcher = sendAndCacheRequest
|
||||
this.authHelper = authHelper
|
||||
this._handleInternalErrors = handleInternalErrors
|
||||
this._metricHelper = metricHelper
|
||||
@@ -221,10 +217,10 @@ class BaseService {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
let logUrl = url
|
||||
const logOptions = Object.assign({}, options)
|
||||
if ('searchParams' in options) {
|
||||
const params = new URLSearchParams(options.searchParams)
|
||||
if ('qs' in options) {
|
||||
const params = new URLSearchParams(options.qs)
|
||||
logUrl = `${url}?${params.toString()}`
|
||||
delete logOptions.searchParams
|
||||
delete logOptions.qs
|
||||
}
|
||||
logTrace(
|
||||
emojic.bowAndArrow,
|
||||
@@ -279,7 +275,7 @@ class BaseService {
|
||||
/**
|
||||
* Asynchronous function to handle requests for this service. Take the route
|
||||
* parameters (as defined in the `route` property), perform a request using
|
||||
* `this._requestFetcher`, and return the badge data.
|
||||
* `this._sendAndCacheRequest`, and return the badge data.
|
||||
*
|
||||
* @abstract
|
||||
* @param {object} namedParams Params parsed from route pattern
|
||||
@@ -424,16 +420,10 @@ class BaseService {
|
||||
}
|
||||
|
||||
static register(
|
||||
{
|
||||
camp,
|
||||
handleRequest,
|
||||
githubApiProvider,
|
||||
librariesIoApiProvider,
|
||||
metricInstance,
|
||||
},
|
||||
{ camp, handleRequest, githubApiProvider, metricInstance },
|
||||
serviceConfig
|
||||
) {
|
||||
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
|
||||
const { cacheHeaders: cacheHeaderConfig, fetchLimitBytes } = serviceConfig
|
||||
const { regex, captureNames } = prepareRoute(this.route)
|
||||
const queryParams = getQueryParamNames(this.route)
|
||||
|
||||
@@ -442,19 +432,21 @@ class BaseService {
|
||||
ServiceClass: this,
|
||||
})
|
||||
|
||||
const fetcher = fetchFactory(fetchLimitBytes)
|
||||
|
||||
camp.route(
|
||||
regex,
|
||||
handleRequest(cacheHeaderConfig, {
|
||||
queryParams,
|
||||
handler: async (queryParams, match, sendBadge) => {
|
||||
handler: async (queryParams, match, sendBadge, request) => {
|
||||
const metricHandle = metricHelper.startRequest()
|
||||
|
||||
const namedParams = namedParamsForMatch(captureNames, match, this)
|
||||
const serviceData = await this.invoke(
|
||||
{
|
||||
requestFetcher: fetch,
|
||||
sendAndCacheRequest: fetcher,
|
||||
sendAndCacheRequestWithCallbacks: request,
|
||||
githubApiProvider,
|
||||
librariesIoApiProvider,
|
||||
metricHelper,
|
||||
},
|
||||
serviceConfig,
|
||||
@@ -475,6 +467,7 @@ class BaseService {
|
||||
metricHandle.noteResponseSent()
|
||||
},
|
||||
cacheLength: this._cacheLength,
|
||||
fetchLimitBytes,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,11 +124,15 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
describe('Logging', function () {
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.stub(trace, 'logTrace')
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
})
|
||||
beforeEach(function () {
|
||||
sandbox.stub(trace, 'logTrace')
|
||||
})
|
||||
it('Invokes the logger as expected', async function () {
|
||||
await DummyService.invoke(
|
||||
@@ -422,20 +426,24 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
describe('request', function () {
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.stub(trace, 'logTrace')
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
})
|
||||
beforeEach(function () {
|
||||
sandbox.stub(trace, 'logTrace')
|
||||
})
|
||||
|
||||
it('logs appropriate information', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -458,12 +466,12 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
it('handles errors', async function () {
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: '',
|
||||
res: { statusCode: 404 },
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher },
|
||||
{ sendAndCacheRequest },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -490,13 +498,13 @@ describe('BaseService', function () {
|
||||
metricInstance: new PrometheusMetrics({ register }),
|
||||
ServiceClass: DummyServiceWithServiceResponseSizeMetricEnabled,
|
||||
})
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'x'.repeat(65536 + 1),
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance =
|
||||
new DummyServiceWithServiceResponseSizeMetricEnabled(
|
||||
{ requestFetcher, metricHelper },
|
||||
{ sendAndCacheRequest, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -516,12 +524,12 @@ describe('BaseService', function () {
|
||||
metricInstance: new PrometheusMetrics({ register }),
|
||||
ServiceClass: DummyService,
|
||||
})
|
||||
const requestFetcher = async () => ({
|
||||
const sendAndCacheRequest = async () => ({
|
||||
buffer: 'x',
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance = new DummyService(
|
||||
{ requestFetcher, metricHelper },
|
||||
{ sendAndCacheRequest, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
|
||||
@@ -99,11 +99,14 @@ describe('Cache header functions', function () {
|
||||
})
|
||||
|
||||
describe('setHeadersForCacheLength', function () {
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.useFakeTimers()
|
||||
sandbox = sinon.createSandbox()
|
||||
sandbox.useFakeTimers()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
sandbox = undefined
|
||||
})
|
||||
|
||||
it('should set the correct Date header', function () {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import bytes from 'bytes'
|
||||
import configModule from 'config'
|
||||
import Joi from 'joi'
|
||||
import { fileSize } from '../../services/validators.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
fetchLimit: fileSize,
|
||||
userAgentBase: Joi.string().required(),
|
||||
}).required()
|
||||
const config = configModule.util.toObject()
|
||||
const publicConfig = Joi.attempt(config.public, schema, { allowUnknown: true })
|
||||
|
||||
const fetchLimitBytes = bytes(publicConfig.fetchLimit)
|
||||
|
||||
function getUserAgent(userAgentBase = publicConfig.userAgentBase) {
|
||||
let version = 'dev'
|
||||
if (process.env.DOCKER_SHIELDS_VERSION) {
|
||||
version = process.env.DOCKER_SHIELDS_VERSION
|
||||
}
|
||||
if (process.env.HEROKU_SLUG_COMMIT) {
|
||||
version = process.env.HEROKU_SLUG_COMMIT.substring(0, 7)
|
||||
}
|
||||
return `${userAgentBase}/${version}`
|
||||
}
|
||||
|
||||
export { fetchLimitBytes, getUserAgent }
|
||||
@@ -1,27 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { getUserAgent } from './got-config.js'
|
||||
|
||||
describe('getUserAgent function', function () {
|
||||
afterEach(function () {
|
||||
delete process.env.HEROKU_SLUG_COMMIT
|
||||
delete process.env.DOCKER_SHIELDS_VERSION
|
||||
})
|
||||
|
||||
it('uses the default userAgentBase', function () {
|
||||
expect(getUserAgent()).to.equal('shields (self-hosted)/dev')
|
||||
})
|
||||
|
||||
it('applies custom userAgentBase', function () {
|
||||
expect(getUserAgent('custom')).to.equal('custom/dev')
|
||||
})
|
||||
|
||||
it('uses short commit SHA from HEROKU_SLUG_COMMIT if available', function () {
|
||||
process.env.HEROKU_SLUG_COMMIT = '92090bd44742a5fac03bcb117002088fc7485834'
|
||||
expect(getUserAgent('custom')).to.equal('custom/92090bd')
|
||||
})
|
||||
|
||||
it('uses short commit SHA from DOCKER_SHIELDS_VERSION if available', function () {
|
||||
process.env.DOCKER_SHIELDS_VERSION = 'server-2021-11-22'
|
||||
expect(getUserAgent('custom')).to.equal('custom/server-2021-11-22')
|
||||
})
|
||||
})
|
||||
@@ -1,23 +1,61 @@
|
||||
import got, { CancelError } from 'got'
|
||||
import got from 'got'
|
||||
import { Inaccessible, InvalidResponse } from './errors.js'
|
||||
import {
|
||||
fetchLimitBytes as fetchLimitBytesDefault,
|
||||
getUserAgent,
|
||||
} from './got-config.js'
|
||||
|
||||
const userAgent = getUserAgent()
|
||||
const userAgent = 'Shields.io/2003a'
|
||||
|
||||
function requestOptions2GotOptions(options) {
|
||||
const requestOptions = Object.assign({}, options)
|
||||
const gotOptions = {}
|
||||
const interchangableOptions = ['body', 'form', 'headers', 'method', 'url']
|
||||
|
||||
interchangableOptions.forEach(function (opt) {
|
||||
if (opt in requestOptions) {
|
||||
gotOptions[opt] = requestOptions[opt]
|
||||
delete requestOptions[opt]
|
||||
}
|
||||
})
|
||||
|
||||
if ('qs' in requestOptions) {
|
||||
gotOptions.searchParams = requestOptions.qs
|
||||
delete requestOptions.qs
|
||||
}
|
||||
|
||||
if ('gzip' in requestOptions) {
|
||||
gotOptions.decompress = requestOptions.gzip
|
||||
delete requestOptions.gzip
|
||||
}
|
||||
|
||||
if ('strictSSL' in requestOptions) {
|
||||
gotOptions.https = {
|
||||
rejectUnauthorized: requestOptions.strictSSL,
|
||||
}
|
||||
delete requestOptions.strictSSL
|
||||
}
|
||||
|
||||
if ('auth' in requestOptions) {
|
||||
gotOptions.username = requestOptions.auth.user
|
||||
gotOptions.password = requestOptions.auth.pass
|
||||
delete requestOptions.auth
|
||||
}
|
||||
|
||||
if (Object.keys(requestOptions).length > 0) {
|
||||
throw new Error(`Found unrecognised options ${Object.keys(requestOptions)}`)
|
||||
}
|
||||
|
||||
return gotOptions
|
||||
}
|
||||
|
||||
async function sendRequest(gotWrapper, url, options) {
|
||||
const gotOptions = Object.assign({}, options)
|
||||
const gotOptions = requestOptions2GotOptions(options)
|
||||
gotOptions.throwHttpErrors = false
|
||||
gotOptions.retry = { limit: 0 }
|
||||
gotOptions.retry = 0
|
||||
gotOptions.headers = gotOptions.headers || {}
|
||||
gotOptions.headers['User-Agent'] = userAgent
|
||||
try {
|
||||
const resp = await gotWrapper(url, gotOptions)
|
||||
return { res: resp, buffer: resp.body }
|
||||
} catch (err) {
|
||||
if (err instanceof CancelError) {
|
||||
if (err instanceof got.CancelError) {
|
||||
throw new InvalidResponse({
|
||||
underlyingError: new Error('Maximum response size exceeded'),
|
||||
})
|
||||
@@ -26,7 +64,7 @@ async function sendRequest(gotWrapper, url, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function _fetchFactory(fetchLimitBytes = fetchLimitBytesDefault) {
|
||||
function fetchFactory(fetchLimitBytes) {
|
||||
const gotWithLimit = got.extend({
|
||||
handlers: [
|
||||
(options, next) => {
|
||||
@@ -55,6 +93,4 @@ function _fetchFactory(fetchLimitBytes = fetchLimitBytesDefault) {
|
||||
return sendRequest.bind(sendRequest, gotWithLimit)
|
||||
}
|
||||
|
||||
const fetch = _fetchFactory()
|
||||
|
||||
export { fetch, _fetchFactory }
|
||||
export { requestOptions2GotOptions, fetchFactory }
|
||||
|
||||
@@ -1,15 +1,50 @@
|
||||
import { expect } from 'chai'
|
||||
import nock from 'nock'
|
||||
import { _fetchFactory } from './got.js'
|
||||
import { requestOptions2GotOptions, fetchFactory } from './got.js'
|
||||
import { Inaccessible, InvalidResponse } from './errors.js'
|
||||
|
||||
describe('requestOptions2GotOptions function', function () {
|
||||
it('translates valid options', function () {
|
||||
expect(
|
||||
requestOptions2GotOptions({
|
||||
body: 'body',
|
||||
form: 'form',
|
||||
headers: 'headers',
|
||||
method: 'method',
|
||||
url: 'url',
|
||||
qs: 'qs',
|
||||
gzip: 'gzip',
|
||||
strictSSL: 'strictSSL',
|
||||
auth: { user: 'user', pass: 'pass' },
|
||||
})
|
||||
).to.deep.equal({
|
||||
body: 'body',
|
||||
form: 'form',
|
||||
headers: 'headers',
|
||||
method: 'method',
|
||||
url: 'url',
|
||||
searchParams: 'qs',
|
||||
decompress: 'gzip',
|
||||
https: { rejectUnauthorized: 'strictSSL' },
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
})
|
||||
})
|
||||
|
||||
it('throws if unrecognised options are found', function () {
|
||||
expect(() =>
|
||||
requestOptions2GotOptions({ body: 'body', foobar: 'foobar' })
|
||||
).to.throw(Error, 'Found unrecognised options foobar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('got wrapper', function () {
|
||||
it('should not throw an error if the response <= fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(100))
|
||||
const sendRequest = _fetchFactory(100)
|
||||
const sendRequest = fetchFactory(100)
|
||||
const { res } = await sendRequest('https://www.google.com/foo/bar')
|
||||
expect(res.statusCode).to.equal(200)
|
||||
})
|
||||
@@ -19,7 +54,7 @@ describe('got wrapper', function () {
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(101))
|
||||
const sendRequest = _fetchFactory(100)
|
||||
const sendRequest = fetchFactory(100)
|
||||
return expect(
|
||||
sendRequest('https://www.google.com/foo/bar')
|
||||
).to.be.rejectedWith(InvalidResponse, 'Maximum response size exceeded')
|
||||
@@ -27,7 +62,7 @@ describe('got wrapper', function () {
|
||||
|
||||
it('should throw an Inaccessible error if the request throws a (non-HTTP) error', async function () {
|
||||
nock('https://www.google.com').get('/foo/bar').replyWithError('oh no')
|
||||
const sendRequest = _fetchFactory(1024)
|
||||
const sendRequest = fetchFactory(1024)
|
||||
return expect(
|
||||
sendRequest('https://www.google.com/foo/bar')
|
||||
).to.be.rejectedWith(Inaccessible, 'oh no')
|
||||
@@ -36,7 +71,7 @@ describe('got wrapper', function () {
|
||||
it('should throw an Inaccessible error if the host can not be accessed', async function () {
|
||||
this.timeout(5000)
|
||||
nock.disableNetConnect()
|
||||
const sendRequest = _fetchFactory(1024)
|
||||
const sendRequest = fetchFactory(1024)
|
||||
return expect(
|
||||
sendRequest('https://www.google.com/foo/bar')
|
||||
).to.be.rejectedWith(
|
||||
@@ -49,14 +84,14 @@ describe('got wrapper', function () {
|
||||
nock('https://www.google.com', {
|
||||
reqheaders: {
|
||||
'user-agent': function (agent) {
|
||||
return agent.startsWith('shields (self-hosted)')
|
||||
return agent.startsWith('Shields.io')
|
||||
},
|
||||
},
|
||||
})
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200)
|
||||
const sendRequest = _fetchFactory(1024)
|
||||
const sendRequest = fetchFactory(1024)
|
||||
await sendRequest('https://www.google.com/foo/bar')
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Inaccessible,
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
ImproperlyConfigured,
|
||||
} from './errors.js'
|
||||
|
||||
export {
|
||||
@@ -30,6 +29,5 @@ export {
|
||||
InvalidResponse,
|
||||
Inaccessible,
|
||||
InvalidParameter,
|
||||
ImproperlyConfigured,
|
||||
Deprecated,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import request from 'request'
|
||||
import makeBadge from '../../badge-maker/lib/make-badge.js'
|
||||
import { setCacheHeaders } from './cache-headers.js'
|
||||
import { Inaccessible, InvalidResponse, ShieldsRuntimeError } from './errors.js'
|
||||
import { makeSend } from './legacy-result-sender.js'
|
||||
import coalesceBadge from './coalesce-badge.js'
|
||||
|
||||
const userAgent = 'Shields.io/2003a'
|
||||
|
||||
// These query parameters are available to any badge. They are handled by
|
||||
// `coalesceBadge`.
|
||||
const globalQueryParams = new Set([
|
||||
@@ -28,12 +32,32 @@ function flattenQueryParams(queryParams) {
|
||||
return Array.from(union).sort()
|
||||
}
|
||||
|
||||
function promisify(cachingRequest) {
|
||||
return (uri, options) =>
|
||||
new Promise((resolve, reject) => {
|
||||
cachingRequest(uri, options, (err, res, buffer) => {
|
||||
if (err) {
|
||||
if (err instanceof ShieldsRuntimeError) {
|
||||
reject(err)
|
||||
} else {
|
||||
// Wrap the error in an Inaccessible so it can be identified
|
||||
// by the BaseService handler.
|
||||
reject(new Inaccessible({ underlyingError: err }))
|
||||
}
|
||||
} else {
|
||||
resolve({ res, buffer })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// handlerOptions can contain:
|
||||
// - handler: The service's request handler function
|
||||
// - queryParams: An array of the field names of any custom query parameters
|
||||
// the service uses
|
||||
// - cacheLength: An optional badge or category-specific cache length
|
||||
// (in number of seconds) to be used in preference to the default
|
||||
// - fetchLimitBytes: A limit on the response size we're willing to parse
|
||||
//
|
||||
// For safety, the service must declare the query parameters it wants to use.
|
||||
// Only the declared parameters (and the global parameters) are provided to
|
||||
@@ -53,7 +77,8 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
}
|
||||
|
||||
const allowedKeys = flattenQueryParams(handlerOptions.queryParams)
|
||||
const { cacheLength: serviceDefaultCacheLengthSeconds } = handlerOptions
|
||||
const { cacheLength: serviceDefaultCacheLengthSeconds, fetchLimitBytes } =
|
||||
handlerOptions
|
||||
|
||||
return (queryParams, match, end, ask) => {
|
||||
/*
|
||||
@@ -114,6 +139,40 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
makeSend(extension, ask.res, end)(svg)
|
||||
}, 25000)
|
||||
|
||||
function cachingRequest(uri, options, callback) {
|
||||
if (typeof options === 'function' && !callback) {
|
||||
callback = options
|
||||
}
|
||||
if (options && typeof options === 'object') {
|
||||
options.uri = uri
|
||||
} else if (typeof uri === 'string') {
|
||||
options = { uri }
|
||||
} else {
|
||||
options = uri
|
||||
}
|
||||
options.headers = options.headers || {}
|
||||
options.headers['User-Agent'] = userAgent
|
||||
|
||||
let bufferLength = 0
|
||||
const r = request(options, callback)
|
||||
r.on('data', chunk => {
|
||||
bufferLength += chunk.length
|
||||
if (bufferLength > fetchLimitBytes) {
|
||||
r.abort()
|
||||
r.emit(
|
||||
'error',
|
||||
new InvalidResponse({
|
||||
prettyMessage: 'Maximum response size exceeded',
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapper around `cachingRequest` that returns a promise rather than needing
|
||||
// to pass a callback.
|
||||
cachingRequest.asPromise = promisify(cachingRequest)
|
||||
|
||||
const result = handlerOptions.handler(
|
||||
filteredQueryParams,
|
||||
match,
|
||||
@@ -128,7 +187,8 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
const svg = makeBadge(badgeData)
|
||||
setCacheHeadersOnResponse(ask.res, badgeData.cacheLengthSeconds)
|
||||
makeSend(format, ask.res, end)(svg)
|
||||
}
|
||||
},
|
||||
cachingRequest
|
||||
)
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
if (result && result.catch) {
|
||||
@@ -140,4 +200,4 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
export { handleRequest }
|
||||
export { handleRequest, promisify, userAgent }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import nock from 'nock'
|
||||
import portfinder from 'portfinder'
|
||||
import Camp from '@shields_io/camp'
|
||||
import got from '../got-test-client.js'
|
||||
@@ -41,6 +42,28 @@ function createFakeHandlerWithCacheLength(cacheLengthSeconds) {
|
||||
}
|
||||
}
|
||||
|
||||
function fakeHandlerWithNetworkIo(queryParams, match, sendBadge, request) {
|
||||
const [, someValue, format] = match
|
||||
request('https://www.google.com/foo/bar', (err, res, buffer) => {
|
||||
let message
|
||||
if (err) {
|
||||
message = err.prettyMessage
|
||||
} else {
|
||||
message = someValue
|
||||
}
|
||||
const badgeData = coalesceBadge(
|
||||
queryParams,
|
||||
{
|
||||
label: 'testing',
|
||||
message,
|
||||
format,
|
||||
},
|
||||
{}
|
||||
)
|
||||
sendBadge(format, badgeData)
|
||||
})
|
||||
}
|
||||
|
||||
describe('The request handler', function () {
|
||||
let port, baseUrl
|
||||
beforeEach(async function () {
|
||||
@@ -110,6 +133,60 @@ describe('The request handler', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('the response size limit', function () {
|
||||
beforeEach(function () {
|
||||
camp.route(
|
||||
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
|
||||
handleRequest(standardCacheHeaders, {
|
||||
handler: fakeHandlerWithNetworkIo,
|
||||
fetchLimitBytes: 100,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should not throw an error if the response <= fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(100))
|
||||
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
|
||||
responseType: 'json',
|
||||
})
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
name: 'testing',
|
||||
value: '123',
|
||||
label: 'testing',
|
||||
message: '123',
|
||||
color: 'lightgrey',
|
||||
link: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw an error if the response is > fetchLimitBytes', async function () {
|
||||
nock('https://www.google.com')
|
||||
.get('/foo/bar')
|
||||
.once()
|
||||
.reply(200, 'x'.repeat(101))
|
||||
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
|
||||
responseType: 'json',
|
||||
})
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(body).to.deep.equal({
|
||||
name: 'testing',
|
||||
value: 'Maximum response size exceeded',
|
||||
label: 'testing',
|
||||
message: 'Maximum response size exceeded',
|
||||
color: 'lightgrey',
|
||||
link: [],
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
nock.cleanAll()
|
||||
})
|
||||
})
|
||||
|
||||
describe('caching', function () {
|
||||
describe('standard query parameters', function () {
|
||||
function register({ cacheHeaderConfig }) {
|
||||
|
||||
@@ -11,14 +11,12 @@ function streamFromString(str) {
|
||||
|
||||
function sendSVG(res, askres, end) {
|
||||
askres.setHeader('Content-Type', 'image/svg+xml;charset=utf-8')
|
||||
askres.setHeader('Content-Length', Buffer.byteLength(res, 'utf8'))
|
||||
end(null, { template: streamFromString(res) })
|
||||
}
|
||||
|
||||
function sendJSON(res, askres, end) {
|
||||
askres.setHeader('Content-Type', 'application/json')
|
||||
askres.setHeader('Access-Control-Allow-Origin', '*')
|
||||
askres.setHeader('Content-Length', Buffer.byteLength(res, 'utf8'))
|
||||
end(null, { template: streamFromString(res) })
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,6 @@ const serviceDir = path.join(
|
||||
'services'
|
||||
)
|
||||
|
||||
function toUnixPath(path) {
|
||||
// glob does not allow \ as a path separator
|
||||
// see https://github.com/isaacs/node-glob/blob/main/changelog.md#80
|
||||
// so we need to convert to use / for use with glob
|
||||
return path.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
class InvalidService extends Error {
|
||||
constructor(message) {
|
||||
super(message)
|
||||
@@ -29,9 +22,7 @@ class InvalidService extends Error {
|
||||
|
||||
async function loadServiceClasses(servicePaths) {
|
||||
if (!servicePaths) {
|
||||
servicePaths = glob.sync(
|
||||
toUnixPath(path.join(serviceDir, '**', '*.service.js'))
|
||||
)
|
||||
servicePaths = glob.sync(path.join(serviceDir, '**', '*.service.js'))
|
||||
}
|
||||
|
||||
const serviceClasses = []
|
||||
@@ -51,8 +42,8 @@ async function loadServiceClasses(servicePaths) {
|
||||
if (serviceClass && serviceClass.prototype instanceof BaseService) {
|
||||
// Decorate each service class with the directory that contains it.
|
||||
serviceClass.serviceFamily = servicePath
|
||||
.replace(toUnixPath(serviceDir), '')
|
||||
.split('/')[1]
|
||||
.replace(serviceDir, '')
|
||||
.split(path.sep)[1]
|
||||
serviceClass.validateDefinition()
|
||||
return serviceClasses.push(serviceClass)
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { InvalidResponse } from './errors.js'
|
||||
import { fetch } from './got.js'
|
||||
import checkErrorResponse from './check-error-response.js'
|
||||
|
||||
const oneDay = 24 * 3600 * 1000 // 1 day in milliseconds
|
||||
|
||||
// Map from URL to { timestamp: last fetch time, data: data }.
|
||||
let resourceCache = Object.create(null)
|
||||
|
||||
/**
|
||||
* Make a HTTP request using an in-memory cache
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {number} attrs.ttl Number of milliseconds to keep cached value for
|
||||
* @param {boolean} [attrs.json=true] True if we expect to parse the response as JSON
|
||||
* @param {Function} [attrs.scraper=buffer => buffer] Function to extract value from the response
|
||||
* @param {object} [attrs.options={}] Options to pass to got
|
||||
* @param {Function} [attrs.requestFetcher=fetch] Custom fetch function
|
||||
* @returns {*} Parsed response
|
||||
*/
|
||||
async function getCachedResource({
|
||||
url,
|
||||
ttl = oneDay,
|
||||
json = true,
|
||||
scraper = buffer => buffer,
|
||||
options = {},
|
||||
requestFetcher = fetch,
|
||||
}) {
|
||||
const timestamp = Date.now()
|
||||
const cached = resourceCache[url]
|
||||
if (cached != null && timestamp - cached.timestamp < ttl) {
|
||||
return cached.data
|
||||
}
|
||||
|
||||
const { buffer } = await checkErrorResponse({})(
|
||||
await requestFetcher(url, options)
|
||||
)
|
||||
|
||||
let reqData
|
||||
if (json) {
|
||||
try {
|
||||
reqData = JSON.parse(buffer)
|
||||
} catch (e) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'unparseable intermediate json response',
|
||||
underlyingError: e,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
reqData = buffer
|
||||
}
|
||||
|
||||
const data = scraper(reqData)
|
||||
resourceCache[url] = { timestamp, data }
|
||||
return data
|
||||
}
|
||||
|
||||
function clearResourceCache() {
|
||||
resourceCache = Object.create(null)
|
||||
}
|
||||
|
||||
export { getCachedResource, clearResourceCache }
|
||||
@@ -1,47 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import nock from 'nock'
|
||||
import { getCachedResource, clearResourceCache } from './resource-cache.js'
|
||||
|
||||
describe('Resource Cache', function () {
|
||||
beforeEach(function () {
|
||||
clearResourceCache()
|
||||
})
|
||||
|
||||
it('should use cached response if valid', async function () {
|
||||
let resp
|
||||
|
||||
nock('https://www.foobar.com').get('/baz').reply(200, { value: 1 })
|
||||
resp = await getCachedResource({ url: 'https://www.foobar.com/baz' })
|
||||
expect(resp).to.deep.equal({ value: 1 })
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
nock.cleanAll()
|
||||
|
||||
nock('https://www.foobar.com').get('/baz').reply(200, { value: 2 })
|
||||
resp = await getCachedResource({ url: 'https://www.foobar.com/baz' })
|
||||
expect(resp).to.deep.equal({ value: 1 })
|
||||
expect(nock.isDone()).to.equal(false)
|
||||
nock.cleanAll()
|
||||
})
|
||||
|
||||
it('should not use cached response if expired', async function () {
|
||||
let resp
|
||||
|
||||
nock('https://www.foobar.com').get('/baz').reply(200, { value: 1 })
|
||||
resp = await getCachedResource({
|
||||
url: 'https://www.foobar.com/baz',
|
||||
ttl: 1,
|
||||
})
|
||||
expect(resp).to.deep.equal({ value: 1 })
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
nock.cleanAll()
|
||||
|
||||
nock('https://www.foobar.com').get('/baz').reply(200, { value: 2 })
|
||||
resp = await getCachedResource({
|
||||
url: 'https://www.foobar.com/baz',
|
||||
ttl: 1,
|
||||
})
|
||||
expect(resp).to.deep.equal({ value: 2 })
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
nock.cleanAll()
|
||||
})
|
||||
})
|
||||
@@ -10,11 +10,15 @@ describe('validate', function () {
|
||||
requiredString: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
let sandbox
|
||||
beforeEach(function () {
|
||||
sinon.stub(trace, 'logTrace')
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
sandbox.restore()
|
||||
})
|
||||
beforeEach(function () {
|
||||
sandbox.stub(trace, 'logTrace')
|
||||
})
|
||||
|
||||
const ErrorClass = InvalidParameter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import got from 'got'
|
||||
|
||||
// https://github.com/nock/nock/issues/1523
|
||||
export default got.extend({ retry: { limit: 0 } })
|
||||
export default got.extend({ retry: 0 })
|
||||
|
||||
97
core/legacy/regular-update.js
Normal file
97
core/legacy/regular-update.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import requestModule from 'request'
|
||||
import { Inaccessible, InvalidResponse } from '../base-service/errors.js'
|
||||
|
||||
// Map from URL to { timestamp: last fetch time, data: data }.
|
||||
let regularUpdateCache = Object.create(null)
|
||||
|
||||
// url: a string, scraper: a function that takes string data at that URL.
|
||||
// interval: number in milliseconds.
|
||||
// cb: a callback function that takes an error and data returned by the scraper.
|
||||
//
|
||||
// To use this from a service:
|
||||
//
|
||||
// import { promisify } from 'util'
|
||||
// import { regularUpdate } from '../../core/legacy/regular-update.js'
|
||||
//
|
||||
// function getThing() {
|
||||
// return promisify(regularUpdate)({
|
||||
// url: ...,
|
||||
// ...
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// in handle():
|
||||
//
|
||||
// const thing = await getThing()
|
||||
|
||||
function regularUpdate(
|
||||
{
|
||||
url,
|
||||
intervalMillis,
|
||||
json = true,
|
||||
scraper = buffer => buffer,
|
||||
options = {},
|
||||
request = requestModule,
|
||||
},
|
||||
cb
|
||||
) {
|
||||
const timestamp = Date.now()
|
||||
const cached = regularUpdateCache[url]
|
||||
if (cached != null && timestamp - cached.timestamp < intervalMillis) {
|
||||
cb(null, cached.data)
|
||||
return
|
||||
}
|
||||
request(url, options, (err, res, buffer) => {
|
||||
if (err != null) {
|
||||
cb(
|
||||
new Inaccessible({
|
||||
prettyMessage: 'intermediate resource inaccessible',
|
||||
underlyingError: err,
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
cb(
|
||||
new InvalidResponse({
|
||||
prettyMessage: 'intermediate resource inaccessible',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
let reqData
|
||||
if (json) {
|
||||
try {
|
||||
reqData = JSON.parse(buffer)
|
||||
} catch (e) {
|
||||
cb(
|
||||
new InvalidResponse({
|
||||
prettyMessage: 'unparseable intermediate json response',
|
||||
underlyingError: e,
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
reqData = buffer
|
||||
}
|
||||
|
||||
let data
|
||||
try {
|
||||
data = scraper(reqData)
|
||||
} catch (e) {
|
||||
cb(e)
|
||||
return
|
||||
}
|
||||
|
||||
regularUpdateCache[url] = { timestamp, data }
|
||||
cb(null, data)
|
||||
})
|
||||
}
|
||||
|
||||
function clearRegularUpdateCache() {
|
||||
regularUpdateCache = Object.create(null)
|
||||
}
|
||||
|
||||
export { regularUpdate, clearRegularUpdateCache }
|
||||
@@ -16,7 +16,7 @@ export default class InfluxMetrics {
|
||||
url: this._config.url,
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: await this.metrics(),
|
||||
timeout: { request: this._config.timeoutMillseconds },
|
||||
timeout: this._config.timeoutMillseconds,
|
||||
username: this._config.username,
|
||||
password: this._config.password,
|
||||
throwHttpErrors: false,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { expect } from 'chai'
|
||||
import log from './log.js'
|
||||
import InfluxMetrics from './influx-metrics.js'
|
||||
import '../register-chai-plugins.spec.js'
|
||||
|
||||
describe('Influx metrics', function () {
|
||||
const metricInstance = {
|
||||
metrics() {
|
||||
|
||||
66
core/server/public/monitor.html
Normal file
66
core/server/public/monitor.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!doctype html><meta charset=utf-8>
|
||||
<title> Shields.io Admin Monitoring Interface </title>
|
||||
<style>
|
||||
#monitorPlatform { display: none; }
|
||||
</style>
|
||||
|
||||
<div id=passwordRequest>
|
||||
<p> Please enter your admin secret here:
|
||||
<input type=password id=secretInput>
|
||||
</div>
|
||||
<div id=monitorPlatform>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
let network;
|
||||
const onLoad = function() {
|
||||
const secretInput = document.getElementById('secretInput');
|
||||
const onSecretChange = function() {
|
||||
const secret = secretInput.value;
|
||||
const authentication = `monitor:${secret}`;
|
||||
const headers = new Headers({
|
||||
Authorization: `Basic ${btoa(authentication)}`
|
||||
})
|
||||
fetch('/sys/network', {headers})
|
||||
.then(res => res.json())
|
||||
.then(networkData => {
|
||||
network = networkData;
|
||||
// Show monitor platform.
|
||||
monitorPlatform.style.display = 'block';
|
||||
passwordRequest.parentNode.removeChild(passwordRequest);
|
||||
|
||||
// Show logs for each server.
|
||||
network.ips.forEach(ip => {
|
||||
const logger = document.createElement('div');
|
||||
const pre = document.createElement('pre');
|
||||
logger.textContent = ip;
|
||||
logger.appendChild(pre);
|
||||
monitorPlatform.appendChild(logger);
|
||||
|
||||
// Set up the websocket.
|
||||
const setUpWebsocket = () => {
|
||||
const websocket = new WebSocket(
|
||||
(window.location.protocol === 'http:' ? 'ws' : 'wss') + '://' +
|
||||
ip + ':' + window.location.port + '/sys/logs');
|
||||
websocket.addEventListener('message', event => {
|
||||
pre.textContent += event.data + '\n';
|
||||
});
|
||||
websocket.addEventListener('close', () => {
|
||||
setTimeout(setUpWebsocket, 100);
|
||||
});
|
||||
websocket.addEventListener('open', () => {
|
||||
websocket.send(JSON.stringify({secret}));
|
||||
});
|
||||
};
|
||||
setUpWebsocket();
|
||||
});
|
||||
})
|
||||
.catch(alert)
|
||||
};
|
||||
secretInput.addEventListener('change', onSecretChange);
|
||||
};
|
||||
|
||||
addEventListener('DOMContentLoaded', onLoad);
|
||||
}());
|
||||
</script>
|
||||
18
core/server/secret-is-valid.js
Normal file
18
core/server/secret-is-valid.js
Normal file
@@ -0,0 +1,18 @@
|
||||
function constEq(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false
|
||||
}
|
||||
let zero = 0
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
zero |= a.charCodeAt(i) ^ b.charCodeAt(i)
|
||||
}
|
||||
return zero === 0
|
||||
}
|
||||
|
||||
function makeSecretIsValid(shieldsSecret) {
|
||||
return function secretIsValid(secret = '') {
|
||||
return shieldsSecret && constEq(secret, shieldsSecret)
|
||||
}
|
||||
}
|
||||
|
||||
export { makeSecretIsValid }
|
||||
@@ -6,18 +6,18 @@ import path from 'path'
|
||||
import url, { fileURLToPath } from 'url'
|
||||
import { bootstrap } from 'global-agent'
|
||||
import cloudflareMiddleware from 'cloudflare-middleware'
|
||||
import bytes from 'bytes'
|
||||
import Camp from '@shields_io/camp'
|
||||
import originalJoi from 'joi'
|
||||
import makeBadge from '../../badge-maker/lib/make-badge.js'
|
||||
import GithubConstellation from '../../services/github/github-constellation.js'
|
||||
import LibrariesIoConstellation from '../../services/librariesio/librariesio-constellation.js'
|
||||
import { setRoutes } from '../../services/suggest.js'
|
||||
import { loadServiceClasses } from '../base-service/loader.js'
|
||||
import { makeSend } from '../base-service/legacy-result-sender.js'
|
||||
import { handleRequest } from '../base-service/legacy-request-handler.js'
|
||||
import { clearResourceCache } from '../base-service/resource-cache.js'
|
||||
import { clearRegularUpdateCache } from '../legacy/regular-update.js'
|
||||
import { rasterRedirectUrl } from '../badge-urls/make-badge-url.js'
|
||||
import { fileSize, nonNegativeInteger } from '../../services/validators.js'
|
||||
import { nonNegativeInteger } from '../../services/validators.js'
|
||||
import log from './log.js'
|
||||
import PrometheusMetrics from './prometheus-metrics.js'
|
||||
import InfluxMetrics from './influx-metrics.js'
|
||||
@@ -134,7 +134,6 @@ const publicConfigSchema = Joi.object({
|
||||
}).default({ authorizedOrigins: [] }),
|
||||
nexus: defaultService,
|
||||
npm: defaultService,
|
||||
obs: defaultService,
|
||||
sonar: defaultService,
|
||||
teamcity: defaultService,
|
||||
weblate: defaultService,
|
||||
@@ -142,8 +141,7 @@ const publicConfigSchema = Joi.object({
|
||||
}).required(),
|
||||
cacheHeaders: { defaultCacheLengthSeconds: nonNegativeInteger },
|
||||
handleInternalErrors: Joi.boolean().required(),
|
||||
fetchLimit: fileSize,
|
||||
userAgentBase: Joi.string().required(),
|
||||
fetchLimit: Joi.string().regex(/^[0-9]+(b|kb|mb|gb|tb)$/i),
|
||||
requestTimeoutSeconds: nonNegativeInteger,
|
||||
requestTimeoutMaxAgeSeconds: nonNegativeInteger,
|
||||
documentRoot: Joi.string().default(
|
||||
@@ -169,18 +167,14 @@ const privateConfigSchema = Joi.object({
|
||||
jenkins_pass: Joi.string(),
|
||||
jira_user: Joi.string(),
|
||||
jira_pass: Joi.string(),
|
||||
bitbucket_username: Joi.string(),
|
||||
bitbucket_password: Joi.string(),
|
||||
bitbucket_server_username: Joi.string(),
|
||||
bitbucket_server_password: Joi.string(),
|
||||
librariesio_tokens: Joi.arrayFromString().items(Joi.string()),
|
||||
nexus_user: Joi.string(),
|
||||
nexus_pass: Joi.string(),
|
||||
npm_token: Joi.string(),
|
||||
obs_user: Joi.string(),
|
||||
obs_pass: Joi.string(),
|
||||
redis_url: Joi.string().uri({ scheme: ['redis', 'rediss'] }),
|
||||
sentry_dsn: Joi.string(),
|
||||
shields_secret: Joi.string(),
|
||||
sl_insight_userUuid: Joi.string(),
|
||||
sl_insight_apiToken: Joi.string(),
|
||||
sonarqube_token: Joi.string(),
|
||||
@@ -203,14 +197,6 @@ function addHandlerAtIndex(camp, index, handlerFn) {
|
||||
camp.stack.splice(index, 0, handlerFn)
|
||||
}
|
||||
|
||||
function isOnHeroku() {
|
||||
return !!process.env.DYNO
|
||||
}
|
||||
|
||||
function isOnFly() {
|
||||
return !!process.env.FLY_APP_NAME
|
||||
}
|
||||
|
||||
/**
|
||||
* The Server is based on the web framework Scoutcamp. It creates
|
||||
* an http server, sets up helpers for token persistence and monitoring.
|
||||
@@ -253,10 +239,6 @@ class Server {
|
||||
private: privateConfig,
|
||||
})
|
||||
|
||||
this.librariesioConstellation = new LibrariesIoConstellation({
|
||||
private: privateConfig,
|
||||
})
|
||||
|
||||
if (publicConfig.metrics.prometheus.enabled) {
|
||||
this.metricInstance = new PrometheusMetrics()
|
||||
if (publicConfig.metrics.influx.enabled) {
|
||||
@@ -311,21 +293,13 @@ class Server {
|
||||
// Set `req.ip`, which is expected by `cloudflareMiddleware()`. This is set
|
||||
// by Express but not Scoutcamp.
|
||||
addHandlerAtIndex(this.camp, 0, function (req, res, next) {
|
||||
if (isOnHeroku()) {
|
||||
// On Heroku, `req.socket.remoteAddress` is the Heroku router. However,
|
||||
// the router ensures that the last item in the `X-Forwarded-For` header
|
||||
// is the real origin.
|
||||
// https://stackoverflow.com/a/18517550/893113
|
||||
req.ip = req.headers['x-forwarded-for'].split(', ').pop()
|
||||
} else if (isOnFly()) {
|
||||
// On Fly we can use the Fly-Client-IP header
|
||||
// https://fly.io/docs/reference/runtime-environment/#request-headers
|
||||
req.ip = req.headers['fly-client-ip']
|
||||
? req.headers['fly-client-ip']
|
||||
: req.socket.remoteAddress
|
||||
} else {
|
||||
req.ip = req.socket.remoteAddress
|
||||
}
|
||||
// On Heroku, `req.socket.remoteAddress` is the Heroku router. However,
|
||||
// the router ensures that the last item in the `X-Forwarded-For` header
|
||||
// is the real origin.
|
||||
// https://stackoverflow.com/a/18517550/893113
|
||||
req.ip = process.env.DYNO
|
||||
? req.headers['x-forwarded-for'].split(', ').pop()
|
||||
: req.socket.remoteAddress
|
||||
next()
|
||||
})
|
||||
addHandlerAtIndex(this.camp, 1, cloudflareMiddleware())
|
||||
@@ -437,20 +411,14 @@ class Server {
|
||||
async registerServices() {
|
||||
const { config, camp, metricInstance } = this
|
||||
const { apiProvider: githubApiProvider } = this.githubConstellation
|
||||
const { apiProvider: librariesIoApiProvider } =
|
||||
this.librariesioConstellation
|
||||
|
||||
;(await loadServiceClasses()).forEach(serviceClass =>
|
||||
serviceClass.register(
|
||||
{
|
||||
camp,
|
||||
handleRequest,
|
||||
githubApiProvider,
|
||||
librariesIoApiProvider,
|
||||
metricInstance,
|
||||
},
|
||||
{ camp, handleRequest, githubApiProvider, metricInstance },
|
||||
{
|
||||
handleInternalErrors: config.public.handleInternalErrors,
|
||||
cacheHeaders: config.public.cacheHeaders,
|
||||
fetchLimitBytes: bytes(config.public.fetchLimit),
|
||||
rasterUrl: config.public.rasterUrl,
|
||||
private: config.private,
|
||||
public: config.public,
|
||||
@@ -524,12 +492,6 @@ class Server {
|
||||
const { apiProvider: githubApiProvider } = this.githubConstellation
|
||||
setRoutes(allowedOrigin, githubApiProvider, camp)
|
||||
|
||||
// https://github.com/badges/shields/issues/3273
|
||||
camp.handle((req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
next()
|
||||
})
|
||||
|
||||
this.registerErrorHandlers()
|
||||
this.registerRedirects()
|
||||
await this.registerServices()
|
||||
@@ -555,7 +517,7 @@ class Server {
|
||||
static resetGlobalState() {
|
||||
// This state should be migrated to instance state. When possible, do not add new
|
||||
// global state.
|
||||
clearResourceCache()
|
||||
clearRegularUpdateCache()
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
||||
@@ -64,12 +64,6 @@ describe('The server', function () {
|
||||
expect(headers['cache-control']).to.equal('max-age=3600, s-maxage=3600')
|
||||
})
|
||||
|
||||
it('should return cors header for the request', async function () {
|
||||
const { statusCode, headers } = await got(`${baseUrl}npm/v/express.svg`)
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(headers['access-control-allow-origin']).to.equal('*')
|
||||
})
|
||||
|
||||
it('should redirect colorscheme PNG badges as configured', async function () {
|
||||
const { statusCode, headers } = await got(
|
||||
`${baseUrl}:fruit-apple-green.png`,
|
||||
@@ -93,28 +87,12 @@ describe('The server', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should produce SVG badges with expected headers', async function () {
|
||||
const { statusCode, headers } = await got(
|
||||
`${baseUrl}:fruit-apple-green.svg`
|
||||
)
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(headers['content-type']).to.equal('image/svg+xml;charset=utf-8')
|
||||
expect(headers['content-length']).to.equal('1130')
|
||||
})
|
||||
|
||||
it('correctly calculates the content-length header for multi-byte unicode characters', async function () {
|
||||
const { headers } = await got(`${baseUrl}:fruit-apple🍏-green.json`)
|
||||
expect(headers['content-length']).to.equal('100')
|
||||
})
|
||||
|
||||
it('should produce JSON badges with expected headers', async function () {
|
||||
it('should produce json badges', async function () {
|
||||
const { statusCode, body, headers } = await got(
|
||||
`${baseUrl}:fruit-apple-green.json`
|
||||
`${baseUrl}twitter/follow/_Pyves.json`
|
||||
)
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(headers['content-type']).to.equal('application/json')
|
||||
expect(headers['access-control-allow-origin']).to.equal('*')
|
||||
expect(headers['content-length']).to.equal('92')
|
||||
expect(() => JSON.parse(body)).not.to.throw()
|
||||
})
|
||||
|
||||
@@ -207,12 +185,6 @@ describe('The server', function () {
|
||||
.and.to.include('410')
|
||||
.and.to.include('jpg no longer available')
|
||||
})
|
||||
|
||||
it('should return cors header for the request', async function () {
|
||||
const { statusCode, headers } = await got(`${baseUrl}npm/v/express.svg`)
|
||||
expect(statusCode).to.equal(200)
|
||||
expect(headers['access-control-allow-origin']).to.equal('*')
|
||||
})
|
||||
})
|
||||
|
||||
context('`requireCloudflare` is enabled', function () {
|
||||
|
||||
@@ -49,29 +49,14 @@ const factory = superclass =>
|
||||
return this
|
||||
}
|
||||
|
||||
expectBadge(badge) {
|
||||
const expectedKeys = [
|
||||
'label',
|
||||
'message',
|
||||
'logoWidth',
|
||||
'labelColor',
|
||||
'color',
|
||||
'link',
|
||||
]
|
||||
|
||||
for (const key of Object.keys(badge)) {
|
||||
if (!expectedKeys.includes(key)) {
|
||||
throw new Error(`Found unexpected object key '${key}'`)
|
||||
}
|
||||
}
|
||||
|
||||
expectBadge({ label, message, logoWidth, labelColor, color, link }) {
|
||||
return this.afterJSON(json => {
|
||||
this.constructor._expectField(json, 'label', badge.label)
|
||||
this.constructor._expectField(json, 'message', badge.message)
|
||||
this.constructor._expectField(json, 'logoWidth', badge.logoWidth)
|
||||
this.constructor._expectField(json, 'labelColor', badge.labelColor)
|
||||
this.constructor._expectField(json, 'color', badge.color)
|
||||
this.constructor._expectField(json, 'link', badge.link)
|
||||
this.constructor._expectField(json, 'label', label)
|
||||
this.constructor._expectField(json, 'message', message)
|
||||
this.constructor._expectField(json, 'logoWidth', logoWidth)
|
||||
this.constructor._expectField(json, 'labelColor', labelColor)
|
||||
this.constructor._expectField(json, 'color', color)
|
||||
this.constructor._expectField(json, 'link', link)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,7 @@ function _inferPullRequestFromTravisEnv(env) {
|
||||
}
|
||||
|
||||
function _inferPullRequestFromCircleEnv(env) {
|
||||
return parseGithubPullRequestUrl(
|
||||
env.CI_PULL_REQUEST || env.CIRCLE_PULL_REQUEST
|
||||
)
|
||||
return parseGithubPullRequestUrl(env.CI_PULL_REQUEST)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,10 +80,6 @@ class Token {
|
||||
return this.usesRemaining <= 0 && !this.hasReset
|
||||
}
|
||||
|
||||
get decrementedUsesRemaining() {
|
||||
return this._usesRemaining - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the uses remaining and next reset time for a token.
|
||||
*
|
||||
@@ -192,6 +188,10 @@ class TokenPool {
|
||||
this.priorityQueue = new PriorityQueue(this.constructor.compareTokens)
|
||||
}
|
||||
|
||||
count() {
|
||||
return this.tokenIds.size
|
||||
}
|
||||
|
||||
/**
|
||||
* compareTokens
|
||||
*
|
||||
@@ -332,6 +332,29 @@ class TokenPool {
|
||||
this.fifoQueue.forEach(visit)
|
||||
this.priorityQueue.forEach(visit)
|
||||
}
|
||||
|
||||
allValidTokenIds() {
|
||||
const result = []
|
||||
this.forEach(({ id }) => result.push(id))
|
||||
return result
|
||||
}
|
||||
|
||||
serializeDebugInfo({ sanitize = true } = {}) {
|
||||
const maybeSanitize = sanitize ? id => sanitizeToken(id) : id => id
|
||||
|
||||
const priorityQueue = []
|
||||
this.priorityQueue.forEach(t =>
|
||||
priorityQueue.push(t.getDebugInfo({ sanitize }))
|
||||
)
|
||||
|
||||
return {
|
||||
utcEpochSeconds: getUtcEpochSeconds(),
|
||||
allValidTokenIds: this.allValidTokenIds().map(maybeSanitize),
|
||||
fifoQueue: this.fifoQueue.map(t => t.getDebugInfo({ sanitize })),
|
||||
priorityQueue,
|
||||
sanitized: sanitize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { sanitizeToken, Token, TokenPool }
|
||||
|
||||
@@ -19,6 +19,10 @@ describe('The token pool', function () {
|
||||
ids.forEach(id => tokenPool.add(id))
|
||||
})
|
||||
|
||||
it('allValidTokenIds() should return the full list', function () {
|
||||
expect(tokenPool.allValidTokenIds()).to.deep.equal(ids)
|
||||
})
|
||||
|
||||
it('should yield the expected tokens', function () {
|
||||
ids.forEach(id =>
|
||||
times(batchSize, () => expect(tokenPool.next().id).to.equal(id))
|
||||
@@ -34,6 +38,67 @@ describe('The token pool', function () {
|
||||
)
|
||||
})
|
||||
|
||||
describe('serializeDebugInfo should initially return the expected', function () {
|
||||
beforeEach(function () {
|
||||
sinon.useFakeTimers({ now: 1544307744484 })
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
context('sanitize is not specified', function () {
|
||||
it('returns fully sanitized results', function () {
|
||||
// This is `sha()` of '1', '2', '3', '4', '5'. These are written
|
||||
// literally for avoidance of doubt as to whether sanitization is
|
||||
// happening.
|
||||
const sanitizedIds = [
|
||||
'6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b',
|
||||
'd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35',
|
||||
'4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce',
|
||||
'4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a',
|
||||
'ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d',
|
||||
]
|
||||
|
||||
expect(tokenPool.serializeDebugInfo()).to.deep.equal({
|
||||
allValidTokenIds: sanitizedIds,
|
||||
priorityQueue: [],
|
||||
fifoQueue: sanitizedIds.map(id => ({
|
||||
data: '[redacted]',
|
||||
id,
|
||||
isFrozen: false,
|
||||
isValid: true,
|
||||
nextReset: Token.nextResetNever,
|
||||
usesRemaining: batchSize,
|
||||
})),
|
||||
sanitized: true,
|
||||
utcEpochSeconds: 1544307744,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('with sanitize: false', function () {
|
||||
it('returns unsanitized results', function () {
|
||||
expect(tokenPool.serializeDebugInfo({ sanitize: false })).to.deep.equal(
|
||||
{
|
||||
allValidTokenIds: ids,
|
||||
priorityQueue: [],
|
||||
fifoQueue: ids.map(id => ({
|
||||
data: undefined,
|
||||
id,
|
||||
isFrozen: false,
|
||||
isValid: true,
|
||||
nextReset: Token.nextResetNever,
|
||||
usesRemaining: batchSize,
|
||||
})),
|
||||
sanitized: false,
|
||||
utcEpochSeconds: 1544307744,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('tokens are marked exhausted immediately', function () {
|
||||
it('should be exhausted', function () {
|
||||
ids.forEach(() => {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
fixturesFolder: false,
|
||||
env: {
|
||||
backend_url: 'http://localhost:8080',
|
||||
},
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
supportFile: false,
|
||||
},
|
||||
})
|
||||
9
cypress.json
Normal file
9
cypress.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"fixturesFolder": false,
|
||||
"pluginsFile": false,
|
||||
"supportFile": false,
|
||||
"env": {
|
||||
"backend_url": "http://localhost:8080"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
import { registerCommand } from 'cypress-wait-for-stable-dom'
|
||||
|
||||
registerCommand()
|
||||
|
||||
describe('Main page', function () {
|
||||
const backendUrl = Cypress.env('backend_url')
|
||||
const SEARCH_INPUT = 'input[placeholder="search / project URL"]'
|
||||
@@ -13,13 +9,8 @@ describe('Main page', function () {
|
||||
.should('have.attr', 'src', previewUrl)
|
||||
}
|
||||
|
||||
function visitAndWait(page) {
|
||||
cy.visit(page)
|
||||
cy.waitForStableDOM({ pollInterval: 1000, timeout: 10000 })
|
||||
}
|
||||
|
||||
it('Search for badges', function () {
|
||||
visitAndWait('/')
|
||||
cy.visit('/')
|
||||
|
||||
cy.get(SEARCH_INPUT).type('pypi')
|
||||
|
||||
@@ -27,7 +18,7 @@ describe('Main page', function () {
|
||||
})
|
||||
|
||||
it('Shows badge from category', function () {
|
||||
visitAndWait('/category/chat')
|
||||
cy.visit('/category/chat')
|
||||
|
||||
expectBadgeExample(
|
||||
'Discourse status',
|
||||
@@ -38,7 +29,7 @@ describe('Main page', function () {
|
||||
|
||||
it('Suggest badges', function () {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
|
||||
visitAndWait('/')
|
||||
cy.visit('/')
|
||||
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
@@ -48,7 +39,7 @@ describe('Main page', function () {
|
||||
|
||||
it('Customization form is filled with suggested badge details', function () {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
|
||||
visitAndWait('/')
|
||||
cy.visit('/')
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
|
||||
@@ -60,7 +51,7 @@ describe('Main page', function () {
|
||||
|
||||
it('Customizate suggested badge', function () {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
|
||||
visitAndWait('/')
|
||||
cy.visit('/')
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
cy.contains(badgeUrl).click()
|
||||
@@ -71,7 +62,7 @@ describe('Main page', function () {
|
||||
})
|
||||
|
||||
it('Do not duplicate example parameters', function () {
|
||||
visitAndWait('/category/funding')
|
||||
cy.visit('/category/funding')
|
||||
|
||||
cy.contains('GitHub Sponsors').click()
|
||||
cy.get('[name="style"]').should($style => {
|
||||
@@ -114,7 +114,7 @@ if (allFiles.length > 100) {
|
||||
if (diff.includes('authHelper') && !secretsDocs.modified) {
|
||||
warn(
|
||||
[
|
||||
':books: Remember to ensure any changes to `config.private` ',
|
||||
`:books: Remember to ensure any changes to \`config.private\` `,
|
||||
`in \`${file}\` are reflected in the [server secrets documentation]`,
|
||||
'(https://github.com/badges/shields/blob/master/doc/server-secrets.md)',
|
||||
].join('')
|
||||
|
||||
@@ -25,7 +25,7 @@ and learn about the [GitHub workflow](http://try.github.io/).
|
||||
|
||||
#### Node, NPM
|
||||
|
||||
Node >=16 and NPM >=8 is required. If you don't already have them,
|
||||
Node >=14 and NPM >=7 is required. If you don't already have them,
|
||||
install node and npm: https://nodejs.org/en/download/
|
||||
|
||||
### Setup a dev install
|
||||
@@ -228,14 +228,14 @@ Description of the code:
|
||||
9. Working our way upward, the `async fetch()` method is responsible for calling an API endpoint to get data. Extending `BaseJsonService` gives us the helper function `_requestJson()`. Note here that we pass the schema we defined in step 4 as an argument. `_requestJson()` will deal with validating the response against the schema and throwing an error if necessary.
|
||||
|
||||
- `_requestJson()` automatically adds an Accept header, checks the status code, parses the response as JSON, and returns the parsed response.
|
||||
- `_requestJson()` uses [got](https://github.com/sindresorhus/got) to perform the HTTP request. Options can be passed to got, including method, query string, and headers. If headers are provided they will override the ones automatically set by `_requestJson()`. There is no need to specify json, as the JSON parsing is handled by `_requestJson()`. See the `got` docs for [supported options](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md).
|
||||
- `_requestJson()` uses [request](https://github.com/request/request) to perform the HTTP request. Options can be passed to request, including method, query string, and headers. If headers are provided they will override the ones automatically set by `_requestJson()`. There is no need to specify json, as the JSON parsing is handled by `_requestJson()`. See the `request` docs for [supported options](https://github.com/request/request#requestoptions-callback).
|
||||
- Error messages corresponding to each status code can be returned by passing a dictionary of status codes -> messages in `errorMessages`.
|
||||
- A more complex call to `_requestJson()` might look like this:
|
||||
```js
|
||||
return this._requestJson({
|
||||
schema: mySchema,
|
||||
url,
|
||||
options: { searchParams: { branch: 'master' } },
|
||||
options: { qs: { branch: 'master' } },
|
||||
errorMessages: {
|
||||
401: 'private application not supported',
|
||||
404: 'application not found',
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Adding New Config Values
|
||||
|
||||
The Badge Server supports a [variety of methods for defining configuration settings and secrets](./server-secrets.md), and provides a framework for loading those values during bootstrapping.
|
||||
|
||||
Any new configuration setting or secret must be correctly registered so that it will be loaded at startup along with the others.
|
||||
|
||||
This generally includes adding the corresponding information for your new setting(s)/secret(s) to the following locations:
|
||||
|
||||
- [core/server/server.js](https://github.com/badges/shields/blob/master/core/server/server.js) - Add the new values to the [schemas](https://github.com/badges/shields/blob/master/core/server/server.js#L118-L193). Secrets/tokens/etc. should go in the `privateConfigSchema` while non-secret configuration settings should go in the `publicConfigSchema`.
|
||||
- [config/custom-environment-variables.yml](https://github.com/badges/shields/blob/master/config/custom-environment-variables.yml)
|
||||
- [docs/server-secrets.md](https://github.com/badges/shields/blob/master/doc/server-secrets.md) (only applicable for secrets)
|
||||
- [config/default.yml](https://github.com/badges/shields/blob/master/config/default.yml) (optional)
|
||||
- Any other template config files (e.g. `config/local.template.yml`) (optional)
|
||||
|
||||
The exact values needed will depend on what type of secret/setting you are adding, but for reference a few commits are included below which added secrets and or settings:
|
||||
|
||||
- (secret) [8a9efb2fc99f97e78ab133c836ab1685803bf4df](https://github.com/badges/shields/commit/8a9efb2fc99f97e78ab133c836ab1685803bf4df)
|
||||
- (secret) [bd6f4ee1465d14a8f188c37823748a21b6a46762](https://github.com/badges/shields/commit/bd6f4ee1465d14a8f188c37823748a21b6a46762)
|
||||
- (secret) [0fd557d7bb623e3852c92cebac586d5f6d6d89d8](https://github.com/badges/shields/commit/0fd557d7bb623e3852c92cebac586d5f6d6d89d8)
|
||||
- (configuration setting) [b1fc4925928c061234e9492f3794c0797467e123](https://github.com/badges/shields/commit/b1fc4925928c061234e9492f3794c0797467e123)
|
||||
|
||||
Don't hesitate to reach out if you're unsure of the exact values needed for your new secret/setting, or have any other questions. Feel free to post questions on your corresponding Issue/Pull Request, and/or ping us on the `contributing` channel on our Discord server.
|
||||
@@ -3,9 +3,6 @@
|
||||
- The format of new badges should be of the form `/SERVICE/NOUN/PARAMETERS?QUERYSTRING` e.g:
|
||||
`/github/issues/:user/:repo`. The service is github, the
|
||||
badge is for issues, and the parameters are `:user/:repo`.
|
||||
- The `NOUN` part of the route is:
|
||||
- singular if the badge message represents a single entity, such as the current status of a build (e.g: `/build`), or a more abstract or aggregate representation of the thing (e.g.: `/coverage`, `/quality`)
|
||||
- plural if there are (or may) be many of the thing (e.g: `/dependencies`, `/stars`)
|
||||
- Parameters should always be part of the route if they are required to display a badge e.g: `:packageName`.
|
||||
- Common optional params like, `:branch` or `:tag` should also be passed as part of the route.
|
||||
- Query string parameters should be used when:
|
||||
|
||||
@@ -58,7 +58,7 @@ The tests are also divided into several parts:
|
||||
[redis-token-persistence.integration]: https://github.com/badges/shields/blob/master/core/token-pooling/redis-token-persistence.integration.js
|
||||
[github-api-provider.integration]: https://github.com/badges/shields/blob/master/services/github/github-api-provider.integration.js
|
||||
|
||||
Our goal is to reach 100% coverage of the code in the
|
||||
Our goal is for the core code is to reach 100% coverage of the code in the
|
||||
frontend, core, and service helper functions when the unit and functional
|
||||
tests are run.
|
||||
|
||||
@@ -95,8 +95,8 @@ test this kind of logic through unit tests (e.g. of `render()` and
|
||||
callback with the four parameters `( queryParams, match, end, ask )` which
|
||||
is created in a legacy helper function in
|
||||
[`legacy-request-handler.js`][legacy-request-handler]. This callback
|
||||
delegates to a callback in `BaseService.register` with three different
|
||||
parameters `( queryParams, match, sendBadge )`, which
|
||||
delegates to a callback in `BaseService.register` with four different
|
||||
parameters `( queryParams, match, sendBadge, request )`, which
|
||||
then runs `BaseService.invoke`. `BaseService.invoke` instantiates the
|
||||
service and runs `BaseService#handle`.
|
||||
|
||||
@@ -129,12 +129,12 @@ test this kind of logic through unit tests (e.g. of `render()` and
|
||||
handle unresponsive service code and the next callback is invoked: the
|
||||
legacy handler function.
|
||||
3. The legacy handler function receives
|
||||
`( queryParams, match, sendBadge )`. Its job is to extract data
|
||||
from the regex `match` and `queryParams`, and then invoke `sendBadge`
|
||||
with the result.
|
||||
`( queryParams, match, sendBadge, request )`. Its job is to extract data
|
||||
from the regex `match` and `queryParams`, invoke `request` to fetch
|
||||
whatever data it needs, and then invoke `sendBadge` with the result.
|
||||
4. The implementation of this function is in `BaseService.register`. It
|
||||
works by running `BaseService.invoke`, which instantiates the service,
|
||||
injects more dependencies, and invokes `BaseService.handle` which is
|
||||
injects more dependencies, and invokes `BaseService#handle` which is
|
||||
implemented by the service subclass.
|
||||
5. The job of `handle()`, which should be implemented by each service
|
||||
subclass, is to return an object which partially describes a badge or
|
||||
|
||||
@@ -94,8 +94,6 @@ Here is a listing of all deleted badges that were once part of the Shields.io se
|
||||
- Cauditor
|
||||
- CocoaPods Apps
|
||||
- CocoaPods Downloads
|
||||
- Codetally
|
||||
- continuousphp
|
||||
- Coverity
|
||||
- Dockbit
|
||||
- Dotnet Status
|
||||
|
||||
@@ -40,7 +40,7 @@ If you are submitting a pull request for a custom logo, please:
|
||||
- Install SVGO
|
||||
- With npm: `npm install -g svgo`
|
||||
- With Homebrew: `brew install svgo`
|
||||
- Run the following command `svgo --precision=3 icon.svg -o icon.min.svg`
|
||||
- Run the following command `svgo --precision=3 icon.svg icon.min.svg`
|
||||
- Check if there is a loss of quality in the output, if so increase the precision.
|
||||
- The [SVGOMG Online Tool][svgomg]
|
||||
- Click "Open SVG" and select an SVG file.
|
||||
|
||||
@@ -16,11 +16,14 @@ Production hosting is managed by the Shields ops team:
|
||||
|
||||
| Component | Subcomponent | People with access |
|
||||
| ----------------------------- | ------------------------------- | --------------------------------------------------------------- |
|
||||
| shields-io-production | Full access | @calebcartwright, @chris48s, @paulmelnikow |
|
||||
| shields-io-production | Access management | @calebcartwright, @chris48s, @paulmelnikow |
|
||||
| shields-production-us | Account owner | @paulmelnikow |
|
||||
| shields-production-us | Full access | @calebcartwright, @chris48s, @paulmelnikow, @pyvesb |
|
||||
| shields-production-us | Access management | @calebcartwright, @chris48s, @paulmelnikow, @pyvesb |
|
||||
| Compose.io Redis | Account owner | @paulmelnikow |
|
||||
| Compose.io Redis | Account access | @paulmelnikow |
|
||||
| Compose.io Redis | Database connection credentials | @calebcartwright, @chris48s, @paulmelnikow, @pyvesb |
|
||||
| Zeit Now | Team owner | @paulmelnikow |
|
||||
| Zeit Now | Team members | @paulmelnikow, @chris48s, @calebcartwright, @platan |
|
||||
| Raster server | Full access as team members | @paulmelnikow, @chris48s, @calebcartwright, @platan |
|
||||
| shields-server.com redirector | Full access as team members | @paulmelnikow, @chris48s, @calebcartwright, @platan |
|
||||
| Cloudflare (CDN) | Account owner | @espadrine |
|
||||
@@ -46,11 +49,11 @@ Shields has mercifully little persistent state:
|
||||
1. The GitHub tokens we collect are saved on each server in a cloud Redis
|
||||
database. They can also be fetched from the [GitHub auth admin endpoint][]
|
||||
for debugging.
|
||||
2. The server keeps the [resource cache][] in memory. It is neither
|
||||
2. The server keeps the [regular-update cache][] in memory. It is neither
|
||||
persisted nor inspectable.
|
||||
|
||||
[github auth admin endpoint]: https://github.com/badges/shields/blob/master/services/github/auth/admin.js
|
||||
[resource cache]: https://github.com/badges/shields/blob/master/core/base-service/resource-cache.js
|
||||
[regular-update cache]: https://github.com/badges/shields/blob/master/core/legacy/regular-update.js
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -91,13 +94,19 @@ Cloudflare is configured to respect the servers' cache headers.
|
||||
## Raster server
|
||||
|
||||
The raster server `raster.shields.io` (a.k.a. the rasterizing proxy) is
|
||||
hosted on Heroku. It's managed in the
|
||||
[squint](https://github.com/badges/squint/) repo.
|
||||
hosted on [Zeit Now][]. It's managed in the
|
||||
[svg-to-image-proxy repo][svg-to-image-proxy].
|
||||
|
||||
### Fly.io Deployment
|
||||
[zeit now]: https://zeit.co/now
|
||||
[svg-to-image-proxy]: https://github.com/badges/svg-to-image-proxy
|
||||
|
||||
Both the badge server and frontend are served from Fly.io. Deployments are
|
||||
triggered using GitHub actions in a private repo.
|
||||
### Heroku Deployment
|
||||
|
||||
Both the badge server and frontend are served from Heroku.
|
||||
|
||||
After merging a commit to master, heroku should create a staging deploy. Check this has deployed correctly in the `shields-staging` pipeline and review https://shields-staging.herokuapp.com/
|
||||
|
||||
If we're happy with it, "promote to production". This will deploy what's on staging to the `shields-production-eu` and `shields-production-us` pieplines.
|
||||
|
||||
## DNS
|
||||
|
||||
@@ -105,15 +114,19 @@ DNS is registered with [DNSimple][].
|
||||
|
||||
[dnsimple]: https://dnsimple.com/
|
||||
|
||||
## Logs
|
||||
|
||||
Logs can be retrieved [from heroku](https://devcenter.heroku.com/articles/logging#log-retrieval).
|
||||
|
||||
## Error reporting
|
||||
|
||||
[Error reporting][sentry] is one of the most useful tools we have for monitoring
|
||||
the server. It's generously donated by [Sentry][sentry home]. We bundle
|
||||
[`@sentry/node`][sentry-node] into the application, and the Sentry DSN is configured
|
||||
via `local-shields-io-production.yml` (see [documentation][sentry configuration]).
|
||||
[`raven`][raven] into the application, and the Sentry DSN is configured via
|
||||
`local-shields-io-production.yml` (see [documentation][sentry configuration]).
|
||||
|
||||
[sentry]: https://sentry.io/shields/
|
||||
[sentry-node]: https://www.npmjs.com/package/@sentry/node
|
||||
[raven]: https://www.npmjs.com/package/raven
|
||||
[sentry home]: https://sentry.io/shields/
|
||||
[sentry configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#sentry
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Shields is a community project that is stewarded by a handful of core maintainers who contribute on a volunteer basis. We do our best to maintain the availability and reliability of the service, and enhance and improve the project overall. However, if you've spotted something wrong or would like to see a specific feature implemented, please consider helping us resolve it by submitting a pull request. All community contributions, even documentation improvements, are welcome!
|
||||
|
||||
https://github.com/badges/shields is a monorepo and hosts the Shields frontend and server code as well as the [badge-maker][npm package] NPM library (and the [badge design specification](https://github.com/badges/shields/tree/master/spec)). The packaging and release processes for these items are described in the respective sections below.
|
||||
https://github.com/badges/shields is a monorepo and hosts the Shields frontend and server code as well as the [badge-maker][npm package] NPM library (and the [badge design specification](https://github.com/badges/shields/tree/master/spec)). The packaging and release processes for these items is described in the respective sections below.
|
||||
|
||||
## badge-maker package
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ This document describes how to host your own shields server either from source o
|
||||
|
||||
## Installing from Source
|
||||
|
||||
You will need Node 16 or later, which you can install using a
|
||||
You will need Node 14 or later, which you can install using a
|
||||
[package manager][].
|
||||
|
||||
On Ubuntu / Debian:
|
||||
|
||||
```sh
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -; sudo apt-get install -y nodejs
|
||||
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -; sudo apt-get install -y nodejs
|
||||
```
|
||||
|
||||
```sh
|
||||
@@ -94,18 +94,20 @@ Sending build context to Docker daemon 3.923 MB
|
||||
Successfully built 4471b442c220
|
||||
```
|
||||
|
||||
Optionally, alter the default values for configuration by setting them via [environment variables](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file).
|
||||
See [server-secrets.md](server-secrets.md) and [config/custom-environment-variables.yml](/config/custom-environment-variables.yml) for possible values.
|
||||
In [config/custom-environment-variables.yml](/config/custom-environment-variables.yml), environment variable names are specified as the quoted, uppercase key values (e.g. `GH_TOKEN`).
|
||||
Optionally, create a file called `shields.env` that contains the needed
|
||||
configuration. See [server-secrets.md](server-secrets.md) and [config/custom-environment-variables.yml](/config/custom-environment-variables.yml) for examples.
|
||||
|
||||
Then run the container, and be sure to specify the same mapped port as the one Shields is listening on :
|
||||
Then run the container:
|
||||
|
||||
```console
|
||||
$ docker run --rm -p 8080:8080 --env PORT=8080 --name shields shieldsio/shields:next
|
||||
$ docker run --rm -p 8080:80 --name shields shields
|
||||
# or if you have shields.env file, run the following instead
|
||||
$ docker run --rm -p 8080:80 --env-file shields.env --name shields shields
|
||||
|
||||
Configuration:
|
||||
...
|
||||
0916211515 Server is starting up: http://0.0.0.0:8080/
|
||||
> badge-maker@3.0.0 start /usr/src/app
|
||||
> node server.js
|
||||
|
||||
http://[::1]/
|
||||
```
|
||||
|
||||
Assuming Docker is running locally, you should be able to get to the
|
||||
@@ -115,11 +117,15 @@ If you run Docker in a virtual machine (such as boot2docker or Docker Machine)
|
||||
then you will need to replace `localhost` with the IP address of that virtual
|
||||
machine.
|
||||
|
||||
[shields.example.env]: ../shields.example.env
|
||||
|
||||
## Raster server
|
||||
|
||||
If you want to host PNG badges, you can also self-host a [raster server][]
|
||||
which points to your badge server. It's a docker container. We host it on
|
||||
Fly.io but should be possible to host on a wide variety of platforms.
|
||||
which points to your badge server. It's designed as a web function which is
|
||||
tested on Zeit Now, though you may be able to run it on AWS Lambda. It's
|
||||
built on the [micro][] framework, and comes with a `start` script that allows
|
||||
it to run as a standalone Node service.
|
||||
|
||||
- In your raster instance, set `BASE_URL` to your Shields instance, e.g.
|
||||
`https://shields.example.co`.
|
||||
@@ -128,9 +134,11 @@ Fly.io but should be possible to host on a wide variety of platforms.
|
||||
for the legacy raster URLs instead of 404's.
|
||||
|
||||
If anyone has set this up, more documentation on how to do this would be
|
||||
welcome!
|
||||
welcome! It would also be nice to ship a Docker image that includes a
|
||||
preconfigured raster server.
|
||||
|
||||
[raster server]: https://github.com/badges/squint
|
||||
[raster server]: https://github.com/badges/svg-to-image-proxy
|
||||
[micro]: https://github.com/zeit/micro
|
||||
|
||||
## Server secrets
|
||||
|
||||
|
||||
@@ -174,24 +174,6 @@ access to a private Jenkins CI instance.
|
||||
Provide a username and password to give your self-hosted Shields installation
|
||||
access to a private JIRA instance.
|
||||
|
||||
### Libraries.io/Bower
|
||||
|
||||
- `LIBRARIESIO_TOKENS` (yml: `private.librariesio_tokens`)
|
||||
|
||||
Note that the Bower badges utilize the Libraries.io API, so use this secret for both Libraries.io badges and/or Bower badges.
|
||||
|
||||
Just like the `*_ORIGINS` type secrets, this value can accept a single token as a string, or a group of tokens provided as an array of strings. For example:
|
||||
|
||||
```yaml
|
||||
private:
|
||||
librariesio_tokens: my-token
|
||||
## Or
|
||||
private:
|
||||
librariesio_tokens: [my-token some-other-token]
|
||||
```
|
||||
|
||||
When using the environment variable with multiple tokens, be sure to use a space to separate the tokens, e.g. `LIBRARIESIO_TOKENS="my-token some-other-token"`
|
||||
|
||||
### Nexus
|
||||
|
||||
- `NEXUS_ORIGINS` (yml: `public.services.nexus.authorizedOrigins`)
|
||||
@@ -211,21 +193,6 @@ installation access to private npm packages
|
||||
|
||||
[npm token]: https://docs.npmjs.com/getting-started/working_with_tokens
|
||||
|
||||
## Open Build Service
|
||||
|
||||
- `OBS_USER` (yml: `private.obs_user`)
|
||||
- `OBS_PASS` (yml: `private.obs_user`)
|
||||
|
||||
Only authenticated users are allowed to access the Open Build Service API.
|
||||
Authentication is done by sending a Basic HTTP Authorisation header. A user
|
||||
account for the [reference instance](https://build.opensuse.org) is a SUSE
|
||||
IdP account, which can be created [here](https://idp-portal.suse.com/univention/self-service/#page=createaccount).
|
||||
|
||||
While OBS supports [API tokens](https://openbuildservice.org/help/manuals/obs-user-guide/cha.obs.authorization.token.html#id-1.5.10.16.4),
|
||||
they can only be scoped to execute specific actions on a POST request. This
|
||||
means however, that an actual account is required to read the build status
|
||||
of a package.
|
||||
|
||||
### SymfonyInsight (formerly Sensiolabs)
|
||||
|
||||
- `SL_INSIGHT_USER_UUID` (yml: `private.sl_insight_userUuid`)
|
||||
|
||||
@@ -67,7 +67,7 @@ t.create('Build status')
|
||||
- All badges on shields can be requested in a number of formats. As well as calling https://img.shields.io/wercker/build/wercker/go-wercker-api.svg to generate  we can also call https://img.shields.io/wercker/build/wercker/go-wercker-api.json to request the same content as JSON. When writing service tests, we request the badge in JSON format so it is easier to make assertions about the content.
|
||||
- We don't need to explicitly call `/wercker/build/wercker/go-wercker-api.json` here, only `/build/wercker/go-wercker-api.json`. When we create a tester object with `createServiceTester()` the URL base defined in our service class (in this case `/wercker`) is used as the base URL for any requests made by the tester object.
|
||||
3. `expectBadge()` is a helper function which accepts either a string literal, a [RegExp][] or a [Joi][] schema for the different fields.
|
||||
Joi is a validation library that is built into IcedFrisby which you can use to
|
||||
Joi is a validation library that is build into IcedFrisby which you can use to
|
||||
match based on a set of allowed strings, regexes, or specific values. You can
|
||||
refer to their [API reference][joi api].
|
||||
4. We expect `label` to be a string literal `"build"`.
|
||||
@@ -254,7 +254,7 @@ By checking code coverage, we can make sure we've covered all our bases.
|
||||
We can generate a coverage report and open it:
|
||||
|
||||
```
|
||||
npm run coverage:test:services -- -- --only=wercker
|
||||
npm run coverage:test:services -- --only=wercker
|
||||
npm run coverage:report:open
|
||||
```
|
||||
|
||||
|
||||
@@ -43,12 +43,9 @@ function Example({
|
||||
exampleData: RenderableExample
|
||||
isBadgeSuggestion: boolean
|
||||
}): JSX.Element {
|
||||
const handleClick = React.useCallback(
|
||||
function (): void {
|
||||
onClick(exampleData, isBadgeSuggestion)
|
||||
},
|
||||
[exampleData, isBadgeSuggestion, onClick]
|
||||
)
|
||||
function handleClick(): void {
|
||||
onClick(exampleData, isBadgeSuggestion)
|
||||
}
|
||||
|
||||
let exampleUrl, previewUrl
|
||||
if (isBadgeSuggestion) {
|
||||
|
||||
@@ -50,16 +50,13 @@ function _CopiedContentIndicator(
|
||||
},
|
||||
}))
|
||||
|
||||
const handlePoseComplete = React.useCallback(
|
||||
function (): void {
|
||||
if (pose === 'effectStart') {
|
||||
setPose('effectEnd')
|
||||
} else {
|
||||
setPose('hidden')
|
||||
}
|
||||
},
|
||||
[pose, setPose]
|
||||
)
|
||||
function handlePoseComplete(): void {
|
||||
if (pose === 'effectStart') {
|
||||
setPose('effectEnd')
|
||||
} else {
|
||||
setPose('hidden')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentAnchor>
|
||||
|
||||
@@ -40,13 +40,10 @@ export default function Customizer({
|
||||
const [markup, setMarkup] = useState<string>()
|
||||
const [message, setMessage] = useState<string>()
|
||||
|
||||
const generateBuiltBadgeUrl = React.useCallback(
|
||||
function (): string {
|
||||
const suffix = queryString ? `?${queryString}` : ''
|
||||
return `${baseUrl}${path}${suffix}`
|
||||
},
|
||||
[baseUrl, path, queryString]
|
||||
)
|
||||
function generateBuiltBadgeUrl(): string {
|
||||
const suffix = queryString ? `?${queryString}` : ''
|
||||
return `${baseUrl}${path}${suffix}`
|
||||
}
|
||||
|
||||
function renderLivePreview(): JSX.Element {
|
||||
// There are some usability issues here. It would be better if the message
|
||||
@@ -70,31 +67,28 @@ export default function Customizer({
|
||||
)
|
||||
}
|
||||
|
||||
const copyMarkup = React.useCallback(
|
||||
async function (markupFormat: MarkupFormat): Promise<void> {
|
||||
const builtBadgeUrl = generateBuiltBadgeUrl()
|
||||
const markup = generateMarkup({
|
||||
badgeUrl: builtBadgeUrl,
|
||||
link,
|
||||
title,
|
||||
markupFormat,
|
||||
})
|
||||
|
||||
try {
|
||||
await clipboardCopy(markup)
|
||||
} catch (e) {
|
||||
setMessage('Copy failed')
|
||||
setMarkup(markup)
|
||||
return
|
||||
}
|
||||
async function copyMarkup(markupFormat: MarkupFormat): Promise<void> {
|
||||
const builtBadgeUrl = generateBuiltBadgeUrl()
|
||||
const markup = generateMarkup({
|
||||
badgeUrl: builtBadgeUrl,
|
||||
link,
|
||||
title,
|
||||
markupFormat,
|
||||
})
|
||||
|
||||
try {
|
||||
await clipboardCopy(markup)
|
||||
} catch (e) {
|
||||
setMessage('Copy failed')
|
||||
setMarkup(markup)
|
||||
if (indicatorRef.current) {
|
||||
indicatorRef.current.trigger()
|
||||
}
|
||||
},
|
||||
[generateBuiltBadgeUrl, link, title, setMessage, setMarkup]
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setMarkup(markup)
|
||||
if (indicatorRef.current) {
|
||||
indicatorRef.current.trigger()
|
||||
}
|
||||
}
|
||||
|
||||
function renderMarkupAndLivePreview(): JSX.Element {
|
||||
return (
|
||||
@@ -116,32 +110,26 @@ export default function Customizer({
|
||||
)
|
||||
}
|
||||
|
||||
const handlePathChange = React.useCallback(
|
||||
function ({
|
||||
path,
|
||||
isComplete,
|
||||
}: {
|
||||
path: string
|
||||
isComplete: boolean
|
||||
}): void {
|
||||
setPath(path)
|
||||
setPathIsComplete(isComplete)
|
||||
},
|
||||
[setPath, setPathIsComplete]
|
||||
)
|
||||
function handlePathChange({
|
||||
path,
|
||||
isComplete,
|
||||
}: {
|
||||
path: string
|
||||
isComplete: boolean
|
||||
}): void {
|
||||
setPath(path)
|
||||
setPathIsComplete(isComplete)
|
||||
}
|
||||
|
||||
const handleQueryStringChange = React.useCallback(
|
||||
function ({
|
||||
queryString,
|
||||
isComplete,
|
||||
}: {
|
||||
queryString: string
|
||||
isComplete: boolean
|
||||
}): void {
|
||||
setQueryString(queryString)
|
||||
},
|
||||
[setQueryString]
|
||||
)
|
||||
function handleQueryStringChange({
|
||||
queryString,
|
||||
isComplete,
|
||||
}: {
|
||||
queryString: string
|
||||
isComplete: boolean
|
||||
}): void {
|
||||
setQueryString(queryString)
|
||||
}
|
||||
|
||||
return (
|
||||
<form action="">
|
||||
|
||||
@@ -149,17 +149,14 @@ export default function PathBuilder({
|
||||
}
|
||||
}, [tokens, namedParams, onChange])
|
||||
|
||||
const handleTokenChange = React.useCallback(
|
||||
function ({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
|
||||
setNamedParams({
|
||||
...namedParams,
|
||||
[name]: value,
|
||||
})
|
||||
},
|
||||
[setNamedParams, namedParams]
|
||||
)
|
||||
function handleTokenChange({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
|
||||
setNamedParams({
|
||||
...namedParams,
|
||||
[name]: value,
|
||||
})
|
||||
}
|
||||
|
||||
function renderLiteral(
|
||||
literal: string,
|
||||
|
||||
@@ -270,24 +270,18 @@ export default function QueryStringBuilder({
|
||||
}, {} as Record<BadgeOptionName, string>)
|
||||
)
|
||||
|
||||
const handleServiceQueryParamChange = React.useCallback(
|
||||
function ({
|
||||
target: { name, type: targetType, checked, value },
|
||||
}: ChangeEvent<HTMLInputElement>): void {
|
||||
const outValue = targetType === 'checkbox' ? checked : value
|
||||
setQueryParams({ ...queryParams, [name]: outValue })
|
||||
},
|
||||
[setQueryParams, queryParams]
|
||||
)
|
||||
function handleServiceQueryParamChange({
|
||||
target: { name, type: targetType, checked, value },
|
||||
}: ChangeEvent<HTMLInputElement>): void {
|
||||
const outValue = targetType === 'checkbox' ? checked : value
|
||||
setQueryParams({ ...queryParams, [name]: outValue })
|
||||
}
|
||||
|
||||
const handleBadgeOptionChange = React.useCallback(
|
||||
function ({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement>): void {
|
||||
setBadgeOptions({ ...badgeOptions, [name]: value })
|
||||
},
|
||||
[setBadgeOptions, badgeOptions]
|
||||
)
|
||||
function handleBadgeOptionChange({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement>): void {
|
||||
setBadgeOptions({ ...badgeOptions, [name]: value })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user