Compare commits
94 Commits
server-202
...
3.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81c7d377d2 | ||
|
|
42a1b82d14 | ||
|
|
984be9a214 | ||
|
|
830bb15b58 | ||
|
|
4fa51257b2 | ||
|
|
e2cf6a14c4 | ||
|
|
fde37922cf | ||
|
|
8202c88c46 | ||
|
|
27b104e416 | ||
|
|
4508b1a636 | ||
|
|
17489c0413 | ||
|
|
9b5cad0c4e | ||
|
|
764696b358 | ||
|
|
ae2b843ee0 | ||
|
|
128d75834a | ||
|
|
caebda4a9d | ||
|
|
9cd66cea8c | ||
|
|
7a3fc0721a | ||
|
|
ecdaaf5011 | ||
|
|
45e86726bd | ||
|
|
8ee8f49781 | ||
|
|
4fecc88c0e | ||
|
|
45bf74f8fa | ||
|
|
92be29e13c | ||
|
|
23edaf12d7 | ||
|
|
1ed183ea42 | ||
|
|
ef5ad055f0 | ||
|
|
1383509c9e | ||
|
|
cdd68fee7f | ||
|
|
a9d75aa864 | ||
|
|
64b89e932d | ||
|
|
341c5e505c | ||
|
|
eacf14e270 | ||
|
|
f18d8ce557 | ||
|
|
9aa95d64c2 | ||
|
|
88b16a66e5 | ||
|
|
fd56d4bb1e | ||
|
|
256252513d | ||
|
|
f293dcd7e3 | ||
|
|
b40f7d4dda | ||
|
|
385114be91 | ||
|
|
6f937953ce | ||
|
|
be86b06c59 | ||
|
|
5e5ce00463 | ||
|
|
e219c8d0fa | ||
|
|
c628ef853a | ||
|
|
59dcdf24f3 | ||
|
|
52cff6ceb3 | ||
|
|
a51ef2996a | ||
|
|
cc60800dbc | ||
|
|
5961a97833 | ||
|
|
5e01757850 | ||
|
|
636ed87057 | ||
|
|
79c9e1a1d2 | ||
|
|
2f3ecfcea2 | ||
|
|
1ae67f927b | ||
|
|
a51f64d794 | ||
|
|
2a7c9406ec | ||
|
|
32e4f8e99a | ||
|
|
a5f36f0126 | ||
|
|
f1f3c7b310 | ||
|
|
f2dbb53407 | ||
|
|
e0f521ea5f | ||
|
|
ac8c0ca3bd | ||
|
|
5bfeda004c | ||
|
|
2dbff5ab44 | ||
|
|
7da63db53e | ||
|
|
da0bff6e58 | ||
|
|
a094c8a373 | ||
|
|
9dd2861c5a | ||
|
|
3d203e70dc | ||
|
|
fa253bab13 | ||
|
|
ef5e3c9195 | ||
|
|
87943433f9 | ||
|
|
afd7af632f | ||
|
|
f6be332ad9 | ||
|
|
260123bf51 | ||
|
|
2aa253695d | ||
|
|
cfdf299ae4 | ||
|
|
666b72fece | ||
|
|
0a3d9bc24c | ||
|
|
2c418be5fe | ||
|
|
e271488f5b | ||
|
|
fba0bdf7bc | ||
|
|
d02da3d487 | ||
|
|
661d1f1bfc | ||
|
|
5d13afa77c | ||
|
|
78454f4312 | ||
|
|
b9a3a14d42 | ||
|
|
b546e8df74 | ||
|
|
1b07126207 | ||
|
|
a0d6b4203d | ||
|
|
3c158bbea8 | ||
|
|
79016de181 |
@@ -104,15 +104,6 @@ package_steps: &package_steps
|
||||
# https://github.com/badges/shields/blob/master/badge-maker/README.md#node-version-support
|
||||
# https://nodejs.org/en/about/releases/
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v10/results.xml
|
||||
NODE_VERSION: v10
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 10
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
@@ -131,6 +122,15 @@ package_steps: &package_steps
|
||||
name: Run package tests on Node 14
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v16/results.xml
|
||||
NODE_VERSION: v16
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 16
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
|
||||
13
.github/actions/approve-bot/helpers.js
vendored
13
.github/actions/approve-bot/helpers.js
vendored
@@ -33,11 +33,14 @@ function allChangelogLinesAreVersionBump(changelogLines) {
|
||||
)
|
||||
}
|
||||
|
||||
function isPointlessGatsbyBump(body) {
|
||||
function isPointlessVersionBump(body) {
|
||||
const pointlessBumpLinks = [
|
||||
'https://github.com/gatsbyjs/gatsby',
|
||||
'https://github.com/typescript-eslint/typescript-eslint',
|
||||
]
|
||||
|
||||
const lines = body.split(/\r?\n/)
|
||||
if (
|
||||
!lines[0].includes('https://github.com/gatsbyjs/gatsby') // lgtm [js/incomplete-url-substring-sanitization]
|
||||
) {
|
||||
if (!pointlessBumpLinks.some(link => lines[0].includes(link))) {
|
||||
return false
|
||||
}
|
||||
const start = findChangelogStart(lines)
|
||||
@@ -62,4 +65,4 @@ function shouldAutoMerge(body) {
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = { isPointlessGatsbyBump, shouldAutoMerge }
|
||||
module.exports = { isPointlessVersionBump, shouldAutoMerge }
|
||||
|
||||
10
.github/actions/approve-bot/index.js
vendored
10
.github/actions/approve-bot/index.js
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
const core = require('@actions/core')
|
||||
const github = require('@actions/github')
|
||||
const { isPointlessGatsbyBump, shouldAutoMerge } = require('./helpers')
|
||||
const { isPointlessVersionBump, shouldAutoMerge } = require('./helpers')
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
@@ -18,9 +18,9 @@ async function run() {
|
||||
if (
|
||||
['dependabot[bot]', 'dependabot-preview[bot]'].includes(pr.user.login)
|
||||
) {
|
||||
if (isPointlessGatsbyBump(pr.body)) {
|
||||
if (isPointlessVersionBump(pr.body)) {
|
||||
core.debug(`Closing pull request #${pr.number}`)
|
||||
await client.pulls.update({
|
||||
await client.rest.pulls.update({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
@@ -30,7 +30,7 @@ async function run() {
|
||||
core.debug(`Done.`)
|
||||
} else if (shouldAutoMerge(pr.body)) {
|
||||
core.debug(`Adding label to pull request #${pr.number}`)
|
||||
await client.issues.addLabels({
|
||||
await client.rest.issues.addLabels({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
@@ -38,7 +38,7 @@ async function run() {
|
||||
})
|
||||
|
||||
core.debug(`Creating approving review for pull request #${pr.number}`)
|
||||
await client.pulls.createReview({
|
||||
await client.rest.pulls.createReview({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
|
||||
93
.github/actions/approve-bot/package-lock.json
generated
vendored
93
.github/actions/approve-bot/package-lock.json
generated
vendored
@@ -5,25 +5,25 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.7.tgz",
|
||||
"integrity": "sha512-kzLFD5BgEvq6ubcxdgPbRKGD2Qrgya/5j+wh4LZzqT915I0V3rED+MvjH6NXghbvk1MXknpNNQ3uKjXSEN00Ig=="
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.3.0.tgz",
|
||||
"integrity": "sha512-xxtX0Cwdhb8LcgatfJkokqT8KzPvcIbwL9xpLU09nOwBzaStbfm0dNncsP0M4us+EpoPdWy7vbzU5vSOH7K6pg=="
|
||||
},
|
||||
"@actions/github": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-4.0.0.tgz",
|
||||
"integrity": "sha512-Ej/Y2E+VV6sR9X7pWL5F3VgEWrABaT292DRqRU6R4hnQjPtC/zD3nagxVdXWiRQvYDh8kHXo7IDmG42eJ/dOMA==",
|
||||
"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": "^1.0.8",
|
||||
"@octokit/core": "^3.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.2.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^4.0.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": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
|
||||
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
|
||||
"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"
|
||||
}
|
||||
@@ -37,15 +37,16 @@
|
||||
}
|
||||
},
|
||||
"@octokit/core": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.5.tgz",
|
||||
"integrity": "sha512-+DCtPykGnvXKWWQI0E1XD+CCeWSBhB6kwItXqfFmNBlIlhczuDPbg+P6BtLnVBaRJDAjv+1mrUJuRsFSjktopg==",
|
||||
"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.4.12",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.1.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
@@ -60,9 +61,9 @@
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"version": "4.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.9.tgz",
|
||||
"integrity": "sha512-c+0yofIugUNqo+ktrLaBlWSbjSq/UF8ChAyxQzbD3X74k1vAuyLKdDJmPwVExUFSp6+U1FzWe+3OkeRsIqV0vg==",
|
||||
"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.3.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
@@ -70,39 +71,37 @@
|
||||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-3.3.0.tgz",
|
||||
"integrity": "sha512-s3dd32gagPmKaSLNJ9aPNok7U+tl69YLESf6DgQz5Ml/iipPZtif3GLvWpNXoA6qspFm1LFUZX+C3SqWX/Y/TQ=="
|
||||
"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.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.9.0.tgz",
|
||||
"integrity": "sha512-XxbOg45r2n/2QpU6hnGDxQNDRrJ7gjYpMXeDbUCigWTHECmjoyFLizkFO2jMEtidMkfiELn7AF8GBAJ/cbPTnA==",
|
||||
"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.6.0"
|
||||
"@octokit/types": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.9.0.tgz",
|
||||
"integrity": "sha512-EAr2epvY8JfXSi/cdMsyyfBctdKkolDH7xSgu3MKBqPRm0WfQ2QvI050jz61XZXoVK3ZgrhdMCyd1GgOFz7CSw==",
|
||||
"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.6.0",
|
||||
"@octokit/types": "^6.14.1",
|
||||
"deprecation": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "5.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.13.tgz",
|
||||
"integrity": "sha512-WcNRH5XPPtg7i1g9Da5U9dvZ6YbTffw9BN2rVezYiE7couoSyaRsw0e+Tl8uk1fArHE7Dn14U7YqUDy59WaqEw==",
|
||||
"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.0.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"@octokit/types": "^6.7.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"once": "^1.4.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
@@ -117,23 +116,17 @@
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.6.0.tgz",
|
||||
"integrity": "sha512-nmFoU3HCbw1AmnZU/eto2VvzV06+N7oAqXwMmAHGlNDF+KFykksh/VlAl85xc1P5T7Mw8fKYvXNaImNHCCH/rg==",
|
||||
"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": "^3.3.0",
|
||||
"@types/node": ">= 8"
|
||||
"@octokit/openapi-types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
|
||||
"integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw=="
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz",
|
||||
"integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A=="
|
||||
"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",
|
||||
|
||||
4
.github/actions/approve-bot/package.json
vendored
4
.github/actions/approve-bot/package.json
vendored
@@ -10,7 +10,7 @@
|
||||
"author": "chris48s",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.7",
|
||||
"@actions/github": "^4.0.0"
|
||||
"@actions/core": "^1.3.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -4,6 +4,18 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2021-06-01
|
||||
|
||||
- Changed creating badges to open a new Window/Tab [#6536](https://github.com/badges/shields/issues/6536)
|
||||
- Make for-the-badge letter spacing more predictable, and rewrite layout logic [#5754](https://github.com/badges/shields/issues/5754)
|
||||
- deprecate DockerBuild service [#6529](https://github.com/badges/shields/issues/6529)
|
||||
- Remove rate limiting functionality [#6513](https://github.com/badges/shields/issues/6513)
|
||||
- [GitHub] Move to 'funding' category [#5846](https://github.com/badges/shields/issues/5846)
|
||||
- Add GitHub discussions total badge [GithubTotalDiscussions] [#6472](https://github.com/badges/shields/issues/6472)
|
||||
- Add optional query parameter (include_prereleases) to [GemVersion] [#6451](https://github.com/badges/shields/issues/6451)
|
||||
- Add [PingPong] Service [#6327](https://github.com/badges/shields/issues/6327)
|
||||
- Dependency updates
|
||||
|
||||
## server-2021-05-01
|
||||
|
||||
- Add setting which allows setting a timeout on HTTP requests
|
||||
|
||||
@@ -969,15 +969,15 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="145.5"
|
||||
width="146.75"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="73" height="28" fill="#0f0" />
|
||||
<rect x="73" width="72.5" height="28" fill="#b3e" />
|
||||
<rect width="72.5" height="28" fill="#0f0" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -986,16 +986,16 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="100"
|
||||
>
|
||||
<text fill="#fff" x="365" y="175" transform="scale(.1)" textLength="490">
|
||||
<text transform="scale(.1)" x="362.5" y="175" textLength="485" fill="#fff">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="1092.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1008,15 +1008,15 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="162.5"
|
||||
width="163.75"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="90" height="28" fill="#0f0" />
|
||||
<rect x="90" width="72.5" height="28" fill="#b3e" />
|
||||
<rect width="89.5" height="28" fill="#0f0" />
|
||||
<rect x="89.5" width="74.25" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1032,16 +1032,16 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
height="14"
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
|
||||
/>
|
||||
<text fill="#fff" x="535" y="175" transform="scale(.1)" textLength="490">
|
||||
<text transform="scale(.1)" x="532.5" y="175" textLength="485" fill="#fff">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="1262.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
x="1266.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1054,15 +1054,14 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="72.5"
|
||||
width="74.25"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="GROWN"
|
||||
>
|
||||
<title>GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="0" height="28" fill="#b3e" />
|
||||
<rect x="0" width="72.5" height="28" fill="#b3e" />
|
||||
<rect width="74.25" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1072,12 +1071,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
font-size="100"
|
||||
>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="362.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
x="371.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1090,15 +1089,14 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="90.5"
|
||||
width="94.25"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="GROWN"
|
||||
>
|
||||
<title>GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="0" height="28" fill="#555" />
|
||||
<rect x="0" width="90.5" height="28" fill="#b3e" />
|
||||
<rect width="94.25" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1115,12 +1113,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
|
||||
/>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="542.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
x="571.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1133,7 +1131,7 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="104.5"
|
||||
width="106.25"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="GROWN"
|
||||
@@ -1141,7 +1139,7 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<title>GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="32" height="28" fill="#0f0" />
|
||||
<rect x="32" width="72.5" height="28" fill="#b3e" />
|
||||
<rect x="32" width="74.25" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1158,19 +1156,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
|
||||
/>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="230"
|
||||
y="175"
|
||||
transform="scale(.1)"
|
||||
textLength="-60"
|
||||
></text>
|
||||
<text
|
||||
fill="#fff"
|
||||
x="682.5"
|
||||
x="691.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1183,12 +1174,12 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="145.5"
|
||||
width="146.75"
|
||||
height="28"
|
||||
>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="73" height="28" fill="#0f0" />
|
||||
<rect x="73" width="72.5" height="28" fill="#b3e" />
|
||||
<rect width="72.5" height="28" fill="#0f0" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#b3e" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1198,20 +1189,26 @@ exports['The badge generator "for-the-badge" template badge generation should ma
|
||||
font-size="100"
|
||||
>
|
||||
<a target="_blank" xlink:href="https://shields.io/">
|
||||
<rect width="73" height="28" fill="rgba(0,0,0,0)" />
|
||||
<text fill="#fff" x="365" y="175" transform="scale(.1)" textLength="490">
|
||||
<rect width="72.5" height="28" fill="rgba(0,0,0,0)" />
|
||||
<text
|
||||
transform="scale(.1)"
|
||||
x="362.5"
|
||||
y="175"
|
||||
textLength="485"
|
||||
fill="#fff"
|
||||
>
|
||||
CACTUS
|
||||
</text>
|
||||
</a>
|
||||
<a target="_blank" xlink:href="https://www.google.co.uk/">
|
||||
<rect width="72.5" height="28" x="73" fill="rgba(0,0,0,0)" />
|
||||
<rect width="74.25" height="28" x="72.5" fill="rgba(0,0,0,0)" />
|
||||
<text
|
||||
fill="#fff"
|
||||
x="1092.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#fff"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
@@ -1911,15 +1908,15 @@ exports['The badge generator text colors should use black text when the message
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="145.5"
|
||||
width="146.75"
|
||||
height="28"
|
||||
role="img"
|
||||
aria-label="CACTUS: GROWN"
|
||||
>
|
||||
<title>CACTUS: GROWN</title>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="73" height="28" fill="#000" />
|
||||
<rect x="73" width="72.5" height="28" fill="#e2ffe1" />
|
||||
<rect width="72.5" height="28" fill="#000" />
|
||||
<rect x="72.5" width="74.25" height="28" fill="#e2ffe1" />
|
||||
</g>
|
||||
<g
|
||||
fill="#fff"
|
||||
@@ -1928,16 +1925,16 @@ exports['The badge generator text colors should use black text when the message
|
||||
text-rendering="geometricPrecision"
|
||||
font-size="100"
|
||||
>
|
||||
<text fill="#fff" x="365" y="175" transform="scale(.1)" textLength="490">
|
||||
<text transform="scale(.1)" x="362.5" y="175" textLength="485" fill="#fff">
|
||||
CACTUS
|
||||
</text>
|
||||
<text
|
||||
fill="#333"
|
||||
x="1092.5"
|
||||
y="175"
|
||||
font-weight="bold"
|
||||
transform="scale(.1)"
|
||||
textLength="485"
|
||||
x="1096.25"
|
||||
y="175"
|
||||
textLength="502.5"
|
||||
fill="#333"
|
||||
font-weight="bold"
|
||||
>
|
||||
GROWN
|
||||
</text>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## 3.3.1
|
||||
|
||||
- Improve font measuring in for-the-badge and social styles
|
||||
- Make for-the-badge letter spacing more predictable
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Readability improvements: a dark font color is automatically used when the badge's background is too light. For example: 
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
|
||||
const anafanafo = require('anafanafo')
|
||||
const { brightness } = require('./color')
|
||||
const { XmlElement, escapeXml } = require('./xml')
|
||||
|
||||
const fontFamily = 'font-family="Verdana,Geneva,DejaVu Sans,sans-serif"'
|
||||
// https://github.com/badges/shields/pull/1132
|
||||
const FONT_SCALE_UP_FACTOR = 10
|
||||
const FONT_SCALE_DOWN_VALUE = 'scale(.1)'
|
||||
|
||||
const FONT_FAMILY = 'Verdana,Geneva,DejaVu Sans,sans-serif'
|
||||
const fontFamily = `font-family="${FONT_FAMILY}"`
|
||||
const socialFontFamily =
|
||||
'font-family="Helvetica Neue,Helvetica,Arial,sans-serif"'
|
||||
const brightnessThreshold = 0.69
|
||||
@@ -20,19 +26,6 @@ function colorsForBackground(color) {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeXml(s) {
|
||||
if (s === undefined || typeof s !== 'string') {
|
||||
return undefined
|
||||
} else {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
}
|
||||
|
||||
function roundUpToOdd(val) {
|
||||
return val % 2 === 0 ? val + 1 : val
|
||||
}
|
||||
@@ -576,126 +569,232 @@ function forTheBadge({
|
||||
links,
|
||||
logo,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
color = '#4c1',
|
||||
labelColor,
|
||||
}) {
|
||||
// For the Badge is styled in all caps. Convert to caps here so widths can
|
||||
// be measured using the correct characters.
|
||||
const FONT_SIZE = 10
|
||||
const BADGE_HEIGHT = 28
|
||||
const LOGO_HEIGHT = 14
|
||||
const TEXT_MARGIN = 12
|
||||
const LOGO_MARGIN = 9
|
||||
const LOGO_TEXT_GUTTER = 6
|
||||
const LETTER_SPACING = 1.25
|
||||
|
||||
// Prepare content. For the Badge is styled in all caps. It's important to to
|
||||
// convert to uppercase first so the widths can be measured using the correct
|
||||
// symbols.
|
||||
label = label.toUpperCase()
|
||||
message = message.toUpperCase()
|
||||
|
||||
let labelWidth = preferredWidthOf(label, { font: '10px Verdana' }) || 0
|
||||
let messageWidth =
|
||||
preferredWidthOf(message, { font: 'bold 10px Verdana' }) || 0
|
||||
const height = 28
|
||||
const hasLabel = label.length || labelColor
|
||||
if (labelColor == null) {
|
||||
labelColor = '#555'
|
||||
}
|
||||
const horizPadding = 9
|
||||
const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
|
||||
logo,
|
||||
badgeHeight: height,
|
||||
horizPadding,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
})
|
||||
|
||||
labelWidth += 10 + totalLogoWidth
|
||||
if (label.length) {
|
||||
// Add 10 px of padding, plus approximately 1 px of letter spacing per
|
||||
// character.
|
||||
labelWidth += 10 + 2 * label.length
|
||||
} else if (hasLogo) {
|
||||
if (hasLabel) {
|
||||
labelWidth += 7
|
||||
} else {
|
||||
labelWidth -= 7
|
||||
}
|
||||
} else {
|
||||
labelWidth -= 11
|
||||
}
|
||||
|
||||
// Add 20 px of padding, plus approximately 1.5 px of letter spacing per
|
||||
// character.
|
||||
messageWidth += 20 + 1.5 * message.length
|
||||
const leftWidth = hasLogo && !hasLabel ? 0 : labelWidth
|
||||
const rightWidth =
|
||||
hasLogo && !hasLabel ? messageWidth + labelWidth : messageWidth
|
||||
|
||||
labelColor = hasLabel || hasLogo ? labelColor : color
|
||||
|
||||
color = escapeXml(color)
|
||||
labelColor = escapeXml(labelColor)
|
||||
|
||||
let [leftLink, rightLink] = links
|
||||
leftLink = escapeXml(leftLink)
|
||||
rightLink = escapeXml(rightLink)
|
||||
const [leftLink, rightLink] = links
|
||||
const { hasLeftLink, hasRightLink } = hasLinks({ links })
|
||||
|
||||
const accessibleText = createAccessibleText({ label, message })
|
||||
const outLabelColor = labelColor || '#555'
|
||||
|
||||
function renderLabelText() {
|
||||
const { textColor } = colorsForBackground(labelColor)
|
||||
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
|
||||
const labelTextLength = (labelWidth - (24 + totalLogoWidth)) * 10
|
||||
const escapedLabel = escapeXml(label)
|
||||
// Compute text width.
|
||||
// TODO: This really should count the symbols rather than just using `.length`.
|
||||
// https://mathiasbynens.be/notes/javascript-unicode
|
||||
// This is not using `preferredWidthOf()` as it tends to produce larger
|
||||
// inconsistencies in the letter spacing. The badges look fine, however if you
|
||||
// replace `textLength` with `letterSpacing` in the rendered SVG, you can see
|
||||
// the discrepancy. Ideally, swapping out `textLength` for `letterSpacing`
|
||||
// should not affect the appearance.
|
||||
const labelTextWidth = label.length
|
||||
? (anafanafo(label, { font: `${FONT_SIZE}px Verdana` }) | 0) +
|
||||
LETTER_SPACING * label.length
|
||||
: 0
|
||||
const messageTextWidth = message.length
|
||||
? (anafanafo(message, { font: `bold ${FONT_SIZE}px Verdana` }) | 0) +
|
||||
LETTER_SPACING * message.length
|
||||
: 0
|
||||
|
||||
const text = `<text fill="${textColor}" x="${labelTextX}" y="175" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>`
|
||||
// Compute horizontal layout.
|
||||
// If a `labelColor` is set, the logo is always set against it, even when
|
||||
// there is no label. When `needsLabelRect` is true, render a label rect and a
|
||||
// message rect; when false, only a message rect.
|
||||
const hasLabel = Boolean(label.length)
|
||||
const needsLabelRect = hasLabel || (logo && labelColor)
|
||||
let logoMinX, labelTextMinX
|
||||
if (logo) {
|
||||
logoMinX = LOGO_MARGIN
|
||||
labelTextMinX = logoMinX + logoWidth + LOGO_TEXT_GUTTER
|
||||
} else {
|
||||
labelTextMinX = TEXT_MARGIN
|
||||
}
|
||||
let labelRectWidth, messageTextMinX, messageRectWidth
|
||||
if (needsLabelRect) {
|
||||
if (hasLabel) {
|
||||
labelRectWidth = labelTextMinX + labelTextWidth + TEXT_MARGIN
|
||||
} else {
|
||||
labelRectWidth = 2 * LOGO_MARGIN + logoWidth
|
||||
}
|
||||
messageTextMinX = labelRectWidth + TEXT_MARGIN
|
||||
messageRectWidth = 2 * TEXT_MARGIN + messageTextWidth
|
||||
} else {
|
||||
if (logo) {
|
||||
messageTextMinX = TEXT_MARGIN + logoWidth + LOGO_TEXT_GUTTER
|
||||
messageRectWidth =
|
||||
2 * TEXT_MARGIN + logoWidth + LOGO_TEXT_GUTTER + messageTextWidth
|
||||
} else {
|
||||
messageTextMinX = TEXT_MARGIN
|
||||
messageRectWidth = 2 * TEXT_MARGIN + messageTextWidth
|
||||
}
|
||||
}
|
||||
|
||||
const logoElement = new XmlElement({
|
||||
name: 'image',
|
||||
attrs: {
|
||||
x: logoMinX,
|
||||
y: 0.5 * (BADGE_HEIGHT - LOGO_HEIGHT),
|
||||
width: logoWidth,
|
||||
height: LOGO_HEIGHT,
|
||||
'xlink:href': logo,
|
||||
},
|
||||
})
|
||||
|
||||
function getLabelElement() {
|
||||
const { textColor } = colorsForBackground(outLabelColor)
|
||||
const midX = labelTextMinX + 0.5 * labelTextWidth
|
||||
const text = new XmlElement({
|
||||
name: 'text',
|
||||
content: [label],
|
||||
attrs: {
|
||||
transform: FONT_SCALE_DOWN_VALUE,
|
||||
x: FONT_SCALE_UP_FACTOR * midX,
|
||||
y: 175,
|
||||
textLength: FONT_SCALE_UP_FACTOR * labelTextWidth,
|
||||
fill: textColor,
|
||||
},
|
||||
})
|
||||
|
||||
if (hasLeftLink && !shouldWrapBodyWithLink({ links })) {
|
||||
return `
|
||||
<a target="_blank" xlink:href="${leftLink}">
|
||||
<rect width="${leftWidth}" height="${height}" fill="rgba(0,0,0,0)"/>
|
||||
${text}
|
||||
</a>
|
||||
`
|
||||
const rect = new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: labelRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: 'rgba(0,0,0,0)',
|
||||
},
|
||||
})
|
||||
return new XmlElement({
|
||||
name: 'a',
|
||||
content: [rect, text],
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
'xlink:href': leftLink,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
function renderMessageText() {
|
||||
function getMessageElement() {
|
||||
const { textColor } = colorsForBackground(color)
|
||||
|
||||
const text = `<text fill="${textColor}" x="${
|
||||
(labelWidth + messageWidth / 2) * 10
|
||||
}" y="175" font-weight="bold" transform="scale(.1)" textLength="${
|
||||
(messageWidth - 24) * 10
|
||||
}">
|
||||
${escapeXml(message)}</text>`
|
||||
const midX = messageTextMinX + 0.5 * messageTextWidth
|
||||
const text = new XmlElement({
|
||||
name: 'text',
|
||||
content: [message],
|
||||
attrs: {
|
||||
transform: FONT_SCALE_DOWN_VALUE,
|
||||
x: FONT_SCALE_UP_FACTOR * midX,
|
||||
y: 175,
|
||||
textLength: FONT_SCALE_UP_FACTOR * messageTextWidth,
|
||||
fill: textColor,
|
||||
'font-weight': 'bold',
|
||||
},
|
||||
})
|
||||
|
||||
if (hasRightLink) {
|
||||
return `
|
||||
<a target="_blank" xlink:href="${rightLink}">
|
||||
<rect width="${rightWidth}" height="${height}" x="${labelWidth}" fill="rgba(0,0,0,0)"/>
|
||||
${text}
|
||||
</a>
|
||||
`
|
||||
const rect = new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: messageRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
x: labelRectWidth || 0,
|
||||
fill: 'rgba(0,0,0,0)',
|
||||
},
|
||||
})
|
||||
return new XmlElement({
|
||||
name: 'a',
|
||||
content: [rect, text],
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
'xlink:href': rightLink,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundContent
|
||||
if (needsLabelRect) {
|
||||
backgroundContent = [
|
||||
new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: labelRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: outLabelColor,
|
||||
},
|
||||
}),
|
||||
new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
x: labelRectWidth,
|
||||
width: messageRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: color,
|
||||
},
|
||||
}),
|
||||
]
|
||||
} else {
|
||||
backgroundContent = [
|
||||
new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: messageRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: color,
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
const backgroundGroup = new XmlElement({
|
||||
name: 'g',
|
||||
content: backgroundContent,
|
||||
attrs: {
|
||||
'shape-rendering': 'crispEdges',
|
||||
},
|
||||
})
|
||||
const foregroundGroup = new XmlElement({
|
||||
name: 'g',
|
||||
content: [
|
||||
logo ? logoElement : '',
|
||||
hasLabel ? getLabelElement() : '',
|
||||
getMessageElement(),
|
||||
],
|
||||
attrs: {
|
||||
fill: '#fff',
|
||||
'text-anchor': 'middle',
|
||||
'font-family': FONT_FAMILY,
|
||||
'text-rendering': 'geometricPrecision',
|
||||
'font-size': FONT_SCALE_UP_FACTOR * FONT_SIZE,
|
||||
},
|
||||
})
|
||||
|
||||
// Render.
|
||||
return renderBadge(
|
||||
{
|
||||
links,
|
||||
leftWidth,
|
||||
rightWidth,
|
||||
accessibleText,
|
||||
height,
|
||||
leftWidth: labelRectWidth || 0,
|
||||
rightWidth: messageRectWidth,
|
||||
accessibleText: createAccessibleText({ label, message }),
|
||||
height: BADGE_HEIGHT,
|
||||
},
|
||||
`
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="${leftWidth}" height="${height}" fill="${labelColor}"/>
|
||||
<rect x="${leftWidth}" width="${rightWidth}" height="${height}" fill="${color}"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" ${fontFamily} text-rendering="geometricPrecision" font-size="100">
|
||||
${renderedLogo}
|
||||
${hasLabel ? renderLabelText() : ''}
|
||||
${renderMessageText()}
|
||||
</g>`
|
||||
[backgroundGroup.render(), foregroundGroup.render()].join('')
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
const { normalizeColor, toSvgColor } = require('./color')
|
||||
const badgeRenderers = require('./badge-renderers')
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
const { stripXmlWhitespace } = require('./xml')
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
|
||||
76
badge-maker/lib/xml.js
Normal file
76
badge-maker/lib/xml.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
function escapeXml(s) {
|
||||
if (typeof s === 'number') {
|
||||
return s
|
||||
} else if (s === undefined || typeof s !== 'string') {
|
||||
return undefined
|
||||
} else {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of an XML element
|
||||
*/
|
||||
class XmlElement {
|
||||
/**
|
||||
* Xml Element Constructor
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.name
|
||||
* Name of the XML tag
|
||||
* @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.
|
||||
* @param {object} [attrs.attrs={}]
|
||||
* Object representing the tag's attributes as name/value pairs
|
||||
*/
|
||||
constructor({ name, content = [], attrs = {} }) {
|
||||
this.name = name
|
||||
this.content = content
|
||||
this.attrs = attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the XML element to a string, applying appropriate escaping
|
||||
*
|
||||
* @returns {string} String representation of the XML element
|
||||
*/
|
||||
render() {
|
||||
const attrsStr = Object.entries(this.attrs)
|
||||
.map(([k, v]) => ` ${k}="${escapeXml(v)}"`)
|
||||
.join('')
|
||||
if (this.content.length > 0) {
|
||||
const content = this.content
|
||||
.map(function (el) {
|
||||
if (el instanceof XmlElement) {
|
||||
return el.render()
|
||||
} else {
|
||||
return escapeXml(el)
|
||||
}
|
||||
})
|
||||
.join(' ')
|
||||
return stripXmlWhitespace(
|
||||
`<${this.name}${attrsStr}>${content}</${this.name}>`
|
||||
)
|
||||
}
|
||||
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { escapeXml, stripXmlWhitespace, XmlElement }
|
||||
50
badge-maker/lib/xml.spec.js
Normal file
50
badge-maker/lib/xml.spec.js
Normal file
@@ -0,0 +1,50 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const { XmlElement } = require('./xml')
|
||||
|
||||
function testRender(params) {
|
||||
return new XmlElement(params).render()
|
||||
}
|
||||
|
||||
describe('XmlElement class', function () {
|
||||
test(testRender, () => {
|
||||
given({ name: 'tag' }).expect('<tag/>')
|
||||
|
||||
given({ name: 'tag', content: ['text'] }).expect('<tag>text</tag>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
content: ['not xml>>>', 'text', new XmlElement({ name: 'xml' })],
|
||||
}).expect('<tag>not xml>>> text <xml/></tag>')
|
||||
|
||||
given({
|
||||
name: 'nested1',
|
||||
content: [
|
||||
new XmlElement({
|
||||
name: 'nested2',
|
||||
content: [new XmlElement({ name: 'nested3' })],
|
||||
}),
|
||||
],
|
||||
}).expect('<nested1><nested2><nested3/></nested2></nested1>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
attrs: {
|
||||
int: 47,
|
||||
text: 'text',
|
||||
escape: '<escape me>',
|
||||
},
|
||||
}).expect('<tag int="47" text="text" escape="<escape me>"/>')
|
||||
|
||||
given({
|
||||
name: 'tag',
|
||||
content: ['text'],
|
||||
attrs: {
|
||||
int: 47,
|
||||
text: 'text',
|
||||
escape: '<escape me>',
|
||||
},
|
||||
}).expect('<tag int="47" text="text" escape="<escape me>">text</tag>')
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "3.3.0",
|
||||
"version": "3.3.1",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
|
||||
@@ -57,8 +57,6 @@ public:
|
||||
cacheHeaders:
|
||||
defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS'
|
||||
|
||||
rateLimit: 'RATE_LIMIT'
|
||||
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
|
||||
requestTimeoutSeconds: 'REQUEST_TIMEOUT_SECONDS'
|
||||
|
||||
@@ -27,8 +27,6 @@ public:
|
||||
cacheHeaders:
|
||||
defaultCacheLengthSeconds: 120
|
||||
|
||||
rateLimit: true
|
||||
|
||||
handleInternalErrors: true
|
||||
|
||||
fetchLimit: '10MB'
|
||||
|
||||
@@ -5,6 +5,4 @@ public:
|
||||
cors:
|
||||
allowedOrigin: ['http://localhost:3000']
|
||||
|
||||
rateLimit: false
|
||||
|
||||
handleInternalErrors: false
|
||||
|
||||
@@ -3,8 +3,6 @@ public:
|
||||
address: 'localhost'
|
||||
port: 1111
|
||||
|
||||
rateLimit: false
|
||||
|
||||
redirectUrl: 'http://frontend.example.test'
|
||||
|
||||
rasterUrl: 'http://raster.example.test'
|
||||
|
||||
@@ -183,10 +183,8 @@ class AuthHelper {
|
||||
}
|
||||
|
||||
static _mergeQueryParams(requestParams, query) {
|
||||
const {
|
||||
options: { qs: existingQuery, ...restOptions } = {},
|
||||
...rest
|
||||
} = requestParams
|
||||
const { options: { qs: existingQuery, ...restOptions } = {}, ...rest } =
|
||||
requestParams
|
||||
return {
|
||||
options: {
|
||||
qs: {
|
||||
|
||||
@@ -143,6 +143,8 @@ class BaseService {
|
||||
license: 3600,
|
||||
version: 300,
|
||||
debug: 60,
|
||||
downloads: 900,
|
||||
social: 900,
|
||||
}
|
||||
return cacheLengths[this.category]
|
||||
}
|
||||
|
||||
@@ -353,10 +353,8 @@ describe('BaseService', function () {
|
||||
it('handles the request', async function () {
|
||||
expect(mockHandleRequest).to.have.been.calledOnce
|
||||
|
||||
const {
|
||||
queryParams: serviceQueryParams,
|
||||
handler: requestHandler,
|
||||
} = mockHandleRequest.getCall(0).args[1]
|
||||
const { queryParams: serviceQueryParams, handler: requestHandler } =
|
||||
mockHandleRequest.getCall(0).args[1]
|
||||
expect(serviceQueryParams).to.deep.equal([
|
||||
'queryParamA',
|
||||
'legacyQueryParamA',
|
||||
@@ -390,13 +388,8 @@ describe('BaseService', function () {
|
||||
|
||||
describe('getDefinition', function () {
|
||||
it('returns the expected result', function () {
|
||||
const {
|
||||
category,
|
||||
name,
|
||||
isDeprecated,
|
||||
route,
|
||||
examples,
|
||||
} = DummyService.getDefinition()
|
||||
const { category, name, isDeprecated, route, examples } =
|
||||
DummyService.getDefinition()
|
||||
expect({
|
||||
category,
|
||||
name,
|
||||
@@ -510,10 +503,11 @@ describe('BaseService', function () {
|
||||
buffer: 'x'.repeat(65536 + 1),
|
||||
res: { statusCode: 200 },
|
||||
})
|
||||
const serviceInstance = new DummyServiceWithServiceResponseSizeMetricEnabled(
|
||||
{ sendAndCacheRequest, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
const serviceInstance =
|
||||
new DummyServiceWithServiceResponseSizeMetricEnabled(
|
||||
{ sendAndCacheRequest, metricHelper },
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
await serviceInstance._request({ url })
|
||||
|
||||
|
||||
@@ -268,8 +268,7 @@ describe('coalesceBadge', function () {
|
||||
coalesceBadge(
|
||||
{ link: 'https://circleci.com/gh/badges/daily-tests' },
|
||||
{
|
||||
link:
|
||||
'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
|
||||
link: 'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
|
||||
},
|
||||
{}
|
||||
).links
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('mergeQueries function', function () {
|
||||
print(
|
||||
mergeQueries(
|
||||
gql`
|
||||
query($param: String!) {
|
||||
query ($param: String!) {
|
||||
foo(param: $param) {
|
||||
bar
|
||||
}
|
||||
@@ -29,7 +29,7 @@ describe('mergeQueries function', function () {
|
||||
print(
|
||||
mergeQueries(
|
||||
gql`
|
||||
query($param: String!) {
|
||||
query ($param: String!) {
|
||||
foo(param: $param) {
|
||||
bar
|
||||
}
|
||||
|
||||
@@ -83,10 +83,8 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
}
|
||||
|
||||
const allowedKeys = flattenQueryParams(handlerOptions.queryParams)
|
||||
const {
|
||||
cacheLength: serviceDefaultCacheLengthSeconds,
|
||||
fetchLimitBytes,
|
||||
} = handlerOptions
|
||||
const { cacheLength: serviceDefaultCacheLengthSeconds, fetchLimitBytes } =
|
||||
handlerOptions
|
||||
|
||||
return (queryParams, match, end, ask) => {
|
||||
/*
|
||||
@@ -198,7 +196,9 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
},
|
||||
cachingRequest
|
||||
)
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
if (result && result.catch) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
result.catch(err => {
|
||||
throw err
|
||||
})
|
||||
|
||||
@@ -11,13 +11,12 @@ class MetricHelper {
|
||||
serviceFamily,
|
||||
name,
|
||||
})
|
||||
this.serviceResponseSizeHistogram = metricInstance.createServiceResponseSizeHistogram(
|
||||
{
|
||||
this.serviceResponseSizeHistogram =
|
||||
metricInstance.createServiceResponseSizeHistogram({
|
||||
category,
|
||||
serviceFamily,
|
||||
name,
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
this.metricInstance = undefined
|
||||
this.serviceRequestCounter = undefined
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const RateLimit = require('./rate-limit')
|
||||
|
||||
function setRoutes({ rateLimit }, { server, metricInstance }) {
|
||||
const ipRateLimit = new RateLimit({
|
||||
// Exclude IPs for GitHub Camo, determined experimentally by running e.g.
|
||||
// `curl --insecure -u ":shields-secret" https://s0.shields-server.com/sys/rate-limit`
|
||||
safelist: /^(?:192\.30\.252\.\d+)|(?:140\.82\.115\.\d+)$/,
|
||||
})
|
||||
const badgeTypeRateLimit = new RateLimit({ maxHitsPerPeriod: 3000 })
|
||||
const refererRateLimit = new RateLimit({
|
||||
maxHitsPerPeriod: 300,
|
||||
safelist: /^https?:\/\/shields\.io\/$/,
|
||||
})
|
||||
|
||||
server.handle((req, res, next) => {
|
||||
if (rateLimit) {
|
||||
const ip =
|
||||
(req.headers['x-forwarded-for'] || '').split(', ')[0] ||
|
||||
req.socket.remoteAddress
|
||||
const badgeType = req.url.split(/[/-]/).slice(0, 3).join('')
|
||||
const referer = req.headers.referer
|
||||
|
||||
if (ipRateLimit.isBanned(ip, req, res)) {
|
||||
metricInstance.noteRateLimitExceeded('ip')
|
||||
return
|
||||
}
|
||||
if (badgeTypeRateLimit.isBanned(badgeType, req, res)) {
|
||||
metricInstance.noteRateLimitExceeded('badge_type')
|
||||
return
|
||||
}
|
||||
if (refererRateLimit.isBanned(referer, req, res)) {
|
||||
metricInstance.noteRateLimitExceeded('referrer')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
server.get('/sys/rate-limit', (req, res) => {
|
||||
res.json({
|
||||
ip: ipRateLimit.toJSON(),
|
||||
badgeType: badgeTypeRateLimit.toJSON(),
|
||||
referer: refererRateLimit.toJSON(),
|
||||
})
|
||||
})
|
||||
|
||||
return function () {
|
||||
ipRateLimit.stop()
|
||||
badgeTypeRateLimit.stop()
|
||||
refererRateLimit.stop()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { setRoutes }
|
||||
@@ -19,46 +19,12 @@ module.exports = class PrometheusMetrics {
|
||||
// 250 ms increments up to 2 seconds, then 500 ms increments up to 8
|
||||
// seconds, then 1 second increments up to 15 seconds.
|
||||
buckets: [
|
||||
250,
|
||||
500,
|
||||
750,
|
||||
1000,
|
||||
1250,
|
||||
1500,
|
||||
1750,
|
||||
2000,
|
||||
2250,
|
||||
2500,
|
||||
2750,
|
||||
3000,
|
||||
3250,
|
||||
3500,
|
||||
3750,
|
||||
4000,
|
||||
4500,
|
||||
5000,
|
||||
5500,
|
||||
6000,
|
||||
6500,
|
||||
7000,
|
||||
7500,
|
||||
8000,
|
||||
9000,
|
||||
10000,
|
||||
11000,
|
||||
12000,
|
||||
13000,
|
||||
14000,
|
||||
15000,
|
||||
250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000,
|
||||
3250, 3500, 3750, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500,
|
||||
8000, 9000, 10000, 11000, 12000, 13000, 14000, 15000,
|
||||
],
|
||||
registers: [this.register],
|
||||
}),
|
||||
rateLimitExceeded: new prometheus.Counter({
|
||||
name: 'rate_limit_exceeded_total',
|
||||
help: 'Count of rate limit exceeded by type',
|
||||
labelNames: ['rate_limit_type'],
|
||||
registers: [this.register],
|
||||
}),
|
||||
serviceResponseSize: new prometheus.Histogram({
|
||||
name: 'service_response_bytes',
|
||||
help: 'Service response size in bytes',
|
||||
@@ -110,10 +76,6 @@ module.exports = class PrometheusMetrics {
|
||||
return this.counters.responseTime.observe(responseTime)
|
||||
}
|
||||
|
||||
noteRateLimitExceeded(rateLimitType) {
|
||||
return this.counters.rateLimitExceeded.labels(rateLimitType).inc()
|
||||
}
|
||||
|
||||
createServiceResponseSizeHistogram({ category, serviceFamily, name }) {
|
||||
const service = decamelize(name)
|
||||
return this.counters.serviceResponseSize.labels(
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
// A rate limit ensures that a request parameter gets flagged if it goes
|
||||
// above a limit.
|
||||
module.exports = class RateLimit {
|
||||
constructor(options = {}) {
|
||||
// this.hits: Map from request parameters to the number of hits.
|
||||
this.hits = new Map()
|
||||
this.period = options.period || 200 // 3 min ⅓, in seconds
|
||||
this.maxHitsPerPeriod = options.maxHitsPerPeriod || 500
|
||||
this.banned = new Set()
|
||||
this.bannedUrls = new Set()
|
||||
this.safelist = options.safelist || /(?!)/ // Matches nothing by default.
|
||||
this.interval = setInterval(this.resetHits.bind(this), this.period * 1000)
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.interval)
|
||||
this.interval = undefined
|
||||
}
|
||||
|
||||
resetHits() {
|
||||
this.hits.clear()
|
||||
this.banned.clear()
|
||||
this.bannedUrls.clear()
|
||||
}
|
||||
|
||||
isBanned(reqParam, req, res) {
|
||||
const hitsInCurrentPeriod = this.hits.get(reqParam) || 0
|
||||
if (
|
||||
reqParam != null &&
|
||||
!this.safelist.test(reqParam) &&
|
||||
hitsInCurrentPeriod > this.maxHitsPerPeriod
|
||||
) {
|
||||
this.banned.add(reqParam)
|
||||
}
|
||||
|
||||
if (this.banned.has(reqParam)) {
|
||||
res.statusCode = 429
|
||||
res.setHeader('Retry-After', String(this.period))
|
||||
res.end(
|
||||
`Exceeded limit ${this.maxHitsPerPeriod} requests ` +
|
||||
`per ${this.period} seconds`
|
||||
)
|
||||
this.bannedUrls.add(req.url)
|
||||
return true
|
||||
}
|
||||
|
||||
this.hits.set(reqParam, hitsInCurrentPeriod + 1)
|
||||
return false
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
banned: [...this.banned],
|
||||
hits: [...this.hits],
|
||||
urls: [...this.bannedUrls],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ const { clearRegularUpdateCache } = require('../legacy/regular-update')
|
||||
const { rasterRedirectUrl } = require('../badge-urls/make-badge-url')
|
||||
const { nonNegativeInteger } = require('../../services/validators')
|
||||
const log = require('./log')
|
||||
const sysMonitor = require('./monitor')
|
||||
const PrometheusMetrics = require('./prometheus-metrics')
|
||||
const InfluxMetrics = require('./influx-metrics')
|
||||
|
||||
@@ -139,7 +138,6 @@ const publicConfigSchema = Joi.object({
|
||||
trace: Joi.boolean().required(),
|
||||
}).required(),
|
||||
cacheHeaders: { defaultCacheLengthSeconds: nonNegativeInteger },
|
||||
rateLimit: Joi.boolean().required(),
|
||||
handleInternalErrors: Joi.boolean().required(),
|
||||
fetchLimit: Joi.string().regex(/^[0-9]+(b|kb|mb|gb|tb)$/i),
|
||||
requestTimeoutSeconds: nonNegativeInteger,
|
||||
@@ -431,7 +429,6 @@ class Server {
|
||||
bind: { port, address: hostname },
|
||||
ssl: { isSecure: secure, cert, key },
|
||||
cors: { allowedOrigin },
|
||||
rateLimit,
|
||||
requireCloudflare,
|
||||
} = this.config.public
|
||||
|
||||
@@ -451,13 +448,7 @@ class Server {
|
||||
this.requireCloudflare()
|
||||
}
|
||||
|
||||
const { metricInstance } = this
|
||||
this.cleanupMonitor = sysMonitor.setRoutes(
|
||||
{ rateLimit },
|
||||
{ server: camp, metricInstance }
|
||||
)
|
||||
|
||||
const { githubConstellation } = this
|
||||
const { githubConstellation, metricInstance } = this
|
||||
await githubConstellation.initialize(camp)
|
||||
if (metricInstance) {
|
||||
if (this.config.public.metrics.prometheus.endpointEnabled) {
|
||||
|
||||
@@ -84,7 +84,7 @@ class ServiceTester {
|
||||
.before(() => {
|
||||
this.beforeEach()
|
||||
})
|
||||
// eslint-disable-next-line mocha/prefer-arrow-callback
|
||||
// eslint-disable-next-line mocha/prefer-arrow-callback, promise/prefer-await-to-then
|
||||
.finally(function () {
|
||||
// `this` is the IcedFrisby instance.
|
||||
let responseBody
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('Main page', function () {
|
||||
})
|
||||
|
||||
it('Do not duplicate example parameters', function () {
|
||||
cy.visit('/category/social')
|
||||
cy.visit('/category/funding')
|
||||
|
||||
cy.contains('GitHub Sponsors').click()
|
||||
cy.get('[name="style"]').should($style => {
|
||||
|
||||
@@ -32,7 +32,8 @@ export default function Customizer({
|
||||
}): JSX.Element {
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/35572
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884#issuecomment-471341041
|
||||
const indicatorRef = useRef<CopiedContentIndicatorHandle>() as React.MutableRefObject<CopiedContentIndicatorHandle>
|
||||
const indicatorRef =
|
||||
useRef<CopiedContentIndicatorHandle>() as React.MutableRefObject<CopiedContentIndicatorHandle>
|
||||
const [path, setPath] = useState('')
|
||||
const [queryString, setQueryString] = useState<string>()
|
||||
const [pathIsComplete, setPathIsComplete] = useState<boolean>()
|
||||
|
||||
@@ -57,16 +57,19 @@ export default function DynamicBadgeMaker({
|
||||
e.preventDefault()
|
||||
|
||||
const { datatype, label, dataUrl, query, color, prefix, suffix } = values
|
||||
window.location.href = dynamicBadgeUrl({
|
||||
baseUrl,
|
||||
datatype,
|
||||
label,
|
||||
dataUrl,
|
||||
query,
|
||||
color,
|
||||
prefix,
|
||||
suffix,
|
||||
})
|
||||
window.open(
|
||||
dynamicBadgeUrl({
|
||||
baseUrl,
|
||||
datatype,
|
||||
label,
|
||||
dataUrl,
|
||||
query,
|
||||
color,
|
||||
prefix,
|
||||
suffix,
|
||||
}),
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -45,14 +45,13 @@ export default function Main({
|
||||
}): JSX.Element {
|
||||
const [searchIsInProgress, setSearchIsInProgress] = useState(false)
|
||||
const [queryIsTooShort, setQueryIsTooShort] = useState(false)
|
||||
const [searchResults, setSearchResults] = useState<{
|
||||
[k: string]: ServiceDefinition[]
|
||||
}>()
|
||||
const [searchResults, setSearchResults] =
|
||||
useState<{
|
||||
[k: string]: ServiceDefinition[]
|
||||
}>()
|
||||
const [selectedExample, setSelectedExample] = useState<RenderableExample>()
|
||||
const [
|
||||
selectedExampleIsSuggestion,
|
||||
setSelectedExampleIsSuggestion,
|
||||
] = useState(false)
|
||||
const [selectedExampleIsSuggestion, setSelectedExampleIsSuggestion] =
|
||||
useState(false)
|
||||
const searchTimeout = useRef(0)
|
||||
const baseUrl = getBaseUrl()
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function StaticBadgeMaker({
|
||||
e.preventDefault()
|
||||
|
||||
const { label, message, color } = values
|
||||
window.location.href = staticBadgeUrl({ baseUrl, label, message, color })
|
||||
window.open(staticBadgeUrl({ baseUrl, label, message, color }), '_blank')
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
13930
package-lock.json
generated
13930
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
72
package.json
72
package.json
@@ -22,9 +22,9 @@
|
||||
"url": "https://github.com/badges/shields"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/lato": "^4.2.2",
|
||||
"@fontsource/lekton": "^4.2.2",
|
||||
"@sentry/node": "^6.3.0",
|
||||
"@fontsource/lato": "^4.4.1",
|
||||
"@fontsource/lekton": "^4.4.0",
|
||||
"@sentry/node": "^6.4.1",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.0",
|
||||
@@ -38,11 +38,11 @@
|
||||
"emojic": "^1.1.16",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^3.19.0",
|
||||
"glob": "^7.1.6",
|
||||
"glob": "^7.1.7",
|
||||
"got": "11.8.2",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"ioredis": "4.26.0",
|
||||
"graphql-tag": "^2.12.4",
|
||||
"ioredis": "4.27.3",
|
||||
"joi": "17.4.0",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -60,7 +60,7 @@
|
||||
"query-string": "^7.0.0",
|
||||
"request": "~2.88.2",
|
||||
"semver": "~7.3.5",
|
||||
"simple-icons": "4.20.0",
|
||||
"simple-icons": "4.25.0",
|
||||
"webextension-store-meta": "^1.0.3",
|
||||
"xmldom": "~0.6.0",
|
||||
"xpath": "~0.0.32"
|
||||
@@ -141,21 +141,21 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.13.16",
|
||||
"@mapbox/react-click-to-select": "^2.2.1",
|
||||
"@types/chai": "^4.2.17",
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.groupby": "^4.6.6",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/node": "^15.6.1",
|
||||
"@types/react-helmet": "^6.1.1",
|
||||
"@types/react-modal": "^3.12.0",
|
||||
"@types/react-select": "^4.0.15",
|
||||
"@types/styled-components": "5.1.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-preset-gatsby": "^1.2.0",
|
||||
@@ -166,35 +166,35 @@
|
||||
"chai-string": "^1.4.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^6.0.2",
|
||||
"cypress": "^7.2.0",
|
||||
"concurrently": "^6.2.0",
|
||||
"cypress": "^7.4.0",
|
||||
"danger": "^10.6.4",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-config-prettier": "^8.2.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint": "^7.27.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.6.0",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsdoc": "^32.3.1",
|
||||
"eslint-plugin-mocha": "^8.1.0",
|
||||
"eslint-plugin-chai-friendly": "^0.7.1",
|
||||
"eslint-plugin-cypress": "^2.11.3",
|
||||
"eslint-plugin-import": "^2.23.3",
|
||||
"eslint-plugin-jsdoc": "^35.0.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": "^4.3.1",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"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": "3.3.1",
|
||||
"gatsby": "3.6.1",
|
||||
"gatsby-plugin-catch-links": "^3.1.0",
|
||||
"gatsby-plugin-page-creator": "^3.3.0",
|
||||
"gatsby-plugin-page-creator": "^3.6.0",
|
||||
"gatsby-plugin-react-helmet": "^4.1.0",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^3.1.0",
|
||||
"gatsby-plugin-styled-components": "^4.3.0",
|
||||
"gatsby-plugin-styled-components": "^4.6.0",
|
||||
"gatsby-plugin-typescript": "^3.2.0",
|
||||
"humanize-string": "^2.1.0",
|
||||
"husky": "^4.3.8",
|
||||
@@ -202,12 +202,12 @@
|
||||
"icedfrisby-nock": "^2.0.0",
|
||||
"is-svg": "^4.3.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdoc": "^3.6.6",
|
||||
"lint-staged": "^10.5.4",
|
||||
"jsdoc": "^3.6.7",
|
||||
"lint-staged": "^11.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.5",
|
||||
"mocha": "^8.3.2",
|
||||
"mocha": "^8.4.0",
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.0.0",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
@@ -218,27 +218,27 @@
|
||||
"nyc": "^15.1.0",
|
||||
"opn-cli": "^5.0.0",
|
||||
"portfinder": "^1.0.28",
|
||||
"prettier": "2.2.1",
|
||||
"prettier": "2.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-overlay": "^6.0.9",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-modal": "^3.13.1",
|
||||
"react-pose": "^4.0.10",
|
||||
"react-select": "^4.3.0",
|
||||
"react-select": "^4.3.1",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"redis-server": "^1.2.2",
|
||||
"require-hacker": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"sazerac": "^2.0.0",
|
||||
"sinon": "^10.0.1",
|
||||
"sinon-chai": "^3.6.0",
|
||||
"sinon": "^11.1.1",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"snap-shot-it": "^7.9.6",
|
||||
"start-server-and-test": "1.12.1",
|
||||
"styled-components": "^5.2.3",
|
||||
"start-server-and-test": "1.12.3",
|
||||
"styled-components": "^5.3.0",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"tsd": "^0.14.0",
|
||||
"typescript": "^4.2.4"
|
||||
"typescript": "^4.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.18.3",
|
||||
|
||||
@@ -14,22 +14,21 @@ set -euo pipefail
|
||||
node --version
|
||||
|
||||
# Install the shields.io dependencies.
|
||||
if [[ "$NODE_VERSION" == "v10" ]]; then
|
||||
# Avoid a depcheck error.
|
||||
npm ci --ignore-scripts
|
||||
else
|
||||
npm ci
|
||||
fi
|
||||
npm ci
|
||||
|
||||
# Run the package tests.
|
||||
npm run test:package
|
||||
npm run check-types:package
|
||||
|
||||
# Delete the shields.io dependencies.
|
||||
# Delete the full shields.io dependency tree
|
||||
rm -rf node_modules/
|
||||
|
||||
|
||||
# Run a smoke test (render a badge with the CLI) with only the package
|
||||
# dependencies installed.
|
||||
cd badge-maker
|
||||
|
||||
npm install # install only the package dependencies for this test
|
||||
npm link
|
||||
badge cactus grown :green @flat
|
||||
rm package-lock.json && rm -rf node_modules/ # clean up package dependencies
|
||||
|
||||
@@ -12,16 +12,10 @@ const schema = Joi.object()
|
||||
)
|
||||
.required(),
|
||||
|
||||
// latest_stable_release can be object or NULL
|
||||
latest_stable_release: Joi.object()
|
||||
.keys({
|
||||
name: Joi.string().required(),
|
||||
})
|
||||
.allow(null),
|
||||
|
||||
// latest_release_number can be NULL for bower because bower
|
||||
// Keys can be NULL for bower because bower
|
||||
// has no registry to enforce any release exists
|
||||
latest_release_number: Joi.string().allow(null),
|
||||
latest_stable_release_number: Joi.string().allow(null),
|
||||
})
|
||||
.required()
|
||||
|
||||
|
||||
@@ -32,17 +32,15 @@ class BowerVersion extends BaseBowerService {
|
||||
async handle({ packageName }, queryParams) {
|
||||
const data = await this.fetch({ packageName })
|
||||
const includePrereleases = queryParams.include_prereleases !== undefined
|
||||
const version = includePrereleases
|
||||
? data.latest_release_number
|
||||
: data.latest_stable_release_number
|
||||
|
||||
if (includePrereleases) {
|
||||
if (data.latest_release_number) {
|
||||
return renderVersionBadge({ version: data.latest_release_number })
|
||||
}
|
||||
} else {
|
||||
if (data.latest_stable_release) {
|
||||
return renderVersionBadge({ version: data.latest_stable_release.name })
|
||||
}
|
||||
if (!version) {
|
||||
throw new InvalidResponse({ prettyMessage: 'no releases' })
|
||||
}
|
||||
throw new InvalidResponse({ prettyMessage: 'no releases' })
|
||||
|
||||
return renderVersionBadge({ version })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,16 +113,14 @@ module.exports = class CIIBestPracticesService extends BaseJsonService {
|
||||
|
||||
async handle({ metric, projectId }) {
|
||||
// No official API documentation is available.
|
||||
const {
|
||||
badge_level: level,
|
||||
tiered_percentage: percentage,
|
||||
} = await this._requestJson({
|
||||
schema,
|
||||
url: `https://bestpractices.coreinfrastructure.org/projects/${projectId}/badge.json`,
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
})
|
||||
const { badge_level: level, tiered_percentage: percentage } =
|
||||
await this._requestJson({
|
||||
schema,
|
||||
url: `https://bestpractices.coreinfrastructure.org/projects/${projectId}/badge.json`,
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
})
|
||||
|
||||
if (metric === 'level') {
|
||||
return this.constructor.renderLevelBadge({ level })
|
||||
|
||||
@@ -26,7 +26,7 @@ class CranLicense extends BaseCranService {
|
||||
{
|
||||
title: 'CRAN/METACRAN',
|
||||
namedParams: { packageName: 'devtools' },
|
||||
staticPreview: this.render({ license: 'GPL (>= 2)' }),
|
||||
staticPreview: this.render({ license: 'MIT + file LICENSE' }),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ t.create('version (not found)')
|
||||
|
||||
t.create('license (valid)')
|
||||
.get('/l/devtools.json')
|
||||
.expectBadge({ label: 'license', message: 'GPL (>= 2)' })
|
||||
.expectBadge({ label: 'license', message: 'MIT + file LICENSE' })
|
||||
|
||||
t.create('license (not found)')
|
||||
.get('/l/some-bogus-package.json')
|
||||
|
||||
@@ -83,12 +83,12 @@ module.exports = class David extends BaseJsonService {
|
||||
options: { qs: { path } },
|
||||
errorMessages: {
|
||||
/* note:
|
||||
david returns a 500 response for 'not found'
|
||||
david returns a 504 response for 'not found'
|
||||
e.g: https://david-dm.org/foo/barbaz/info.json
|
||||
not a 404 so we can't handle 'not found' cleanly
|
||||
because this might also be some other error.
|
||||
*/
|
||||
500: 'repo or path not found or david internal error',
|
||||
504: 'repo or path not found or david internal error',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const isDependencyStatus = Joi.string().valid(
|
||||
|
||||
t.create('david dependencies (valid)')
|
||||
.get('/expressjs/express.json')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'dependencies',
|
||||
message: isDependencyStatus,
|
||||
@@ -18,6 +19,7 @@ t.create('david dependencies (valid)')
|
||||
|
||||
t.create('david dev dependencies (valid)')
|
||||
.get('/dev/expressjs/express.json')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'dev dependencies',
|
||||
message: isDependencyStatus,
|
||||
@@ -25,6 +27,7 @@ t.create('david dev dependencies (valid)')
|
||||
|
||||
t.create('david optional dependencies (valid)')
|
||||
.get('/optional/elnounch/byebye.json')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'optional dependencies',
|
||||
message: isDependencyStatus,
|
||||
@@ -32,6 +35,7 @@ t.create('david optional dependencies (valid)')
|
||||
|
||||
t.create('david peer dependencies (valid)')
|
||||
.get('/peer/webcomponents/generator-element.json')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'peer dependencies',
|
||||
message: isDependencyStatus,
|
||||
@@ -39,6 +43,7 @@ t.create('david peer dependencies (valid)')
|
||||
|
||||
t.create('david dependencies with path (valid)')
|
||||
.get('/babel/babel.json?path=packages/babel-core')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'dependencies',
|
||||
message: isDependencyStatus,
|
||||
@@ -46,10 +51,12 @@ t.create('david dependencies with path (valid)')
|
||||
|
||||
t.create('david dependencies (none)')
|
||||
.get('/peer/expressjs/express.json') // express does not specify peer dependencies
|
||||
.timeout(15000)
|
||||
.expectBadge({ label: 'peer dependencies', message: 'none' })
|
||||
|
||||
t.create('david dependencies (repo not found)')
|
||||
.get('/pyvesb/emptyrepo.json')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'dependencies',
|
||||
message: 'repo or path not found or david internal error',
|
||||
@@ -57,6 +64,7 @@ t.create('david dependencies (repo not found)')
|
||||
|
||||
t.create('david dependencies (path not found')
|
||||
.get('/babel/babel.json?path=invalid/path')
|
||||
.timeout(15000)
|
||||
.expectBadge({
|
||||
label: 'dependencies',
|
||||
message: 'repo or path not found or david internal error',
|
||||
|
||||
@@ -25,43 +25,31 @@ const documentation = `
|
||||
`
|
||||
|
||||
module.exports = class Discord extends BaseJsonService {
|
||||
static get category() {
|
||||
return 'chat'
|
||||
static category = 'chat'
|
||||
|
||||
static route = {
|
||||
base: 'discord',
|
||||
pattern: ':serverId',
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'discord',
|
||||
pattern: ':serverId',
|
||||
}
|
||||
static auth = {
|
||||
passKey: 'discord_bot_token',
|
||||
authorizedOrigins: ['https://discord.com'],
|
||||
isRequired: false,
|
||||
}
|
||||
|
||||
static get auth() {
|
||||
return {
|
||||
passKey: 'discord_bot_token',
|
||||
authorizedOrigins: ['https://discord.com'],
|
||||
isRequired: false,
|
||||
}
|
||||
}
|
||||
static examples = [
|
||||
{
|
||||
title: 'Discord',
|
||||
namedParams: { serverId: '102860784329052160' },
|
||||
staticPreview: this.render({ members: 23 }),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Discord',
|
||||
namedParams: { serverId: '102860784329052160' },
|
||||
staticPreview: this.render({ members: 23 }),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
static _cacheLength = 30
|
||||
|
||||
static get _cacheLength() {
|
||||
return 30
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'chat' }
|
||||
}
|
||||
static defaultBadgeData = { label: 'chat' }
|
||||
|
||||
static render({ members }) {
|
||||
return {
|
||||
|
||||
@@ -1,57 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { anyInteger } = require('../validators')
|
||||
const { BaseJsonService } = require('..')
|
||||
const {
|
||||
dockerBlue,
|
||||
buildDockerUrl,
|
||||
getDockerHubUser,
|
||||
} = require('./docker-helpers')
|
||||
const { deprecatedService } = require('..')
|
||||
|
||||
const buildSchema = Joi.object({
|
||||
results: Joi.array()
|
||||
.items(Joi.object({ status: anyInteger }).required())
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
module.exports = class DockerBuild extends BaseJsonService {
|
||||
static category = 'build'
|
||||
static route = buildDockerUrl('build')
|
||||
static examples = [
|
||||
{
|
||||
title: 'Docker Build Status',
|
||||
namedParams: {
|
||||
user: 'jrottenberg',
|
||||
repo: 'ffmpeg',
|
||||
},
|
||||
staticPreview: this.render({ status: 10 }),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'docker build' }
|
||||
|
||||
static render({ status }) {
|
||||
if (status === 10) {
|
||||
return { message: 'passing', color: 'brightgreen' }
|
||||
} else if (status < 0) {
|
||||
return { message: 'failing', color: 'red' }
|
||||
}
|
||||
return { message: 'building', color: dockerBlue }
|
||||
}
|
||||
|
||||
async fetch({ user, repo }) {
|
||||
return this._requestJson({
|
||||
schema: buildSchema,
|
||||
url: `https://registry.hub.docker.com/v2/repositories/${getDockerHubUser(
|
||||
user
|
||||
)}/${repo}/buildhistory`,
|
||||
errorMessages: { 404: 'repo not found' },
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ user, repo }) {
|
||||
const data = await this.fetch({ user, repo })
|
||||
return this.constructor.render({ status: data.results[0].status })
|
||||
}
|
||||
}
|
||||
module.exports = deprecatedService({
|
||||
category: 'build',
|
||||
route: {
|
||||
base: 'docker/build',
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'docker build',
|
||||
dateAdded: new Date('2021-05-24'),
|
||||
})
|
||||
|
||||
@@ -1,51 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
const { isBuildStatus } = require('../build-status')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
const { dockerBlue } = require('./docker-helpers')
|
||||
const { ServiceTester } = require('../tester')
|
||||
|
||||
t.create('docker build status (valid, user)')
|
||||
.get('/jrottenberg/ffmpeg.json')
|
||||
.expectBadge({
|
||||
label: 'docker build',
|
||||
message: isBuildStatus,
|
||||
})
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'dockerbuild',
|
||||
title: 'DockerBuild',
|
||||
pathPrefix: '/docker/build',
|
||||
}))
|
||||
|
||||
t.create('docker build status (not found)')
|
||||
.get('/_/not-a-real-repo.json')
|
||||
.expectBadge({ label: 'docker build', message: 'repo not found' })
|
||||
|
||||
t.create('docker build status (passing)')
|
||||
.get('/_/ubuntu.json')
|
||||
.intercept(nock =>
|
||||
nock('https://registry.hub.docker.com/')
|
||||
.get('/v2/repositories/library/ubuntu/buildhistory')
|
||||
.reply(200, { results: [{ status: 10 }] })
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'docker build',
|
||||
message: 'passing',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
|
||||
t.create('docker build status (failing)')
|
||||
.get('/_/ubuntu.json')
|
||||
.intercept(nock =>
|
||||
nock('https://registry.hub.docker.com/')
|
||||
.get('/v2/repositories/library/ubuntu/buildhistory')
|
||||
.reply(200, { results: [{ status: -1 }] })
|
||||
)
|
||||
.expectBadge({ label: 'docker build', message: 'failing', color: 'red' })
|
||||
|
||||
t.create('docker build status (building)')
|
||||
.get('/_/ubuntu.json')
|
||||
.intercept(nock =>
|
||||
nock('https://registry.hub.docker.com/')
|
||||
.get('/v2/repositories/library/ubuntu/buildhistory')
|
||||
.reply(200, { results: [{ status: 1 }] })
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'docker build',
|
||||
message: 'building',
|
||||
color: `#${dockerBlue}`,
|
||||
})
|
||||
t.create('no longer available').get('/jrottenberg/ffmpeg.json').expectBadge({
|
||||
label: 'docker build',
|
||||
message: 'no longer available',
|
||||
})
|
||||
|
||||
@@ -23,7 +23,12 @@ const totalResponseSchema = Joi.object({
|
||||
}).required()
|
||||
|
||||
function DownloadsForInterval(interval) {
|
||||
const { base, schema, messageSuffix = '', name } = {
|
||||
const {
|
||||
base,
|
||||
schema,
|
||||
messageSuffix = '',
|
||||
name,
|
||||
} = {
|
||||
month: {
|
||||
base: 'eclipse-marketplace/dm',
|
||||
messageSuffix: '/month',
|
||||
|
||||
@@ -126,16 +126,14 @@ module.exports = class GemDownloads extends BaseJsonService {
|
||||
}
|
||||
|
||||
async fetchDownloadCountForGem({ gem }) {
|
||||
const {
|
||||
downloads: totalDownloads,
|
||||
version_downloads: versionDownloads,
|
||||
} = await this._requestJson({
|
||||
url: `https://rubygems.org/api/v1/gems/${gem}.json`,
|
||||
schema: gemSchema,
|
||||
errorMessages: {
|
||||
404: 'gem not found',
|
||||
},
|
||||
})
|
||||
const { downloads: totalDownloads, version_downloads: versionDownloads } =
|
||||
await this._requestJson({
|
||||
url: `https://rubygems.org/api/v1/gems/${gem}.json`,
|
||||
schema: gemSchema,
|
||||
errorMessages: {
|
||||
404: 'gem not found',
|
||||
},
|
||||
})
|
||||
return { totalDownloads, versionDownloads }
|
||||
}
|
||||
|
||||
@@ -154,10 +152,8 @@ module.exports = class GemDownloads extends BaseJsonService {
|
||||
}
|
||||
downloads = await this.fetchDownloadCountForVersion({ gem, version })
|
||||
} else {
|
||||
const {
|
||||
totalDownloads,
|
||||
versionDownloads,
|
||||
} = await this.fetchDownloadCountForGem({ gem, variant })
|
||||
const { totalDownloads, versionDownloads } =
|
||||
await this.fetchDownloadCountForGem({ gem, variant })
|
||||
downloads = variant === 'dtv' ? versionDownloads : totalDownloads
|
||||
}
|
||||
return this.constructor.render({ variant, version, downloads })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { renderVersionBadge } = require('../version')
|
||||
const { renderVersionBadge, latest } = require('../version')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
const schema = Joi.object({
|
||||
@@ -10,9 +10,22 @@ const schema = Joi.object({
|
||||
version: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
const versionSchema = Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
number: Joi.string().required(),
|
||||
})
|
||||
)
|
||||
.min(1)
|
||||
.required()
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
include_prereleases: Joi.equal(''),
|
||||
}).required()
|
||||
|
||||
module.exports = class GemVersion extends BaseJsonService {
|
||||
static category = 'version'
|
||||
static route = { base: 'gem/v', pattern: ':gem' }
|
||||
static route = { base: 'gem/v', pattern: ':gem', queryParamSchema }
|
||||
static examples = [
|
||||
{
|
||||
title: 'Gem',
|
||||
@@ -20,6 +33,15 @@ module.exports = class GemVersion extends BaseJsonService {
|
||||
staticPreview: this.render({ version: '2.1.0' }),
|
||||
keywords: ['ruby'],
|
||||
},
|
||||
{
|
||||
title: 'Gem (including prereleases)',
|
||||
namedParams: { gem: 'flame' },
|
||||
queryParams: {
|
||||
include_prereleases: null,
|
||||
},
|
||||
staticPreview: this.render({ version: '5.0.0.rc6' }),
|
||||
keywords: ['ruby'],
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'gem' }
|
||||
@@ -35,8 +57,21 @@ module.exports = class GemVersion extends BaseJsonService {
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ gem }) {
|
||||
const { version } = await this.fetch({ gem })
|
||||
return this.constructor.render({ version })
|
||||
async fetchLatest({ gem }) {
|
||||
return this._requestJson({
|
||||
schema: versionSchema,
|
||||
url: `https://rubygems.org/api/v1/versions/${gem}.json`,
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ gem }, queryParams) {
|
||||
if (queryParams && typeof queryParams.include_prereleases !== 'undefined') {
|
||||
const data = await this.fetchLatest({ gem })
|
||||
const versions = data.map(version => version.number)
|
||||
return this.constructor.render({ version: latest(versions) })
|
||||
} else {
|
||||
const { version } = await this.fetch({ gem })
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const { isVPlusDottedVersionAtLeastOne } = require('../test-validators')
|
||||
const {
|
||||
isVPlusDottedVersionAtLeastOne,
|
||||
withRegex,
|
||||
} = require('../test-validators')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('version (valid)').get('/formatador.json').expectBadge({
|
||||
@@ -11,3 +14,19 @@ t.create('version (valid)').get('/formatador.json').expectBadge({
|
||||
t.create('version (not found)')
|
||||
.get('/not-a-package.json')
|
||||
.expectBadge({ label: 'gem', message: 'not found' })
|
||||
|
||||
// this is the same as isVPlusDottedVersionNClausesWithOptionalSuffix from test-validators.js
|
||||
// except that it also accepts regexes like 5.0.0.rc5 - the . before the rc5 is not accepted in the original
|
||||
const isVPlusDottedVersionNClausesWithOptionalSuffix = withRegex(
|
||||
/^v\d+(\.\d+)*([-+~.].*)?$/
|
||||
)
|
||||
t.create('version including prereleases (valid)')
|
||||
.get('/flame.json?include_prereleases')
|
||||
.expectBadge({
|
||||
label: 'gem',
|
||||
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
})
|
||||
|
||||
t.create('version including prereleases (not found)')
|
||||
.get('/not-a-package.json?include_prereleases')
|
||||
.expectBadge({ label: 'gem', message: 'not found' })
|
||||
|
||||
@@ -101,11 +101,8 @@ class GithubApiProvider {
|
||||
let rateLimit, totalUsesRemaining, nextReset
|
||||
if (url.startsWith('/graphql')) {
|
||||
try {
|
||||
;({
|
||||
rateLimit,
|
||||
totalUsesRemaining,
|
||||
nextReset,
|
||||
} = this.getV4RateLimitFromBody(res.body))
|
||||
;({ rateLimit, totalUsesRemaining, nextReset } =
|
||||
this.getV4RateLimitFromBody(res.body))
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Could not extract rate limit info from response body ${res.body}`
|
||||
@@ -115,11 +112,8 @@ class GithubApiProvider {
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
;({
|
||||
rateLimit,
|
||||
totalUsesRemaining,
|
||||
nextReset,
|
||||
} = this.getV3RateLimitFromHeaders(res.headers))
|
||||
;({ rateLimit, totalUsesRemaining, nextReset } =
|
||||
this.getV3RateLimitFromHeaders(res.headers))
|
||||
} catch (e) {
|
||||
const logHeaders = {
|
||||
'x-ratelimit-limit': res.headers['x-ratelimit-limit'],
|
||||
@@ -188,6 +182,7 @@ class GithubApiProvider {
|
||||
'User-Agent': userAgent,
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `token ${tokenString}`,
|
||||
...options.headers,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ module.exports = class GithubDeployments extends GithubAuthV4Service {
|
||||
async fetch({ user, repo, environment }) {
|
||||
return this._requestGraphql({
|
||||
query: gql`
|
||||
query($user: String!, $repo: String!, $environment: String!) {
|
||||
query ($user: String!, $repo: String!, $environment: String!) {
|
||||
repository(owner: $user, name: $repo) {
|
||||
deployments(last: 1, environments: [$environment]) {
|
||||
nodes {
|
||||
|
||||
74
services/github/github-discussions-total.service.js
Normal file
74
services/github/github-discussions-total.service.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const { default: gql } = require('graphql-tag')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { GithubAuthV4Service } = require('./github-auth-service')
|
||||
const { transformErrors } = require('./github-helpers')
|
||||
|
||||
const schema = Joi.object({
|
||||
data: Joi.object({
|
||||
repository: Joi.object({
|
||||
discussions: Joi.object({
|
||||
totalCount: nonNegativeInteger,
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required()
|
||||
|
||||
module.exports = class GithubTotalDiscussions extends GithubAuthV4Service {
|
||||
static category = 'other'
|
||||
static route = {
|
||||
base: 'github/discussions',
|
||||
pattern: ':user/:repo',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'GitHub Discussions',
|
||||
namedParams: {
|
||||
user: 'vercel',
|
||||
repo: 'next.js',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
discussions: '6000 total',
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'discussions', color: 'blue' }
|
||||
|
||||
static render({ discussions }) {
|
||||
return { message: discussions }
|
||||
}
|
||||
|
||||
async fetch({ user, repo }) {
|
||||
return this._requestGraphql({
|
||||
query: gql`
|
||||
query ($user: String!, $repo: String!) {
|
||||
repository(name: $repo, owner: $user) {
|
||||
discussions {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { user, repo },
|
||||
schema,
|
||||
options: { headers: { 'GraphQL-Features': 'discussions_api' } },
|
||||
transformErrors,
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ user, repo }) {
|
||||
const json = await this.fetch({ user, repo })
|
||||
const {
|
||||
data: {
|
||||
repository: {
|
||||
discussions: { totalCount },
|
||||
},
|
||||
},
|
||||
} = json
|
||||
return this.constructor.render({ discussions: `${totalCount} total` })
|
||||
}
|
||||
}
|
||||
15
services/github/github-discussions-total.tester.js
Normal file
15
services/github/github-discussions-total.tester.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const { withRegex } = require('../test-validators')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('GitHub Total Discussions (repo not found)')
|
||||
.get('/not-a-user/not-a-repo.json')
|
||||
.expectBadge({ label: 'discussions', message: 'repo not found' })
|
||||
|
||||
// example: 6000 total
|
||||
const numberSpaceTotal = withRegex(/^\d+ total$/)
|
||||
|
||||
t.create('GitHub Total Discussions (repo having discussions)')
|
||||
.get('/vercel/next.js.json')
|
||||
.expectBadge({ label: 'discussions', message: numberSpaceTotal })
|
||||
@@ -58,7 +58,7 @@ module.exports = class GithubForks extends GithubAuthV4Service {
|
||||
async handle({ user, repo }) {
|
||||
const json = await this._requestGraphql({
|
||||
query: gql`
|
||||
query($user: String!, $repo: String!) {
|
||||
query ($user: String!, $repo: String!) {
|
||||
repository(owner: $user, name: $repo) {
|
||||
forkCount
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ module.exports = class GithubHacktoberfestCombinedStatus extends (
|
||||
},
|
||||
} = await this._requestGraphql({
|
||||
query: gql`
|
||||
query(
|
||||
query (
|
||||
$user: String!
|
||||
$repo: String!
|
||||
$suggestionLabel: String!
|
||||
|
||||
@@ -30,7 +30,7 @@ class BaseGithubIssuesSearch extends GithubAuthV4Service {
|
||||
async fetch({ query }) {
|
||||
const data = await this._requestGraphql({
|
||||
query: gql`
|
||||
query($query: String!) {
|
||||
query ($query: String!) {
|
||||
search(query: $query, type: ISSUE) {
|
||||
issueCount
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ module.exports = class GithubIssues extends GithubAuthV4Service {
|
||||
},
|
||||
} = await this._requestGraphql({
|
||||
query: gql`
|
||||
query(
|
||||
query (
|
||||
$user: String!
|
||||
$repo: String!
|
||||
$states: [PullRequestState!]
|
||||
@@ -359,7 +359,7 @@ module.exports = class GithubIssues extends GithubAuthV4Service {
|
||||
},
|
||||
} = await this._requestGraphql({
|
||||
query: gql`
|
||||
query(
|
||||
query (
|
||||
$user: String!
|
||||
$repo: String!
|
||||
$states: [IssueState!]
|
||||
|
||||
@@ -151,17 +151,14 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthV3Service
|
||||
{ user, repo, kind, branch = 'HEAD', scope, packageName },
|
||||
{ filename = 'package.json' }
|
||||
) {
|
||||
const {
|
||||
dependencies,
|
||||
devDependencies,
|
||||
peerDependencies,
|
||||
} = await fetchJsonFromRepo(this, {
|
||||
schema: isPackageJsonWithDependencies,
|
||||
user,
|
||||
repo,
|
||||
branch,
|
||||
filename,
|
||||
})
|
||||
const { dependencies, devDependencies, peerDependencies } =
|
||||
await fetchJsonFromRepo(this, {
|
||||
schema: isPackageJsonWithDependencies,
|
||||
user,
|
||||
repo,
|
||||
branch,
|
||||
filename,
|
||||
})
|
||||
|
||||
const wantedDependency = scope ? `${scope}/${packageName}` : packageName
|
||||
const { range } = getDependencyVersion({
|
||||
|
||||
@@ -19,24 +19,19 @@ const schema = Joi.object({
|
||||
}).required()
|
||||
|
||||
module.exports = class GithubSponsors extends GithubAuthV4Service {
|
||||
static category = 'social'
|
||||
static category = 'funding'
|
||||
static route = { base: 'github/sponsors', pattern: ':user' }
|
||||
static examples = [
|
||||
{
|
||||
title: 'GitHub Sponsors',
|
||||
namedParams: { user: 'Homebrew' },
|
||||
queryParams: { style: 'social' },
|
||||
staticPreview: {
|
||||
message: '217',
|
||||
style: 'social',
|
||||
},
|
||||
staticPreview: this.render({ count: 217 }),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
label: 'sponsors',
|
||||
namedLogo: 'github',
|
||||
}
|
||||
|
||||
static render({ count }) {
|
||||
@@ -49,7 +44,7 @@ module.exports = class GithubSponsors extends GithubAuthV4Service {
|
||||
async fetch({ user }) {
|
||||
return this._requestGraphql({
|
||||
query: gql`
|
||||
query($user: String!) {
|
||||
query ($user: String!) {
|
||||
repositoryOwner(login: $user) {
|
||||
... on User {
|
||||
sponsorshipsAsMaintainer {
|
||||
|
||||
@@ -79,7 +79,7 @@ class GithubTag extends GithubAuthV4Service {
|
||||
const limit = sort === 'semver' ? 100 : 1
|
||||
return this._requestGraphql({
|
||||
query: gql`
|
||||
query($user: String!, $repo: String!, $limit: Int!) {
|
||||
query ($user: String!, $repo: String!, $limit: Int!) {
|
||||
repository(owner: $user, name: $repo) {
|
||||
refs(
|
||||
refPrefix: "refs/tags/"
|
||||
|
||||
@@ -115,9 +115,8 @@ module.exports = class JenkinsCoverage extends JenkinsBase {
|
||||
}
|
||||
|
||||
async handle({ format }, { jobUrl, disableStrictSSL }) {
|
||||
const { schema, transform, treeQueryParam, pluginSpecificPath } = formatMap[
|
||||
format
|
||||
]
|
||||
const { schema, transform, treeQueryParam, pluginSpecificPath } =
|
||||
formatMap[format]
|
||||
const json = await this.fetch({
|
||||
url: buildUrl({ jobUrl, plugin: pluginSpecificPath }),
|
||||
schema,
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
const { createServiceFamily } = require('../nuget/nuget-v3-service-family')
|
||||
|
||||
const {
|
||||
NugetVersionService: Version,
|
||||
NugetDownloadService: Downloads,
|
||||
} = createServiceFamily({
|
||||
defaultLabel: 'myget',
|
||||
serviceBaseUrl: 'myget',
|
||||
apiDomain: 'myget.org',
|
||||
})
|
||||
const { NugetVersionService: Version, NugetDownloadService: Downloads } =
|
||||
createServiceFamily({
|
||||
defaultLabel: 'myget',
|
||||
serviceBaseUrl: 'myget',
|
||||
apiDomain: 'myget.org',
|
||||
})
|
||||
|
||||
class MyGetVersionService extends Version {
|
||||
static examples = [
|
||||
@@ -29,11 +27,11 @@ class MyGetVersionService extends Version {
|
||||
title: 'MyGet tenant',
|
||||
pattern: ':tenant.myget/:feed/v/:packageName',
|
||||
namedParams: {
|
||||
tenant: 'dotnet',
|
||||
feed: 'dotnet-coreclr',
|
||||
packageName: 'Microsoft.DotNet.CoreCLR',
|
||||
tenant: 'cefsharp',
|
||||
feed: 'cefsharp',
|
||||
packageName: 'cef.sdk',
|
||||
},
|
||||
staticPreview: this.render({ version: '1.0.2-prerelease' }),
|
||||
staticPreview: this.render({ version: '91.1.1' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -50,9 +48,9 @@ class MyGetDownloadService extends Downloads {
|
||||
title: 'MyGet tenant',
|
||||
pattern: ':tenant.myget/:feed/dt/:packageName',
|
||||
namedParams: {
|
||||
tenant: 'dotnet',
|
||||
feed: 'dotnet-coreclr',
|
||||
packageName: 'Microsoft.DotNet.CoreCLR',
|
||||
tenant: 'cefsharp',
|
||||
feed: 'cefsharp',
|
||||
packageName: 'CefSharp',
|
||||
},
|
||||
staticPreview: this.render({ downloads: 9748 }),
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ t.create('total downloads (valid)')
|
||||
})
|
||||
|
||||
t.create('total downloads (tenant)')
|
||||
.get('/dotnet.myget/dotnet-coreclr/dt/Microsoft.DotNet.CoreCLR.json')
|
||||
.get('/cefsharp.myget/cefsharp/dt/CefSharp.Common.json')
|
||||
.expectBadge({
|
||||
label: 'downloads',
|
||||
message: isMetric,
|
||||
@@ -70,10 +70,10 @@ t.create('version (valid)')
|
||||
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
})
|
||||
|
||||
t.create('total downloads (tenant)')
|
||||
.get('/dotnet.myget/dotnet-coreclr/v/Microsoft.DotNet.CoreCLR.json')
|
||||
t.create('version (tenant)')
|
||||
.get('/cefsharp.myget/cefsharp/v/cef.sdk.json')
|
||||
.expectBadge({
|
||||
label: 'dotnet-coreclr',
|
||||
label: 'cefsharp',
|
||||
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
})
|
||||
|
||||
|
||||
@@ -106,12 +106,8 @@ module.exports = class NodeVersionBase extends NPMBase {
|
||||
}
|
||||
|
||||
async handle(namedParams, queryParams) {
|
||||
const {
|
||||
scope,
|
||||
packageName,
|
||||
tag,
|
||||
registryUrl,
|
||||
} = this.constructor.unpackParams(namedParams, queryParams)
|
||||
const { scope, packageName, tag, registryUrl } =
|
||||
this.constructor.unpackParams(namedParams, queryParams)
|
||||
const { engines } = await this.fetchPackageData({
|
||||
scope,
|
||||
packageName,
|
||||
|
||||
@@ -40,8 +40,7 @@ async function getCurrentVersion() {
|
||||
|
||||
async function getLtsVersions() {
|
||||
const versions = await promisify(regularUpdate)({
|
||||
url:
|
||||
'https://raw.githubusercontent.com/nodejs/Release/master/schedule.json',
|
||||
url: 'https://raw.githubusercontent.com/nodejs/Release/master/schedule.json',
|
||||
intervalMillis: 24 * 3600 * 1000,
|
||||
json: true,
|
||||
scraper: ltsVersionsScraper,
|
||||
|
||||
@@ -19,27 +19,29 @@ const templates = {
|
||||
|
||||
const getTemplate = template => JSON.parse(templates[template])
|
||||
|
||||
const mockPackageData = ({ packageName, engines, scope, tag }) => nock => {
|
||||
let packageJson
|
||||
let urlPath
|
||||
if (scope || tag) {
|
||||
if (scope) {
|
||||
urlPath = `/${scope}%2F${packageName}`
|
||||
const mockPackageData =
|
||||
({ packageName, engines, scope, tag }) =>
|
||||
nock => {
|
||||
let packageJson
|
||||
let urlPath
|
||||
if (scope || tag) {
|
||||
if (scope) {
|
||||
urlPath = `/${scope}%2F${packageName}`
|
||||
} else {
|
||||
urlPath = `/${packageName}`
|
||||
}
|
||||
packageJson = getTemplate('packageJsonVersionsTemplate')
|
||||
packageJson['dist-tags'][tag || 'latest'] = '0.0.91'
|
||||
packageJson.versions['0.0.91'].engines.node = engines
|
||||
} else {
|
||||
urlPath = `/${packageName}`
|
||||
urlPath = `/${packageName}/latest`
|
||||
packageJson = getTemplate('packageJsonTemplate')
|
||||
packageJson.engines.node = engines
|
||||
}
|
||||
packageJson = getTemplate('packageJsonVersionsTemplate')
|
||||
packageJson['dist-tags'][tag || 'latest'] = '0.0.91'
|
||||
packageJson.versions['0.0.91'].engines.node = engines
|
||||
} else {
|
||||
urlPath = `/${packageName}/latest`
|
||||
packageJson = getTemplate('packageJsonTemplate')
|
||||
packageJson.engines.node = engines
|
||||
return nock('https://registry.npmjs.org/')
|
||||
.get(urlPath)
|
||||
.reply(200, packageJson)
|
||||
}
|
||||
return nock('https://registry.npmjs.org/')
|
||||
.get(urlPath)
|
||||
.reply(200, packageJson)
|
||||
}
|
||||
|
||||
const mockCurrentSha = latestVersion => nock => {
|
||||
const latestSha = `node-v${latestVersion}.12.0-aix-ppc64.tar.gz`
|
||||
|
||||
@@ -122,15 +122,12 @@ module.exports = class NpmDependencyVersion extends NpmBase {
|
||||
dependencyScope ? `${dependencyScope}/` : ''
|
||||
}${dependency}`
|
||||
|
||||
const {
|
||||
dependencies,
|
||||
devDependencies,
|
||||
peerDependencies,
|
||||
} = await this.fetchPackageData({
|
||||
scope,
|
||||
packageName,
|
||||
registryUrl,
|
||||
})
|
||||
const { dependencies, devDependencies, peerDependencies } =
|
||||
await this.fetchPackageData({
|
||||
scope,
|
||||
packageName,
|
||||
registryUrl,
|
||||
})
|
||||
|
||||
const { range } = getDependencyVersion({
|
||||
kind,
|
||||
|
||||
@@ -71,12 +71,8 @@ module.exports = class NpmVersion extends NpmBase {
|
||||
}
|
||||
|
||||
async handle(namedParams, queryParams) {
|
||||
const {
|
||||
scope,
|
||||
packageName,
|
||||
tag,
|
||||
registryUrl,
|
||||
} = this.constructor.unpackParams(namedParams, queryParams)
|
||||
const { scope, packageName, tag, registryUrl } =
|
||||
this.constructor.unpackParams(namedParams, queryParams)
|
||||
|
||||
const slug =
|
||||
scope === undefined
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
const { createServiceFamily } = require('./nuget-v3-service-family')
|
||||
|
||||
const {
|
||||
NugetVersionService: Version,
|
||||
NugetDownloadService: Downloads,
|
||||
} = createServiceFamily({
|
||||
defaultLabel: 'nuget',
|
||||
serviceBaseUrl: 'nuget',
|
||||
apiBaseUrl: 'https://api.nuget.org/v3',
|
||||
withTenant: false,
|
||||
withFeed: false,
|
||||
})
|
||||
const { NugetVersionService: Version, NugetDownloadService: Downloads } =
|
||||
createServiceFamily({
|
||||
defaultLabel: 'nuget',
|
||||
serviceBaseUrl: 'nuget',
|
||||
apiBaseUrl: 'https://api.nuget.org/v3',
|
||||
withTenant: false,
|
||||
withFeed: false,
|
||||
})
|
||||
|
||||
class NugetVersionService extends Version {
|
||||
static examples = [
|
||||
|
||||
70
services/pingpong/pingpong-status.service.js
Normal file
70
services/pingpong/pingpong-status.service.js
Normal file
@@ -0,0 +1,70 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { BaseJsonService, InvalidParameter, InvalidResponse } = require('..')
|
||||
|
||||
const schema = Joi.object({
|
||||
status: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
const pingpongDocumentation = `
|
||||
<p>
|
||||
To see more details about this badge and obtain your api key, visit
|
||||
<a href="https://my.pingpong.one/integrations/badge-status/" target="_blank">https://my.pingpong.one/integrations/badge-status/</a>
|
||||
</p>
|
||||
`
|
||||
|
||||
module.exports = class PingPongStatus extends BaseJsonService {
|
||||
static category = 'monitoring'
|
||||
static route = { base: 'pingpong/status', pattern: ':apiKey' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'PingPong status',
|
||||
namedParams: { apiKey: 'sp_2e80bc00b6054faeb2b87e2464be337e' },
|
||||
staticPreview: this.render({ status: 'Operational' }),
|
||||
documentation: pingpongDocumentation,
|
||||
keywords: ['statuspage', 'status page'],
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'status' }
|
||||
|
||||
static validateApiKey({ apiKey }) {
|
||||
if (!apiKey.startsWith('sp_')) {
|
||||
throw new InvalidParameter({
|
||||
prettyMessage: 'invalid api key',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
static render({ status }) {
|
||||
switch (status) {
|
||||
case 'Operational':
|
||||
return { message: 'up', color: 'brightgreen' }
|
||||
case 'Major issues':
|
||||
return { message: 'issues', color: 'orange' }
|
||||
case 'Critical state':
|
||||
return { message: 'down', color: 'red' }
|
||||
case 'Maintenance mode':
|
||||
return { message: 'maintenance', color: 'lightgrey' }
|
||||
default:
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'Unknown status received',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ apiKey }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://api.pingpong.one/widget/shields/status/${apiKey}`,
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ apiKey }) {
|
||||
this.constructor.validateApiKey({ apiKey })
|
||||
const { status } = await this.fetch({ apiKey })
|
||||
return this.constructor.render({ status })
|
||||
}
|
||||
}
|
||||
29
services/pingpong/pingpong-status.tester.js
Normal file
29
services/pingpong/pingpong-status.tester.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
const isCorrectStatus = Joi.string().valid(
|
||||
'up',
|
||||
'issues',
|
||||
'down',
|
||||
'maintenance'
|
||||
)
|
||||
|
||||
t.create('PingPong: Status (valid)')
|
||||
.get('/sp_eb705b7c189f42e3b574dc790291c33f.json')
|
||||
.expectBadge({ label: 'status', message: isCorrectStatus })
|
||||
|
||||
t.create('PingPong: Status (valid, incorrect format)')
|
||||
.get('/eb705b7c189f42e3b574dc790291c33f.json')
|
||||
.expectBadge({ label: 'status', message: 'invalid api key' })
|
||||
|
||||
t.create('PingPong: Status (unexpected response)')
|
||||
.get('/sp_key.json')
|
||||
.intercept(
|
||||
nock =>
|
||||
nock('https://api.pingpong.one')
|
||||
.get('/widget/shields/status/sp_key')
|
||||
.reply(200, '{"status": "up"}') // unexpected status message
|
||||
)
|
||||
.expectBadge({ label: 'status', message: 'Unknown status received' })
|
||||
61
services/pingpong/pingpong-uptime.service.js
Normal file
61
services/pingpong/pingpong-uptime.service.js
Normal file
@@ -0,0 +1,61 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { coveragePercentage } = require('../color-formatters')
|
||||
const { BaseJsonService, InvalidParameter } = require('..')
|
||||
|
||||
const schema = Joi.object({
|
||||
uptime: Joi.number().min(0).max(100).required(),
|
||||
}).required()
|
||||
|
||||
const pingpongDocumentation = `
|
||||
<p>
|
||||
To see more details about this badge and obtain your api key, visit
|
||||
<a href="https://my.pingpong.one/integrations/badge-uptime/" target="_blank">https://my.pingpong.one/integrations/badge-uptime/</a>
|
||||
</p>
|
||||
`
|
||||
|
||||
module.exports = class PingPongUptime extends BaseJsonService {
|
||||
static category = 'monitoring'
|
||||
static route = { base: 'pingpong/uptime', pattern: ':apiKey' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'PingPong uptime (last 30 days)',
|
||||
namedParams: { apiKey: 'sp_2e80bc00b6054faeb2b87e2464be337e' },
|
||||
staticPreview: this.render({ uptime: 100 }),
|
||||
documentation: pingpongDocumentation,
|
||||
keywords: ['statuspage', 'status page'],
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'uptime' }
|
||||
|
||||
static validateApiKey({ apiKey }) {
|
||||
if (!apiKey.startsWith('sp_')) {
|
||||
throw new InvalidParameter({
|
||||
prettyMessage: 'invalid api key',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
static render({ uptime }) {
|
||||
return {
|
||||
message: `${uptime}%`,
|
||||
color: coveragePercentage(uptime),
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ apiKey }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://api.pingpong.one/widget/shields/uptime/${apiKey}`,
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ apiKey }) {
|
||||
this.constructor.validateApiKey({ apiKey })
|
||||
const { uptime } = await this.fetch({ apiKey })
|
||||
return this.constructor.render({ uptime })
|
||||
}
|
||||
}
|
||||
12
services/pingpong/pingpong-uptime.tester.js
Normal file
12
services/pingpong/pingpong-uptime.tester.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const { isPercentage } = require('../test-validators')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('PingPong: Uptime (valid)')
|
||||
.get('/sp_eb705b7c189f42e3b574dc790291c33f.json')
|
||||
.expectBadge({ label: 'uptime', message: isPercentage })
|
||||
|
||||
t.create('PingPong: Uptime (valid, incorrect format)')
|
||||
.get('/eb705b7c189f42e3b574dc790291c33f.json')
|
||||
.expectBadge({ label: 'uptime', message: 'invalid api key' })
|
||||
@@ -8,7 +8,8 @@ const {
|
||||
const { fetchJsonFromRepo } = require('../github/github-common-fetch')
|
||||
const { renderVersionBadge } = require('../version')
|
||||
|
||||
const gitHubRepoRegExp = /https:\/\/github.com\/(?<user>.*?)\/(?<repo>.*?)(\/|$)/
|
||||
const gitHubRepoRegExp =
|
||||
/https:\/\/github.com\/(?<user>.*?)\/(?<repo>.*?)(\/|$)/
|
||||
const bucketsSchema = Joi.object()
|
||||
.pattern(/.+/, Joi.string().pattern(gitHubRepoRegExp).required())
|
||||
.required()
|
||||
|
||||
@@ -98,8 +98,7 @@ describe('Badge suggestions for', function () {
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
|
||||
link: 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
|
||||
example: {
|
||||
pattern: '/twitter/url',
|
||||
namedParams: {},
|
||||
@@ -169,8 +168,7 @@ describe('Badge suggestions for', function () {
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fbadges%2Fnot-a-real-project',
|
||||
link: 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fbadges%2Fnot-a-real-project',
|
||||
example: {
|
||||
pattern: '/twitter/url',
|
||||
namedParams: {},
|
||||
@@ -213,8 +211,7 @@ describe('Badge suggestions for', function () {
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgitlab.com%2Fgitlab-org%2Fgitlab',
|
||||
link: 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgitlab.com%2Fgitlab-org%2Fgitlab',
|
||||
example: {
|
||||
pattern: '/twitter/url',
|
||||
namedParams: {},
|
||||
@@ -255,8 +252,7 @@ describe('Badge suggestions for', function () {
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgitlab.com%2Fgitlab-org%2Fnot-gitlab',
|
||||
link: 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgitlab.com%2Fgitlab-org%2Fnot-gitlab',
|
||||
example: {
|
||||
pattern: '/twitter/url',
|
||||
namedParams: {},
|
||||
|
||||
@@ -193,6 +193,7 @@ function setRoutes(allowedOrigin, githubApiProvider, server) {
|
||||
.then(suggestions => {
|
||||
end({ suggestions })
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
.catch(err => {
|
||||
end({ suggestions: [], err })
|
||||
})
|
||||
|
||||
@@ -157,8 +157,7 @@ describe('Badge suggestions', function () {
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
|
||||
link: 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
|
||||
example: {
|
||||
pattern: '/twitter/url',
|
||||
namedParams: {},
|
||||
|
||||
@@ -5,7 +5,8 @@ const { withRegex } = require('../test-validators')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
const buildStatusValues = Joi.equal('passing', 'failure', 'error').required()
|
||||
const buildStatusTextRegex = /^success|failure|error|tests( failed: \d+( \(\d+ new\))?)?(,)?( passed: \d+)?(,)?( ignored: \d+)?(,)?( muted: \d+)?/
|
||||
const buildStatusTextRegex =
|
||||
/^success|failure|error|tests( failed: \d+( \(\d+ new\))?)?(,)?( passed: \d+)?(,)?( ignored: \d+)?(,)?( muted: \d+)?/
|
||||
|
||||
t.create('unknown build')
|
||||
.get('/s/btabc.json?server=https://teamcity.jetbrains.com')
|
||||
|
||||
@@ -41,13 +41,8 @@ module.exports = class TestspaceTestCount extends TestspaceBase {
|
||||
}
|
||||
|
||||
transform({ json, metric }) {
|
||||
const {
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
errored,
|
||||
total,
|
||||
} = this.transformCaseCounts(json)
|
||||
const { passed, failed, skipped, errored, total } =
|
||||
this.transformCaseCounts(json)
|
||||
if (metric === 'total') {
|
||||
return { value: total }
|
||||
} else if (metric === 'passed') {
|
||||
|
||||
@@ -34,8 +34,8 @@ module.exports = class TravisPhpVersion extends BaseJsonService {
|
||||
static examples = [
|
||||
{
|
||||
title: 'PHP version from Travis config',
|
||||
namedParams: { user: 'symfony', repo: 'symfony', branch: 'master' },
|
||||
staticPreview: this.render({ reduction: ['^7.1.3'] }),
|
||||
namedParams: { user: 'yiisoft', repo: 'yii', branch: 'master' },
|
||||
staticPreview: this.render({ reduction: ['5.3 - 7.4'] }),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -3,12 +3,8 @@
|
||||
const { isPhpVersionReduction } = require('../test-validators')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('gets the package version of symfony')
|
||||
.get('/symfony/symfony/master.json')
|
||||
.expectBadge({ label: 'php', message: isPhpVersionReduction })
|
||||
|
||||
t.create('gets the package version of symfony 2.8')
|
||||
.get('/symfony/symfony/2.8.json')
|
||||
t.create('gets the package version of symfony 5.1')
|
||||
.get('/symfony/symfony/5.1.json')
|
||||
.expectBadge({ label: 'php', message: isPhpVersionReduction })
|
||||
|
||||
t.create('gets the package version of yii')
|
||||
|
||||
@@ -30,6 +30,8 @@ module.exports = class TwitchStatus extends TwitchBase {
|
||||
},
|
||||
]
|
||||
|
||||
static _cacheLength = 30
|
||||
|
||||
static defaultBadgeData = {
|
||||
label: 'twitch',
|
||||
namedLogo: 'twitch',
|
||||
|
||||
@@ -14,13 +14,13 @@ This document specifies the visual design of Shields badges.
|
||||
|
||||
#### Bad
|
||||
|
||||

|
||||

|
||||
|
||||
The key is shamelessly promoting the service provider instead of giving context to the value. If service providers want to promote themselves, they can simply encourage people to link back to them and let folks who are curious click on the button for more information.
|
||||
|
||||
#### Good
|
||||
|
||||

|
||||

|
||||
|
||||
The key clearly explains what the value stands for (the version of the software provided). The platform or service hosting the version of the software is only relevant to people who decide to click on an eventual link added to the badge itself but the value stands on its own with the metadata value it provides to viewers.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user