Compare commits
126 Commits
requires-p
...
assert-log
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9908df571 | ||
|
|
d79b0ec46a | ||
|
|
2104135a68 | ||
|
|
c2d1a8cd6d | ||
|
|
84b7413524 | ||
|
|
ef56865d11 | ||
|
|
3701780f20 | ||
|
|
0927689f45 | ||
|
|
2d92500961 | ||
|
|
40d7cc8ac6 | ||
|
|
4121a57af3 | ||
|
|
37b0edcfbc | ||
|
|
c69ed93a0b | ||
|
|
f1a5dd9427 | ||
|
|
d60b90b860 | ||
|
|
7e7162156b | ||
|
|
47a9df7b1c | ||
|
|
dd2223407f | ||
|
|
77826cf257 | ||
|
|
de0a98c3a4 | ||
|
|
feeeda113a | ||
|
|
f77fd65f57 | ||
|
|
7ad2410de2 | ||
|
|
36600fee1f | ||
|
|
11a2084a65 | ||
|
|
0737e317da | ||
|
|
800b61ab46 | ||
|
|
6dbf8d009a | ||
|
|
7a19dc40ef | ||
|
|
10ddd866da | ||
|
|
e786d69dbe | ||
|
|
e236c9a15c | ||
|
|
a1c50ea4d5 | ||
|
|
6822922ad2 | ||
|
|
4415d07e8b | ||
|
|
668ea878d9 | ||
|
|
21c15e4029 | ||
|
|
1184938ed1 | ||
|
|
e1bd1602a6 | ||
|
|
aa5addb442 | ||
|
|
374bf4aafa | ||
|
|
43f7adbe64 | ||
|
|
af3f720113 | ||
|
|
d1ec834cb5 | ||
|
|
cd70bf5246 | ||
|
|
cbd8eba2c4 | ||
|
|
a4a8258fd5 | ||
|
|
e03549d88d | ||
|
|
1aa25ab321 | ||
|
|
b9d5da66fd | ||
|
|
740947ba06 | ||
|
|
3576fbd7b8 | ||
|
|
1fab1a7140 | ||
|
|
4da15aafc1 | ||
|
|
a0008ba7d1 | ||
|
|
ace467246d | ||
|
|
1d2d92b968 | ||
|
|
1a5eb7376f | ||
|
|
57ae9cdb6b | ||
|
|
d1808c8d26 | ||
|
|
681d2c0505 | ||
|
|
19e6f18d9b | ||
|
|
498f57d960 | ||
|
|
c134d4914d | ||
|
|
5d8adbe54f | ||
|
|
7c0f211e0b | ||
|
|
564a9145e9 | ||
|
|
dc32a5fc4b | ||
|
|
3b1aaf3137 | ||
|
|
e1ac63d3be | ||
|
|
3a23695f89 | ||
|
|
e0604d855b | ||
|
|
ff9273a958 | ||
|
|
4d712e8386 | ||
|
|
c18bc82347 | ||
|
|
8fe7547a08 | ||
|
|
64f82dd4eb | ||
|
|
de8033f8bb | ||
|
|
a0080f1d7a | ||
|
|
fba860fbe9 | ||
|
|
5d3c26efe1 | ||
|
|
2fa69ba359 | ||
|
|
a0313437b6 | ||
|
|
7c45593ca4 | ||
|
|
7642f2d4eb | ||
|
|
30f3be44bc | ||
|
|
d4fa62f0b2 | ||
|
|
85e3772de9 | ||
|
|
9d173930ef | ||
|
|
fcf6678a12 | ||
|
|
f0b8480280 | ||
|
|
5931b86c85 | ||
|
|
17b975b53f | ||
|
|
0c28fe7144 | ||
|
|
504015c0ba | ||
|
|
86366588a2 | ||
|
|
eb9743c25c | ||
|
|
bdc41670a9 | ||
|
|
39756bde78 | ||
|
|
0e90b7e6ed | ||
|
|
342ea93c52 | ||
|
|
5d0116f959 | ||
|
|
a4216434f8 | ||
|
|
c02050a13a | ||
|
|
501c53a0b4 | ||
|
|
63f56f0762 | ||
|
|
0eedc8d180 | ||
|
|
0e0f6903c8 | ||
|
|
4eda1c7593 | ||
|
|
b31b7b15ea | ||
|
|
8322bbddaf | ||
|
|
02642ac289 | ||
|
|
d784525049 | ||
|
|
613154b8ee | ||
|
|
473aa16d66 | ||
|
|
84583f510f | ||
|
|
37de6fe742 | ||
|
|
9d4dcb5886 | ||
|
|
cba467ecde | ||
|
|
b92f05b9ab | ||
|
|
6818765d89 | ||
|
|
bc2832e566 | ||
|
|
1b91b03b2b | ||
|
|
88b3bc72dd | ||
|
|
b149659187 | ||
|
|
ec5b976c0d |
@@ -86,33 +86,6 @@ services_steps: &services_steps
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
run_package_tests: &run_package_tests
|
||||
when: always
|
||||
command: |
|
||||
# https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/3
|
||||
set +e
|
||||
export NVM_DIR="/opt/circleci/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
nvm install $NODE_VERSION
|
||||
nvm use $NODE_VERSION
|
||||
node --version
|
||||
|
||||
# install the shields.io dependencies
|
||||
npm ci
|
||||
|
||||
# run the package tests
|
||||
npm run test:package
|
||||
npm run check-types:package
|
||||
|
||||
# delete the sheilds.io dependencies
|
||||
rm -rf node_modules/
|
||||
|
||||
# run a smoke test (render a badge with the CLI)
|
||||
# with only the package dependencies installed
|
||||
cd badge-maker
|
||||
npm link
|
||||
badge cactus grown :green @flat
|
||||
|
||||
package_steps: &package_steps
|
||||
steps:
|
||||
- checkout
|
||||
@@ -132,31 +105,31 @@ package_steps: &package_steps
|
||||
# https://nodejs.org/en/about/releases/
|
||||
|
||||
- run:
|
||||
<<: *run_package_tests
|
||||
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:
|
||||
<<: *run_package_tests
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v12/results.xml
|
||||
NODE_VERSION: v12
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 12
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- run:
|
||||
<<: *run_package_tests
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/badge-maker/v14/results.xml
|
||||
NODE_VERSION: v14
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
name: Run package tests on Node 14
|
||||
command: scripts/run_package_tests.sh
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
@@ -14,9 +14,6 @@ update_configs:
|
||||
- match:
|
||||
dependency_name: 'eslint*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'enzyme*'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_name: 'mocha*'
|
||||
update_type: 'semver:minor'
|
||||
|
||||
10
.github/probot.js
vendored
10
.github/probot.js
vendored
@@ -1,10 +0,0 @@
|
||||
on('pull_request.closed')
|
||||
.filter(context => context.payload.pull_request.merged)
|
||||
.filter(
|
||||
context =>
|
||||
context.payload.pull_request.head.ref.slice(0, 11) !== 'dependabot/'
|
||||
)
|
||||
.filter(context => context.payload.pull_request.base.ref === 'master')
|
||||
.comment(`This pull request was merged to [{{ pull_request.base.ref }}]({{ repository.html_url }}/tree/{{ pull_request.base.ref }}) branch. This change is now waiting for deployment, which will usually happen within a few days. Stay tuned by joining our \`#ops\` channel on [Discord](https://discordapp.com/invite/HjJCwm5)!
|
||||
|
||||
After deployment, changes are copied to [gh-pages]({{ repository.html_url }}/tree/gh-pages) branch: `)
|
||||
28
.github/workflows/add_deployment_status.yml
vendored
Normal file
28
.github/workflows/add_deployment_status.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Ddd deployment status
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
add_deployment_status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create comment
|
||||
if: ${{ github.event_name == 'pull_request_target'
|
||||
&& github.event.action == 'closed'
|
||||
&& github.event.pull_request.merged
|
||||
&& !startsWith(github.event.pull_request.head.ref, 'dependabot/')
|
||||
&& github.event.pull_request.base.ref == 'master' }}
|
||||
# From a security perspective it's good practice to reference the commit hash
|
||||
# https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#security
|
||||
uses: peter-evans/create-or-update-comment@41f3207a84f33bd70388036109082784d059dcaa
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
edit-mode: replace
|
||||
body: |
|
||||
This pull request was merged to [${{ github.event.pull_request.base.ref }}](${{ github.event.repository.html_url }}/tree/${{ github.event.pull_request.base.ref }}) branch. This change is now waiting for deployment, which will usually happen within a few days. Stay tuned by joining our `#ops` channel on [Discord](https://discordapp.com/invite/HjJCwm5)!
|
||||
|
||||
After deployment, changes are copied to [gh-pages](${{ github.event.repository.html_url }}/tree/gh-pages) branch: 
|
||||
@@ -10,6 +10,7 @@
|
||||
"**/*-test-helpers.js",
|
||||
"**/*-fixtures.js",
|
||||
"**/mocha-*.js",
|
||||
"**/*.test-d.ts",
|
||||
"dangerfile.js",
|
||||
"gatsby-*.js",
|
||||
"core/service-test-runner",
|
||||
|
||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@@ -1,7 +1,3 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"EditorConfig.EditorConfig",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
|
||||
}
|
||||
|
||||
10
README.md
10
README.md
@@ -86,12 +86,16 @@ and pull requests! You can peruse the [contributing guidelines][contributing].
|
||||
When adding or changing a service [please add tests][service-tests].
|
||||
|
||||
This project has quite a backlog of suggestions! If you're new to the project,
|
||||
maybe you'd like to open a pull request to address one of them:
|
||||
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
maybe you'd like to open a pull request to address one of them.
|
||||
|
||||
You can read a [tutorial on how to add a badge][tutorial].
|
||||
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aopen+is%3Aissue+label%3Ahacktoberfest)
|
||||
|
||||
Let's see if we can beat last year!
|
||||
[](https://github.com/badges/shields/issues?q=is%3Aopen+is%3Aissue+label%3Ahacktoberfest)
|
||||
|
||||
[service-tests]: https://github.com/badges/shields/blob/master/doc/service-tests.md
|
||||
[tutorial]: doc/TUTORIAL.md
|
||||
[contributing]: CONTRIBUTING.md
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Readability improvements: a dark font color is automatically used when the badge's background is too light. For example: 
|
||||
- Better CSS color compliance: thanks to a switch from _is-css-color_ to _[css-color-converter](https://www.npmjs.com/package/css-color-converter)_, you can use a wider range of color formats from the latest CSS specification, for example `rgb(0 255 0)`
|
||||
- Less dependencies: _badge-maker_ no longer depends on _camelcase_
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Accessibility improvements: Help users of assistive technologies to read the badges when used inline
|
||||
|
||||
@@ -14,14 +14,9 @@ function capitalize(s) {
|
||||
|
||||
function colorsForBackground(color) {
|
||||
if (brightness(color) <= brightnessThreshold) {
|
||||
return {
|
||||
textColor: '#fff',
|
||||
shadowColor: '#010101',
|
||||
}
|
||||
}
|
||||
return {
|
||||
textColor: '#333',
|
||||
shadowColor: '#ccc',
|
||||
return { textColor: '#fff', shadowColor: '#010101' }
|
||||
} else {
|
||||
return { textColor: '#333', shadowColor: '#ccc' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,19 +34,12 @@ function escapeXml(s) {
|
||||
}
|
||||
|
||||
function roundUpToOdd(val) {
|
||||
// Increase chances of pixel grid alignment.
|
||||
return val % 2 === 0 ? val + 1 : val
|
||||
}
|
||||
|
||||
function preferredWidthOf(str) {
|
||||
return roundUpToOdd((anafanafo(str) / 10) | 0)
|
||||
}
|
||||
|
||||
function computeWidths({ label, message }) {
|
||||
return {
|
||||
labelWidth: preferredWidthOf(label),
|
||||
messageWidth: preferredWidthOf(message),
|
||||
}
|
||||
function preferredWidthOf(str, options) {
|
||||
// Increase chances of pixel grid alignment.
|
||||
return roundUpToOdd(anafanafo(str, options) | 0)
|
||||
}
|
||||
|
||||
function createAccessibleText({ label, message }) {
|
||||
@@ -89,22 +77,19 @@ function renderLogo({
|
||||
logoWidth = 14,
|
||||
logoPadding = 0,
|
||||
}) {
|
||||
if (!logo) {
|
||||
if (logo) {
|
||||
const logoHeight = 14
|
||||
const y = (badgeHeight - logoHeight) / 2
|
||||
const x = horizPadding
|
||||
return {
|
||||
hasLogo: false,
|
||||
totalLogoWidth: 0,
|
||||
renderedLogo: '',
|
||||
hasLogo: true,
|
||||
totalLogoWidth: logoWidth + logoPadding,
|
||||
renderedLogo: `<image x="${x}" y="${y}" width="${logoWidth}" height="${logoHeight}" xlink:href="${escapeXml(
|
||||
logo
|
||||
)}"/>`,
|
||||
}
|
||||
}
|
||||
const logoHeight = 14
|
||||
const y = (badgeHeight - logoHeight) / 2
|
||||
const x = horizPadding
|
||||
return {
|
||||
hasLogo: true,
|
||||
totalLogoWidth: logoWidth + logoPadding,
|
||||
renderedLogo: `<image x="${x}" y="${y}" width="${logoWidth}" height="14" xlink:href="${escapeXml(
|
||||
logo
|
||||
)}"/>`,
|
||||
} else {
|
||||
return { hasLogo: false, totalLogoWidth: 0, renderedLogo: '' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +124,7 @@ function renderText({
|
||||
return { renderedText: '', width: 0 }
|
||||
}
|
||||
|
||||
const textLength = preferredWidthOf(content)
|
||||
const textLength = preferredWidthOf(content, { font: '11px Verdana' })
|
||||
const escapedContent = escapeXml(content)
|
||||
|
||||
const shadowMargin = 150 + verticalMargin
|
||||
@@ -191,10 +176,6 @@ function renderBadge(
|
||||
</svg>`
|
||||
}
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
class Badge {
|
||||
static get fontFamily() {
|
||||
throw new Error('Not implemented')
|
||||
@@ -301,6 +282,10 @@ class Badge {
|
||||
this.renderedMessage = renderedMessage
|
||||
}
|
||||
|
||||
static render(params) {
|
||||
return new this(params).render()
|
||||
}
|
||||
|
||||
render() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
@@ -451,30 +436,6 @@ class FlatSquare extends Badge {
|
||||
}
|
||||
}
|
||||
|
||||
function plastic(params) {
|
||||
const badge = new Plastic(params)
|
||||
if (params.minify) {
|
||||
return stripXmlWhitespace(badge.render())
|
||||
}
|
||||
return badge.render()
|
||||
}
|
||||
|
||||
function flat(params) {
|
||||
const badge = new Flat(params)
|
||||
if (params.minify) {
|
||||
return stripXmlWhitespace(badge.render())
|
||||
}
|
||||
return badge.render()
|
||||
}
|
||||
|
||||
function flatSquare(params) {
|
||||
const badge = new FlatSquare(params)
|
||||
if (params.minify) {
|
||||
return stripXmlWhitespace(badge.render())
|
||||
}
|
||||
return badge.render()
|
||||
}
|
||||
|
||||
function social({
|
||||
label,
|
||||
message,
|
||||
@@ -484,7 +445,6 @@ function social({
|
||||
logoPadding,
|
||||
color = '#4c1',
|
||||
labelColor = '#555',
|
||||
minify,
|
||||
}) {
|
||||
// Social label is styled with a leading capital. Convert to caps here so
|
||||
// width can be measured using the correct characters.
|
||||
@@ -492,24 +452,23 @@ function social({
|
||||
|
||||
const externalHeight = 20
|
||||
const internalHeight = 19
|
||||
const horizPadding = 5
|
||||
const labelHorizPadding = 5
|
||||
const messageHorizPadding = 4
|
||||
const horizGutter = 6
|
||||
const { totalLogoWidth, renderedLogo } = renderLogo({
|
||||
logo,
|
||||
badgeHeight: externalHeight,
|
||||
horizPadding,
|
||||
horizPadding: labelHorizPadding,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
})
|
||||
const hasMessage = message.length
|
||||
|
||||
let { labelWidth, messageWidth } = computeWidths({ label, message })
|
||||
labelWidth += 10 + totalLogoWidth
|
||||
messageWidth += 10
|
||||
messageWidth -= 4
|
||||
|
||||
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
|
||||
const labelTextLength = (labelWidth - (10 + totalLogoWidth)) * 10
|
||||
const escapedLabel = escapeXml(label)
|
||||
const font = 'bold 11px Helvetica'
|
||||
const labelTextWidth = preferredWidthOf(label, { font })
|
||||
const messageTextWidth = preferredWidthOf(message, { font })
|
||||
const labelRectWidth = labelTextWidth + totalLogoWidth + 2 * labelHorizPadding
|
||||
const messageRectWidth = messageTextWidth + 2 * messageHorizPadding
|
||||
|
||||
let [leftLink, rightLink] = links
|
||||
leftLink = escapeXml(leftLink)
|
||||
@@ -519,29 +478,35 @@ function social({
|
||||
const accessibleText = createAccessibleText({ label, message })
|
||||
|
||||
function renderMessageBubble() {
|
||||
const messageBubbleMainX = labelWidth + 6.5
|
||||
const messageBubbleNotchX = labelWidth + 6
|
||||
const messageBubbleMainX = labelRectWidth + horizGutter + 0.5
|
||||
const messageBubbleNotchX = labelRectWidth + horizGutter
|
||||
return `
|
||||
<rect x="${messageBubbleMainX}" y="0.5" width="${messageWidth}" height="${internalHeight}" rx="2" fill="#fafafa"/>
|
||||
<rect x="${messageBubbleMainX}" y="0.5" width="${messageRectWidth}" height="${internalHeight}" rx="2" fill="#fafafa"/>
|
||||
<rect x="${messageBubbleNotchX}" y="7.5" width="0.5" height="5" stroke="#fafafa"/>
|
||||
<path d="M${messageBubbleMainX} 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/>
|
||||
`
|
||||
}
|
||||
|
||||
function renderLabelText() {
|
||||
const rect = `<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="${labelWidth}" height="${internalHeight}" rx="2" />`
|
||||
const labelTextX =
|
||||
10 * (totalLogoWidth + labelTextWidth / 2 + labelHorizPadding)
|
||||
const labelTextLength = 10 * labelTextWidth
|
||||
const escapedLabel = escapeXml(label)
|
||||
const shouldWrapWithLink = hasLeftLink && !shouldWrapBodyWithLink({ links })
|
||||
|
||||
const rect = `<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="${labelRectWidth}" height="${internalHeight}" rx="2" />`
|
||||
const shadow = `<text aria-hidden="true" x="${labelTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>`
|
||||
const text = `<text x="${labelTextX}" y="140" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>`
|
||||
if (hasLeftLink && !shouldWrapBodyWithLink({ links })) {
|
||||
return `
|
||||
|
||||
return shouldWrapWithLink
|
||||
? `
|
||||
<a target="_blank" xlink:href="${leftLink}">
|
||||
${shadow}
|
||||
${text}
|
||||
${rect}
|
||||
</a>
|
||||
`
|
||||
}
|
||||
return `
|
||||
: `
|
||||
${rect}
|
||||
${shadow}
|
||||
${text}
|
||||
@@ -549,34 +514,36 @@ function social({
|
||||
}
|
||||
|
||||
function renderMessageText() {
|
||||
const messageTextX = (labelWidth + messageWidth / 2 + 6) * 10
|
||||
const messageTextLength = (messageWidth - 8) * 10
|
||||
const messageTextX =
|
||||
10 * (labelRectWidth + horizGutter + messageRectWidth / 2)
|
||||
const messageTextLength = 10 * messageTextWidth
|
||||
const escapedMessage = escapeXml(message)
|
||||
const rect = `<rect width="${messageWidth + 1}" x="${
|
||||
labelWidth + 6
|
||||
|
||||
const rect = `<rect width="${messageRectWidth + 1}" x="${
|
||||
labelRectWidth + horizGutter
|
||||
}" height="${internalHeight + 1}" fill="rgba(0,0,0,0)" />`
|
||||
const shadow = `<text aria-hidden="true" x="${messageTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
|
||||
const text = `<text id="rlink" x="${messageTextX}" y="140" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
|
||||
if (hasRightLink) {
|
||||
return `
|
||||
|
||||
return hasRightLink
|
||||
? `
|
||||
<a target="_blank" xlink:href="${rightLink}">
|
||||
${rect}
|
||||
${shadow}
|
||||
${text}
|
||||
</a>
|
||||
`
|
||||
}
|
||||
return `
|
||||
: `
|
||||
${shadow}
|
||||
${text}
|
||||
`
|
||||
}
|
||||
|
||||
const badge = renderBadge(
|
||||
return renderBadge(
|
||||
{
|
||||
links,
|
||||
leftWidth: labelWidth + 1,
|
||||
rightWidth: hasMessage ? messageWidth + 6 : 0,
|
||||
leftWidth: labelRectWidth + 1,
|
||||
rightWidth: hasMessage ? horizGutter + messageRectWidth : 0,
|
||||
accessibleText,
|
||||
height: externalHeight,
|
||||
},
|
||||
@@ -591,7 +558,7 @@ function social({
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<g stroke="#d5d5d5">
|
||||
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="${labelWidth}" height="${internalHeight}" rx="2"/>
|
||||
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="${labelRectWidth}" height="${internalHeight}" rx="2"/>
|
||||
${hasMessage ? renderMessageBubble() : ''}
|
||||
</g>
|
||||
${renderedLogo}
|
||||
@@ -601,11 +568,6 @@ function social({
|
||||
</g>
|
||||
`
|
||||
)
|
||||
|
||||
if (minify) {
|
||||
return stripXmlWhitespace(badge)
|
||||
}
|
||||
return badge
|
||||
}
|
||||
|
||||
function forTheBadge({
|
||||
@@ -617,14 +579,15 @@ function forTheBadge({
|
||||
logoPadding,
|
||||
color = '#4c1',
|
||||
labelColor,
|
||||
minify,
|
||||
}) {
|
||||
// For the Badge is styled in all caps. Convert to caps here so widths can
|
||||
// be measured using the correct characters.
|
||||
label = label.toUpperCase()
|
||||
message = message.toUpperCase()
|
||||
|
||||
let { labelWidth, messageWidth } = computeWidths({ label, message })
|
||||
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) {
|
||||
@@ -641,7 +604,9 @@ function forTheBadge({
|
||||
|
||||
labelWidth += 10 + totalLogoWidth
|
||||
if (label.length) {
|
||||
labelWidth += 10 + label.length * 1.5
|
||||
// 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
|
||||
@@ -652,8 +617,9 @@ function forTheBadge({
|
||||
labelWidth -= 11
|
||||
}
|
||||
|
||||
messageWidth += 10
|
||||
messageWidth += 10 + message.length * 2
|
||||
// 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
|
||||
@@ -675,7 +641,9 @@ function forTheBadge({
|
||||
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
|
||||
const labelTextLength = (labelWidth - (24 + totalLogoWidth)) * 10
|
||||
const escapedLabel = escapeXml(label)
|
||||
|
||||
const text = `<text fill="${textColor}" x="${labelTextX}" y="175" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>`
|
||||
|
||||
if (hasLeftLink && !shouldWrapBodyWithLink({ links })) {
|
||||
return `
|
||||
<a target="_blank" xlink:href="${leftLink}">
|
||||
@@ -683,18 +651,21 @@ function forTheBadge({
|
||||
${text}
|
||||
</a>
|
||||
`
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function renderMessageText() {
|
||||
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>`
|
||||
|
||||
if (hasRightLink) {
|
||||
return `
|
||||
<a target="_blank" xlink:href="${rightLink}">
|
||||
@@ -702,11 +673,12 @@ function forTheBadge({
|
||||
${text}
|
||||
</a>
|
||||
`
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
const badge = renderBadge(
|
||||
return renderBadge(
|
||||
{
|
||||
links,
|
||||
leftWidth,
|
||||
@@ -725,17 +697,12 @@ function forTheBadge({
|
||||
${renderMessageText()}
|
||||
</g>`
|
||||
)
|
||||
|
||||
if (minify) {
|
||||
return stripXmlWhitespace(badge)
|
||||
}
|
||||
return badge
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
plastic,
|
||||
flat,
|
||||
plastic: params => Plastic.render(params),
|
||||
flat: params => Flat.render(params),
|
||||
'flat-square': params => FlatSquare.render(params),
|
||||
social,
|
||||
'flat-square': flatSquare,
|
||||
'for-the-badge': forTheBadge,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const cssColorConverter = require('css-color-converter')
|
||||
const { fromString } = require('css-color-converter')
|
||||
|
||||
// When updating these, be sure also to update the list in `badge-maker/README.md`.
|
||||
const namedColors = {
|
||||
@@ -38,10 +38,7 @@ function isHexColor(s = '') {
|
||||
}
|
||||
|
||||
function isCSSColor(color) {
|
||||
return (
|
||||
typeof color === 'string' &&
|
||||
typeof cssColorConverter(color.trim()).toRgbaArray() !== 'undefined'
|
||||
)
|
||||
return typeof color === 'string' && fromString(color.trim())
|
||||
}
|
||||
|
||||
function normalizeColor(color) {
|
||||
@@ -73,8 +70,9 @@ function toSvgColor(color) {
|
||||
|
||||
function brightness(color) {
|
||||
if (color) {
|
||||
const rgb = cssColorConverter(color).toRgbaArray()
|
||||
if (rgb) {
|
||||
const cssColor = fromString(color)
|
||||
if (cssColor) {
|
||||
const rgb = cssColor.toRgbaArray()
|
||||
return +((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 255000).toFixed(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ test(normalizeColor, () => {
|
||||
given(' blue ').expect(' blue ')
|
||||
given('rgb(100%, 200%, 222%)').expect('rgb(100%, 200%, 222%)')
|
||||
given('rgb(122, 200, 222)').expect('rgb(122, 200, 222)')
|
||||
given('rgb(100%, 200, 222)').expect('rgb(100%, 200, 222)')
|
||||
given('rgb(122, 200, 222, 1)').expect('rgb(122, 200, 222, 1)')
|
||||
given('rgba(100, 20, 111, 1)').expect('rgba(100, 20, 111, 1)')
|
||||
given('hsl(122, 200%, 222%)').expect('hsl(122, 200%, 222%)')
|
||||
given('hsla(122, 200%, 222%, 1)').expect('hsla(122, 200%, 222%, 1)')
|
||||
@@ -46,8 +46,8 @@ test(normalizeColor, () => {
|
||||
given(''),
|
||||
given('not-a-color'),
|
||||
given('#ABCFGH'),
|
||||
given('rgb(122, 200, 222, 1)'),
|
||||
given('rgb(-100, 20, 111)'),
|
||||
given('rgb(100%, 200, 222)'),
|
||||
given('rgba(-100, 20, 111, 1.1)'),
|
||||
given('hsl(122, 200, 222, 1)'),
|
||||
given('hsl(122, 200, 222)'),
|
||||
|
||||
@@ -51,14 +51,8 @@ function _clean(format) {
|
||||
}
|
||||
})
|
||||
|
||||
// convert "public" format to "internal" format
|
||||
cleaned.text = [cleaned.label || '', cleaned.message]
|
||||
delete cleaned.label
|
||||
delete cleaned.message
|
||||
if ('style' in cleaned) {
|
||||
cleaned.template = cleaned.style
|
||||
delete cleaned.style
|
||||
}
|
||||
// Legacy.
|
||||
cleaned.label = cleaned.label || ''
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
const { normalizeColor, toSvgColor } = require('./color')
|
||||
const badgeRenderers = require('./badge-renderers')
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
it is likely this will impact on the package's public interface in index.js
|
||||
*/
|
||||
module.exports = function makeBadge({
|
||||
format,
|
||||
template = 'flat',
|
||||
text,
|
||||
style = 'flat',
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
labelColor,
|
||||
logo,
|
||||
@@ -18,10 +23,13 @@ module.exports = function makeBadge({
|
||||
logoWidth,
|
||||
links = ['', ''],
|
||||
}) {
|
||||
// String coercion and whitespace removal.
|
||||
text = text.map(value => `${value}`.trim())
|
||||
if (!logo && (logoPosition !== undefined || logoWidth !== undefined)) {
|
||||
throw Error('`logoPosition` and `logoWidth` require `logo`')
|
||||
}
|
||||
|
||||
const [label, message] = text
|
||||
// String coercion and whitespace removal.
|
||||
label = `${label}`.trim()
|
||||
message = `${message}`.trim()
|
||||
|
||||
// This ought to be the responsibility of the server, not `makeBadge`.
|
||||
if (format === 'json') {
|
||||
@@ -39,23 +47,24 @@ module.exports = function makeBadge({
|
||||
})
|
||||
}
|
||||
|
||||
const render = badgeRenderers[template]
|
||||
const render = badgeRenderers[style]
|
||||
if (!render) {
|
||||
throw new Error(`Unknown template: '${template}'`)
|
||||
throw new Error(`Unknown badge style: '${style}'`)
|
||||
}
|
||||
|
||||
logoWidth = +logoWidth || (logo ? 14 : 0)
|
||||
|
||||
return render({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
minify: true,
|
||||
})
|
||||
return stripXmlWhitespace(
|
||||
render({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@ const { test, given, forCases } = require('sazerac')
|
||||
const { expect } = require('chai')
|
||||
const snapshot = require('snap-shot-it')
|
||||
const isSvg = require('is-svg')
|
||||
const prettier = require('prettier')
|
||||
const makeBadge = require('./make-badge')
|
||||
|
||||
function expectBadgeToMatchSnapshot(format) {
|
||||
snapshot(prettier.format(makeBadge(format), { parser: 'html' }))
|
||||
}
|
||||
|
||||
function testColor(color = '', colorAttr = 'color') {
|
||||
return JSON.parse(
|
||||
makeBadge({
|
||||
text: ['name', 'Bob'],
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
[colorAttr]: color,
|
||||
format: 'json',
|
||||
})
|
||||
@@ -34,10 +40,14 @@ describe('The badge generator', function () {
|
||||
]).expect('#abc123')
|
||||
// valid rgb(a)
|
||||
given('rgb(0,128,255)').expect('rgb(0,128,255)')
|
||||
given('rgb(220,128,255,0.5)').expect('rgb(220,128,255,0.5)')
|
||||
given('rgba(0,0,255)').expect('rgba(0,0,255)')
|
||||
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)')
|
||||
// valid hsl(a)
|
||||
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)')
|
||||
given('hsl(360,50%,50%,0.5)').expect('hsl(360,50%,50%,0.5)')
|
||||
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)')
|
||||
given('hsla(0,50%,101%)').expect('hsla(0,50%,101%)')
|
||||
// CSS named color.
|
||||
given('papayawhip').expect('papayawhip')
|
||||
// Shields named color.
|
||||
@@ -53,12 +63,6 @@ describe('The badge generator', function () {
|
||||
// invalid hex
|
||||
given('#123red'), // contains letter above F
|
||||
given('#red'), // contains letter above F
|
||||
// invalid rgb(a)
|
||||
given('rgb(220,128,255,0.5)'), // has alpha
|
||||
given('rgba(0,0,255)'), // no alpha
|
||||
// invalid hsl(a)
|
||||
given('hsl(360,50%,50%,0.5)'), // has alpha
|
||||
given('hsla(0,50%,101%)'), // no alpha
|
||||
// neither a css named color nor colorscheme
|
||||
given('notacolor'),
|
||||
given('bluish'),
|
||||
@@ -77,23 +81,26 @@ describe('The badge generator', function () {
|
||||
|
||||
describe('SVG', function () {
|
||||
it('should produce SVG', function () {
|
||||
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
|
||||
expect(svg)
|
||||
expect(makeBadge({ label: 'cactus', message: 'grown', format: 'svg' }))
|
||||
.to.satisfy(isSvg)
|
||||
.and.to.include('cactus')
|
||||
.and.to.include('grown')
|
||||
})
|
||||
|
||||
it('should match snapshot', function () {
|
||||
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
|
||||
snapshot(svg)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('JSON', function () {
|
||||
it('should produce the expected JSON', function () {
|
||||
const json = makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'json',
|
||||
links: ['https://example.com/', 'https://other.example.com/'],
|
||||
})
|
||||
@@ -106,484 +113,471 @@ describe('The badge generator', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('should replace undefined svg template with "flat"', function () {
|
||||
it('should replace undefined svg badge style with "flat"', function () {
|
||||
const jsonBadgeWithUnknownStyle = makeBadge({
|
||||
text: ['name', 'Bob'],
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
format: 'svg',
|
||||
})
|
||||
const jsonBadgeWithDefaultStyle = makeBadge({
|
||||
text: ['name', 'Bob'],
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
style: 'flat',
|
||||
})
|
||||
expect(jsonBadgeWithUnknownStyle)
|
||||
.to.equal(jsonBadgeWithDefaultStyle)
|
||||
.and.to.satisfy(isSvg)
|
||||
})
|
||||
|
||||
it('should fail with unknown svg template', function () {
|
||||
it('should fail with unknown svg badge style', function () {
|
||||
expect(() =>
|
||||
makeBadge({
|
||||
text: ['name', 'Bob'],
|
||||
label: 'name',
|
||||
message: 'Bob',
|
||||
format: 'svg',
|
||||
template: 'unknown_style',
|
||||
style: 'unknown_style',
|
||||
})
|
||||
).to.throw(Error, "Unknown template: 'unknown_style'")
|
||||
).to.throw(Error, "Unknown badge style: 'unknown_style'")
|
||||
})
|
||||
})
|
||||
|
||||
describe('"flat" template badge generation', function () {
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"flat-square" template badge generation', function () {
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat-square',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"plastic" template badge generation', function () {
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'plastic',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"for-the-badge" template badge generation', function () {
|
||||
// https://github.com/badges/shields/issues/1280
|
||||
it('numbers should produce a string', function () {
|
||||
const svg = makeBadge({
|
||||
text: [1998, 1999],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
})
|
||||
expect(svg).to.include('1998').and.to.include('1999')
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 1998,
|
||||
message: 1999,
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
})
|
||||
)
|
||||
.to.include('1998')
|
||||
.and.to.include('1999')
|
||||
})
|
||||
|
||||
it('lowercase/mixedcase string should produce uppercase string', function () {
|
||||
const svg = makeBadge({
|
||||
text: ['Label', '1 string'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
})
|
||||
expect(svg).to.include('LABEL').and.to.include('1 STRING')
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 'Label',
|
||||
message: '1 string',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
})
|
||||
)
|
||||
.to.include('LABEL')
|
||||
.and.to.include('1 STRING')
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"social" template badge generation', function () {
|
||||
it('should produce capitalized string for badge key', function () {
|
||||
const svg = makeBadge({
|
||||
text: ['some-key', 'some-value'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
})
|
||||
expect(svg).to.include('Some-key').and.to.include('some-value')
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 'some-key',
|
||||
message: 'some-value',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
})
|
||||
)
|
||||
.to.include('Some-key')
|
||||
.and.to.include('some-value')
|
||||
})
|
||||
|
||||
// https://github.com/badges/shields/issues/1606
|
||||
it('should handle empty strings used as badge keys', function () {
|
||||
const svg = makeBadge({
|
||||
text: ['', 'some-value'],
|
||||
format: 'json',
|
||||
template: 'social',
|
||||
})
|
||||
expect(svg).to.include('""').and.to.include('some-value')
|
||||
expect(
|
||||
makeBadge({
|
||||
label: '',
|
||||
message: 'some-value',
|
||||
format: 'json',
|
||||
style: 'social',
|
||||
})
|
||||
)
|
||||
.to.include('""')
|
||||
.and.to.include('some-value')
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, no logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message only, with logo and labelColor', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: '',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
})
|
||||
|
||||
it('should match snapshots: message/label, with links', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'social',
|
||||
color: '#b3e',
|
||||
labelColor: '#0f0',
|
||||
links: ['https://shields.io/', 'https://www.google.co.uk/'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('badges with logos should always produce the same badge', function () {
|
||||
it('badge with logo', function () {
|
||||
const svg = makeBadge({
|
||||
text: ['label', 'message'],
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'label',
|
||||
message: 'message',
|
||||
format: 'svg',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
})
|
||||
snapshot(svg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('text colors', function () {
|
||||
it('should use black text when the label color is light', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'flat',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'flat',
|
||||
color: '#000',
|
||||
labelColor: '#f3f3f3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should use black text when the message color is light', function () {
|
||||
snapshot(
|
||||
makeBadge({
|
||||
text: ['cactus', 'grown'],
|
||||
format: 'svg',
|
||||
template: 'for-the-badge',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
)
|
||||
expectBadgeToMatchSnapshot({
|
||||
label: 'cactus',
|
||||
message: 'grown',
|
||||
format: 'svg',
|
||||
style: 'for-the-badge',
|
||||
color: '#e2ffe1',
|
||||
labelColor: '#000',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
@@ -35,8 +35,8 @@
|
||||
"logo": "https://opencollective.com/opencollective/logo.txt"
|
||||
},
|
||||
"dependencies": {
|
||||
"anafanafo": "^1.0.0",
|
||||
"css-color-converter": "^1.1.1"
|
||||
"anafanafo": "2.0.0",
|
||||
"css-color-converter": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo 'Run tests from parent dir'; false"
|
||||
|
||||
@@ -30,9 +30,6 @@ public:
|
||||
__name: 'ALLOWED_ORIGIN'
|
||||
__format: 'json'
|
||||
|
||||
persistence:
|
||||
dir: 'PERSISTENCE_DIR'
|
||||
|
||||
services:
|
||||
bitbucketServer:
|
||||
authorizedOrigins: 'BITBUCKET_SERVER_ORIGINS'
|
||||
@@ -64,6 +61,8 @@ public:
|
||||
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
|
||||
requireCloudflare: 'REQUIRE_CLOUDFLARE'
|
||||
|
||||
private:
|
||||
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
|
||||
bintray_user: 'BINTRAY_USER'
|
||||
|
||||
@@ -16,9 +16,6 @@ public:
|
||||
cors:
|
||||
allowedOrigin: []
|
||||
|
||||
persistence:
|
||||
dir: './private'
|
||||
|
||||
services:
|
||||
github:
|
||||
baseUri: 'https://api.github.com/'
|
||||
@@ -36,4 +33,6 @@ public:
|
||||
|
||||
fetchLimit: '10MB'
|
||||
|
||||
requireCloudflare: false
|
||||
|
||||
private: {}
|
||||
|
||||
@@ -18,7 +18,3 @@ public:
|
||||
redirectUrl: 'https://shields.io/'
|
||||
|
||||
rasterUrl: 'https://raster.shields.io'
|
||||
|
||||
private:
|
||||
# These are not really private; they should be moved to `public`.
|
||||
shields_ips: ['192.99.59.72', '51.254.114.150', '149.56.96.133']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const gql = require('graphql-tag')
|
||||
const sinon = require('sinon')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseJsonService = require('./base-json')
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const BaseSvgScrapingService = require('./base-svg-scraping')
|
||||
|
||||
function makeExampleSvg({ label, message }) {
|
||||
return makeBadge({ text: ['this is the label', 'this is the result!'] })
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.string().required(),
|
||||
}).required()
|
||||
@@ -29,10 +25,7 @@ class DummySvgScrapingService extends BaseSvgScrapingService {
|
||||
describe('BaseSvgScrapingService', function () {
|
||||
const exampleLabel = 'this is the label'
|
||||
const exampleMessage = 'this is the result!'
|
||||
const exampleSvg = makeExampleSvg({
|
||||
label: exampleLabel,
|
||||
message: exampleMessage,
|
||||
})
|
||||
const exampleSvg = makeBadge({ label: exampleLabel, message: exampleMessage })
|
||||
|
||||
describe('valueFromSvgBadge', function () {
|
||||
it('should find the correct value', function () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseXmlService = require('./base-xml')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const BaseYamlService = require('./base-yaml')
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
// See available emoji at http://emoji.muan.co/
|
||||
const emojic = require('emojic')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const log = require('../server/log')
|
||||
const { AuthHelper } = require('./auth-helper')
|
||||
const { MetricHelper, MetricNames } = require('./metric-helper')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
@@ -373,9 +373,10 @@ describe('BaseService', function () {
|
||||
const expectedFormat = 'svg'
|
||||
expect(mockSendBadge).to.have.been.calledOnce
|
||||
expect(mockSendBadge).to.have.been.calledWith(expectedFormat, {
|
||||
text: ['cat', 'Hello namedParamA: bar with queryParamA: ?'],
|
||||
label: 'cat',
|
||||
message: 'Hello namedParamA: bar with queryParamA: ?',
|
||||
color: 'lightgrey',
|
||||
template: 'flat',
|
||||
style: 'flat',
|
||||
namedLogo: undefined,
|
||||
logo: undefined,
|
||||
logoWidth: undefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const coalesce = require('./coalesce')
|
||||
|
||||
const serverStartTimeGMTString = new Date().toGMTString()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const categories = require('../../services/categories')
|
||||
|
||||
const isRealCategory = Joi.equal(...categories.map(({ id }) => id)).required()
|
||||
|
||||
@@ -160,12 +160,10 @@ module.exports = function coalesceBadge(
|
||||
}
|
||||
|
||||
return {
|
||||
text: [
|
||||
// Use `coalesce()` to support empty labels and messages, as in the
|
||||
// static badge.
|
||||
coalesce(overrideLabel, serviceLabel, defaultLabel, category),
|
||||
coalesce(serviceMessage, 'n/a'),
|
||||
],
|
||||
// Use `coalesce()` to support empty labels and messages, as in the static
|
||||
// badge.
|
||||
label: coalesce(overrideLabel, serviceLabel, defaultLabel, category),
|
||||
message: coalesce(serviceMessage, 'n/a'),
|
||||
color: coalesce(
|
||||
// In case of an error, disregard user's color override.
|
||||
isError ? undefined : overrideColor,
|
||||
@@ -179,7 +177,7 @@ module.exports = function coalesceBadge(
|
||||
serviceLabelColor,
|
||||
defaultLabelColor
|
||||
),
|
||||
template: style,
|
||||
style,
|
||||
namedLogo,
|
||||
logo: logoSvgBase64,
|
||||
logoWidth,
|
||||
|
||||
@@ -7,63 +7,61 @@ const coalesceBadge = require('./coalesce-badge')
|
||||
describe('coalesceBadge', function () {
|
||||
describe('Label', function () {
|
||||
it('uses the default label', function () {
|
||||
expect(coalesceBadge({}, {}, { label: 'heyo' }).text).to.deep.equal([
|
||||
'heyo',
|
||||
'n/a',
|
||||
])
|
||||
expect(coalesceBadge({}, {}, { label: 'heyo' })).to.include({
|
||||
label: 'heyo',
|
||||
})
|
||||
})
|
||||
|
||||
// This behavior isn't great and we might want to remove it.
|
||||
it('uses the category as a default label', function () {
|
||||
expect(
|
||||
coalesceBadge({}, {}, {}, { category: 'cat' }).text
|
||||
).to.deep.equal(['cat', 'n/a'])
|
||||
expect(coalesceBadge({}, {}, {}, { category: 'cat' })).to.include({
|
||||
label: 'cat',
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves an empty label', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { label: '', message: '10k' }, {}).text
|
||||
).to.deep.equal(['', '10k'])
|
||||
expect(coalesceBadge({}, { label: '', message: '10k' }, {})).to.include({
|
||||
label: '',
|
||||
})
|
||||
})
|
||||
|
||||
it('overrides the label', function () {
|
||||
expect(
|
||||
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {}).text
|
||||
).to.deep.equal(['purr count', 'n/a'])
|
||||
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {})
|
||||
).to.include({ label: 'purr count' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Message', function () {
|
||||
it('applies the service message', function () {
|
||||
expect(coalesceBadge({}, { message: '10k' }, {}).text).to.deep.equal([
|
||||
undefined,
|
||||
'10k',
|
||||
])
|
||||
expect(coalesceBadge({}, { message: '10k' }, {})).to.include({
|
||||
message: '10k',
|
||||
})
|
||||
})
|
||||
|
||||
it('applies a numeric service message', function () {
|
||||
// https://github.com/badges/shields/issues/1280
|
||||
it('converts a number to a string', function () {
|
||||
// While a number of badges use this, in the long run we may want
|
||||
// `render()` to always return a string.
|
||||
expect(coalesceBadge({}, { message: 10 }, {}).text).to.deep.equal([
|
||||
undefined,
|
||||
10,
|
||||
])
|
||||
expect(coalesceBadge({}, { message: 10 }, {})).to.include({
|
||||
message: 10,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Right color', function () {
|
||||
it('uses the default color', function () {
|
||||
expect(coalesceBadge({}, {}, {}).color).to.equal('lightgrey')
|
||||
expect(coalesceBadge({}, {}, {})).to.include({ color: 'lightgrey' })
|
||||
})
|
||||
|
||||
it('overrides the color', function () {
|
||||
expect(
|
||||
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {}).color
|
||||
).to.equal('10ADED')
|
||||
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {})
|
||||
).to.include({ color: '10ADED' })
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {}).color
|
||||
).to.equal('B0ADED')
|
||||
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {})
|
||||
).to.include({ color: 'B0ADED' })
|
||||
})
|
||||
|
||||
context('In case of an error', function () {
|
||||
@@ -73,21 +71,23 @@ describe('coalesceBadge', function () {
|
||||
{ color: '10ADED' },
|
||||
{ isError: true, color: 'lightgray' },
|
||||
{}
|
||||
).color
|
||||
).to.equal('lightgray')
|
||||
)
|
||||
).to.include({ color: 'lightgray' })
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ colorB: 'B0ADED' },
|
||||
{ isError: true, color: 'lightgray' },
|
||||
{}
|
||||
).color
|
||||
).to.equal('lightgray')
|
||||
)
|
||||
).to.include({ color: 'lightgray' })
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the service color', function () {
|
||||
expect(coalesceBadge({}, { color: 'red' }, {}).color).to.equal('red')
|
||||
expect(coalesceBadge({}, { color: 'red' }, {})).to.include({
|
||||
color: 'red',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -97,20 +97,19 @@ describe('coalesceBadge', function () {
|
||||
})
|
||||
|
||||
it('applies the service label color', function () {
|
||||
expect(coalesceBadge({}, { labelColor: 'red' }, {}).labelColor).to.equal(
|
||||
'red'
|
||||
)
|
||||
expect(coalesceBadge({}, { labelColor: 'red' }, {})).to.include({
|
||||
labelColor: 'red',
|
||||
})
|
||||
})
|
||||
|
||||
it('overrides the label color', function () {
|
||||
expect(
|
||||
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {})
|
||||
.labelColor
|
||||
).to.equal('42f483')
|
||||
).to.include({ labelColor: '42f483' })
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {}).labelColor
|
||||
).to.equal('B2f483')
|
||||
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {})
|
||||
).to.include({ labelColor: 'B2f483' })
|
||||
})
|
||||
|
||||
it('converts a query-string numeric color to a string', function () {
|
||||
@@ -120,8 +119,8 @@ describe('coalesceBadge', function () {
|
||||
{ color: 123 },
|
||||
{ color: 'green' },
|
||||
{}
|
||||
).color
|
||||
).to.equal('123')
|
||||
)
|
||||
).to.include({ color: '123' })
|
||||
// also expected for legacy name
|
||||
expect(
|
||||
coalesceBadge(
|
||||
@@ -129,8 +128,8 @@ describe('coalesceBadge', function () {
|
||||
{ colorB: 123 },
|
||||
{ color: 'green' },
|
||||
{}
|
||||
).color
|
||||
).to.equal('123')
|
||||
)
|
||||
).to.include({ color: '123' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -148,9 +147,9 @@ describe('coalesceBadge', function () {
|
||||
})
|
||||
|
||||
it('applies the named logo', function () {
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).namedLogo).to.equal(
|
||||
'npm'
|
||||
)
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {})).to.include({
|
||||
namedLogo: 'npm',
|
||||
})
|
||||
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).logo).to.equal(
|
||||
getShieldsIcon({ name: 'npm' })
|
||||
).and.not.to.be.empty
|
||||
@@ -219,8 +218,8 @@ describe('coalesceBadge', function () {
|
||||
it('overrides the logo with custom svg', function () {
|
||||
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
|
||||
expect(
|
||||
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {}).logo
|
||||
).to.equal(logoSvg)
|
||||
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {})
|
||||
).to.include({ logo: logoSvg })
|
||||
})
|
||||
|
||||
it('ignores the color when custom svg is provided', function () {
|
||||
@@ -230,35 +229,36 @@ describe('coalesceBadge', function () {
|
||||
{ logo: logoSvg, logoColor: 'brightgreen' },
|
||||
{ namedLogo: 'appveyor' },
|
||||
{}
|
||||
).logo
|
||||
).to.equal(logoSvg)
|
||||
)
|
||||
).to.include({ logo: logoSvg })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo width', function () {
|
||||
it('overrides the logoWidth', function () {
|
||||
expect(coalesceBadge({ logoWidth: 20 }, {}, {}).logoWidth).to.equal(20)
|
||||
expect(coalesceBadge({ logoWidth: 20 }, {}, {})).to.include({
|
||||
logoWidth: 20,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the logo width', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {}).logoWidth
|
||||
).to.equal(275)
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {})
|
||||
).to.include({ logoWidth: 275 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo position', function () {
|
||||
it('overrides the logoPosition', function () {
|
||||
expect(
|
||||
coalesceBadge({ logoPosition: -10 }, {}, {}).logoPosition
|
||||
).to.equal(-10)
|
||||
expect(coalesceBadge({ logoPosition: -10 }, {}, {})).to.include({
|
||||
logoPosition: -10,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the logo position', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {})
|
||||
.logoPosition
|
||||
).to.equal(-10)
|
||||
).to.include({ logoPosition: -10 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -279,20 +279,24 @@ describe('coalesceBadge', function () {
|
||||
|
||||
describe('Style', function () {
|
||||
it('falls back to flat with invalid style', function () {
|
||||
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('flat')
|
||||
expect(coalesceBadge({ style: 7 }, {}, {}).template).to.equal('flat')
|
||||
expect(coalesceBadge({ style: undefined }, {}, {}).template).to.equal(
|
||||
'flat'
|
||||
)
|
||||
expect(coalesceBadge({ style: 'pill' }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: 7 }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: undefined }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
})
|
||||
|
||||
it('replaces legacy popout styles', function () {
|
||||
expect(coalesceBadge({ style: 'popout' }, {}, {}).template).to.equal(
|
||||
'flat'
|
||||
)
|
||||
expect(
|
||||
coalesceBadge({ style: 'popout-square' }, {}, {}).template
|
||||
).to.equal('flat-square')
|
||||
expect(coalesceBadge({ style: 'popout' }, {}, {})).to.include({
|
||||
style: 'flat',
|
||||
})
|
||||
expect(coalesceBadge({ style: 'popout-square' }, {}, {})).to.include({
|
||||
style: 'flat-square',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -300,8 +304,7 @@ describe('coalesceBadge', function () {
|
||||
it('overrides the cache length', function () {
|
||||
expect(
|
||||
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {})
|
||||
.cacheLengthSeconds
|
||||
).to.equal(123)
|
||||
).to.include({ cacheLengthSeconds: 123 })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const camelcase = require('camelcase')
|
||||
const BaseService = require('./base')
|
||||
const { isValidCategory } = require('./categories')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { pathToRegexp, compile } = require('path-to-regexp')
|
||||
const categories = require('../../services/categories')
|
||||
const coalesceBadge = require('./coalesce-badge')
|
||||
@@ -124,12 +124,7 @@ function transformExample(inExample, index, ServiceClass) {
|
||||
documentation,
|
||||
} = validateExample(inExample, index, ServiceClass)
|
||||
|
||||
const {
|
||||
text: [label, message],
|
||||
color,
|
||||
template: style,
|
||||
namedLogo,
|
||||
} = coalesceBadge(
|
||||
const { label, message, color, style, namedLogo } = coalesceBadge(
|
||||
{},
|
||||
staticPreview,
|
||||
ServiceClass.defaultBadgeData,
|
||||
|
||||
@@ -268,7 +268,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
|
||||
let dataHasChanged = false
|
||||
if (
|
||||
cached !== undefined &&
|
||||
cached.data.badgeData.text[1] !== badgeData.text[1]
|
||||
cached.data.badgeData.message !== badgeData.message
|
||||
) {
|
||||
dataHasChanged = true
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const camelcase = require('camelcase')
|
||||
const emojic = require('emojic')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const queryString = require('query-string')
|
||||
const BaseService = require('./base')
|
||||
const {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const escapeStringRegexp = require('escape-string-regexp')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { pathToRegexp } = require('path-to-regexp')
|
||||
|
||||
function makeFullUrl(base, partialUrl) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const {
|
||||
prepareRoute,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
|
||||
// This should be kept in sync with the schema in
|
||||
// `frontend/lib/service-definitions/index.ts`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const emojic = require('emojic')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const trace = require('./trace')
|
||||
|
||||
function validate(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const trace = require('./trace')
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const config = require('config').util.toObject()
|
||||
const secretIsValid = require('./secret-is-valid')
|
||||
const RateLimit = require('./rate-limit')
|
||||
const log = require('./log')
|
||||
|
||||
function secretInvalid(req, res) {
|
||||
if (!secretIsValid(req.password)) {
|
||||
// An unknown entity tries to connect. Let the connection linger for a minute.
|
||||
setTimeout(() => {
|
||||
res.json({ errors: [{ code: 'invalid_secrets' }] })
|
||||
}, 10000)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function setRoutes({ rateLimit }, { server, metricInstance }) {
|
||||
const ipRateLimit = new RateLimit({
|
||||
@@ -29,12 +15,6 @@ function setRoutes({ rateLimit }, { server, metricInstance }) {
|
||||
})
|
||||
|
||||
server.handle((req, res, next) => {
|
||||
if (req.url.startsWith('/sys/')) {
|
||||
if (secretInvalid(req, res)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (rateLimit) {
|
||||
const ip =
|
||||
(req.headers['x-forwarded-for'] || '').split(', ')[0] ||
|
||||
@@ -59,27 +39,6 @@ function setRoutes({ rateLimit }, { server, metricInstance }) {
|
||||
next()
|
||||
})
|
||||
|
||||
server.get('/sys/network', (req, res) => {
|
||||
res.json({ ips: config.public.shields_ips })
|
||||
})
|
||||
|
||||
server.ws('/sys/logs', socket => {
|
||||
const listener = (...msg) => socket.send(msg.join(' '))
|
||||
socket.on('close', () => log.removeListener(listener))
|
||||
socket.on('message', msg => {
|
||||
let req
|
||||
try {
|
||||
req = JSON.parse(msg)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
if (!secretIsValid(req.secret)) {
|
||||
return socket.close()
|
||||
}
|
||||
log.addListener(listener)
|
||||
})
|
||||
})
|
||||
|
||||
server.get('/sys/rate-limit', (req, res) => {
|
||||
res.json({
|
||||
ip: ipRateLimit.toJSON(),
|
||||
@@ -95,6 +54,4 @@ function setRoutes({ rateLimit }, { server, metricInstance }) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setRoutes,
|
||||
}
|
||||
module.exports = { setRoutes }
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const serverSecrets = require('../../lib/server-secrets')
|
||||
|
||||
function constEq(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false
|
||||
@@ -13,9 +11,10 @@ function constEq(a, b) {
|
||||
return zero === 0
|
||||
}
|
||||
|
||||
module.exports = function secretIsValid(secret = '') {
|
||||
return (
|
||||
serverSecrets.shields_secret &&
|
||||
constEq(secret, serverSecrets.shields_secret)
|
||||
)
|
||||
function makeSecretIsValid(shieldsSecret) {
|
||||
return function secretIsValid(secret = '') {
|
||||
return shieldsSecret && constEq(secret, shieldsSecret)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { makeSecretIsValid }
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const { URL } = url
|
||||
const cloudflareMiddleware = require('cloudflare-middleware')
|
||||
const bytes = require('bytes')
|
||||
const Camp = require('@shields_io/camp')
|
||||
const originalJoi = require('@hapi/joi')
|
||||
const originalJoi = require('joi')
|
||||
const makeBadge = require('../../badge-maker/lib/make-badge')
|
||||
const GithubConstellation = require('../../services/github/github-constellation')
|
||||
const suggest = require('../../services/suggest')
|
||||
@@ -117,9 +118,6 @@ const publicConfigSchema = Joi.object({
|
||||
cors: {
|
||||
allowedOrigin: Joi.array().items(optionalUrl).required(),
|
||||
},
|
||||
persistence: {
|
||||
dir: Joi.string().required(),
|
||||
},
|
||||
services: Joi.object({
|
||||
bitbucketServer: defaultService,
|
||||
drone: defaultService,
|
||||
@@ -148,6 +146,7 @@ const publicConfigSchema = Joi.object({
|
||||
rateLimit: Joi.boolean().required(),
|
||||
handleInternalErrors: Joi.boolean().required(),
|
||||
fetchLimit: Joi.string().regex(/^[0-9]+(b|kb|mb|gb|tb)$/i),
|
||||
requireCloudflare: Joi.boolean().required(),
|
||||
}).required()
|
||||
|
||||
const privateConfigSchema = Joi.object({
|
||||
@@ -168,7 +167,6 @@ const privateConfigSchema = Joi.object({
|
||||
npm_token: Joi.string(),
|
||||
redis_url: Joi.string().uri({ scheme: ['redis', 'rediss'] }),
|
||||
sentry_dsn: Joi.string(),
|
||||
shields_ips: Joi.array().items(Joi.string().ip()),
|
||||
shields_secret: Joi.string(),
|
||||
sl_insight_userUuid: Joi.string(),
|
||||
sl_insight_apiToken: Joi.string(),
|
||||
@@ -186,6 +184,11 @@ const privateMetricsInfluxConfigSchema = privateConfigSchema.append({
|
||||
influx_username: Joi.string().required(),
|
||||
influx_password: Joi.string().required(),
|
||||
})
|
||||
|
||||
function addHandlerAtIndex(camp, index, handlerFn) {
|
||||
camp.stack.splice(index, 0, handlerFn)
|
||||
}
|
||||
|
||||
/**
|
||||
* The Server is based on the web framework Scoutcamp. It creates
|
||||
* an http server, sets up helpers for token persistence and monitoring.
|
||||
@@ -224,7 +227,6 @@ class Server {
|
||||
}
|
||||
|
||||
this.githubConstellation = new GithubConstellation({
|
||||
persistence: publicConfig.persistence,
|
||||
service: publicConfig.services.github,
|
||||
private: privateConfig,
|
||||
})
|
||||
@@ -278,6 +280,23 @@ class Server {
|
||||
})
|
||||
}
|
||||
|
||||
// See https://www.viget.com/articles/heroku-cloudflare-the-right-way/
|
||||
requireCloudflare() {
|
||||
// Set `req.ip`, which is expected by `cloudflareMiddleware()`. This is set
|
||||
// by Express but not Scoutcamp.
|
||||
addHandlerAtIndex(this.camp, 0, function (req, res, next) {
|
||||
// On Heroku, `req.socket.remoteAddress` is the Heroku router. However,
|
||||
// the router ensures that the last item in the `X-Forwarded-For` header
|
||||
// is the real origin.
|
||||
// https://stackoverflow.com/a/18517550/893113
|
||||
req.ip = process.env.DYNO
|
||||
? req.headers['x-forwarded-for'].split(', ').pop()
|
||||
: req.socket.remoteAddress
|
||||
next()
|
||||
})
|
||||
addHandlerAtIndex(this.camp, 1, cloudflareMiddleware())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up Scoutcamp routes for 404/not found responses
|
||||
*/
|
||||
@@ -295,7 +314,8 @@ class Server {
|
||||
end
|
||||
)(
|
||||
makeBadge({
|
||||
text: ['410', `${format} no longer available`],
|
||||
label: '410',
|
||||
message: `${format} no longer available`,
|
||||
color: 'lightgray',
|
||||
format: 'svg',
|
||||
})
|
||||
@@ -310,7 +330,8 @@ class Server {
|
||||
end
|
||||
)(
|
||||
makeBadge({
|
||||
text: ['404', 'raster badges not available'],
|
||||
label: '404',
|
||||
message: 'raster badges not available',
|
||||
color: 'lightgray',
|
||||
format: 'svg',
|
||||
})
|
||||
@@ -328,7 +349,8 @@ class Server {
|
||||
end
|
||||
)(
|
||||
makeBadge({
|
||||
text: ['404', 'badge not found'],
|
||||
label: '404',
|
||||
message: 'badge not found',
|
||||
color: 'red',
|
||||
format,
|
||||
})
|
||||
@@ -409,6 +431,7 @@ class Server {
|
||||
ssl: { isSecure: secure, cert, key },
|
||||
cors: { allowedOrigin },
|
||||
rateLimit,
|
||||
requireCloudflare,
|
||||
} = this.config.public
|
||||
|
||||
log(`Server is starting up: ${this.baseUrl}`)
|
||||
@@ -422,6 +445,10 @@ class Server {
|
||||
key,
|
||||
}))
|
||||
|
||||
if (requireCloudflare) {
|
||||
this.requireCloudflare()
|
||||
}
|
||||
|
||||
const { metricInstance } = this
|
||||
this.cleanupMonitor = sysMonitor.setRoutes(
|
||||
{ rateLimit },
|
||||
|
||||
@@ -168,6 +168,28 @@ describe('The server', function () {
|
||||
})
|
||||
})
|
||||
|
||||
context('`requireCloudflare` is enabled', function () {
|
||||
let server
|
||||
afterEach(async function () {
|
||||
if (server) {
|
||||
server.stop()
|
||||
}
|
||||
})
|
||||
|
||||
it('should reject requests from localhost with an empty 200 response', async function () {
|
||||
this.timeout(10000)
|
||||
server = await createTestServer({ public: { requireCloudflare: true } })
|
||||
await server.start()
|
||||
|
||||
const { statusCode, body } = await got(
|
||||
`${server.baseUrl}badge/foo-bar-blue.svg`
|
||||
)
|
||||
|
||||
expect(statusCode).to.be.equal(200)
|
||||
expect(body).to.equal('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('configuration', function () {
|
||||
let server
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { expect } = require('chai')
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const fsos = require('fsos')
|
||||
const TokenPersistence = require('./token-persistence')
|
||||
|
||||
class FsTokenPersistence extends TokenPersistence {
|
||||
constructor({ path }) {
|
||||
super()
|
||||
this.path = path
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
let contents
|
||||
try {
|
||||
contents = await fsos.get(this.path)
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
contents = '[]'
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const tokens = JSON.parse(contents)
|
||||
this._tokens = new Set(tokens)
|
||||
return tokens
|
||||
}
|
||||
|
||||
async save() {
|
||||
const tokens = Array.from(this._tokens)
|
||||
await fsos.set(this.path, JSON.stringify(tokens))
|
||||
}
|
||||
|
||||
async onTokenAdded(token) {
|
||||
if (!this._tokens) {
|
||||
throw Error('initialize() has not been called')
|
||||
}
|
||||
this._tokens.add(token)
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async onTokenRemoved(token) {
|
||||
if (!this._tokens) {
|
||||
throw Error('initialize() has not been called')
|
||||
}
|
||||
this._tokens.delete(token)
|
||||
await this.save()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FsTokenPersistence
|
||||
@@ -1,72 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const tmp = require('tmp')
|
||||
const readFile = require('fs-readfile-promise')
|
||||
const { expect } = require('chai')
|
||||
const FsTokenPersistence = require('./fs-token-persistence')
|
||||
|
||||
describe('File system token persistence', function () {
|
||||
let path, persistence
|
||||
beforeEach(function () {
|
||||
path = tmp.tmpNameSync()
|
||||
persistence = new FsTokenPersistence({ path })
|
||||
})
|
||||
|
||||
context('when the file does not exist', function () {
|
||||
it('does nothing', async function () {
|
||||
const tokens = await persistence.initialize()
|
||||
expect(tokens).to.deep.equal([])
|
||||
})
|
||||
|
||||
it('saving creates an empty file', async function () {
|
||||
await persistence.initialize()
|
||||
|
||||
await persistence.save()
|
||||
|
||||
const json = JSON.parse(await readFile(path))
|
||||
expect(json).to.deep.deep.equal([])
|
||||
})
|
||||
})
|
||||
|
||||
context('when the file exists', function () {
|
||||
const initialTokens = ['a', 'b', 'c'].map(char => char.repeat(40))
|
||||
|
||||
beforeEach(async function () {
|
||||
fs.writeFileSync(path, JSON.stringify(initialTokens))
|
||||
})
|
||||
|
||||
it('loads the contents', async function () {
|
||||
const tokens = await persistence.initialize()
|
||||
expect(tokens).to.deep.equal(initialTokens)
|
||||
})
|
||||
|
||||
context('when tokens are added', function () {
|
||||
it('saves the change', async function () {
|
||||
const newToken = 'e'.repeat(40)
|
||||
const expected = Array.from(initialTokens)
|
||||
expected.push(newToken)
|
||||
|
||||
await persistence.initialize()
|
||||
await persistence.noteTokenAdded(newToken)
|
||||
|
||||
const savedTokens = JSON.parse(await readFile(path))
|
||||
expect(savedTokens).to.deep.equal(expected)
|
||||
})
|
||||
})
|
||||
|
||||
context('when tokens are removed', function () {
|
||||
it('saves the change', async function () {
|
||||
const expected = Array.from(initialTokens)
|
||||
const toRemove = expected.pop()
|
||||
|
||||
await persistence.initialize()
|
||||
|
||||
await persistence.noteTokenRemoved(toRemove)
|
||||
|
||||
const savedTokens = JSON.parse(await readFile(path))
|
||||
expect(savedTokens).to.deep.equal(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,13 +3,13 @@
|
||||
const { URL } = require('url')
|
||||
const Redis = require('ioredis')
|
||||
const log = require('../server/log')
|
||||
const TokenPersistence = require('./token-persistence')
|
||||
|
||||
module.exports = class RedisTokenPersistence extends TokenPersistence {
|
||||
module.exports = class RedisTokenPersistence {
|
||||
constructor({ url, key }) {
|
||||
super()
|
||||
this.url = url
|
||||
this.key = key
|
||||
this.noteTokenAdded = this.noteTokenAdded.bind(this)
|
||||
this.noteTokenRemoved = this.noteTokenRemoved.bind(this)
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
@@ -40,4 +40,20 @@ module.exports = class RedisTokenPersistence extends TokenPersistence {
|
||||
async onTokenRemoved(token) {
|
||||
await this.redis.srem(this.key, token)
|
||||
}
|
||||
|
||||
async noteTokenAdded(token) {
|
||||
try {
|
||||
await this.onTokenAdded(token)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async noteTokenRemoved(token) {
|
||||
try {
|
||||
await this.onTokenRemoved(token)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const log = require('../server/log')
|
||||
|
||||
// This is currently bound to the legacy github auth code. That will be
|
||||
// replaced with a dependency-injected token provider.
|
||||
class TokenPersistence {
|
||||
constructor() {
|
||||
this.noteTokenAdded = this.noteTokenAdded.bind(this)
|
||||
this.noteTokenRemoved = this.noteTokenRemoved.bind(this)
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
throw Error('initialize() is not implemented')
|
||||
}
|
||||
|
||||
async stop() {}
|
||||
|
||||
async onTokenAdded(token) {
|
||||
throw Error('onTokenAdded() is not implemented')
|
||||
}
|
||||
|
||||
async noteTokenAdded(token) {
|
||||
try {
|
||||
await this.onTokenAdded(token)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenRemoved(token) {
|
||||
throw Error('onTokenRemoved() is not implemented')
|
||||
}
|
||||
|
||||
async noteTokenRemoved(token) {
|
||||
try {
|
||||
await this.onTokenRemoved(token)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TokenPersistence
|
||||
@@ -111,10 +111,7 @@ if (allFiles.length > 100) {
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
danger.git.diffForFile(file).then(({ diff }) => {
|
||||
if (
|
||||
(diff.includes('authHelper') || diff.includes('serverSecrets')) &&
|
||||
!secretsDocs.modified
|
||||
) {
|
||||
if (diff.includes('authHelper') && !secretsDocs.modified) {
|
||||
warn(
|
||||
[
|
||||
`:books: Remember to ensure any changes to \`config.private\` `,
|
||||
@@ -134,11 +131,11 @@ if (allFiles.length > 100) {
|
||||
)
|
||||
}
|
||||
|
||||
if (diff.includes("require('joi')")) {
|
||||
if (diff.includes("require('@hapi/joi')")) {
|
||||
fail(
|
||||
[
|
||||
`Found import of 'joi' in \`${file}\`. <br>`,
|
||||
"Joi must be imported as '@hapi/joi'.",
|
||||
`Found import of '@hapi/joi' in \`${file}\`. <br>`,
|
||||
"Joi must be imported as 'joi'.",
|
||||
].join('')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ const { renderVersionBadge } = require('..//version')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
// (4)
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const schema = Joi.object({
|
||||
version: Joi.string().required(),
|
||||
}).required()
|
||||
@@ -311,7 +311,7 @@ Save, run `npm start`, and you can see it [locally](http://127.0.0.1:3000/).
|
||||
|
||||
If you update `examples`, you don't have to restart the server. Run `npm run defs` in another terminal window and the frontend will update.
|
||||
|
||||
### (4.5) Write Tests <!-- Change the link below when you change the heading -->
|
||||
### (4.5) Write Tests<!-- Change the link below when you change the heading -->
|
||||
|
||||
[write tests]: #45-write-tests
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ Once the route is working, fill out `render()` and `handle()`.
|
||||
<details>
|
||||
|
||||
```js
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { errorMessagesFor } = require('./github-helpers')
|
||||
|
||||
const issueSchema = Joi.object({
|
||||
@@ -174,7 +174,7 @@ or create an abstract superclass like **PypiBase**:
|
||||
<details>
|
||||
|
||||
```js
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const BaseJsonService = require('../base-json')
|
||||
|
||||
const schema = Joi.object({
|
||||
|
||||
@@ -184,9 +184,14 @@ Start the server using the Sentry DSN. You can set it:
|
||||
sudo SENTRY_DSN=https://xxx:yyy@sentry.io/zzz node server
|
||||
```
|
||||
|
||||
- or by `sentry_dsn` secret property defined in `private/secret.json`
|
||||
Or via config as you would do with [server secrets](server-secrets.md):
|
||||
|
||||
```yml
|
||||
private:
|
||||
sentry_dsn: ...
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo node server
|
||||
```
|
||||
|
||||
@@ -200,3 +205,9 @@ METRICS_PROMETHEUS_ENABLED=true npm start
|
||||
```
|
||||
|
||||
Metrics are available at `/metrics` resource.
|
||||
|
||||
### Cloudflare
|
||||
|
||||
Shields uses Cloudflare as a downstream CDN. If your installation does the same,
|
||||
you can configure your server to only accept requests coming from Cloudflare's IPs.
|
||||
Set `public.requireCloudflare: true`.
|
||||
|
||||
@@ -66,7 +66,7 @@ t.create('Build status')
|
||||
- Note that when we call our badge, we are allowing it to communicate with an external service without mocking the response. We write tests which interact with external services, which is unusual practice in unit testing. We do this because one of the purposes of service tests is to notify us if a badge has broken due to an upstream API change. For this reason it is important for at least one test to call the live API without mocking the interaction.
|
||||
- All badges on shields can be requested in a number of formats. As well as calling https://img.shields.io/wercker/build/wercker/go-wercker-api.svg to generate  we can also call https://img.shields.io/wercker/build/wercker/go-wercker-api.json to request the same content as JSON. When writing service tests, we request the badge in JSON format so it is easier to make assertions about the content.
|
||||
- We don't need to explicitly call `/wercker/build/wercker/go-wercker-api.json` here, only `/build/wercker/go-wercker-api.json`. When we create a tester object with `createServiceTester()` the URL base defined in our service class (in this case `/wercker`) is used as the base URL for any requests made by the tester object.
|
||||
3. `expectBadge()` is a helper function which accepts either a string literal or a [Joi][] schema for the different fields.
|
||||
3. `expectBadge()` is a helper function which accepts either a string literal, a [RegExp][] or a [Joi][] schema for the different fields.
|
||||
Joi is a validation library that is build into IcedFrisby which you can use to
|
||||
match based on a set of allowed strings, regexes, or specific values. You can
|
||||
refer to their [API reference][joi api].
|
||||
@@ -82,6 +82,7 @@ harness will call it for you.
|
||||
[icedfrisby api]: https://github.com/MarkHerhold/IcedFrisby/blob/master/API.md
|
||||
[joi]: https://github.com/hapijs/joi
|
||||
[joi api]: https://github.com/hapijs/joi/blob/master/API.md
|
||||
[regexp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
||||
|
||||
### (3) Running the Tests
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ function StyleTable({ style }: { style: string }): JSX.Element {
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>Badges (new)</td>
|
||||
<td>Badges (old)</td>
|
||||
<td>Badges (img.shields.io)</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import Enzyme from 'enzyme'
|
||||
import Adapter from 'enzyme-adapter-react-16'
|
||||
import chai from 'chai'
|
||||
import chaiEnzyme from 'chai-enzyme'
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() })
|
||||
|
||||
chai.use(chaiEnzyme())
|
||||
@@ -209,7 +209,7 @@ export default function EndpointPage(): JSX.Element {
|
||||
<dt>logoColor</dt>
|
||||
<dd>
|
||||
Default: none. Same meaning as the query string. Can be overridden by
|
||||
the query string.
|
||||
the query string. Only works for named logos.
|
||||
</dd>
|
||||
<dt>logoWidth</dt>
|
||||
<dd>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const {
|
||||
toSvgColor,
|
||||
brightness,
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
prependPrefix,
|
||||
isDataUrl,
|
||||
prepareNamedLogo,
|
||||
getSimpleIcon,
|
||||
makeLogo,
|
||||
} = require('./logos')
|
||||
|
||||
@@ -98,6 +99,13 @@ describe('Logo helpers', function () {
|
||||
})
|
||||
})
|
||||
|
||||
test(getSimpleIcon, () => {
|
||||
// https://github.com/badges/shields/issues/4016
|
||||
given({ name: 'get' }).expect(undefined)
|
||||
// https://github.com/badges/shields/issues/4263
|
||||
given({ name: 'get', color: 'blue' }).expect(undefined)
|
||||
})
|
||||
|
||||
test(makeLogo, () => {
|
||||
forCases([
|
||||
given('npm', { logo: 'image/svg+xml;base64,PHN2ZyB4bWxu' }),
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const config = require('config').util.toObject()
|
||||
|
||||
const legacySecretsPath = path.join(__dirname, '..', 'private', 'secret.json')
|
||||
if (fs.existsSync(legacySecretsPath)) {
|
||||
console.error(
|
||||
`Legacy secrets file found at ${legacySecretsPath}. It should be deleted and secrets replaced with environment variables or config/local.yml`
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
module.exports = config.private
|
||||
6386
package-lock.json
generated
6386
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -22,47 +22,46 @@
|
||||
"url": "https://github.com/badges/shields"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^17.1.1",
|
||||
"@sentry/node": "^5.24.2",
|
||||
"bytes": "^3.1.0",
|
||||
"camelcase": "^5.3.1",
|
||||
"@sentry/node": "^5.27.1",
|
||||
"@shields_io/camp": "^18.0.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.0",
|
||||
"camelcase": "^6.1.0",
|
||||
"chalk": "^4.1.0",
|
||||
"check-node-version": "^4.0.3",
|
||||
"chrome-web-store-item-property": "~1.2.0",
|
||||
"config": "^3.3.1",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"decamelize": "^3.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"emojic": "^1.1.16",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^3.17.4",
|
||||
"fsos": "^1.1.6",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"glob": "^7.1.6",
|
||||
"graphql": "^14.7.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"ioredis": "4.17.3",
|
||||
"joi-extension-semver": "4.1.1",
|
||||
"joi": "17.2.1",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^3.14.0",
|
||||
"jsonpath": "~1.0.2",
|
||||
"lodash.countby": "^4.6.0",
|
||||
"lodash.groupby": "^4.6.0",
|
||||
"lodash.times": "^4.3.2",
|
||||
"moment": "^2.28.0",
|
||||
"moment": "^2.29.1",
|
||||
"node-env-flag": "^0.1.0",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"path-to-regexp": "^5.0.0",
|
||||
"pretty-bytes": "^5.4.1",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^11.5.3",
|
||||
"query-string": "^6.13.2",
|
||||
"query-string": "^6.13.6",
|
||||
"request": "~2.88.2",
|
||||
"semver": "~7.3.2",
|
||||
"simple-icons": "3.8.0",
|
||||
"simple-icons": "3.12.1",
|
||||
"webextension-store-meta": "^1.0.2",
|
||||
"xmldom": "~0.2.1",
|
||||
"xpath": "~0.0.29"
|
||||
"xpath": "~0.0.32"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage:test:core": "nyc npm run test:core",
|
||||
@@ -142,20 +141,18 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/polyfill": "^7.11.5",
|
||||
"@babel/register": "7.11.5",
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.12.1",
|
||||
"@mapbox/react-click-to-select": "^2.2.0",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/chai-enzyme": "^0.6.7",
|
||||
"@types/enzyme": "^3.10.6",
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.groupby": "^4.6.6",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^14.11.1",
|
||||
"@types/node": "^14.11.8",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-modal": "^3.10.6",
|
||||
"@types/react-select": "^3.0.19",
|
||||
"@types/react-select": "^3.0.22",
|
||||
"@types/styled-components": "5.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^2.34.0",
|
||||
"@typescript-eslint/parser": "^2.34.0",
|
||||
@@ -164,45 +161,43 @@
|
||||
"babel-preset-gatsby": "^0.5.1",
|
||||
"caller": "^1.0.1",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-datetime": "^1.7.0",
|
||||
"chai-enzyme": "^1.0.0-beta.1",
|
||||
"chai-string": "^1.4.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^3.1.0",
|
||||
"concurrently": "^5.3.0",
|
||||
"cypress": "^5.1.0",
|
||||
"danger": "^10.4.0",
|
||||
"cypress": "^5.3.0",
|
||||
"danger": "^10.5.0",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.4",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-config-prettier": "^6.13.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-config-standard-react": "^9.2.0",
|
||||
"eslint-plugin-chai-friendly": "^0.6.0",
|
||||
"eslint-plugin-cypress": "^2.11.1",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-jsdoc": "^30.4.2",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsdoc": "^30.7.3",
|
||||
"eslint-plugin-mocha": "^6.3.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^2.5.1",
|
||||
"eslint-plugin-sort-class-members": "^1.8.0",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-standard": "^4.0.2",
|
||||
"fetch-ponyfill": "^6.1.1",
|
||||
"form-data": "^3.0.0",
|
||||
"fs-readfile-promise": "^3.0.1",
|
||||
"gatsby": "2.24.57",
|
||||
"gatsby": "2.24.73",
|
||||
"gatsby-plugin-catch-links": "^2.3.10",
|
||||
"gatsby-plugin-page-creator": "^2.3.27",
|
||||
"gatsby-plugin-page-creator": "^2.3.33",
|
||||
"gatsby-plugin-react-helmet": "^3.3.9",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^2.3.10",
|
||||
"gatsby-plugin-styled-components": "^3.3.9",
|
||||
"gatsby-plugin-typescript": "^2.4.16",
|
||||
"gatsby-plugin-typescript": "^2.4.24",
|
||||
"got": "11.7.0",
|
||||
"humanize-string": "^2.1.0",
|
||||
"husky": "^4.3.0",
|
||||
@@ -211,8 +206,8 @@
|
||||
"is-png": "^2.0.0",
|
||||
"is-svg": "^4.2.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdoc": "^3.6.5",
|
||||
"lint-staged": "^10.4.0",
|
||||
"jsdoc": "^3.6.6",
|
||||
"lint-staged": "^10.4.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.5",
|
||||
@@ -222,13 +217,13 @@
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.0.4",
|
||||
"node-mocks-http": "^1.9.0",
|
||||
"nodemon": "^2.0.4",
|
||||
"nodemon": "^2.0.6",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.1.0",
|
||||
"opn-cli": "^5.0.0",
|
||||
"portfinder": "^1.0.28",
|
||||
"prettier": "2.1.2",
|
||||
"react": "^16.13.1",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-error-overlay": "^6.0.7",
|
||||
"react-helmet": "^6.1.0",
|
||||
@@ -240,14 +235,14 @@
|
||||
"require-hacker": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"sazerac": "^2.0.0",
|
||||
"sinon": "^9.0.3",
|
||||
"sinon": "^9.2.0",
|
||||
"sinon-chai": "^3.5.0",
|
||||
"snap-shot-it": "^7.9.3",
|
||||
"start-server-and-test": "1.11.3",
|
||||
"start-server-and-test": "1.11.5",
|
||||
"styled-components": "^5.2.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsd": "^0.13.1",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -5,7 +5,6 @@ console.log(config)
|
||||
const GithubConstellation = require('../services/github/github-constellation')
|
||||
|
||||
const { persistence } = new GithubConstellation({
|
||||
persistence: config.public.persistence,
|
||||
service: config.public.services.github,
|
||||
private: config.private,
|
||||
})
|
||||
|
||||
35
scripts/run_package_tests.sh
Executable file
35
scripts/run_package_tests.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/3
|
||||
|
||||
# Start off less strict to work around various nvm errors.
|
||||
set -e
|
||||
export NVM_DIR="/opt/circleci/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"
|
||||
nvm install $NODE_VERSION
|
||||
nvm use $NODE_VERSION
|
||||
|
||||
# Stricter.
|
||||
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
|
||||
|
||||
# Run the package tests.
|
||||
npm run test:package
|
||||
npm run check-types:package
|
||||
|
||||
# Delete the shields.io dependencies.
|
||||
rm -rf node_modules/
|
||||
|
||||
# Run a smoke test (render a badge with the CLI) with only the package
|
||||
# dependencies installed.
|
||||
cd badge-maker
|
||||
npm link
|
||||
badge cactus grown :green @flat
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const t = (module.exports = new ServiceTester({
|
||||
}))
|
||||
|
||||
t.create('Weekly Downloads')
|
||||
.get('/dw/dustman.json')
|
||||
.get('/dw/duckduckgo-for-firefox.json')
|
||||
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
|
||||
|
||||
t.create('Weekly Downloads (not found)')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { isStarRating } = require('../test-validators')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { floorCount } = require('../color-formatters')
|
||||
const { BaseJsonService, InvalidResponse } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { downloadCount } = require('../color-formatters')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { renderLicenseBadge } = require('../licenses')
|
||||
const { renderVersionBadge } = require('../version')
|
||||
const { metric } = require('../text-formatters')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { isBuildStatus } = require('../build-status')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const queryString = require('querystring')
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
const isAppveyorTestTotals = Joi.string().regex(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { renderVersionBadge } = require('../version')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { floorCount: floorCountColor } = require('../color-formatters')
|
||||
const { addv, metric } = require('../text-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
18
services/aur/aur.spec.js
Normal file
18
services/aur/aur.spec.js
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const { AurVersion } = require('./aur.service')
|
||||
|
||||
describe('AurVersion', function () {
|
||||
test(AurVersion.render, () => {
|
||||
given({ version: '1:1.1.42.622-1', outOfDate: 1 }).expect({
|
||||
color: 'orange',
|
||||
message: 'v1:1.1.42.622-1',
|
||||
})
|
||||
|
||||
given({ version: '7', outOfDate: null }).expect({
|
||||
color: 'blue',
|
||||
message: 'v7',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -22,14 +22,6 @@ t.create('version (valid)')
|
||||
color: 'blue',
|
||||
})
|
||||
|
||||
t.create('version (valid, out of date)')
|
||||
.get('/version/gog-gemini-rue.json')
|
||||
.expectBadge({
|
||||
label: 'aur',
|
||||
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
color: 'orange',
|
||||
})
|
||||
|
||||
t.create('version (not found)')
|
||||
.get('/version/not-a-package.json')
|
||||
.expectBadge({ label: 'aur', message: 'package not found' })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { BaseJsonService, NotFound } = require('..')
|
||||
|
||||
const latestBuildSchema = Joi.object({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { renderBuildStatusBadge } = require('../build-status')
|
||||
const { BaseSvgScrapingService, NotFound } = require('..')
|
||||
const { keywords, fetch } = require('./azure-devops-helpers')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const {
|
||||
coveragePercentage: coveragePercentageColor,
|
||||
} = require('../color-formatters')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus } = require('../build-status')
|
||||
|
||||
const keywords = ['vso', 'vsts', 'azure-devops']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const {
|
||||
testResultQueryParamSchema,
|
||||
renderTestResultBadge,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
const org = 'azuredevops-powershell'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
const schema = Joi.object({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { downloadCount } = require('../color-formatters')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { renderVersionBadge } = require('../version')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { downloadCount } = require('../color-formatters')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { renderBuildStatusBadge } = require('../build-status')
|
||||
const { BaseJsonService, redirector } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { AuthHelper } = require('../../core/base-service/auth-helper')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { nonNegativeInteger, optionalUrl } = require('../validators')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
// https://devcenter.bitrise.io/api/app-status-badge/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
const schema = Joi.object()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { renderVersionBadge } = require('../version')
|
||||
const { InvalidResponse, redirector } = require('..')
|
||||
const BaseBowerService = require('./bower-base')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { isVPlusDottedVersionAtLeastOne } = require('../test-validators')
|
||||
const { ServiceTester } = require('../tester')
|
||||
const t = (module.exports = new ServiceTester({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../text-formatters')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { optionalUrl } = require('../validators')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
const bzBugStatus = Joi.equal(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
|
||||
const greenStatuses = [
|
||||
'fixed',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus, renderBuildStatusBadge } = require('../build-status')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus } = require('../build-status')
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user