Compare commits
5 Commits
server-202
...
github-oau
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6e31d7f32 | ||
|
|
3aadb79325 | ||
|
|
b8412fd80b | ||
|
|
345188e34b | ||
|
|
a92dc72ff5 |
@@ -6,8 +6,7 @@ main_steps: &main_steps
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
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.
|
||||
@@ -48,8 +47,7 @@ integration_steps: &integration_steps
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -70,8 +68,7 @@ services_steps: &services_steps
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
@@ -113,7 +110,6 @@ package_steps: &package_steps
|
||||
MOCHA_FILE: junit/badge-maker/v12/results.xml
|
||||
NODE_VERSION: v12
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
name: Run package tests on Node 12
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
@@ -123,7 +119,6 @@ package_steps: &package_steps
|
||||
MOCHA_FILE: junit/badge-maker/v14/results.xml
|
||||
NODE_VERSION: v14
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
name: Run package tests on Node 14
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
@@ -142,37 +137,33 @@ package_steps: &package_steps
|
||||
jobs:
|
||||
main:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: circleci/node:14
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
main@node-17:
|
||||
main@node-16:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
- image: circleci/node:16
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
integration:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: circleci/node:14
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
integration@node-17:
|
||||
integration@node-16:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
- image: circleci/node:16
|
||||
- image: redis
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
danger:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: circleci/node:14
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
@@ -192,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
|
||||
|
||||
@@ -229,29 +218,25 @@ jobs:
|
||||
command: npm run build
|
||||
|
||||
package:
|
||||
machine:
|
||||
image: 'ubuntu-2004:202111-02'
|
||||
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
|
||||
|
||||
@@ -262,8 +247,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
command: npm ci
|
||||
|
||||
- run:
|
||||
name: Frontend build
|
||||
@@ -301,11 +285,11 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- main@node-17:
|
||||
- main@node-16:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- integration@node-17:
|
||||
- integration@node-16:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
@@ -323,7 +307,7 @@ workflows:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- services@node-17:
|
||||
- services@node-16:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
294
.github/actions/close-bot/package-lock.json
generated
vendored
294
.github/actions/close-bot/package-lock.json
generated
vendored
@@ -9,53 +9,50 @@
|
||||
"version": "0.0.0",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.9.0",
|
||||
"@actions/github": "^5.0.3"
|
||||
"@actions/core": "^1.5.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1"
|
||||
}
|
||||
"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.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
|
||||
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
|
||||
"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",
|
||||
@@ -63,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",
|
||||
@@ -73,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": {
|
||||
@@ -111,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",
|
||||
@@ -134,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",
|
||||
@@ -160,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": {
|
||||
@@ -186,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",
|
||||
@@ -204,20 +185,6 @@
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"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",
|
||||
@@ -226,48 +193,45 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1"
|
||||
}
|
||||
"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.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
|
||||
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
|
||||
"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",
|
||||
@@ -275,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",
|
||||
@@ -285,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",
|
||||
@@ -340,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",
|
||||
@@ -363,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",
|
||||
@@ -378,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",
|
||||
@@ -393,20 +349,6 @@
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"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.9.0",
|
||||
"@actions/github": "^5.0.3"
|
||||
"@actions/core": "^1.5.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
2
.github/workflows/auto-close.yml
vendored
2
.github/workflows/auto-close.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
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
|
||||
|
||||
11
.github/workflows/build-docker-image.yml
vendored
11
.github/workflows/build-docker-image.yml
vendored
@@ -7,19 +7,14 @@ jobs:
|
||||
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 }}
|
||||
|
||||
15
.github/workflows/deploy-docs.yml
vendored
15
.github/workflows/deploy-docs.yml
vendored
@@ -3,16 +3,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
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
|
||||
|
||||
6
.github/workflows/draft-release.yml
vendored
6
.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:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Draft Release
|
||||
uses: ./.github/actions/draft-release
|
||||
|
||||
11
.github/workflows/enforce-dependency-review.yml
vendored
11
.github/workflows/enforce-dependency-review.yml
vendored
@@ -1,11 +0,0 @@
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v2
|
||||
13
.github/workflows/publish-docker-next.yml
vendored
13
.github/workflows/publish-docker-next.yml
vendored
@@ -9,25 +9,20 @@ jobs:
|
||||
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 }}
|
||||
|
||||
159
CHANGELOG.md
159
CHANGELOG.md
@@ -4,165 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
|
||||
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": {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
@@ -171,14 +169,12 @@ const privateConfigSchema = Joi.object({
|
||||
jira_pass: 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(),
|
||||
@@ -201,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.
|
||||
@@ -251,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) {
|
||||
@@ -309,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())
|
||||
@@ -435,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,
|
||||
@@ -522,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()
|
||||
@@ -553,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"
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -86,30 +86,24 @@ export default function GetMarkupButton({
|
||||
Select<Option>
|
||||
>
|
||||
|
||||
const onControlMouseDown = React.useCallback(
|
||||
async function (event: MouseEvent): Promise<void> {
|
||||
if (onMarkupRequested) {
|
||||
await onMarkupRequested('link')
|
||||
}
|
||||
if (selectRef.current) {
|
||||
selectRef.current.blur()
|
||||
}
|
||||
},
|
||||
[onMarkupRequested, selectRef]
|
||||
)
|
||||
async function onControlMouseDown(event: MouseEvent): Promise<void> {
|
||||
if (onMarkupRequested) {
|
||||
await onMarkupRequested('link')
|
||||
}
|
||||
if (selectRef.current) {
|
||||
selectRef.current.blur()
|
||||
}
|
||||
}
|
||||
|
||||
const onOptionClick = React.useCallback(
|
||||
async function onOptionClick(
|
||||
// Eeesh.
|
||||
value: Option | readonly Option[] | null | undefined
|
||||
): Promise<void> {
|
||||
const { value: markupFormat } = value as Option
|
||||
if (onMarkupRequested) {
|
||||
await onMarkupRequested(markupFormat)
|
||||
}
|
||||
},
|
||||
[onMarkupRequested]
|
||||
)
|
||||
async function onOptionClick(
|
||||
// Eeesh.
|
||||
value: Option | readonly Option[] | null | undefined
|
||||
): Promise<void> {
|
||||
const { value: markupFormat } = value as Option
|
||||
if (onMarkupRequested) {
|
||||
await onMarkupRequested(markupFormat)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// TODO It doesn't seem to be possible to check the types and wrap with
|
||||
|
||||
@@ -44,39 +44,33 @@ export default function DynamicBadgeMaker({
|
||||
const isValid =
|
||||
values.datatype && values.label && values.dataUrl && values.query
|
||||
|
||||
const onChange = React.useCallback(
|
||||
function ({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
|
||||
setValues({
|
||||
...values,
|
||||
[name]: value,
|
||||
})
|
||||
},
|
||||
[values]
|
||||
)
|
||||
function onChange({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
|
||||
setValues({
|
||||
...values,
|
||||
[name]: value,
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = React.useCallback(
|
||||
function onSubmit(e: React.FormEvent): void {
|
||||
e.preventDefault()
|
||||
function onSubmit(e: React.FormEvent): void {
|
||||
e.preventDefault()
|
||||
|
||||
const { datatype, label, dataUrl, query, color, prefix, suffix } = values
|
||||
window.open(
|
||||
dynamicBadgeUrl({
|
||||
baseUrl,
|
||||
datatype,
|
||||
label,
|
||||
dataUrl,
|
||||
query,
|
||||
color,
|
||||
prefix,
|
||||
suffix,
|
||||
}),
|
||||
'_blank'
|
||||
)
|
||||
},
|
||||
[baseUrl, values]
|
||||
)
|
||||
const { datatype, label, dataUrl, query, color, prefix, suffix } = values
|
||||
window.open(
|
||||
dynamicBadgeUrl({
|
||||
baseUrl,
|
||||
datatype,
|
||||
label,
|
||||
dataUrl,
|
||||
query,
|
||||
color,
|
||||
prefix,
|
||||
suffix,
|
||||
}),
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
|
||||
@@ -54,28 +54,24 @@ export default function Main({
|
||||
const searchTimeout = useRef(0)
|
||||
const baseUrl = getBaseUrl()
|
||||
|
||||
const performSearch = React.useCallback(
|
||||
function (query: string): void {
|
||||
setSearchIsInProgress(false)
|
||||
function performSearch(query: string): void {
|
||||
setSearchIsInProgress(false)
|
||||
|
||||
setQueryIsTooShort(query.length === 1)
|
||||
setQueryIsTooShort(query.length === 1)
|
||||
|
||||
if (query.length >= 2) {
|
||||
const flat = ServiceDefinitionSetHelper.create(services)
|
||||
.notDeprecated()
|
||||
.search(query)
|
||||
.toArray()
|
||||
setSearchResults(groupBy(flat, 'category'))
|
||||
} else {
|
||||
setSearchResults(undefined)
|
||||
}
|
||||
},
|
||||
[setSearchIsInProgress, setQueryIsTooShort, setSearchResults]
|
||||
)
|
||||
if (query.length >= 2) {
|
||||
const flat = ServiceDefinitionSetHelper.create(services)
|
||||
.notDeprecated()
|
||||
.search(query)
|
||||
.toArray()
|
||||
setSearchResults(groupBy(flat, 'category'))
|
||||
} else {
|
||||
setSearchResults(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const searchQueryChanged = React.useCallback(
|
||||
function (query: string): void {
|
||||
/*
|
||||
function searchQueryChanged(query: string): void {
|
||||
/*
|
||||
Add a small delay before showing search results
|
||||
so that we wait until the user has stopped typing
|
||||
before we start loading stuff.
|
||||
@@ -85,27 +81,22 @@ export default function Main({
|
||||
b) stops the page from 'flashing' as the user types, like this:
|
||||
https://user-images.githubusercontent.com/7288322/42600206-9b278470-85b5-11e8-9f63-eb4a0c31cb4a.gif
|
||||
*/
|
||||
setSearchIsInProgress(true)
|
||||
window.clearTimeout(searchTimeout.current)
|
||||
searchTimeout.current = window.setTimeout(() => performSearch(query), 500)
|
||||
},
|
||||
[setSearchIsInProgress, performSearch]
|
||||
)
|
||||
setSearchIsInProgress(true)
|
||||
window.clearTimeout(searchTimeout.current)
|
||||
searchTimeout.current = window.setTimeout(() => performSearch(query), 500)
|
||||
}
|
||||
|
||||
const exampleClicked = React.useCallback(
|
||||
function (example: RenderableExample, isSuggestion: boolean): void {
|
||||
setSelectedExample(example)
|
||||
setSelectedExampleIsSuggestion(isSuggestion)
|
||||
},
|
||||
[setSelectedExample, setSelectedExampleIsSuggestion]
|
||||
)
|
||||
function exampleClicked(
|
||||
example: RenderableExample,
|
||||
isSuggestion: boolean
|
||||
): void {
|
||||
setSelectedExample(example)
|
||||
setSelectedExampleIsSuggestion(isSuggestion)
|
||||
}
|
||||
|
||||
const dismissMarkupModal = React.useCallback(
|
||||
function (): void {
|
||||
setSelectedExample(undefined)
|
||||
},
|
||||
[setSelectedExample]
|
||||
)
|
||||
function dismissMarkupModal(): void {
|
||||
setSelectedExample(undefined)
|
||||
}
|
||||
|
||||
function Category({
|
||||
category,
|
||||
|
||||
@@ -18,27 +18,21 @@ export default function StaticBadgeMaker({
|
||||
|
||||
const isValid = values.message && values.color
|
||||
|
||||
const onChange = React.useCallback(
|
||||
function onChange({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
|
||||
setValues({
|
||||
...values,
|
||||
[name]: value,
|
||||
})
|
||||
},
|
||||
[setValues, values]
|
||||
)
|
||||
function onChange({
|
||||
target: { name, value },
|
||||
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
|
||||
setValues({
|
||||
...values,
|
||||
[name]: value,
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = React.useCallback(
|
||||
function (e: React.FormEvent): void {
|
||||
e.preventDefault()
|
||||
function onSubmit(e: React.FormEvent): void {
|
||||
e.preventDefault()
|
||||
|
||||
const { label, message, color } = values
|
||||
window.open(staticBadgeUrl({ baseUrl, label, message, color }), '_blank')
|
||||
},
|
||||
[baseUrl, values]
|
||||
)
|
||||
const { label, message, color } = values
|
||||
window.open(staticBadgeUrl({ baseUrl, label, message, color }), '_blank')
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
|
||||
@@ -41,47 +41,41 @@ export default function SuggestionAndSearch({
|
||||
const [projectUrl, setProjectUrl] = useState<string>()
|
||||
const [suggestions, setSuggestions] = useState<SuggestionItem[]>([])
|
||||
|
||||
const onQueryChanged = React.useCallback(
|
||||
function ({
|
||||
target: { value: query },
|
||||
}: ChangeEvent<HTMLInputElement>): void {
|
||||
const isUrl = query.startsWith('https://') || query.startsWith('http://')
|
||||
setIsUrl(isUrl)
|
||||
setProjectUrl(isUrl ? query : undefined)
|
||||
function onQueryChanged({
|
||||
target: { value: query },
|
||||
}: ChangeEvent<HTMLInputElement>): void {
|
||||
const isUrl = query.startsWith('https://') || query.startsWith('http://')
|
||||
setIsUrl(isUrl)
|
||||
setProjectUrl(isUrl ? query : undefined)
|
||||
|
||||
queryChangedDebounced.current(query)
|
||||
},
|
||||
[setIsUrl, setProjectUrl, queryChangedDebounced]
|
||||
)
|
||||
queryChangedDebounced.current(query)
|
||||
}
|
||||
|
||||
const getSuggestions = React.useCallback(
|
||||
async function (): Promise<void> {
|
||||
if (!projectUrl) {
|
||||
setSuggestions([])
|
||||
return
|
||||
}
|
||||
async function getSuggestions(): Promise<void> {
|
||||
if (!projectUrl) {
|
||||
setSuggestions([])
|
||||
return
|
||||
}
|
||||
|
||||
setInProgress(true)
|
||||
setInProgress(true)
|
||||
|
||||
const fetch = window.fetch || fetchPonyfill
|
||||
const res = await fetch(
|
||||
`${baseUrl}/$suggest/v1?url=${encodeURIComponent(projectUrl)}`
|
||||
)
|
||||
let suggestions = [] as SuggestionItem[]
|
||||
try {
|
||||
const json = (await res.json()) as SuggestionResponse
|
||||
// This doesn't validate the response. The default value here prevents
|
||||
// a crash if the server returns {"err":"Disallowed"}.
|
||||
suggestions = json.suggestions || []
|
||||
} catch (e) {
|
||||
suggestions = []
|
||||
}
|
||||
const fetch = window.fetch || fetchPonyfill
|
||||
const res = await fetch(
|
||||
`${baseUrl}/$suggest/v1?url=${encodeURIComponent(projectUrl)}`
|
||||
)
|
||||
let suggestions = [] as SuggestionItem[]
|
||||
try {
|
||||
const json = (await res.json()) as SuggestionResponse
|
||||
// This doesn't validate the response. The default value here prevents
|
||||
// a crash if the server returns {"err":"Disallowed"}.
|
||||
suggestions = json.suggestions || []
|
||||
} catch (e) {
|
||||
suggestions = []
|
||||
}
|
||||
|
||||
setInProgress(false)
|
||||
setSuggestions(suggestions)
|
||||
},
|
||||
[setSuggestions, setInProgress, baseUrl, projectUrl]
|
||||
)
|
||||
setInProgress(false)
|
||||
setSuggestions(suggestions)
|
||||
}
|
||||
|
||||
function renderSuggestions(): JSX.Element | null {
|
||||
if (suggestions.length === 0) {
|
||||
@@ -111,8 +105,6 @@ export default function SuggestionAndSearch({
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Warning: A future version of React will block javascript: URLs as a security precaution
|
||||
// how else to do this?
|
||||
return (
|
||||
<section>
|
||||
<form action="javascript:void 0" autoComplete="off">
|
||||
|
||||
@@ -21,14 +21,11 @@ export function getBaseUrl(): string {
|
||||
https://img.shields.io/
|
||||
*/
|
||||
try {
|
||||
const { protocol, hostname, port } = window.location
|
||||
const { protocol, hostname } = window.location
|
||||
if (['shields.io', 'www.shields.io'].includes(hostname)) {
|
||||
return 'https://img.shields.io'
|
||||
}
|
||||
if (!port) {
|
||||
return `${protocol}//${hostname}`
|
||||
}
|
||||
return `${protocol}//${hostname}:${port}`
|
||||
return `${protocol}//${hostname}`
|
||||
} catch (e) {
|
||||
// server-side rendering
|
||||
return ''
|
||||
|
||||
@@ -5,6 +5,7 @@ import Meta from '../components/meta'
|
||||
import Header from '../components/header'
|
||||
import Footer from '../components/footer'
|
||||
import { BaseFont, GlobalStyle, H3 } from '../components/common'
|
||||
import Heroku from '../../static/images/heroku-logotype-horizontal-purple.svg'
|
||||
import NodePing from '../../static/images/nodeping.svg'
|
||||
import Sentry from '../../static/images/sentry-logo-black.svg'
|
||||
const MainContainer = styled(BaseFont)`
|
||||
@@ -42,6 +43,11 @@ export default function SponsorsPage(): JSX.Element {
|
||||
❤️ These companies help us by donating their services to shields:
|
||||
<ul style={{ listStyleType: 'none' }}>
|
||||
<SponsorItems>
|
||||
<li>
|
||||
<a href="https://www.heroku.com/">
|
||||
<Heroku alt="heroku_logo" height={120} />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://nodeping.com/">
|
||||
<NodePing alt="nodeping_logo" height={60} />
|
||||
|
||||
@@ -134,7 +134,7 @@ export default function EndpointPage(): JSX.Element {
|
||||
</p>
|
||||
<p>
|
||||
The endpoint badge is a better alternative than redirecting to the
|
||||
static badge endpoint or generating SVG on your server:
|
||||
static badge enpoint or generating SVG on your server:
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
@@ -142,7 +142,7 @@ export default function EndpointPage(): JSX.Element {
|
||||
Content and presentation are separate.
|
||||
</a>{' '}
|
||||
The service provider authors the badge, and Shields takes input from
|
||||
the user to format it. As a service provider, you author the badge
|
||||
the user to format it. As a service provider you author the badge
|
||||
but don't have to concern yourself with styling. You don't even have
|
||||
to pass the formatting options through to Shields.
|
||||
</li>
|
||||
@@ -152,12 +152,12 @@ export default function EndpointPage(): JSX.Element {
|
||||
</li>
|
||||
<li>
|
||||
A JSON response is easy to implement; easier than an HTTP redirect.
|
||||
It is trivial in almost any framework and is more compatible with
|
||||
It is trivial in almost any framework, and is more compatible with
|
||||
hosting environments such as{' '}
|
||||
<a href="https://runkit.com/docs/endpoint">RunKit endpoints</a>.
|
||||
</li>
|
||||
<li>
|
||||
As a service provider, you can rely on the Shields CDN. There's no
|
||||
As a service provider you can rely on the Shields CDN. There's no
|
||||
need to study the HTTP headers. Adjusting cache behavior is as
|
||||
simple as setting a property in the JSON response.
|
||||
</li>
|
||||
@@ -197,7 +197,7 @@ export default function EndpointPage(): JSX.Element {
|
||||
<dd>
|
||||
Default: <code>false</code>. <code>true</code> to treat this as an
|
||||
error badge. This prevents the user from overriding the color. In the
|
||||
future, it may affect cache behavior.
|
||||
future it may affect cache behavior.
|
||||
</dd>
|
||||
<dt>namedLogo</dt>
|
||||
<dd>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as originalSimpleIcons from 'simple-icons/icons'
|
||||
import originalSimpleIcons from 'simple-icons'
|
||||
import { svg2base64 } from './svg-helpers.js'
|
||||
|
||||
function loadSimpleIcons() {
|
||||
@@ -14,10 +14,10 @@ function loadSimpleIcons() {
|
||||
// https://github.com/badges/shields/issues/4273
|
||||
Object.keys(originalSimpleIcons).forEach(key => {
|
||||
const icon = originalSimpleIcons[key]
|
||||
const { title, slug, hex } = icon
|
||||
|
||||
const title = icon.title.toLowerCase()
|
||||
const legacyTitle = title.replace(/ /g, '-')
|
||||
icon.base64 = {
|
||||
default: svg2base64(icon.svg.replace('<svg', `<svg fill="#${hex}"`)),
|
||||
default: svg2base64(icon.svg.replace('<svg', `<svg fill="#${icon.hex}"`)),
|
||||
light: svg2base64(icon.svg.replace('<svg', `<svg fill="whitesmoke"`)),
|
||||
dark: svg2base64(icon.svg.replace('<svg', `<svg fill="#333"`)),
|
||||
}
|
||||
@@ -26,17 +26,14 @@ function loadSimpleIcons() {
|
||||
// (e.g. 'Hive'). If a by-title reference we generate for
|
||||
// backwards compatibility collides with a proper slug from Simple Icons
|
||||
// then do nothing, so that the proper slug will always map to the correct icon.
|
||||
// Starting in v7, the exported object with the full icon set has updated the keys
|
||||
// to include a lowercase `si` prefix, and utilizes proper case naming conventions.
|
||||
if (!(`si${title}` in originalSimpleIcons)) {
|
||||
simpleIcons[title.toLowerCase()] = icon
|
||||
if (!(title in originalSimpleIcons)) {
|
||||
simpleIcons[title] = icon
|
||||
}
|
||||
const legacyTitle = title.replace(/ /g, '-')
|
||||
if (!(`si${legacyTitle}` in originalSimpleIcons)) {
|
||||
simpleIcons[legacyTitle.toLowerCase()] = icon
|
||||
if (!(legacyTitle in originalSimpleIcons)) {
|
||||
simpleIcons[legacyTitle] = icon
|
||||
}
|
||||
|
||||
simpleIcons[slug] = icon
|
||||
simpleIcons[key] = icon
|
||||
})
|
||||
return simpleIcons
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="93 93 194 194"><defs><style>.b{fill:#fc6d26}</style></defs><path style="fill:#e24329" d="m282.83 170.73-.27-.69-26.14-68.22a6.81 6.81 0 0 0-2.69-3.24 7 7 0 0 0-8 .43 7 7 0 0 0-2.32 3.52l-17.65 54h-71.47l-17.65-54a6.86 6.86 0 0 0-2.32-3.53 7 7 0 0 0-8-.43 6.87 6.87 0 0 0-2.69 3.24L97.44 170l-.26.69a48.54 48.54 0 0 0 16.1 56.1l.09.07.24.17 39.82 29.82 19.7 14.91 12 9.06a8.07 8.07 0 0 0 9.76 0l12-9.06 19.7-14.91 40.06-30 .1-.08a48.56 48.56 0 0 0 16.08-56.04Z"/><path class="b" d="m282.83 170.73-.27-.69a88.3 88.3 0 0 0-35.15 15.8L190 229.25c19.55 14.79 36.57 27.64 36.57 27.64l40.06-30 .1-.08a48.56 48.56 0 0 0 16.1-56.08Z"/><path style="fill:#fca326" d="m153.43 256.89 19.7 14.91 12 9.06a8.07 8.07 0 0 0 9.76 0l12-9.06 19.7-14.91S209.55 244 190 229.25c-19.55 14.75-36.57 27.64-36.57 27.64Z"/><path class="b" d="M132.58 185.84A88.19 88.19 0 0 0 97.44 170l-.26.69a48.54 48.54 0 0 0 16.1 56.1l.09.07.24.17 39.82 29.82L190 229.21Z"/></svg>
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.956 13.587l-1.344-4.133a4549.814 4549.814 0 0 0-2.663-8.189.456.456 0 0 0-.87 0l-2.658 8.189H7.585L4.92 1.265a.456.456 0 0 0-.87 0A4549.814 4549.814 0 0 0 .044 13.587a.908.908 0 0 0 .336 1.02L12 23.054l11.62-8.447a.908.908 0 0 0 .336-1.02" fill="#fc6d26"/><path d="M12 23.054l4.421-13.6H7.58z" fill="#e24329"/><path d="M7.579 9.454H1.388L12 23.054z" fill="#fc6d26"/><path d="M1.388 9.454L.044 13.587a.908.908 0 0 0 .336 1.02L12 23.054z" fill="#fca326"/><path d="M7.579 9.454L4.92 1.265a.456.456 0 0 0-.87 0L1.388 9.454z" fill="#e24329"/><path d="M16.421 9.454h6.191L12 23.054z" fill="#fc6d26"/><path d="M22.612 9.454l1.344 4.133a.908.908 0 0 1-.336 1.02L12 23.054z" fill="#fca326"/><path d="M16.421 9.454l2.658-8.189a.456.456 0 0 1 .87 0l2.663 8.189z" fill="#e24329"/></svg>
|
||||
|
Before Width: | Height: | Size: 986 B After Width: | Height: | Size: 847 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12Z" fill="url(#a)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.425 11.871a796.414 796.414 0 0 1 6.994-3.018c3.328-1.388 4.027-1.628 4.477-1.638.1 0 .32.02.47.14.12.1.15.23.17.33.02.1.04.31.02.47-.18 1.898-.96 6.504-1.36 8.622-.17.9-.5 1.199-.819 1.229-.7.06-1.229-.46-1.898-.9-1.06-.689-1.649-1.119-2.678-1.798-1.19-.78-.42-1.209.26-1.908.18-.18 3.247-2.978 3.307-3.228.01-.03.01-.15-.06-.21-.07-.06-.17-.04-.25-.02-.11.02-1.788 1.14-5.056 3.348-.48.33-.909.49-1.299.48-.43-.01-1.248-.24-1.868-.44-.75-.24-1.349-.37-1.299-.79.03-.22.33-.44.89-.669Z" fill="#fff"/><defs><linearGradient id="a" x1="11.99" y1="0" x2="11.99" y2="23.81" gradientUnits="userSpaceOnUse"><stop stop-color="#2AABEE"/><stop offset="1" stop-color="#229ED9"/></linearGradient></defs></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="12" fill="#2ca5e0"/><path d="M9.8 17.5c-.389 0-.323-.147-.457-.517L8.2 13.221 17 8" fill="#a9c9dd"/><path d="M9.8 17.5c.3 0 .433-.137.6-.3l1.6-1.556-1.996-1.203" fill="#c8daea"/><path d="M10.004 14.441l4.836 3.573c.552.304.95.147 1.088-.512l1.968-9.277c.202-.808-.308-1.174-.836-.935L5.501 11.748c-.789.316-.784.756-.144.952l2.967.926 6.867-4.332c.324-.197.622-.091.377.125" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 909 B After Width: | Height: | Size: 481 B |
24138
package-lock.json
generated
24138
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
179
package.json
179
package.json
@@ -12,7 +12,8 @@
|
||||
],
|
||||
"homepage": "https://shields.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/badges/shields/issues"
|
||||
"url": "https://github.com/badges/shields/issues",
|
||||
"email": "thaddee.tyl@gmail.com"
|
||||
},
|
||||
"license": "CC0-1.0",
|
||||
"author": "Thaddée Tyl <thaddee.tyl@gmail.com>",
|
||||
@@ -21,48 +22,48 @@
|
||||
"url": "https://github.com/badges/shields"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/lato": "^4.5.8",
|
||||
"@fontsource/lekton": "^4.5.9",
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@sentry/node": "^7.8.0",
|
||||
"@fontsource/lato": "^4.5.0",
|
||||
"@fontsource/lekton": "^4.5.0",
|
||||
"@sentry/node": "^6.12.0",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.2",
|
||||
"camelcase": "^7.0.0",
|
||||
"chalk": "^5.0.1",
|
||||
"check-node-version": "^4.2.1",
|
||||
"bytes": "^3.1.0",
|
||||
"camelcase": "^6.2.0",
|
||||
"chalk": "^4.1.2",
|
||||
"check-node-version": "^4.1.0",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.7",
|
||||
"config": "^3.3.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.4",
|
||||
"decamelize": "^3.2.0",
|
||||
"emojic": "^1.1.17",
|
||||
"decamelize": "^5.0.0",
|
||||
"emojic": "^1.1.16",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^4.0.9",
|
||||
"glob": "^8.0.3",
|
||||
"fast-xml-parser": "^3.20.0",
|
||||
"glob": "^7.1.7",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^12.3.0",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"ioredis": "5.2.2",
|
||||
"joi": "17.6.0",
|
||||
"got": "11.8.2",
|
||||
"graphql": "^15.5.3",
|
||||
"graphql-tag": "^2.12.5",
|
||||
"ioredis": "4.27.9",
|
||||
"joi": "17.4.2",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonpath": "~1.1.1",
|
||||
"lodash.countby": "^4.6.0",
|
||||
"lodash.groupby": "^4.6.0",
|
||||
"lodash.times": "^4.3.2",
|
||||
"moment": "^2.29.1",
|
||||
"node-env-flag": "^0.1.0",
|
||||
"parse-link-header": "^2.0.0",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^14.0.1",
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^7.1.1",
|
||||
"semver": "~7.3.7",
|
||||
"simple-icons": "7.5.0",
|
||||
"webextension-store-meta": "^1.0.5",
|
||||
"prom-client": "^13.2.0",
|
||||
"qs": "^6.10.1",
|
||||
"query-string": "^7.0.1",
|
||||
"request": "~2.88.2",
|
||||
"semver": "~7.3.5",
|
||||
"simple-icons": "5.14.0",
|
||||
"webextension-store-meta": "^1.0.4",
|
||||
"xmldom": "~0.6.0",
|
||||
"xpath": "~0.0.32"
|
||||
},
|
||||
@@ -98,7 +99,7 @@
|
||||
"test": "run-s --silent --continue-on-error lint test:frontend test:package test:core test:entrypoint check-types:package check-types:frontend prettier:check",
|
||||
"check-types:package": "tsd badge-maker",
|
||||
"check-types:frontend": "tsc --noEmit --project .",
|
||||
"depcheck": "check-node-version --node \">= 16.0\"",
|
||||
"depcheck": "check-node-version --node \">= 14.0\"",
|
||||
"prebuild": "run-s --silent depcheck",
|
||||
"features": "node scripts/export-supported-features-cli.js > ./frontend/supported-features.json",
|
||||
"defs": "node scripts/export-service-definitions-cli.js > ./frontend/service-definitions.yml",
|
||||
@@ -141,110 +142,118 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.9",
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.18.9",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"@babel/register": "7.15.3",
|
||||
"@mapbox/react-click-to-select": "^2.2.1",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.groupby": "^4.6.7",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/chai": "^4.2.21",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.groupby": "^4.6.6",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^16.7.10",
|
||||
"@types/react-helmet": "^6.1.5",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-helmet": "^6.1.2",
|
||||
"@types/react-modal": "^3.12.1",
|
||||
"@types/react-select": "^4.0.17",
|
||||
"@types/styled-components": "5.1.25",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"@types/styled-components": "5.1.14",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
||||
"@typescript-eslint/parser": "^4.30.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-preset-gatsby": "^2.19.0",
|
||||
"c8": "^7.12.0",
|
||||
"caller": "^1.1.0",
|
||||
"chai": "^4.3.6",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-preset-gatsby": "^1.13.0",
|
||||
"c8": "^7.9.0",
|
||||
"caller": "^1.0.1",
|
||||
"chai": "^4.3.4",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-datetime": "^1.8.0",
|
||||
"chai-string": "^1.4.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^7.3.0",
|
||||
"cypress": "^10.3.1",
|
||||
"danger": "^11.1.1",
|
||||
"concurrently": "^6.2.1",
|
||||
"cypress": "^8.4.0",
|
||||
"danger": "^10.6.6",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-config-standard-jsx": "^10.0.0",
|
||||
"eslint-config-standard-react": "^11.0.1",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsdoc": "^39.3.3",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-jsdoc": "^36.1.0",
|
||||
"eslint-plugin-mocha": "^9.0.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sort-class-members": "^1.14.1",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-sort-class-members": "^1.11.0",
|
||||
"fetch-ponyfill": "^7.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"gatsby": "4.6.2",
|
||||
"gatsby-plugin-catch-links": "^4.19.0",
|
||||
"gatsby-plugin-page-creator": "^4.7.0",
|
||||
"gatsby-plugin-react-helmet": "^5.10.0",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^4.9.0",
|
||||
"gatsby-plugin-styled-components": "^5.19.0",
|
||||
"gatsby-plugin-typescript": "^4.11.1",
|
||||
"gatsby": "3.13.1",
|
||||
"gatsby-plugin-catch-links": "^3.13.0",
|
||||
"gatsby-plugin-page-creator": "^3.13.0",
|
||||
"gatsby-plugin-react-helmet": "^4.13.0",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^3.13.0",
|
||||
"gatsby-plugin-styled-components": "^4.13.0",
|
||||
"gatsby-plugin-typescript": "^3.2.0",
|
||||
"humanize-string": "^2.1.0",
|
||||
"icedfrisby": "4.0.0",
|
||||
"icedfrisby-nock": "^2.1.0",
|
||||
"is-svg": "^4.3.2",
|
||||
"is-svg": "^4.3.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdoc": "^3.6.11",
|
||||
"lint-staged": "^13.0.3",
|
||||
"jsdoc": "^3.6.7",
|
||||
"lint-staged": "^11.1.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.6",
|
||||
"mocha": "^9.2.2",
|
||||
"minimist": "^1.2.5",
|
||||
"mocha": "^9.1.1",
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.0.2",
|
||||
"mocha-junit-reporter": "^2.0.0",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.2.9",
|
||||
"node-mocks-http": "^1.11.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"nock": "13.1.3",
|
||||
"node-mocks-http": "^1.10.1",
|
||||
"nodemon": "^2.0.12",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"open-cli": "^7.0.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"prettier": "2.7.1",
|
||||
"prettier": "2.4.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-overlay": "^6.0.11",
|
||||
"react-error-overlay": "^6.0.9",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-modal": "^3.15.1",
|
||||
"react-modal": "^3.14.3",
|
||||
"react-pose": "^4.0.10",
|
||||
"react-select": "^4.3.1",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"redis-server": "^1.2.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sazerac": "^2.0.0",
|
||||
"simple-git-hooks": "^2.8.0",
|
||||
"sinon": "^14.0.0",
|
||||
"simple-git-hooks": "^2.6.1",
|
||||
"sinon": "^11.1.2",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"snap-shot-it": "^7.9.6",
|
||||
"start-server-and-test": "1.14.0",
|
||||
"styled-components": "^5.3.5",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"tsd": "^0.22.0",
|
||||
"typescript": "^4.7.4",
|
||||
"url": "^0.11.0"
|
||||
"styled-components": "^5.3.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"tsd": "^0.17.0",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13.0",
|
||||
"npm": ">=8.0.0"
|
||||
"node": "^14.17.1",
|
||||
"npm": ">=7.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"babel": {
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": [
|
||||
"istanbul"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/shields",
|
||||
|
||||
19
scripts/github_token_backup.fish
Executable file
19
scripts/github_token_backup.fish
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env fish
|
||||
#
|
||||
# Back up the GitHub tokens from each production server.
|
||||
#
|
||||
|
||||
if test (count $argv) -lt 1
|
||||
echo Usage: (basename (status -f)) shields_secret
|
||||
end
|
||||
|
||||
set shields_secret $argv[1]
|
||||
|
||||
function do_backup
|
||||
set server $argv[1]
|
||||
curl --insecure -u ":$shields_secret" "https://$server.servers.shields.io/\$github-auth/tokens" > "$server""_tokens.json"
|
||||
end
|
||||
|
||||
for server in s0 s1 s2
|
||||
do_backup $server
|
||||
end
|
||||
@@ -21,7 +21,7 @@ class BaseAmoService extends BaseJsonService {
|
||||
async fetch({ addonId }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://addons.mozilla.org/api/v4/addons/addon/${addonId}/`,
|
||||
url: `https://addons.mozilla.org/api/v3/addons/addon/${addonId}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { downloadCount } from '../color-formatters.js'
|
||||
import { redirector } from '../index.js'
|
||||
import { BaseAmoService, keywords } from './amo-base.js'
|
||||
|
||||
@@ -24,12 +25,13 @@ class AmoWeeklyDownloads extends BaseAmoService {
|
||||
},
|
||||
]
|
||||
|
||||
static _cacheLength = 21600
|
||||
|
||||
static defaultBadgeData = { label: 'downloads' }
|
||||
|
||||
static render({ downloads }) {
|
||||
return renderDownloadsBadge({ downloads, interval: 'week' })
|
||||
return {
|
||||
message: `${metric(downloads)}/week`,
|
||||
color: downloadCount(downloads),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ addonId }) {
|
||||
|
||||
@@ -23,8 +23,6 @@ export default class AmoRating extends BaseAmoService {
|
||||
},
|
||||
]
|
||||
|
||||
static _cacheLength = 7200
|
||||
|
||||
static render({ format, rating }) {
|
||||
rating = Math.round(rating)
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { BaseAmoService, keywords } from './amo-base.js'
|
||||
|
||||
export default class AmoUsers extends BaseAmoService {
|
||||
@@ -14,12 +14,13 @@ export default class AmoUsers extends BaseAmoService {
|
||||
},
|
||||
]
|
||||
|
||||
static _cacheLength = 21600
|
||||
|
||||
static defaultBadgeData = { label: 'users' }
|
||||
|
||||
static render({ users: downloads }) {
|
||||
return renderDownloadsBadge({ downloads, colorOverride: 'blue' })
|
||||
static render({ users }) {
|
||||
return {
|
||||
message: metric(users),
|
||||
color: 'blue',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ addonId }) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { downloadCount } from '../color-formatters.js'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
@@ -31,15 +32,22 @@ class AnsibleGalaxyRoleDownloads extends AnsibleGalaxyRole {
|
||||
{
|
||||
title: 'Ansible Role',
|
||||
namedParams: { roleId: '3078' },
|
||||
staticPreview: renderDownloadsBadge({ downloads: 76 }),
|
||||
staticPreview: this.render({ downloads: 76 }),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'role downloads' }
|
||||
|
||||
static render({ downloads }) {
|
||||
return {
|
||||
message: metric(downloads),
|
||||
color: downloadCount(downloads),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ roleId }) {
|
||||
const json = await this.fetch({ roleId })
|
||||
return renderDownloadsBadge({ downloads: json.download_count })
|
||||
return this.constructor.render({ downloads: json.download_count })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user