Compare commits

..

1 Commits

Author SHA1 Message Date
Caleb Cartwright
d60bca325f fix: add some missing spaces in docs 2023-06-17 14:53:52 -05:00
587 changed files with 24728 additions and 4554 deletions

View File

@@ -12,7 +12,7 @@ body:
**fetch and display data from an upstream service**.
If your suggestion is for a static badge
(which shows the same information every time it is requested), it is
[already possible to make these](https://shields.io/docs/static-badges).
[already possible to make these](https://github.com/badges/shields/blob/master/doc/static-badges.md).
We don't add specific routes for badges which only show static information.
- type: textarea
@@ -25,7 +25,7 @@ body:
- Which service is this badge for e.g: GitHub, Travis CI
- What sort of information should this badge show?
Provide an example in plain text e.g: "version | v1.01" or as a static badge
(static badge generator can be found at https://shields.io/badges/static-badge )
(static badge generator can be found at https://shields.io/#your-badge )
validations:
required: true

View File

@@ -28,7 +28,7 @@ function allChangelogLinesAreVersionBump(changelogLines) {
changelogLines.length > 0 &&
changelogLines.length ===
changelogLines.filter(line =>
line.includes('Version bump only for package'),
line.includes('Version bump only for package')
).length
)
}
@@ -52,15 +52,14 @@ function isPointlessVersionBump(body) {
.filter(line => !line.startsWith('<h'))
.filter(line => !line.startsWith('<p>All notable changes'))
.filter(
line =>
!line.startsWith('See <a href="https://conventionalcommits.org">'),
line => !line.startsWith('See <a href="https://conventionalcommits.org">')
)
.filter(line => !line.startsWith('<!--'))
.filter(
line =>
!line.startsWith(
'<p><a href="https://www.gatsbyjs.com/docs/reference/release-notes/',
),
'<p><a href="https://www.gatsbyjs.com/docs/reference/release-notes/'
)
)
return allChangelogLinesAreVersionBump(changelogLines)
}

View File

@@ -1,4 +1,4 @@
FROM node:18-bullseye
FROM node:12-buster
RUN apt-get update
RUN apt-get install -y jq

View File

@@ -2,17 +2,14 @@
set -euxo pipefail
# mark workspace dir as 'safe'
git config --system --add safe.directory '/github/workspace'
# Set up a git user
git config user.name "release[bot]"
git config user.email "actions@users.noreply.github.com"
# Find last server-YYYY-MM-DD tag
git fetch --unshallow --tags
LAST_TAG=$(git tag | grep server | tail -n 1)
# Set up a git user
git config user.name "release[bot]"
git config user.email "actions@users.noreply.github.com"
# Find the marker in CHANGELOG.md
INSERT_POINT=$(grep -n "^\-\-\-$" CHANGELOG.md | cut -f1 -d:)
INSERT_POINT=$((INSERT_POINT+1))

View File

@@ -19,10 +19,6 @@ runs:
with:
node-version: ${{ inputs.node-version }}
- name: Install NPM 9
run: npm install -g npm@^9.0.0
shell: bash
- name: Install dependencies
if: ${{ inputs.cypress == 'false' }}
env:

View File

@@ -10,11 +10,12 @@ org="shields-io"
# This will fail if $PR_NUMBER is not a valid PR
pr_json=$(curl --fail "https://api.github.com/repos/badges/shields/pulls/$PR_NUMBER")
# Checkout the PR branch
# Attempt to apply the PR diff to the target branch
# This will fail if it does not merge cleanly
git config user.name "actions[bot]"
git config user.email "actions@users.noreply.github.com"
git fetch origin "pull/$PR_NUMBER/head:pr-$PR_NUMBER"
git checkout "pr-$PR_NUMBER"
git merge "pr-$PR_NUMBER"
# If the app does not already exist, create it
if ! flyctl status --app "$app"; then

View File

@@ -1,9 +1,6 @@
name: Build Docker Image
on:
pull_request:
push:
branches:
- 'gh-readonly-queue/**'
jobs:
build-docker-image:

View File

@@ -1,67 +0,0 @@
name: Test new bug report badge
run-name: Test bug report on issue ${{ github.event.issue.number }}
on:
issues:
types: [opened]
jobs:
extract-bug-badge-url:
if: ${{ contains(github.event.issue.labels.*.name, 'question') }}
runs-on: ubuntu-latest
outputs:
runBadgeTest: ${{ steps.testCondition.outputs.runNext }}
link: ${{ steps.testCondition.outputs.link }}
steps:
- name: Test badge test run conditions
id: testCondition
run: |
product=$(echo "${{ github.event.issue.body }}" | grep -A2 "Are you experiencing an issue with.*" | tail -n 1)
link=$(echo "${{ github.event.issue.body }}" | grep -A2 "Link to the badge.*" | tail -n 1)
if [[ "$product" == "shields.io" && "$link" == "https://img.shields.io"* ]]; then
echo "runNext=true" >> "$GITHUB_OUTPUT"
echo "link=$link" >> "$GITHUB_OUTPUT"
else
echo "Conditions not met. Skipping the workflow..."
echo "runNext=false" >> "$GITHUB_OUTPUT"
fi
run-bug-badge-url-test:
needs: extract-bug-badge-url
if: needs.extract-bug-badge-url.outputs.runBadgeTest == 'true'
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
cypress: false
- name: Output debug info
env:
TEST_BADGE_LINK: '${{ needs.extract-bug-badge-url.outputs.link }}'
run: npm run badge $TEST_BADGE_LINK
- name: Add Comment to Issue
uses: actions/github-script@v6
with:
script: |
const issueNumber = context.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const runId = context.runId;
const jobUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`;
const issueComment = `
Badge tested using \`npm run badge ${{ needs.extract-bug-badge-url.outputs.link }}\`
Output is available [here](${jobUrl})
`;
github.rest.issues.createComment({
issue_number: issueNumber,
owner: owner,
repo: repo,
body: issueComment
});

View File

@@ -1,4 +1,4 @@
name: Integration@node 18
name: Integration@node 17
on:
pull_request:
types: [opened, reopened, synchronize]
@@ -8,7 +8,7 @@ on:
- 'dependabot/**'
jobs:
test-integration-18:
test-integration-17:
runs-on: ubuntu-latest
env:
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
@@ -35,7 +35,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'

View File

@@ -1,4 +1,4 @@
name: Main@node 18
name: Main@node 17
on:
pull_request:
types: [opened, reopened, synchronize]
@@ -8,7 +8,7 @@ on:
- 'dependabot/**'
jobs:
test-main-18:
test-main-17:
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -17,7 +17,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'

View File

@@ -16,9 +16,12 @@ jobs:
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
engine-strict: 'false'
- node: '18'
- node: '20'
engine-strict: 'true'
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -29,6 +32,9 @@ jobs:
node-version: ${{ matrix.node }}
- name: Install dependencies
env:
CYPRESS_INSTALL_BINARY: 0
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
run: |
cd badge-maker
npm install

View File

@@ -13,12 +13,12 @@ jobs:
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
engine-strict: 'true'
- node: '18'
engine-strict: 'false'
- node: '20'
engine-strict: 'false'
steps:
- name: Checkout
uses: actions/checkout@v3

View File

@@ -1,13 +1,10 @@
name: Services@node 18
name: Services@node 17
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services-18:
test-services-17:
runs-on: ubuntu-latest
steps:
@@ -17,7 +14,7 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 18
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'

View File

@@ -2,9 +2,6 @@ name: Services
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches:
- 'gh-readonly-queue/**'
jobs:
test-services:

View File

@@ -4,51 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2023-08-01
- Convert `examples` arrays to `openApi` objects (part 1) [#9320](https://github.com/badges/shields/issues/9320)
- Migrate from docs.rs' builds API to status API [#9422](https://github.com/badges/shields/issues/9422)
- [OpenVSX] Fix OpenVSX API call for unversioned package URLs [#9408](https://github.com/badges/shields/issues/9408)
- Add support for [Lemmy] [#9368](https://github.com/badges/shields/issues/9368)
- upgrade to npm 9 [#9323](https://github.com/badges/shields/issues/9323)
- Go back to default YouTube cache [#9372](https://github.com/badges/shields/issues/9372)
- Add [GitHubDiscussionsSearch] and GitHubRepoDiscussionsSearch service [#9340](https://github.com/badges/shields/issues/9340)
- Allow user to filter github tags and releases [#9193](https://github.com/badges/shields/issues/9193)
- don't URL encode slash in [githubactionsworkflow] badge [#9322](https://github.com/badges/shields/issues/9322)
- add a bit of border to select boxes [#9348](https://github.com/badges/shields/issues/9348)
- deprecate [snyk] badges [#9349](https://github.com/badges/shields/issues/9349)
- increase max-age on [docker] badges, again [#9350](https://github.com/badges/shields/issues/9350) [#9369](https://github.com/badges/shields/issues/9369)
- Dependency updates
## server-2023-07-02
By far the most significant change in this release is the long-awaited launch of the re-designed frontend:
- migrate frontend to docusaurus [#9014](https://github.com/badges/shields/issues/9014)
- fix a load of spacing issues in frontend content [#9281](https://github.com/badges/shields/issues/9281)
- set a sensible meta description [#9283](https://github.com/badges/shields/issues/9283)
- chore(frontend): open homepage feature links in new tab [#9300](https://github.com/badges/shields/issues/9300)
- adapt opencollective images to theme background [#9298](https://github.com/badges/shields/issues/9298)
- temp fix: wrap code examples tabs in narrow browser windows [#9302](https://github.com/badges/shields/issues/9302)
- add a bit of border to text boxes [#9324](https://github.com/badges/shields/issues/9324)
Other changes in this release:
- cache [dockerpulls] badges for an hour [#9343](https://github.com/badges/shields/issues/9343)
- Mention YouTube API services and link to Google Privacy Policy [#9339](https://github.com/badges/shields/issues/9339)
- allow negative timestamps in relative [date] badge [#9321](https://github.com/badges/shields/issues/9321)
- upgrade to graphql 16 [#9290](https://github.com/badges/shields/issues/9290)
- remove obsolete travis .org examples [#9284](https://github.com/badges/shields/issues/9284)
- increase max age on reddit badges [#9282](https://github.com/badges/shields/issues/9282)
- feat: Add author filter option for [GithubCommitActivity] [#9251](https://github.com/badges/shields/issues/9251)
- Fix: [GithubCommitActivity] invalid branch error handling [#9258](https://github.com/badges/shields/issues/9258)
- Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] [#9233](https://github.com/badges/shields/issues/9233)
- Delete old deprecated services [#9254](https://github.com/badges/shields/issues/9254)
- feat: add 'canceled' status to netlify deploy badge [#9240](https://github.com/badges/shields/issues/9240)
- increase default cache on youtube badges [#9238](https://github.com/badges/shields/issues/9238)
- embiggen youtube cache, again [#9250](https://github.com/badges/shields/issues/9250)
- Dependency updates
## server-2023-06-01
- feat: Add total commits to [GitHubCommitActivity] [#9196](https://github.com/badges/shields/issues/9196)

View File

@@ -9,7 +9,7 @@ COPY package.json package-lock.json /usr/src/app/
COPY badge-maker /usr/src/app/badge-maker/
RUN apk add python3 make g++
RUN npm install -g "npm@^9.0.0"
RUN npm install -g "npm@>=8"
# We need dev deps to build the front end. We don't need Cypress, though.
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci

View File

@@ -70,7 +70,7 @@ This repo hosts:
[Make your own badges!][custom badges]
(Quick example: `https://img.shields.io/badge/left-right-f39f37`)
[custom badges]: http://localhost:3000/badges/static-badge
[custom badges]: https://shields.io/#your-badge
### Quickstart

View File

@@ -2,7 +2,7 @@
## 4.0.0 [WIP]
- Drop compatibility with Node < 16
- Drop compatibility with Node < 14
## 3.3.1
@@ -275,7 +275,7 @@ badge.loadFont('/path/to/Verdana.ttf', err => {
{ text: ['build', 'passed'], colorscheme: 'green', template: 'flat' },
(svg, err) => {
// svg is a string containing your badge
},
}
)
})
```

View File

@@ -7,19 +7,19 @@ expectError(
makeBadge({
message: 'passed',
style: 'invalid style',
}),
})
)
expectType<string>(
makeBadge({
message: 'passed',
}),
})
)
expectType<string>(
makeBadge({
label: 'build',
message: 'passed',
}),
})
)
expectType<string>(
makeBadge({
@@ -28,7 +28,7 @@ expectType<string>(
labelColor: 'green',
color: 'red',
style: 'flat',
}),
})
)
const error = new ValidationError()

View File

@@ -69,7 +69,7 @@ function getLogoElement({ logo, horizPadding, badgeHeight, logoWidth }) {
function renderBadge(
{ links, leftWidth, rightWidth, height, accessibleText },
content,
content
) {
const width = leftWidth + rightWidth
const leftLink = links[0]
@@ -397,7 +397,7 @@ class Plastic extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement],
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -446,7 +446,7 @@ class Flat extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement],
[gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -478,7 +478,7 @@ class FlatSquare extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
[backgroundGroup, this.foregroundGroupElement],
[backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -748,7 +748,7 @@ function social({
accessibleText,
height: externalHeight,
},
[style, gradients, backgroundGroup, logoElement, foregroundGroup],
[style, gradients, backgroundGroup, logoElement, foregroundGroup]
)
}
@@ -978,7 +978,7 @@ function forTheBadge({
accessibleText: createAccessibleText({ label, message }),
height: BADGE_HEIGHT,
},
[backgroundGroup, foregroundGroup],
[backgroundGroup, foregroundGroup]
)
}

View File

@@ -75,7 +75,7 @@ test(toSvgColor, () => {
given('papayawhip').expect('papayawhip')
given('purple').expect('purple')
forCases([given(''), given(undefined), given('not-a-color')]).expect(
undefined,
undefined
)
given('lightgray').expect('#9f9f9f')
given('informational').expect('#007ec6')

View File

@@ -32,7 +32,7 @@ function _validate(format) {
]
if ('style' in format && !styleValues.includes(format.style)) {
throw new ValidationError(
`Field \`style\` must be one of (${styleValues.toString()})`,
`Field \`style\` must be one of (${styleValues.toString()})`
)
}
}
@@ -46,7 +46,7 @@ function _clean(format) {
cleaned[key] = format[key]
} else {
throw new ValidationError(
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`,
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
)
}
})

View File

@@ -10,12 +10,12 @@ describe('makeBadge function', function () {
makeBadge({
label: 'build',
message: 'passed',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
message: 'passed',
}),
})
).to.satisfy(isSvg)
expect(
makeBadge({
@@ -23,7 +23,7 @@ describe('makeBadge function', function () {
message: 'passed',
color: 'green',
style: 'flat',
}),
})
).to.satisfy(isSvg)
})
@@ -32,44 +32,44 @@ describe('makeBadge function', function () {
console.log(x)
expect(() => makeBadge(x)).to.throw(
ValidationError,
'makeBadge takes an argument of type object',
'makeBadge takes an argument of type object'
)
})
expect(() => makeBadge({})).to.throw(
ValidationError,
'Field `message` is required',
'Field `message` is required'
)
expect(() => makeBadge({ label: 'build' })).to.throw(
ValidationError,
'Field `message` is required',
'Field `message` is required'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', labelColor: 7 }),
makeBadge({ label: 'build', message: 'passed', labelColor: 7 })
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
expect(() =>
makeBadge({ label: 'build', message: 'passed', format: 'png' }),
makeBadge({ label: 'build', message: 'passed', format: 'png' })
).to.throw(ValidationError, "Unexpected field 'format'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', template: 'flat' }),
makeBadge({ label: 'build', message: 'passed', template: 'flat' })
).to.throw(ValidationError, "Unexpected field 'template'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', foo: 'bar' }),
makeBadge({ label: 'build', message: 'passed', foo: 'bar' })
).to.throw(ValidationError, "Unexpected field 'foo'")
expect(() =>
makeBadge({
label: 'build',
message: 'passed',
style: 'something else',
}),
})
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', style: 'popout' }),
makeBadge({ label: 'build', message: 'passed', style: 'popout' })
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
})
})

View File

@@ -58,6 +58,6 @@ module.exports = function makeBadge({
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),
}),
})
)
}

View File

@@ -6,8 +6,8 @@ const snapshot = require('snap-shot-it')
const prettier = require('prettier')
const makeBadge = require('./make-badge')
async function expectBadgeToMatchSnapshot(format) {
snapshot(await prettier.format(makeBadge(format), { parser: 'html' }))
function expectBadgeToMatchSnapshot(format) {
snapshot(prettier.format(makeBadge(format), { parser: 'html' }))
}
function testColor(color = '', colorAttr = 'color') {
@@ -17,7 +17,7 @@ function testColor(color = '', colorAttr = 'color') {
message: 'Bob',
[colorAttr]: color,
format: 'json',
}),
})
).color
}
@@ -67,7 +67,7 @@ describe('The badge generator', function () {
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus'),
given('cactus')
).expect(undefined)
})
})
@@ -87,8 +87,8 @@ describe('The badge generator', function () {
.and.to.include('grown')
})
it('should match snapshot', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshot', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -138,14 +138,14 @@ describe('The badge generator', function () {
message: 'Bob',
format: 'svg',
style: 'unknown_style',
}),
})
).to.throw(Error, "Unknown badge style: 'unknown_style'")
})
})
describe('"flat" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -155,8 +155,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -167,8 +167,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -177,8 +177,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -188,8 +188,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -200,8 +200,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -212,8 +212,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -223,8 +223,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -236,8 +236,8 @@ describe('The badge generator', function () {
})
describe('"flat-square" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -247,8 +247,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -259,8 +259,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -269,8 +269,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -280,8 +280,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -292,8 +292,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -304,8 +304,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -315,8 +315,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -328,8 +328,8 @@ describe('The badge generator', function () {
})
describe('"plastic" template badge generation', function () {
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -339,8 +339,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -351,8 +351,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -361,8 +361,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -372,8 +372,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -384,8 +384,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -396,8 +396,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -407,8 +407,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -428,7 +428,7 @@ describe('The badge generator', function () {
message: 1999,
format: 'svg',
style: 'for-the-badge',
}),
})
)
.to.include('1998')
.and.to.include('1999')
@@ -441,14 +441,14 @@ describe('The badge generator', function () {
message: '1 string',
format: 'svg',
style: 'for-the-badge',
}),
})
)
.to.include('LABEL')
.and.to.include('1 STRING')
})
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -458,8 +458,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -470,8 +470,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -480,8 +480,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -491,8 +491,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -503,8 +503,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -515,8 +515,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the label color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the label color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -526,8 +526,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: black text when the message color is light', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: black text when the message color is light', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -546,7 +546,7 @@ describe('The badge generator', function () {
message: 'some-value',
format: 'svg',
style: 'social',
}),
})
)
.to.include('Some-key')
.and.to.include('some-value')
@@ -560,14 +560,14 @@ describe('The badge generator', function () {
message: 'some-value',
format: 'json',
style: 'social',
}),
})
)
.to.include('""')
.and.to.include('some-value')
})
it('should match snapshots: message/label, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, no logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -577,8 +577,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with logo', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -589,8 +589,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, no logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -599,8 +599,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -610,8 +610,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message only, with logo and labelColor', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message only, with logo and labelColor', function () {
expectBadgeToMatchSnapshot({
label: '',
message: 'grown',
format: 'svg',
@@ -622,8 +622,8 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message/label, with links', async function () {
await expectBadgeToMatchSnapshot({
it('should match snapshots: message/label, with links', function () {
expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
@@ -636,8 +636,8 @@ describe('The badge generator', function () {
})
describe('badges with logos should always produce the same badge', function () {
it('badge with logo', async function () {
await expectBadgeToMatchSnapshot({
it('badge with logo', function () {
expectBadgeToMatchSnapshot({
label: 'label',
message: 'message',
format: 'svg',

View File

@@ -66,7 +66,7 @@ class XmlElement {
})
.join(' ')
return stripXmlWhitespace(
`<${this.name}${attrsStr}>${content}</${this.name}>`,
`<${this.name}${attrsStr}>${content}</${this.name}>`
)
}
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
@@ -88,7 +88,7 @@ class ElementList {
typeof el.render === 'function'
? acc + el.render()
: acc + escapeXml(el),
'',
''
)
}
}

View File

@@ -26,7 +26,8 @@
"badge": "lib/badge-cli.js"
},
"engines": {
"node": ">=16"
"node": ">= 14",
"npm": ">= 6"
},
"collective": {
"type": "opencollective",

View File

@@ -7,7 +7,7 @@ describe('Badge URL helper functions', function () {
given('single trailing underscore_').expect('single trailing underscore ')
given('__double leading underscores').expect('_double leading underscores')
given('double trailing underscores__').expect(
'double trailing underscores_',
'double trailing underscores_'
)
given('treble___underscores').expect('treble_ underscores')
given('fourfold____underscores').expect('fourfold__underscores')

View File

@@ -11,7 +11,7 @@ class AuthHelper {
isRequired = false,
defaultToEmptyStringForUser = false,
},
config,
config
) {
if (!userKey && !passKey) {
throw Error('Expected userKey or passKey to be set')
@@ -142,7 +142,7 @@ class AuthHelper {
withBasicAuth(requestParams) {
return this._withAnyAuth(requestParams, requestParams =>
this.constructor._mergeAuth(requestParams, this._basicAuth),
this.constructor._mergeAuth(requestParams, this._basicAuth)
)
}
@@ -172,13 +172,13 @@ class AuthHelper {
withBearerAuthHeader(
requestParams,
bearerKey = 'Bearer', // lgtm [js/hardcoded-credentials]
bearerKey = 'Bearer' // lgtm [js/hardcoded-credentials]
) {
return this._withAnyAuth(requestParams, requestParams =>
this.constructor._mergeHeaders(
requestParams,
this._bearerAuthHeader(bearerKey),
),
this._bearerAuthHeader(bearerKey)
)
)
}
@@ -204,7 +204,7 @@ class AuthHelper {
this.constructor._mergeQueryParams(requestParams, {
...(userKey ? { [userKey]: this._user } : undefined),
...(passKey ? { [passKey]: this._pass } : undefined),
}),
})
)
}
}

View File

@@ -8,13 +8,12 @@ describe('AuthHelper', function () {
it('throws without userKey or passKey', function () {
expect(() => new AuthHelper({}, {})).to.throw(
Error,
'Expected userKey or passKey to be set',
'Expected userKey or passKey to be set'
)
})
it('throws without serviceKey or authorizedOrigins', function () {
expect(
() =>
new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {}),
() => new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {})
).to.throw(Error, 'Expected authorizedOrigins or serviceKey to be set')
})
it('throws when authorizedOrigins is not an array', function () {
@@ -26,8 +25,8 @@ describe('AuthHelper', function () {
passKey: 'myci_pass',
authorizedOrigins: true,
},
{ private: {} },
),
{ private: {} }
)
).to.throw(Error, 'Expected authorizedOrigins to be an array of origins')
})
})
@@ -36,7 +35,7 @@ describe('AuthHelper', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
{ private: privateConfig },
{ private: privateConfig }
).isValid
}
test(validate, () => {
@@ -44,20 +43,20 @@ describe('AuthHelper', function () {
// Fully configured user + pass.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
// Fully configured user or pass.
given(
{ userKey: 'myci_user', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ passKey: 'myci_pass', isRequired: true },
{ myci_pass: 'abc123' },
{ myci_pass: 'abc123' }
),
given({ userKey: 'myci_user' }, { myci_user: 'admin' }),
given({ passKey: 'myci_pass' }, { myci_pass: 'abc123' }),
@@ -71,16 +70,16 @@ describe('AuthHelper', function () {
// Partly configured.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin' },
{ myci_user: 'admin' }
),
// Missing required config.
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{},
{}
),
given({ userKey: 'myci_user', isRequired: true }, {}),
given({ passKey: 'myci_pass', isRequired: true }, {}),
@@ -92,18 +91,18 @@ describe('AuthHelper', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
{ private: privateConfig },
{ private: privateConfig }
)._basicAuth
}
test(validate, () => {
forCases([
given(
{ userKey: 'myci_user', passKey: 'myci_pass', isRequired: true },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
given(
{ userKey: 'myci_user', passKey: 'myci_pass' },
{ myci_user: 'admin', myci_pass: 'abc123' },
{ myci_user: 'admin', myci_pass: 'abc123' }
),
]).expect({ username: 'admin', password: 'abc123' })
given({ userKey: 'myci_user' }, { myci_user: 'admin' }).expect({
@@ -115,11 +114,11 @@ describe('AuthHelper', function () {
password: 'abc123',
})
given({ userKey: 'myci_user', passKey: 'myci_pass' }, {}).expect(
undefined,
undefined
)
given(
{ passKey: 'myci_pass', defaultToEmptyStringForUser: true },
{ myci_pass: 'abc123' },
{ myci_pass: 'abc123' }
).expect({
username: '',
password: 'abc123',
@@ -169,7 +168,7 @@ describe('AuthHelper', function () {
expect(() =>
authHelper.enforceStrictSsl({
options: { https: { rejectUnauthorized: false } },
}),
})
).to.throw(InvalidParameter)
})
})
@@ -193,7 +192,7 @@ describe('AuthHelper', function () {
expect(() =>
authHelper.enforceStrictSsl({
options: { https: { rejectUnauthorized: false } },
}),
})
).not.to.throw()
})
})
@@ -319,7 +318,7 @@ describe('AuthHelper', function () {
},
},
private: { myci_user: 'admin', myci_pass: 'abc123' },
},
}
)
const withBasicAuth = requestOptions =>
authHelper.withBasicAuth(requestOptions)
@@ -377,7 +376,7 @@ describe('AuthHelper', function () {
withBasicAuth({
url: 'https://myci.test/api',
options: { https: { rejectUnauthorized: false } },
}),
})
).to.throw(InvalidParameter)
})
})

View File

@@ -91,7 +91,7 @@ class BaseGraphqlService extends BaseService {
throw exception
} else {
throw Error(
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`,
`transformErrors() must return a ShieldsRuntimeError; got ${exception}`
)
}
}

View File

@@ -35,23 +35,23 @@ describe('BaseGraphqlService', function () {
Promise.resolve({
buffer: '{"some": "json"}',
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/graphql',
{
body: '{"query":"{\\n requiredString\\n}","variables":{}}',
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
headers: { Accept: 'application/json' },
method: 'POST',
},
}
)
})
@@ -74,17 +74,17 @@ describe('BaseGraphqlService', function () {
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/graphql',
{
body: '{"query":"{\\n requiredString\\n}","variables":{}}',
body: '{"query":"{\\n requiredString\\n}\\n","variables":{}}',
headers: { Accept: 'application/json' },
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -98,8 +98,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -113,8 +113,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -130,8 +130,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -149,8 +149,8 @@ describe('BaseGraphqlService', function () {
expect(
await DummyGraphqlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -188,8 +188,8 @@ describe('BaseGraphqlService', function () {
expect(
await WithErrorHandler.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -28,21 +28,21 @@ describe('BaseJsonService', function () {
Promise.resolve({
buffer: '{"some": "json"}',
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.json',
{
headers: { Accept: 'application/json' },
},
}
)
})
@@ -60,7 +60,7 @@ describe('BaseJsonService', function () {
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -69,7 +69,7 @@ describe('BaseJsonService', function () {
headers: { Accept: 'application/json' },
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -83,8 +83,8 @@ describe('BaseJsonService', function () {
expect(
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -98,8 +98,8 @@ describe('BaseJsonService', function () {
expect(
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -115,8 +115,8 @@ describe('BaseJsonService', function () {
expect(
await DummyJsonService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -33,14 +33,14 @@ export default class BaseStaticService extends BaseService {
{},
serviceConfig,
namedParams,
queryParams,
queryParams
)
const badgeData = coalesceBadge(
queryParams,
serviceData,
this.defaultBadgeData,
this,
this
)
// The final capture group is the extension.

View File

@@ -28,7 +28,7 @@ describe('BaseSvgScrapingService', function () {
describe('valueFromSvgBadge', function () {
it('should find the correct value', function () {
expect(BaseSvgScrapingService.valueFromSvgBadge(exampleSvg)).to.equal(
exampleMessage,
exampleMessage
)
})
})
@@ -40,21 +40,21 @@ describe('BaseSvgScrapingService', function () {
Promise.resolve({
buffer: exampleSvg,
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher with the expected header', async function () {
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.svg',
{
headers: { Accept: 'image/svg+xml' },
},
}
)
})
@@ -75,7 +75,7 @@ describe('BaseSvgScrapingService', function () {
await WithCustomOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -84,7 +84,7 @@ describe('BaseSvgScrapingService', function () {
method: 'POST',
headers: { Accept: 'image/svg+xml' },
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -98,8 +98,8 @@ describe('BaseSvgScrapingService', function () {
expect(
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: exampleMessage,
})
@@ -124,8 +124,8 @@ describe('BaseSvgScrapingService', function () {
expect(
await WithValueMatcher.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'a different message',
})
@@ -139,8 +139,8 @@ describe('BaseSvgScrapingService', function () {
expect(
await DummySvgScrapingService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -28,21 +28,21 @@ describe('BaseXmlService', function () {
Promise.resolve({
buffer: '<requiredString>some-string</requiredString>',
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
'http://example.com/foo.xml',
{
headers: { Accept: 'application/xml, text/xml' },
},
}
)
})
@@ -62,7 +62,7 @@ describe('BaseXmlService', function () {
await WithCustomOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -71,7 +71,7 @@ describe('BaseXmlService', function () {
headers: { Accept: 'application/xml, text/xml' },
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -85,8 +85,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -112,8 +112,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlServiceWithParserOption.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string with trailing whitespace ',
})
@@ -127,8 +127,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -144,8 +144,8 @@ describe('BaseXmlService', function () {
expect(
await DummyXmlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -44,14 +44,14 @@ describe('BaseYamlService', function () {
Promise.resolve({
buffer: expectedYaml,
res: { statusCode: 200 },
}),
})
)
})
it('invokes _requestFetcher', async function () {
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -61,7 +61,7 @@ describe('BaseYamlService', function () {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
},
}
)
})
@@ -79,7 +79,7 @@ describe('BaseYamlService', function () {
await WithOptions.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
{ handleInternalErrors: false }
)
expect(requestFetcher).to.have.been.calledOnceWith(
@@ -91,7 +91,7 @@ describe('BaseYamlService', function () {
},
method: 'POST',
searchParams: { queryParam: 123 },
},
}
)
})
})
@@ -105,8 +105,8 @@ describe('BaseYamlService', function () {
expect(
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
@@ -120,8 +120,8 @@ describe('BaseYamlService', function () {
expect(
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -137,8 +137,8 @@ describe('BaseYamlService', function () {
expect(
await DummyYamlService.invoke(
{ requestFetcher },
{ handleInternalErrors: false },
),
{ handleInternalErrors: false }
)
).to.deep.equal({
isError: true,
color: 'lightgray',

View File

@@ -44,7 +44,7 @@ const optionalStringWhenNamedLogoPresent = Joi.alternatives().conditional(
{
is: Joi.string().required(),
then: Joi.string(),
},
}
)
const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
@@ -183,28 +183,12 @@ class BaseService {
Joi.assert(
this.defaultBadgeData,
defaultBadgeDataSchema,
`Default badge data for ${this.name}`,
`Default badge data for ${this.name}`
)
this.examples.forEach((example, index) =>
validateExample(example, index, this),
validateExample(example, index, this)
)
// ensure openApi spec matches route
if (this.openApi) {
const preparedRoute = prepareRoute(this.route)
for (const [key, value] of Object.entries(this.openApi)) {
let example = key
for (const param of value.get.parameters) {
example = example.replace(`{${param.name}}`, param.example)
}
if (!example.match(preparedRoute.regex)) {
throw new Error(
`Inconsistent Open Api spec and Route found for service ${this.name}`,
)
}
}
}
}
static getDefinition() {
@@ -213,7 +197,7 @@ class BaseService {
const queryParams = getQueryParamNames(this.route)
const examples = this.examples.map((example, index) =>
transformExample(example, index, this),
transformExample(example, index, this)
)
let route
@@ -234,7 +218,7 @@ class BaseService {
constructor(
{ requestFetcher, authHelper, metricHelper },
{ handleInternalErrors },
{ handleInternalErrors }
) {
this._requestFetcher = requestFetcher
this.authHelper = authHelper
@@ -250,9 +234,9 @@ class BaseService {
const params = new URLSearchParams(
Object.fromEntries(
Object.entries(options.searchParams).filter(
([k, v]) => v !== undefined,
),
),
([k, v]) => v !== undefined
)
)
)
logUrl = `${url}?${params.toString()}`
delete logOptions.searchParams
@@ -260,12 +244,12 @@ class BaseService {
logTrace(
emojic.bowAndArrow,
'Request',
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`,
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`
)
const { res, buffer } = await this._requestFetcher(
url,
options,
systemErrors,
systemErrors
)
await this._meterResponse(res, buffer)
logTrace(emojic.dart, 'Response status code', res.statusCode)
@@ -295,7 +279,7 @@ class BaseService {
prettyErrorMessage = 'invalid response data',
includeKeys = false,
allowAndStripUnknownKeys = true,
} = {},
} = {}
) {
return validate(
{
@@ -307,7 +291,7 @@ class BaseService {
allowAndStripUnknownKeys,
},
data,
schema,
schema
)
}
@@ -363,7 +347,7 @@ class BaseService {
'unhandledError',
emojic.boom,
'Unhandled internal error',
error,
error
)
) {
// This is where we end up if an unhandled exception is thrown in
@@ -381,7 +365,7 @@ class BaseService {
'unhandledError',
emojic.boom,
'Unhandled internal error',
error,
error
)
throw error
}
@@ -391,7 +375,7 @@ class BaseService {
context = {},
config = {},
namedParams = {},
queryParams = {},
queryParams = {}
) {
trace.logTrace('inbound', emojic.womanCook, 'Service class', this.name)
trace.logTrace('inbound', emojic.ticket, 'Named params', namedParams)
@@ -425,13 +409,13 @@ class BaseService {
traceSuccessMessage: 'Query params after validation',
},
queryParams,
queryParamSchema,
queryParamSchema
)
trace.logTrace(
'inbound',
emojic.crayon,
'Query params after validation',
queryParams,
queryParams
)
} catch (error) {
serviceError = error
@@ -445,7 +429,7 @@ class BaseService {
try {
serviceData = await serviceInstance.handle(
namedParams,
transformedQueryParams,
transformedQueryParams
)
serviceInstance._validateServiceData(serviceData)
} catch (error) {
@@ -470,7 +454,7 @@ class BaseService {
librariesIoApiProvider,
metricInstance,
},
serviceConfig,
serviceConfig
) {
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
const { regex, captureNames } = prepareRoute(this.route)
@@ -498,14 +482,14 @@ class BaseService {
},
serviceConfig,
namedParams,
queryParams,
queryParams
)
const badgeData = coalesceBadge(
queryParams,
serviceData,
this.defaultBadgeData,
this,
this
)
// The final capture group is the extension.
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
@@ -514,7 +498,7 @@ class BaseService {
metricHandle.noteResponseSent()
},
cacheLength: this._cacheLength,
}),
})
)
}
}

View File

@@ -72,8 +72,8 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: '!' },
),
{ queryParamA: '!' }
)
).to.deep.equal({
message: 'Hello namedParamA: bar.bar.bar with queryParamA: !',
})
@@ -85,8 +85,8 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: ['foo', 'bar'] },
),
{ queryParamA: ['foo', 'bar'] }
)
).to.deep.equal({
color: 'red',
isError: true,
@@ -97,13 +97,13 @@ describe('BaseService', function () {
describe('Required overrides', function () {
it('Should throw if render() is not overridden', function () {
expect(() => BaseService.render()).to.throw(
/^render\(\) function not implemented for BaseService$/,
/^render\(\) function not implemented for BaseService$/
)
})
it('Should throw if route is not overridden', function () {
return expect(BaseService.invoke({}, {}, {})).to.be.rejectedWith(
/^Route not defined for BaseService$/,
/^Route not defined for BaseService$/
)
})
@@ -112,13 +112,13 @@ describe('BaseService', function () {
}
it('Should throw if handle() is not overridden', function () {
return expect(WithRoute.invoke({}, {}, {})).to.be.rejectedWith(
/^Handler not implemented for WithRoute$/,
/^Handler not implemented for WithRoute$/
)
})
it('Should throw if category is not overridden', function () {
expect(() => BaseService.category).to.throw(
/^Category not set for BaseService$/,
/^Category not set for BaseService$/
)
})
})
@@ -135,25 +135,25 @@ describe('BaseService', function () {
{},
defaultConfig,
{ namedParamA: 'bar.bar.bar' },
{ queryParamA: '!' },
{ queryParamA: '!' }
)
expect(trace.logTrace).to.be.calledWithMatch(
'inbound',
sinon.match.string,
'Service class',
'DummyService',
'DummyService'
)
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Named params',
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Query params after validation',
{ queryParamA: '!' },
{ queryParamA: '!' }
)
})
})
@@ -171,7 +171,7 @@ describe('BaseService', function () {
const serviceData = await LinkService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect(serviceData).to.deep.equal({
@@ -194,7 +194,7 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect.fail('Expected to throw')
} catch (e) {
@@ -212,7 +212,7 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: false },
{ namedParamA: 'bar.bar.bar' },
{ namedParamA: 'bar.bar.bar' }
)
expect.fail('Expected to throw')
} catch (e) {
@@ -233,8 +233,8 @@ describe('BaseService', function () {
await ThrowingService.invoke(
{},
{ handleInternalErrors: true },
{ namedParamA: 'bar.bar.bar' },
),
{ namedParamA: 'bar.bar.bar' }
)
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -251,7 +251,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'red',
@@ -266,7 +266,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -281,7 +281,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -296,7 +296,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'lightgray',
@@ -311,7 +311,7 @@ describe('BaseService', function () {
}
}
expect(
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' }),
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
).to.deep.equal({
isError: true,
color: 'red',
@@ -336,7 +336,7 @@ describe('BaseService', function () {
mockHandleRequest = sinon.spy()
DummyService.register(
{ camp: mockCamp, handleRequest: mockHandleRequest },
defaultConfig,
defaultConfig
)
})
@@ -413,8 +413,8 @@ describe('BaseService', function () {
expect(() =>
DummyService._validate(
{ requiredString: ['this', "shouldn't", 'work'] },
dummySchema,
),
dummySchema
)
)
.to.throw()
.instanceof(InvalidResponse)
@@ -436,7 +436,7 @@ describe('BaseService', function () {
})
const serviceInstance = new DummyService(
{ requestFetcher },
defaultConfig,
defaultConfig
)
const url = 'some-url'
@@ -453,14 +453,14 @@ describe('BaseService', function () {
`${url}?param1=foobar\n${JSON.stringify(
{ headers: options.headers },
null,
2,
)}`,
2
)}`
)
expect(trace.logTrace).to.be.calledWithMatch(
'fetch',
sinon.match.string,
'Response status code',
200,
200
)
})
@@ -471,7 +471,7 @@ describe('BaseService', function () {
})
const serviceInstance = new DummyService(
{ requestFetcher },
defaultConfig,
defaultConfig
)
try {
@@ -504,17 +504,17 @@ describe('BaseService', function () {
const serviceInstance =
new DummyServiceWithServiceResponseSizeMetricEnabled(
{ requestFetcher, metricHelper },
defaultConfig,
defaultConfig
)
await serviceInstance._request({ url })
expect(await register.getSingleMetricAsString('service_response_bytes'))
.to.contain(
'service_response_bytes_bucket{le="65536",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 0\n',
'service_response_bytes_bucket{le="65536",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 0\n'
)
.and.to.contain(
'service_response_bytes_bucket{le="131072",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 1\n',
'service_response_bytes_bucket{le="131072",category="other",family="undefined",service="dummy_service_with_service_response_size_metric_enabled"} 1\n'
)
})
@@ -529,13 +529,13 @@ describe('BaseService', function () {
})
const serviceInstance = new DummyService(
{ requestFetcher, metricHelper },
defaultConfig,
defaultConfig
)
await serviceInstance._request({ url })
expect(
await register.getSingleMetricAsString('service_response_bytes'),
await register.getSingleMetricAsString('service_response_bytes')
).to.not.contain('service_response_bytes_bucket')
})
})
@@ -565,8 +565,8 @@ describe('BaseService', function () {
},
private: { myci_pass: 'abc123' },
},
{ namedParamA: 'bar.bar.bar' },
),
{ namedParamA: 'bar.bar.bar' }
)
).to.deep.equal({ message: 'The CI password is abc123' })
})
@@ -583,8 +583,8 @@ describe('BaseService', function () {
},
{
namedParamA: 'bar.bar.bar',
},
),
}
)
).to.deep.equal({
color: 'lightgray',
isError: true,

View File

@@ -41,7 +41,7 @@ function coalesceCacheLength({
const cacheLength = coalesce(
serviceOverrideCacheLengthSeconds,
serviceDefaultCacheLengthSeconds,
defaultCacheLengthSeconds,
defaultCacheLengthSeconds
)
// Overrides can apply _more_ caching, but not less. Query param overriding

View File

@@ -125,7 +125,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'no-cache, no-store, must-revalidate',
'no-cache, no-store, must-revalidate'
)
})
@@ -141,7 +141,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'max-age=123, s-maxage=123',
'max-age=123, s-maxage=123'
)
})
@@ -156,7 +156,7 @@ describe('Cache header functions', function () {
it('sets the expected fields', function () {
const expectedFields = ['date', 'cache-control', 'expires']
expectedFields.forEach(field =>
expect(res._headers[field]).to.equal(undefined),
expect(res._headers[field]).to.equal(undefined)
)
setCacheHeaders({
@@ -169,7 +169,7 @@ describe('Cache header functions', function () {
expectedFields.forEach(field =>
expect(res._headers[field])
.to.be.a('string')
.and.have.lengthOf.at.least(1),
.and.have.lengthOf.at.least(1)
)
})
})
@@ -181,7 +181,7 @@ describe('Cache header functions', function () {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
`max-age=${24 * 3600}, s-maxage=${24 * 3600}`,
`max-age=${24 * 3600}, s-maxage=${24 * 3600}`
)
})
@@ -190,7 +190,7 @@ describe('Cache header functions', function () {
expect(new Date(lastModified)).to.be.withinTime(
// Within the last 60 seconds.
new Date(Date.now() - 60 * 1000),
new Date(),
new Date()
)
})
})
@@ -221,7 +221,7 @@ describe('Cache header functions', function () {
})
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(false)
})
},
}
)
context(
'when the If-Modified-Since header is after the process started',
@@ -233,7 +233,7 @@ describe('Cache header functions', function () {
})
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(true)
})
},
}
)
})
})

View File

@@ -13,7 +13,7 @@ export default function checkErrorResponse(httpErrors = {}) {
error = new NotFound({ prettyMessage: httpErrors[404] })
} else if (res.statusCode !== 200) {
const underlying = Error(
`Got status code ${res.statusCode} (expected 200)`,
`Got status code ${res.statusCode} (expected 200)`
)
const props = { underlyingError: underlying }
if (httpErrors[res.statusCode] !== undefined) {

View File

@@ -56,7 +56,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)',
'Invalid Response: Got status code 429 (expected 200)'
)
expect(e.prettyMessage).to.equal('rate limited by upstream service')
expect(e.response).to.equal(res)
@@ -72,10 +72,10 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)',
'Invalid Response: Got status code 429 (expected 200)'
)
expect(e.prettyMessage).to.equal(
"terribly sorry but that's one too many requests",
"terribly sorry but that's one too many requests"
)
}
})
@@ -90,7 +90,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 499 (expected 200)',
'Invalid Response: Got status code 499 (expected 200)'
)
expect(e.prettyMessage).to.equal('invalid')
expect(e.response).to.equal(res)
@@ -106,7 +106,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 403 (expected 200)',
'Invalid Response: Got status code 403 (expected 200)'
)
expect(e.prettyMessage).to.equal('access denied')
}
@@ -122,7 +122,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(Inaccessible)
expect(e.message).to.equal(
'Inaccessible: Got status code 503 (expected 200)',
'Inaccessible: Got status code 503 (expected 200)'
)
expect(e.prettyMessage).to.equal('inaccessible')
expect(e.response).to.equal(res)
@@ -138,7 +138,7 @@ describe('async error handler', function () {
} catch (e) {
expect(e).to.be.an.instanceof(Inaccessible)
expect(e.message).to.equal(
'Inaccessible: Got status code 500 (expected 200)',
'Inaccessible: Got status code 500 (expected 200)'
)
expect(e.prettyMessage).to.equal('server overloaded')
}

View File

@@ -36,7 +36,7 @@ export default function coalesceBadge(
serviceData,
// These two parameters were kept separate to make tests clearer.
defaultBadgeData,
{ category, _cacheLength: defaultCacheSeconds } = {},
{ category, _cacheLength: defaultCacheSeconds } = {}
) {
// The "overrideX" naming is based on services that provide badge
// parameters themselves, which can be overridden by a query string
@@ -141,7 +141,7 @@ export default function coalesceBadge(
} else {
namedLogo = coalesce(
serviceNamedLogo,
style === 'social' ? defaultNamedLogo : undefined,
style === 'social' ? defaultNamedLogo : undefined
)
namedLogoColor = coalesce(overrideLogoColor, serviceLogoColor)
}
@@ -166,13 +166,13 @@ export default function coalesceBadge(
isError ? undefined : overrideColor,
serviceColor,
defaultColor,
'lightgrey',
'lightgrey'
),
labelColor: coalesce(
// In case of an error, disregard user's color override.
isError ? undefined : overrideLabelColor,
serviceLabelColor,
defaultLabelColor,
defaultLabelColor
),
style,
namedLogo,

View File

@@ -25,7 +25,7 @@ describe('coalesceBadge', function () {
it('overrides the label', function () {
expect(
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {}),
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {})
).to.include({ label: 'purr count' })
})
})
@@ -54,11 +54,11 @@ describe('coalesceBadge', function () {
it('overrides the color', function () {
expect(
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {}),
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {})
).to.include({ color: '10ADED' })
// also expected for legacy name
expect(
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {}),
coalesceBadge({ colorB: 'B0ADED' }, { color: 'red' }, {})
).to.include({ color: 'B0ADED' })
})
@@ -68,16 +68,16 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ color: '10ADED' },
{ isError: true, color: 'lightgray' },
{},
),
{}
)
).to.include({ color: 'lightgray' })
// also expected for legacy name
expect(
coalesceBadge(
{ colorB: 'B0ADED' },
{ isError: true, color: 'lightgray' },
{},
),
{}
)
).to.include({ color: 'lightgray' })
})
})
@@ -102,11 +102,11 @@ describe('coalesceBadge', function () {
it('overrides the label color', function () {
expect(
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {}),
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {})
).to.include({ labelColor: '42f483' })
// also expected for legacy name
expect(
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {}),
coalesceBadge({ colorA: 'B2f483' }, { color: 'green' }, {})
).to.include({ labelColor: 'B2f483' })
})
@@ -116,8 +116,8 @@ describe('coalesceBadge', function () {
// Scoutcamp converts numeric query params to numbers.
{ color: 123 },
{ color: 'green' },
{},
),
{}
)
).to.include({ color: '123' })
// also expected for legacy name
expect(
@@ -125,8 +125,8 @@ describe('coalesceBadge', function () {
// Scoutcamp converts numeric query params to numbers.
{ colorB: 123 },
{ color: 'green' },
{},
),
{}
)
).to.include({ color: '123' })
})
})
@@ -140,7 +140,7 @@ describe('coalesceBadge', function () {
it('when a social badge, uses the default named logo', function () {
// .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
expect(
coalesceBadge({ style: 'social' }, {}, { namedLogo: 'appveyor' }).logo,
coalesceBadge({ style: 'social' }, {}, { namedLogo: 'appveyor' }).logo
).to.equal(getSimpleIcon({ name: 'appveyor' })).and.not.be.empty
})
@@ -149,28 +149,28 @@ describe('coalesceBadge', function () {
namedLogo: 'npm',
})
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).logo).to.equal(
getShieldsIcon({ name: 'npm' }),
getShieldsIcon({ name: 'npm' })
).and.not.to.be.empty
})
it('applies the named monochrome logo with color', function () {
expect(
coalesceBadge({}, { namedLogo: 'dependabot', logoColor: 'blue' }, {})
.logo,
.logo
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
.to.be.empty
})
it('applies the named multicolored logo with color', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoColor: 'blue' }, {}).logo,
coalesceBadge({}, { namedLogo: 'npm', logoColor: 'blue' }, {}).logo
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.to.be
.empty
})
it('overrides the logo', function () {
expect(
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo,
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
})
@@ -179,8 +179,8 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ logo: 'dependabot', logoColor: 'blue' },
{ namedLogo: 'appveyor' },
{},
).logo,
{}
).logo
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
.be.empty
})
@@ -190,8 +190,8 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ logo: 'npm', logoColor: 'blue' },
{ namedLogo: 'appveyor' },
{},
).logo,
{}
).logo
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
})
@@ -205,8 +205,8 @@ describe('coalesceBadge', function () {
logoPosition: -3,
logoWidth: 100,
},
{},
).logo,
{}
).logo
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
})
@@ -215,8 +215,8 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ logoColor: 'blue' },
{ namedLogo: 'dependabot', logoColor: 'red' },
{},
).logo,
{}
).logo
).to.equal(getShieldsIcon({ name: 'dependabot', color: 'blue' })).and.not
.be.empty
})
@@ -226,8 +226,8 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ logoColor: 'blue' },
{ namedLogo: 'npm', logoColor: 'red' },
{},
).logo,
{}
).logo
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
})
@@ -235,7 +235,7 @@ describe('coalesceBadge', function () {
it('overrides logoSvg', function () {
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
expect(coalesceBadge({ logo: 'npm' }, { logoSvg }, {}).logo).to.equal(
getShieldsIcon({ name: 'npm' }),
getShieldsIcon({ name: 'npm' })
).and.not.be.empty
})
})
@@ -244,7 +244,7 @@ describe('coalesceBadge', function () {
it('overrides the logo with custom svg', function () {
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
expect(
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {}),
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {})
).to.include({ logo: logoSvg })
})
@@ -254,8 +254,8 @@ describe('coalesceBadge', function () {
coalesceBadge(
{ logo: logoSvg, logoColor: 'brightgreen' },
{ namedLogo: 'appveyor' },
{},
),
{}
)
).to.include({ logo: logoSvg })
})
})
@@ -269,7 +269,7 @@ describe('coalesceBadge', function () {
it('applies the logo width', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {}),
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {})
).to.include({ logoWidth: 275 })
})
})
@@ -283,7 +283,7 @@ describe('coalesceBadge', function () {
it('applies the logo position', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {}),
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {})
).to.include({ logoPosition: -10 })
})
})
@@ -296,8 +296,8 @@ describe('coalesceBadge', function () {
{
link: 'https://circleci.com/workflow-run/184ef3de-4836-4805-a2e4-0ceba099f92d',
},
{},
).links,
{}
).links
).to.deep.equal(['https://circleci.com/gh/badges/daily-tests'])
})
})
@@ -328,7 +328,7 @@ describe('coalesceBadge', function () {
describe('Cache length', function () {
it('overrides the cache length', function () {
expect(
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {}),
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {})
).to.include({ cacheLengthSeconds: 123 })
})
})

View File

@@ -20,7 +20,7 @@ function deprecatedService(attrs) {
const { route, name, label, category, examples, message } = Joi.attempt(
attrs,
attrSchema,
`Deprecated service for ${attrs.route.base}`,
`Deprecated service for ${attrs.route.base}`
)
return class DeprecatedService extends BaseService {

View File

@@ -6,7 +6,7 @@ import { makeFullUrl } from './route.js'
const optionalObjectOfKeyValues = Joi.object().pattern(
/./,
Joi.string().allow(null),
Joi.string().allow(null)
)
const schema = Joi.object({
@@ -32,19 +32,19 @@ function validateExample(example, index, ServiceClass) {
const result = Joi.attempt(
example,
schema,
`Example for ${ServiceClass.name} at index ${index}`,
`Example for ${ServiceClass.name} at index ${index}`
)
const { pattern, namedParams } = result
if (!pattern && !ServiceClass.route.pattern) {
throw new Error(
`Example for ${ServiceClass.name} at index ${index} does not declare a pattern`,
`Example for ${ServiceClass.name} at index ${index} does not declare a pattern`
)
}
if (pattern === ServiceClass.route.pattern) {
throw new Error(
`Example for ${ServiceClass.name} at index ${index} declares a redundant pattern which should be removed`,
`Example for ${ServiceClass.name} at index ${index} declares a redundant pattern which should be removed`
)
}
@@ -57,7 +57,7 @@ function validateExample(example, index, ServiceClass) {
throw Error(
`In example for ${
ServiceClass.name
} at index ${index}, ${e.message.toLowerCase()}`,
} at index ${index}, ${e.message.toLowerCase()}`
)
}
// Make sure there are no extra keys.
@@ -73,8 +73,8 @@ function validateExample(example, index, ServiceClass) {
`In example for ${
ServiceClass.name
} at index ${index}, namedParams contains unknown keys: ${extraKeys.join(
', ',
)}`,
', '
)}`
)
}
@@ -86,22 +86,22 @@ function validateExample(example, index, ServiceClass) {
`In example for ${
ServiceClass.name
} at index ${index}, keywords contains words that are less than two characters long: ${tinyKeywords.join(
', ',
)}`,
', '
)}`
)
}
// Make sure none of the keywords are already included in the title.
const title = (example.title || ServiceClass.name).toLowerCase()
const redundantKeywords = example.keywords.filter(k =>
title.includes(k.toLowerCase()),
title.includes(k.toLowerCase())
)
if (redundantKeywords.length) {
throw Error(
`In example for ${
ServiceClass.name
} at index ${index}, keywords contains words that are already in the title: ${redundantKeywords.join(
', ',
)}`,
', '
)}`
)
}
}
@@ -126,7 +126,7 @@ function transformExample(inExample, index, ServiceClass) {
{},
staticPreview,
ServiceClass.defaultBadgeData,
ServiceClass,
ServiceClass
)
const category = categories.find(c => c.id === ServiceClass.category)
@@ -135,7 +135,7 @@ function transformExample(inExample, index, ServiceClass) {
example: {
pattern: makeFullUrl(
ServiceClass.route.base,
pattern || ServiceClass.route.pattern,
pattern || ServiceClass.route.pattern
),
namedParams,
queryParams,

View File

@@ -16,7 +16,7 @@ describe('validateExample function', function () {
validExamples.forEach(example => {
expect(() =>
validateExample(example, 0, { route: {}, name: 'mockService' }),
validateExample(example, 0, { route: {}, name: 'mockService' })
).not.to.throw(Error)
})
})
@@ -66,7 +66,7 @@ describe('validateExample function', function () {
invalidExamples.forEach(example => {
expect(() =>
validateExample(example, 0, { route: {}, name: 'mockService' }),
validateExample(example, 0, { route: {}, name: 'mockService' })
).to.throw(Error)
})
})
@@ -93,7 +93,7 @@ test(transformExample, function () {
keywords: ['hello'],
},
0,
ExampleService,
ExampleService
).expect({
title: 'ExampleService',
example: {
@@ -119,7 +119,7 @@ test(transformExample, function () {
keywords: ['hello'],
},
0,
ExampleService,
ExampleService
).expect({
title: 'ExampleService',
example: {
@@ -146,7 +146,7 @@ test(transformExample, function () {
keywords: ['hello'],
},
0,
ExampleService,
ExampleService
).expect({
title: 'ExampleService',
example: {

View File

@@ -21,7 +21,7 @@ describe('got wrapper', function () {
.reply(200, 'x'.repeat(101))
const sendRequest = _fetchFactory(100)
return expect(
sendRequest('https://www.google.com/foo/bar'),
sendRequest('https://www.google.com/foo/bar')
).to.be.rejectedWith(InvalidResponse, 'Maximum response size exceeded')
})
@@ -29,7 +29,7 @@ describe('got wrapper', function () {
nock('https://www.google.com').get('/foo/bar').replyWithError('oh no')
const sendRequest = _fetchFactory(1024)
return expect(
sendRequest('https://www.google.com/foo/bar'),
sendRequest('https://www.google.com/foo/bar')
).to.be.rejectedWith(Inaccessible, 'oh no')
})
@@ -38,10 +38,10 @@ describe('got wrapper', function () {
nock.disableNetConnect()
const sendRequest = _fetchFactory(1024)
return expect(
sendRequest('https://www.google.com/foo/bar'),
sendRequest('https://www.google.com/foo/bar')
).to.be.rejectedWith(
Inaccessible,
'Nock: Disallowed net connect for "www.google.com:443/foo/bar"',
'Nock: Disallowed net connect for "www.google.com:443/foo/bar"'
)
})
@@ -57,18 +57,18 @@ describe('got wrapper', function () {
prettyMessage: 'Oh no! A terrible thing has happened',
cacheSeconds: 10,
},
},
),
}
)
)
.to.be.rejectedWith(
Inaccessible,
"Inaccessible: Timeout awaiting 'request' for 1ms",
"Inaccessible: Timeout awaiting 'request' for 1ms"
)
// eslint-disable-next-line promise/prefer-await-to-then
.then(error => {
expect(error).to.have.property(
'prettyMessage',
'Oh no! A terrible thing has happened',
'Oh no! A terrible thing has happened'
)
expect(error).to.have.property('cacheSeconds', 10)
})

View File

@@ -9,16 +9,18 @@ describe('mergeQueries function', function () {
it('merges valid gql queries', function () {
expect(
print(
mergeQueries(gql`
query ($param: String!) {
foo(param: $param) {
bar
mergeQueries(
gql`
query ($param: String!) {
foo(param: $param) {
bar
}
}
}
`),
),
`
)
)
).to.equalIgnoreSpaces(
'query ($param: String!) { foo(param: $param) { bar } }',
'query ($param: String!) { foo(param: $param) { bar } }'
)
expect(
@@ -35,11 +37,11 @@ describe('mergeQueries function', function () {
query {
baz
}
`,
),
),
`
)
)
).to.equalIgnoreSpaces(
'query ($param: String!) { foo(param: $param) { bar } baz }',
'query ($param: String!) { foo(param: $param) { bar } baz }'
)
expect(
@@ -59,9 +61,9 @@ describe('mergeQueries function', function () {
query {
baz
}
`,
),
),
`
)
)
).to.equalIgnoreSpaces('{ foo bar baz }')
expect(
@@ -76,9 +78,9 @@ describe('mergeQueries function', function () {
{
bar
}
`,
),
),
`
)
)
).to.equalIgnoreSpaces('{ foo bar }')
})

View File

@@ -15,7 +15,6 @@ import {
Deprecated,
ImproperlyConfigured,
} from './errors.js'
import { pathParam, pathParams, queryParam, queryParams } from './openapi.js'
export {
BaseService,
@@ -33,8 +32,4 @@ export {
InvalidParameter,
ImproperlyConfigured,
Deprecated,
pathParam,
pathParams,
queryParam,
queryParams,
}

View File

@@ -104,7 +104,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
const badgeData = coalesceBadge(
filteredQueryParams,
{ label: 'vendor', message: 'unresponsive' },
{},
{}
)
const svg = makeBadge(badgeData)
const extension = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
@@ -126,7 +126,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
const svg = makeBadge(badgeData)
setCacheHeadersOnResponse(ask.res, badgeData.cacheLengthSeconds)
makeSend(format, ask.res, end)(svg)
},
}
)
// eslint-disable-next-line promise/prefer-await-to-then
if (result && result.catch) {

View File

@@ -18,7 +18,7 @@ function fakeHandler(queryParams, match, sendBadge, request) {
label: 'testing',
message: someValue,
},
{},
{}
)
sendBadge(format, badgeData)
}
@@ -35,7 +35,7 @@ function createFakeHandlerWithCacheLength(cacheLengthSeconds) {
{},
{
_cacheLength: cacheLengthSeconds,
},
}
)
sendBadge(format, badgeData)
}
@@ -66,7 +66,7 @@ describe('The request handler', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, { handler: fakeHandler }),
handleRequest(standardCacheHeaders, { handler: fakeHandler })
)
})
@@ -90,7 +90,7 @@ describe('The request handler', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, fakeHandler),
handleRequest(standardCacheHeaders, fakeHandler)
)
})
@@ -119,8 +119,8 @@ describe('The request handler', function () {
cacheHeaderConfig,
(queryParams, match, sendBadge, request) => {
fakeHandler(queryParams, match, sendBadge, request)
},
),
}
)
)
}
@@ -128,7 +128,7 @@ describe('The request handler', function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
const { headers } = await got(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(headers.date) + 900000,
+new Date(headers.date) + 900000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=900, s-maxage=900')
@@ -142,7 +142,7 @@ describe('The request handler', function () {
const { headers } = await got(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(headers.date) + 900000,
+new Date(headers.date) + 900000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=900, s-maxage=900')
@@ -158,10 +158,10 @@ describe('The request handler', function () {
queryParams,
match,
sendBadge,
request,
request
)
},
),
}
)
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
@@ -178,10 +178,10 @@ describe('The request handler', function () {
queryParams,
match,
sendBadge,
request,
request
)
},
),
}
)
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
@@ -191,10 +191,10 @@ describe('The request handler', function () {
it('should set the expires header to current time + cacheSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const { headers } = await got(
`${baseUrl}/testing/123.json?cacheSeconds=3600`,
`${baseUrl}/testing/123.json?cacheSeconds=3600`
)
const expectedExpiry = new Date(
+new Date(headers.date) + 3600000,
+new Date(headers.date) + 3600000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=3600, s-maxage=3600')
@@ -203,10 +203,10 @@ describe('The request handler', function () {
it('should ignore cacheSeconds when shorter than defaultCacheLengthSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 600 } })
const { headers } = await got(
`${baseUrl}/testing/123.json?cacheSeconds=300`,
`${baseUrl}/testing/123.json?cacheSeconds=300`
)
const expectedExpiry = new Date(
+new Date(headers.date) + 600000,
+new Date(headers.date) + 600000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=600, s-maxage=600')
@@ -217,7 +217,7 @@ describe('The request handler', function () {
const { headers } = await got(`${baseUrl}/testing/123.json`)
expect(headers.expires).to.equal(headers.date)
expect(headers['cache-control']).to.equal(
'no-cache, no-store, must-revalidate',
'no-cache, no-store, must-revalidate'
)
})
})
@@ -234,7 +234,7 @@ describe('The request handler', function () {
++handlerCallCount
fakeHandler(queryParams, match, sendBadge, request)
},
}),
})
)
})
@@ -242,7 +242,7 @@ describe('The request handler', function () {
await performTwoRequests(
baseUrl,
'/testing/123.svg?foo=1',
'/testing/123.svg?foo=2',
'/testing/123.svg?foo=2'
)
expect(handlerCallCount).to.equal(2)
})

View File

@@ -10,7 +10,7 @@ const serviceDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'services',
'services'
)
function toUnixPath(path) {
@@ -51,14 +51,14 @@ async function loadServiceClasses(servicePaths) {
const serviceClasses = []
for await (const servicePath of servicePaths) {
const currentServiceClasses = Object.values(
await import(`file://${servicePath}`),
await import(`file://${servicePath}`)
).flatMap(element =>
typeof element === 'object' ? Object.values(element) : element,
typeof element === 'object' ? Object.values(element) : element
)
if (currentServiceClasses.length === 0) {
throw new InvalidService(
`Expected ${servicePath} to export a service or a collection of services`,
`Expected ${servicePath} to export a service or a collection of services`
)
}
currentServiceClasses.forEach(serviceClass => {
@@ -71,7 +71,7 @@ async function loadServiceClasses(servicePaths) {
return serviceClasses.push(serviceClass)
}
throw new InvalidService(
`Expected ${servicePath} to export a service or a collection of services; one of them was ${serviceClass}`,
`Expected ${servicePath} to export a service or a collection of services; one of them was ${serviceClass}`
)
})
}
@@ -80,20 +80,8 @@ async function loadServiceClasses(servicePaths) {
serviceClasses.map(({ name }) => name),
{
message: 'Duplicate service names found',
},
)
const routeSummaries = []
serviceClasses.forEach(function (serviceClass) {
if (serviceClass.openApi) {
for (const route of Object.values(serviceClass.openApi)) {
routeSummaries.push(route.get.summary)
}
}
})
assertNamesUnique(routeSummaries, {
message: 'Duplicate route summary found',
})
)
return serviceClasses
}
@@ -114,8 +102,8 @@ async function collectDefinitions() {
async function loadTesters() {
return Promise.all(
getServicePaths('*.tester.js').map(
async path => await import(`file://${path}`),
),
async path => await import(`file://${path}`)
)
)
}

View File

@@ -12,56 +12,50 @@ chai.use(chaiAsPromised)
const { expect } = chai
const fixturesDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'loader-test-fixtures',
'loader-test-fixtures'
)
describe('loadServiceClasses function', function () {
it('throws if module exports empty', async function () {
await expect(
loadServiceClasses([
path.join(fixturesDir, 'empty-undefined.fixture.js'),
]),
loadServiceClasses([path.join(fixturesDir, 'empty-undefined.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([path.join(fixturesDir, 'empty-array.fixture.js')]),
loadServiceClasses([path.join(fixturesDir, 'empty-array.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([path.join(fixturesDir, 'empty-object.fixture.js')]),
loadServiceClasses([path.join(fixturesDir, 'empty-object.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'empty-no-export.fixture.js'),
]),
loadServiceClasses([path.join(fixturesDir, 'empty-no-export.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'valid-array.fixture.js'),
path.join(fixturesDir, 'valid-class.fixture.js'),
path.join(fixturesDir, 'empty-array.fixture.js'),
]),
])
).to.be.rejectedWith(InvalidService)
})
it('throws if module exports invalid', async function () {
await expect(
loadServiceClasses([
path.join(fixturesDir, 'invalid-no-base.fixture.js'),
]),
loadServiceClasses([path.join(fixturesDir, 'invalid-no-base.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'invalid-wrong-base.fixture.js'),
]),
])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([path.join(fixturesDir, 'invalid-mixed.fixture.js')]),
loadServiceClasses([path.join(fixturesDir, 'invalid-mixed.fixture.js')])
).to.be.rejectedWith(InvalidService)
await expect(
loadServiceClasses([
path.join(fixturesDir, 'valid-array.fixture.js'),
path.join(fixturesDir, 'valid-class.fixture.js'),
path.join(fixturesDir, 'invalid-no-base.fixture.js'),
]),
])
).to.be.rejectedWith(InvalidService)
})
@@ -71,7 +65,7 @@ describe('loadServiceClasses function', function () {
path.join(fixturesDir, 'valid-array.fixture.js'),
path.join(fixturesDir, 'valid-object.fixture.js'),
path.join(fixturesDir, 'valid-class.fixture.js'),
]),
])
).to.eventually.have.length(5)
})
})

View File

@@ -1,9 +1,3 @@
/**
* Functions for publishing the shields.io URL schema as an OpenAPI Document
*
* @module
*/
const baseUrl = process.env.BASE_URL
const globalParamRefs = [
{ $ref: '#/components/parameters/style' },
@@ -164,7 +158,7 @@ function examples2openapi(examples) {
const parameters = [
...pathParams,
...Object.entries(queryParams).map(([paramName, exampleValue]) =>
param2openapi(pattern, paramName, exampleValue, 'query'),
param2openapi(pattern, paramName, exampleValue, 'query')
),
...globalParamRefs,
]
@@ -204,7 +198,7 @@ function services2openapi(services) {
if (service.openApi) {
// if the service declares its own OpenAPI definition, use that...
for (const [key, value] of Object.entries(
addGlobalProperties(service.openApi),
addGlobalProperties(service.openApi)
)) {
if (key in paths) {
throw new Error(`Conflicting route: ${key}`)
@@ -214,7 +208,7 @@ function services2openapi(services) {
} else {
// ...otherwise do our best to build one from examples[]
for (const [key, value] of Object.entries(
examples2openapi(service.examples),
examples2openapi(service.examples)
)) {
// allow conflicting routes for legacy examples
paths[key] = value
@@ -253,7 +247,7 @@ function category2openapi(category, services) {
in: 'query',
required: false,
description:
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository. <a href="/docs/logos">Further info</a>.',
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository.',
schema: {
type: 'string',
},
@@ -338,132 +332,4 @@ function category2openapi(category, services) {
return spec
}
/**
* Helper function for assembling an OpenAPI path parameter object
*
* @param {module:core/base-service/openapi~PathParamInput} param Input param
* @returns {module:core/base-service/openapi~OpenApiParam} OpenAPI Parameter Object
* @see https://swagger.io/specification/#parameter-object
*/
function pathParam({
name,
example,
schema = { type: 'string' },
description,
}) {
return { name, in: 'path', required: true, schema, example, description }
}
/**
* Helper function for assembling an array of OpenAPI path parameter objects
* The code
* ```
* const params = pathParams(
* { name: 'name1', example: 'example1' },
* { name: 'name2', example: 'example2' },
* )
* ```
* is equivilent to
* ```
* const params = [
* pathParam({ name: 'name1', example: 'example1' }),
* pathParam({ name: 'name2', example: 'example2' }),
* ]
* ```
*
* @param {...module:core/base-service/openapi~PathParamInput} params Input params
* @returns {Array.<module:core/base-service/openapi~OpenApiParam>} Array of OpenAPI Parameter Objects
* @see {@link module:core/base-service/openapi~pathParam}
*/
function pathParams(...params) {
return params.map(param => pathParam(param))
}
/**
* Helper function for assembling an OpenAPI query parameter object
*
* @param {module:core/base-service/openapi~QueryParamInput} param Input param
* @returns {module:core/base-service/openapi~OpenApiParam} OpenAPI Parameter Object
* @see https://swagger.io/specification/#parameter-object
*/
function queryParam({
name,
example,
schema = { type: 'string' },
required = false,
description,
}) {
const param = { name, in: 'query', required, schema, example, description }
if (example === null && schema.type === 'boolean') {
param.allowEmptyValue = true
}
return param
}
/**
* Helper function for assembling an array of OpenAPI query parameter objects
* The code
* ```
* const params = queryParams(
* { name: 'name1', example: 'example1' },
* { name: 'name2', example: 'example2' },
* )
* ```
* is equivilent to
* ```
* const params = [
* queryParam({ name: 'name1', example: 'example1' }),
* queryParams({ name: 'name2', example: 'example2' }),
* ]
* ```
*
* @param {...module:core/base-service/openapi~QueryParamInput} params Input params
* @returns {Array.<module:core/base-service/openapi~OpenApiParam>} Array of OpenAPI Parameter Objects
* @see {@link module:core/base-service/openapi~queryParam}
*/
function queryParams(...params) {
return params.map(param => queryParam(param))
}
/**
* @typedef {object} PathParamInput
* @property {string} name The name of the parameter. Parameter names are case sensitive
* @property {string} example Example of a valid value for this parameter
* @property {object} [schema={ type: 'string' }] Parameter schema.
* An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
* specifying the parameter type.
* Normally this should be omitted as all path parameters are strings.
* Use this when we also want to pass an enum of valid parameters
* to be presented as a drop-down in the frontend. e.g:
* `{'type': 'string', 'enum': ['github', 'bitbucket'}` (Optional)
* @property {string} description A brief description of the parameter (Optional)
*/
/**
* @typedef {object} QueryParamInput
* @property {string} name The name of the parameter. Parameter names are case sensitive
* @property {string|null} example Example of a valid value for this parameter
* @property {object} [schema={ type: 'string' }] Parameter schema.
* An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
* specifying the parameter type. This can normally be omitted.
* Query params are usually strings. (Optional)
* @property {boolean} [required=false] Determines whether this parameter is mandatory (Optional)
* @property {string} description A brief description of the parameter (Optional)
*/
/**
* OpenAPI Parameter Object
*
* @typedef {object} OpenApiParam
* @property {string} name The name of the parameter
* @property {string|null} example Example of a valid value for this parameter
* @property {('path'|'query')} in The location of the parameter
* @property {object} schema Parameter schema.
* An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
* specifying the parameter type.
* @property {boolean} required Determines whether this parameter is mandatory
* @property {string} description A brief description of the parameter
* @property {boolean} allowEmptyValue If true, allows the ability to pass an empty value to this parameter
*/
export { category2openapi, pathParam, pathParams, queryParam, queryParams }
export { category2openapi }

View File

@@ -1,11 +1,5 @@
import chai from 'chai'
import {
category2openapi,
pathParam,
pathParams,
queryParam,
queryParams,
} from './openapi.js'
import { category2openapi } from './openapi.js'
import BaseJsonService from './base-json.js'
const { expect } = chai
@@ -98,7 +92,7 @@ const expected = {
in: 'query',
required: false,
description:
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository. <a href="/docs/logos">Further info</a>.',
'One of the named logos (bitcoin, dependabot, gitlab, npm, paypal, serverfault, stackexchange, superuser, telegram, travis) or simple-icons. All simple-icons are referenced using icon slugs. You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository.',
schema: { type: 'string' },
example: 'appveyor',
},
@@ -377,153 +371,8 @@ describe('category2openapi', function () {
category2openapi({ name: 'build' }, [
OpenApiService.getDefinition(),
LegacyService.getDefinition(),
]),
),
])
)
).to.deep.equal(expected)
})
})
describe('pathParam, pathParams', function () {
it('generates a pathParam with defaults', function () {
const input = { name: 'name', example: 'example' }
const expected = {
name: 'name',
in: 'path',
required: true,
schema: {
type: 'string',
},
example: 'example',
description: undefined,
}
expect(pathParam(input)).to.deep.equal(expected)
expect(pathParams(input)[0]).to.deep.equal(expected)
})
it('generates a pathParam with custom args', function () {
const input = {
name: 'name',
example: true,
schema: { type: 'boolean' },
description: 'long desc',
}
const expected = {
name: 'name',
in: 'path',
required: true,
schema: {
type: 'boolean',
},
example: true,
description: 'long desc',
}
expect(pathParam(input)).to.deep.equal(expected)
expect(pathParams(input)[0]).to.deep.equal(expected)
})
it('generates multiple pathParams', function () {
expect(
pathParams(
{ name: 'name1', example: 'example1' },
{ name: 'name2', example: 'example2' },
),
).to.deep.equal([
{
name: 'name1',
in: 'path',
required: true,
schema: {
type: 'string',
},
example: 'example1',
description: undefined,
},
{
name: 'name2',
in: 'path',
required: true,
schema: {
type: 'string',
},
example: 'example2',
description: undefined,
},
])
})
})
describe('queryParam, queryParams', function () {
it('generates a queryParam with defaults', function () {
const input = { name: 'name', example: 'example' }
const expected = {
name: 'name',
in: 'query',
required: false,
schema: { type: 'string' },
example: 'example',
description: undefined,
}
expect(queryParam(input)).to.deep.equal(expected)
expect(queryParams(input)[0]).to.deep.equal(expected)
})
it('generates queryParam with custom args', function () {
const input = {
name: 'name',
example: 'example',
required: true,
description: 'long desc',
}
const expected = {
name: 'name',
in: 'query',
required: true,
schema: { type: 'string' },
example: 'example',
description: 'long desc',
}
expect(queryParam(input)).to.deep.equal(expected)
expect(queryParams(input)[0]).to.deep.equal(expected)
})
it('generates a queryParam with boolean/null example', function () {
const input = { name: 'name', example: null, schema: { type: 'boolean' } }
const expected = {
name: 'name',
in: 'query',
required: false,
schema: { type: 'boolean' },
allowEmptyValue: true,
example: null,
description: undefined,
}
expect(queryParam(input)).to.deep.equal(expected)
expect(queryParams(input)[0]).to.deep.equal(expected)
})
it('generates multiple queryParams', function () {
expect(
queryParams(
{ name: 'name1', example: 'example1' },
{ name: 'name2', example: 'example2' },
),
).to.deep.equal([
{
name: 'name1',
in: 'query',
required: false,
schema: { type: 'string' },
example: 'example1',
description: undefined,
},
{
name: 'name2',
in: 'query',
required: false,
schema: { type: 'string' },
example: 'example2',
description: undefined,
},
])
})
})

View File

@@ -23,7 +23,7 @@ const attrSchema = Joi.object({
.required()
.error(
() =>
'"transformPath" must be a function that transforms named params to a new path',
'"transformPath" must be a function that transforms named params to a new path'
),
transformQueryParams: Joi.func().arity(1),
dateAdded: Joi.date().required(),
@@ -80,7 +80,7 @@ export default function redirector(attrs) {
'inbound',
emojic.arrowHeadingUp,
'Redirector',
route.base,
route.base
)
trace.logTrace('inbound', emojic.ticket, 'Named params', namedParams)
trace.logTrace('inbound', emojic.crayon, 'Query params', queryParams)

View File

@@ -27,7 +27,7 @@ describe('Redirector', function () {
redirector({
...attrs,
name: 'ShinyRedirect',
}).name,
}).name
).to.equal('ShinyRedirect')
})
@@ -41,7 +41,7 @@ describe('Redirector', function () {
it('throws the expected error when dateAdded is missing', function () {
expect(() =>
redirector({ route, category, transformPath }).validateDefinition(),
redirector({ route, category, transformPath }).validateDefinition()
).to.throw('"dateAdded" is required')
})
@@ -93,7 +93,7 @@ describe('Redirector', function () {
})
ServiceClass.register(
{ camp },
{ rasterUrl: 'http://raster.example.test' },
{ rasterUrl: 'http://raster.example.test' }
)
})
@@ -102,7 +102,7 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello-world.svg`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
@@ -114,12 +114,12 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello-world.png`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/new/service/hello-world.png',
'http://raster.example.test/new/service/hello-world.png'
)
})
@@ -128,12 +128,12 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello-world.svg?color=123&style=flat-square`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?color=123&style=flat-square',
'/new/service/hello-world.svg?color=123&style=flat-square'
)
})
@@ -142,12 +142,12 @@ describe('Redirector', function () {
`${baseUrl}/very/old/service/hello%0Dworld.svg?foobar=a%0Db`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello%0Dworld.svg?foobar=a%0Db',
'/new/service/hello%0Dworld.svg?foobar=a%0Db'
)
})
@@ -174,12 +174,12 @@ describe('Redirector', function () {
`${baseUrl}/another/old/service/token/abc123/hello-world.svg`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?token=abc123',
'/new/service/hello-world.svg?token=abc123'
)
})
@@ -188,12 +188,12 @@ describe('Redirector', function () {
`${baseUrl}/another/old/service/token/abc123/hello-world.svg?color=123&style=flat-square`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123',
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123'
)
})
@@ -202,12 +202,12 @@ describe('Redirector', function () {
`${baseUrl}/another/old/service/token/abc123/hello-world.svg?color=123&style=flat-square&token=def456`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123',
'/new/service/hello-world.svg?color=123&style=flat-square&token=abc123'
)
})
@@ -229,12 +229,12 @@ describe('Redirector', function () {
`${baseUrl}/override/service/token/abc123/hello-world.svg?style=flat-square&token=def456`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'/new/service/hello-world.svg?style=flat-square&token=def456',
'/new/service/hello-world.svg?style=flat-square&token=def456'
)
})
})

View File

@@ -40,7 +40,7 @@ async function getCachedResource({
}
const { buffer } = await checkErrorResponse({})(
await requestFetcher(url, options),
await requestFetcher(url, options)
)
let reqData

View File

@@ -52,7 +52,7 @@ function namedParamsForMatch(captureNames = [], match, ServiceClass) {
if (captureNames.length !== captures.length) {
throw new Error(
`Service ${ServiceClass.name} declares incorrect number of named params ` +
`(expected ${captures.length}, got ${captureNames.length})`,
`(expected ${captures.length}, got ${captureNames.length})`
)
}

View File

@@ -86,9 +86,9 @@ describe('Route helpers', function () {
expect(() =>
namedParamsForMatch(captureNames, regex.exec('/foo/bar/baz.svg'), {
name: 'MyService',
}),
})
).to.throw(
'Service MyService declares incorrect number of named params (expected 2, got 1)',
'Service MyService declares incorrect number of named params (expected 2, got 1)'
)
})
@@ -96,14 +96,14 @@ describe('Route helpers', function () {
expect(
getQueryParamNames({
queryParamSchema: Joi.object({ foo: Joi.string() }).required(),
}),
})
).to.deep.equal(['foo'])
expect(
getQueryParamNames({
queryParamSchema: Joi.object({ foo: Joi.string() })
.rename('bar', 'foo', { ignoreUndefined: true, override: true })
.required(),
}),
})
).to.deep.equal(['foo', 'bar'])
})
})

View File

@@ -18,7 +18,7 @@ const serviceDefinition = Joi.object({
Joi.object({
format: Joi.string().required(),
queryParams: arrayOfStrings,
}),
})
),
examples: Joi.array()
.items(
@@ -40,7 +40,7 @@ const serviceDefinition = Joi.object({
documentation: Joi.object({
__html: Joi.string().required(), // Valid HTML.
}),
}),
})
)
.default([]),
openApi: Joi.object().pattern(
@@ -48,7 +48,7 @@ const serviceDefinition = Joi.object({
Joi.object({
get: Joi.object({
summary: Joi.string().required(),
description: Joi.string(),
description: Joi.string().required(),
parameters: Joi.array()
.items(
Joi.object({
@@ -56,23 +56,19 @@ const serviceDefinition = Joi.object({
description: Joi.string(),
in: Joi.string().valid('query', 'path').required(),
required: Joi.boolean().required(),
schema: Joi.object({
type: Joi.string().required(),
enum: Joi.array(),
}).required(),
allowEmptyValue: Joi.boolean(),
example: Joi.string().allow(null),
}),
schema: Joi.object({ type: Joi.string().required() }).required(),
example: Joi.string(),
})
)
.min(1)
.required(),
}).required(),
}).required(),
}).required()
),
}).required()
function assertValidServiceDefinition(service, message = undefined) {
Joi.assert(service, serviceDefinition, message)
function assertValidServiceDefinition(example, message = undefined) {
Joi.assert(example, serviceDefinition, message)
}
const serviceDefinitionExport = Joi.object({
@@ -83,7 +79,7 @@ const serviceDefinitionExport = Joi.object({
id: Joi.string().required(),
name: Joi.string().required(),
keywords: arrayOfStrings,
}),
})
)
.required(),
services: Joi.array().items(serviceDefinition).required(),

View File

@@ -12,7 +12,7 @@ function validate(
allowAndStripUnknownKeys = true,
},
data,
schema,
schema
) {
if (!schema || !Joi.isSchema(schema)) {
throw Error('A Joi schema is required')
@@ -28,7 +28,7 @@ function validate(
'validate',
emojic.womanShrugging,
traceErrorMessage,
error.message,
error.message
)
let prettyMessage = prettyErrorMessage

View File

@@ -49,7 +49,7 @@ describe('validate', function () {
sinon.match.string,
traceSuccessMessage,
{ requiredString: 'bar' },
{ deep: true },
{ deep: true }
)
})
})
@@ -60,13 +60,13 @@ describe('validate', function () {
validate(
options,
{ requiredString: ['this', "shouldn't", 'work'] },
schema,
schema
)
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidParameter)
expect(e.message).to.equal(
'Invalid Parameter: "requiredString" must be a string',
'Invalid Parameter: "requiredString" must be a string'
)
expect(e.prettyMessage).to.equal(prettyErrorMessage)
}
@@ -74,7 +74,7 @@ describe('validate', function () {
'validate',
sinon.match.string,
traceErrorMessage,
'"requiredString" must be a string',
'"requiredString" must be a string'
)
})
@@ -86,16 +86,16 @@ describe('validate', function () {
{
requiredString: ['this', "shouldn't", 'work'],
},
schema,
schema
)
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidParameter)
expect(e.message).to.equal(
'Invalid Parameter: "requiredString" must be a string',
'Invalid Parameter: "requiredString" must be a string'
)
expect(e.prettyMessage).to.equal(
`${prettyErrorMessage}: requiredString`,
`${prettyErrorMessage}: requiredString`
)
}
})
@@ -107,13 +107,13 @@ describe('validate', function () {
validate(
{ ...options, allowAndStripUnknownKeys: false, includeKeys: true },
{ requiredString: 'bar', extra: 'nonsense', more: 'bogus' },
schema,
schema
)
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidParameter)
expect(e.message).to.equal(
'Invalid Parameter: "extra" is not allowed. "more" is not allowed',
'Invalid Parameter: "extra" is not allowed. "more" is not allowed'
)
expect(e.prettyMessage).to.equal(`${prettyErrorMessage}: extra, more`)
}

View File

@@ -27,16 +27,14 @@ export default class InfluxMetrics {
response = await got.post(request)
} catch (error) {
log.error(
new Error(
`Cannot push metrics. Cause: ${error.name}: ${error.message}`,
),
new Error(`Cannot push metrics. Cause: ${error.name}: ${error.message}`)
)
}
if (response && response.statusCode >= 300) {
log.error(
new Error(
`Cannot push metrics. ${request.url} responded with status code ${response.statusCode}`,
),
`Cannot push metrics. ${request.url} responded with status code ${response.statusCode}`
)
)
}
}
@@ -44,7 +42,7 @@ export default class InfluxMetrics {
startPushingMetrics() {
this._intervalId = setInterval(
() => this.sendMetrics(),
this._config.intervalSeconds * 1000,
this._config.intervalSeconds * 1000
)
}

View File

@@ -47,7 +47,7 @@ describe('Influx metrics', function () {
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(await influxMetrics.metrics()).to.be.contain(
'instance=test-hostname',
'instance=test-hostname'
)
})
@@ -69,7 +69,7 @@ describe('Influx metrics', function () {
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(await influxMetrics.metrics()).to.be.contain(
'instance=test-hostname-alias',
'instance=test-hostname-alias'
)
})
})
@@ -96,7 +96,7 @@ describe('Influx metrics', function () {
.persist()
.post(
'/metrics',
'prometheus,application=shields,env=test-env,instance=instance2 counter1=11',
'prometheus,application=shields,env=test-env,instance=instance2 counter1=11'
)
.basicAuth({ user: 'metrics-username', pass: 'metrics-password' })
.reply(200)
@@ -117,7 +117,7 @@ describe('Influx metrics', function () {
await clock.tickAsync(10)
expect(scope.isDone()).to.be.equal(
true,
`pending mocks: ${scope.pendingMocks()}`,
`pending mocks: ${scope.pendingMocks()}`
)
})
})
@@ -150,9 +150,9 @@ describe('Influx metrics', function () {
.and(
sinon.match.has(
'message',
'Cannot push metrics. Cause: RequestError: Nock: Disallowed net connect for "shields-metrics.io:443/metrics"',
),
),
'Cannot push metrics. Cause: RequestError: Nock: Disallowed net connect for "shields-metrics.io:443/metrics"'
)
)
)
})
@@ -167,9 +167,9 @@ describe('Influx metrics', function () {
.and(
sinon.match.has(
'message',
'Cannot push metrics. https://shields-metrics.io/metrics responded with status code 400',
),
),
'Cannot push metrics. https://shields-metrics.io/metrics responded with status code 400'
)
)
)
})
})

View File

@@ -4,7 +4,7 @@ function promClientJsonToInfluxV2(metrics, extraLabels = {}) {
return metrics
.flatMap(metric => {
const valuesByLabels = groupBy(metric.values, value =>
JSON.stringify(Object.entries(value.labels).sort()),
JSON.stringify(Object.entries(value.labels).sort())
)
return Object.values(valuesByLabels).map(metricsWithSameLabel => {
const labels = Object.entries(metricsWithSameLabel[0].labels)

View File

@@ -95,7 +95,7 @@ describe('Metric format converters', function () {
prometheus,le=50 histogram1_bucket=2
prometheus,le=15 histogram1_bucket=2
prometheus,le=5 histogram1_bucket=1
prometheus histogram1_count=3,histogram1_sum=111`),
prometheus histogram1_count=3,histogram1_sum=111`)
)
})
@@ -118,7 +118,7 @@ prometheus histogram1_count=3,histogram1_sum=111`),
prometheus,le=50 histogram1_bucket=2
prometheus,le=15 histogram1_bucket=2
prometheus,le=5 histogram1_bucket=1
prometheus histogram1_count=3,histogram1_sum=111`),
prometheus histogram1_count=3,histogram1_sum=111`)
)
})
@@ -145,7 +145,7 @@ prometheus histogram1_count=3,histogram1_sum=111`),
sortLines(`prometheus,quantile=0.99 summary1=100
prometheus,quantile=0.9 summary1=100
prometheus,quantile=0.1 summary1=1
prometheus summary1_count=3,summary1_sum=111`),
prometheus summary1_count=3,summary1_sum=111`)
)
})
@@ -167,7 +167,7 @@ prometheus summary1_count=3,summary1_sum=111`),
sortLines(`prometheus,quantile=0.99 summary1=100
prometheus,quantile=0.9 summary1=100
prometheus,quantile=0.1 summary1=1
prometheus summary1_count=3,summary1_sum=111`),
prometheus summary1_count=3,summary1_sum=111`)
)
})
@@ -204,7 +204,7 @@ prometheus summary1_count=3,summary1_sum=111`),
})
expect(influx).to.be.equal(
'prometheus,env=production,instance=instance1 counter1=11',
'prometheus,env=production,instance=instance1 counter1=11'
)
})
})

View File

@@ -79,7 +79,7 @@ export default class PrometheusMetrics {
return this.counters.serviceResponseSize.labels(
category,
serviceFamily,
service,
service
)
}
}

View File

@@ -65,11 +65,11 @@ const publicConfigSchema = Joi.object({
bind: {
port: Joi.alternatives().try(
Joi.number().port(),
Joi.string().pattern(/^\\\\\.\\pipe\\.+$/),
Joi.string().pattern(/^\\\\\.\\pipe\\.+$/)
),
address: Joi.alternatives().try(
Joi.string().ip().required(),
Joi.string().hostname().required(),
Joi.string().hostname().required()
),
},
metrics: {
@@ -154,8 +154,8 @@ const publicConfigSchema = Joi.object({
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'public',
),
'public'
)
),
requireCloudflare: Joi.boolean().required(),
}).required()
@@ -236,7 +236,7 @@ class Server {
const publicConfig = Joi.attempt(config.public, publicConfigSchema)
const privateConfig = this.validatePrivateConfig(
config.private,
privateConfigSchema,
privateConfigSchema
)
// We want to require an username and a password for the influx metrics
// only if the influx metrics are enabled. The private config schema
@@ -245,7 +245,7 @@ class Server {
if (publicConfig.metrics.influx && publicConfig.metrics.influx.enabled) {
this.validatePrivateConfig(
config.private,
privateMetricsInfluxConfigSchema,
privateMetricsInfluxConfigSchema
)
}
this.config = {
@@ -270,7 +270,7 @@ class Server {
Object.assign({}, publicConfig.metrics.influx, {
username: privateConfig.influx_username,
password: privateConfig.influx_password,
}),
})
)
}
}
@@ -283,8 +283,8 @@ class Server {
const badPaths = e.details.map(({ path }) => path)
throw Error(
`Private configuration is invalid. Check these paths: ${badPaths.join(
',',
)}`,
','
)}`
)
}
}
@@ -350,35 +350,32 @@ class Server {
makeSend(
'svg',
request.res,
end,
end
)(
makeBadge({
label: '410',
message: `${format} no longer available`,
color: 'lightgray',
format: 'svg',
}),
})
)
})
if (!rasterUrl) {
camp.route(
/^\/((?!img|assets\/)).*\.png$/,
(query, match, end, request) => {
makeSend(
'svg',
request.res,
end,
)(
makeBadge({
label: '404',
message: 'raster badges not available',
color: 'lightgray',
format: 'svg',
}),
)
},
)
camp.route(/^\/((?!img\/)).*\.png$/, (query, match, end, request) => {
makeSend(
'svg',
request.res,
end
)(
makeBadge({
label: '404',
message: 'raster badges not available',
color: 'lightgray',
format: 'svg',
})
)
})
}
camp.notfound(/(\.svg|\.json|)$/, (query, match, end, request) => {
@@ -388,14 +385,14 @@ class Server {
makeSend(
format,
request.res,
end,
end
)(
makeBadge({
label: '404',
message: 'badge not found',
color: 'red',
format,
}),
})
)
})
}
@@ -415,21 +412,18 @@ class Server {
if (rasterUrl) {
// Redirect to the raster server for raster versions of modern badges.
camp.route(
/^\/((?!img|assets\/)).*\.png$/,
(queryParams, match, end, ask) => {
ask.res.statusCode = 301
ask.res.setHeader(
'Location',
rasterRedirectUrl({ rasterUrl }, ask.req.url),
)
camp.route(/^\/((?!img\/)).*\.png$/, (queryParams, match, end, ask) => {
ask.res.statusCode = 301
ask.res.setHeader(
'Location',
rasterRedirectUrl({ rasterUrl }, ask.req.url)
)
const cacheDuration = (30 * 24 * 3600) | 0 // 30 days.
ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`)
const cacheDuration = (30 * 24 * 3600) | 0 // 30 days.
ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`)
ask.res.end()
},
)
ask.res.end()
})
}
if (redirectUrl) {
@@ -465,8 +459,8 @@ class Server {
rasterUrl: config.public.rasterUrl,
private: config.private,
public: config.public,
},
),
}
)
)
}

View File

@@ -19,7 +19,7 @@ describe('The server', function () {
public: {
documentRoot: path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'test-public',
'test-public'
),
},
})
@@ -66,7 +66,7 @@ describe('The server', function () {
it('should return cors header for the request', async function () {
const { statusCode, headers } = await got(
`${baseUrl}badge/foo-bar-blue.svg`,
`${baseUrl}badge/foo-bar-blue.svg`
)
expect(statusCode).to.equal(200)
expect(headers['access-control-allow-origin']).to.equal('*')
@@ -77,11 +77,11 @@ describe('The server', function () {
`${baseUrl}:fruit-apple-green.png`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/:fruit-apple-green.png',
'http://raster.example.test/:fruit-apple-green.png'
)
})
@@ -90,11 +90,11 @@ describe('The server', function () {
`${baseUrl}badge/foo-bar-blue.png`,
{
followRedirect: false,
},
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/badge/foo-bar-blue.png',
'http://raster.example.test/badge/foo-bar-blue.png'
)
})
@@ -105,7 +105,7 @@ describe('The server', function () {
it('should produce SVG badges with expected headers', async function () {
const { statusCode, headers } = await got(
`${baseUrl}:fruit-apple-green.svg`,
`${baseUrl}:fruit-apple-green.svg`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('image/svg+xml;charset=utf-8')
@@ -119,7 +119,7 @@ describe('The server', function () {
it('should produce JSON badges with expected headers', async function () {
const { statusCode, body, headers } = await got(
`${baseUrl}:fruit-apple-green.json`,
`${baseUrl}:fruit-apple-green.json`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('application/json')
@@ -137,7 +137,7 @@ describe('The server', function () {
// https://github.com/badges/shields/pull/1319
it('should not crash with a numeric logo', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?logo=1`,
`${baseUrl}:fruit-apple-green.svg?logo=1`
)
expect(statusCode).to.equal(200)
expect(body)
@@ -148,7 +148,7 @@ describe('The server', function () {
it('should not crash with a numeric link', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=1`,
`${baseUrl}:fruit-apple-green.svg?link=1`
)
expect(statusCode).to.equal(200)
expect(body)
@@ -159,7 +159,7 @@ describe('The server', function () {
it('should not crash with a boolean link', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=true`,
`${baseUrl}:fruit-apple-green.svg?link=true`
)
expect(statusCode).to.equal(200)
expect(body)
@@ -173,7 +173,7 @@ describe('The server', function () {
`${baseUrl}this/is/not/a/badge.svg`,
{
throwHttpErrors: false,
},
}
)
expect(statusCode).to.equal(404)
expect(body)
@@ -187,7 +187,7 @@ describe('The server', function () {
`${baseUrl}this/is/most/definitely/not/a/badge.js`,
{
throwHttpErrors: false,
},
}
)
expect(statusCode).to.equal(404)
expect(body)
@@ -211,7 +211,7 @@ describe('The server', function () {
`${baseUrl}badge/foo-bar-blue.jpg`,
{
throwHttpErrors: false,
},
}
)
// TODO It would be nice if this were 404 or 410.
expect(statusCode).to.equal(200)
@@ -236,7 +236,7 @@ describe('The server', function () {
await server.start()
const { statusCode, body } = await got(
`${server.baseUrl}badge/foo-bar-blue.svg`,
`${server.baseUrl}badge/foo-bar-blue.svg`
)
expect(statusCode).to.be.equal(200)
@@ -365,7 +365,7 @@ describe('The server', function () {
it('should require url when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.url
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.url" is required',
'"metrics.influx.url" is required'
)
})
@@ -378,21 +378,21 @@ describe('The server', function () {
it('should require timeoutMilliseconds when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.timeoutMilliseconds
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.timeoutMilliseconds" is required',
'"metrics.influx.timeoutMilliseconds" is required'
)
})
it('should require intervalSeconds when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.intervalSeconds
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.intervalSeconds" is required',
'"metrics.influx.intervalSeconds" is required'
)
})
it('should require instanceIdFrom when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.instanceIdFrom
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.instanceIdFrom" is required',
'"metrics.influx.instanceIdFrom" is required'
)
})
@@ -400,7 +400,7 @@ describe('The server', function () {
customConfig.public.metrics.influx.instanceIdFrom = 'env-var'
delete customConfig.public.metrics.influx.instanceIdEnvVarName
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.instanceIdEnvVarName" is required',
'"metrics.influx.instanceIdEnvVarName" is required'
)
})
@@ -422,7 +422,7 @@ describe('The server', function () {
it('should require envLabel when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.envLabel
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.envLabel" is required',
'"metrics.influx.envLabel" is required'
)
})
@@ -439,14 +439,14 @@ describe('The server', function () {
it('should require username when influx configuration is enabled', function () {
delete customConfig.private.influx_username
expect(() => new Server(customConfig)).to.throw(
'Private configuration is invalid. Check these paths: influx_username',
'Private configuration is invalid. Check these paths: influx_username'
)
})
it('should require password when influx configuration is enabled', function () {
delete customConfig.private.influx_password
expect(() => new Server(customConfig)).to.throw(
'Private configuration is invalid. Check these paths: influx_password',
'Private configuration is invalid. Check these paths: influx_password'
)
})
@@ -505,7 +505,7 @@ describe('The server', function () {
})
.post(
'/metrics',
/prometheus,application=shields,category=static,env=localhost-env,family=static-badge,instance=test-instance,service=static_badge service_requests_total=1\n/,
/prometheus,application=shields,category=static,env=localhost-env,family=static-badge,instance=test-instance,service=static_badge service_requests_total=1\n/
)
.basicAuth({ user: 'influx-username', pass: 'influx-password' })
.reply(200)
@@ -515,7 +515,7 @@ describe('The server', function () {
expect(scope.isDone()).to.be.equal(
true,
`pending mocks: ${scope.pendingMocks()}`,
`pending mocks: ${scope.pendingMocks()}`
)
})
})

View File

@@ -121,8 +121,8 @@ if (typeof onlyServices === 'undefined' || onlyServices.includes('*****')) {
} else {
console.info(
`Running tests for ${onlyServices.length} services: ${onlyServices.join(
', ',
)}.\n`,
', '
)}.\n`
)
runner.only(onlyServices)
}

View File

@@ -23,7 +23,7 @@ async function createServiceTester() {
const ServiceClass = Object.values(await import(servicePath))[0]
if (!(ServiceClass.prototype instanceof BaseService)) {
throw Error(
`${servicePath} does not export a single service. Invoke new ServiceTester() directly.`,
`${servicePath} does not export a single service. Invoke new ServiceTester() directly.`
)
}
return ServiceTester.forServiceClass(ServiceClass)

View File

@@ -91,11 +91,11 @@ const factory = superclass =>
Joi.attempt(
json[name],
Joi.string().regex(expected),
`${name} mismatch:`,
`${name} mismatch:`
)
} else {
throw new Error(
"'expected' must be a string, a number, a regex, an array or a Joi schema",
"'expected' must be a string, a number, a regex, an array or a Joi schema"
)
}
}

View File

@@ -25,7 +25,7 @@ class Runner {
*/
async prepare() {
this.testers = (await loadTesters()).flatMap(testerModule =>
Object.values(testerModule),
Object.values(testerModule)
)
this.testers.forEach(tester => {
tester.beforeEach = () => {

View File

@@ -10,7 +10,7 @@ describe('Services from PR title', function () {
])
given('[CRAN CPAN CTAN] Add test coverage').expect(['cran', 'cpan', 'ctan'])
given(
'[RFC] Add Joi-based request validation to BaseJsonService and rewrite [NPM] badges',
'[RFC] Add Joi-based request validation to BaseJsonService and rewrite [NPM] badges'
).expect(['npm'])
given('make changes to [CRAN] and [CPAN]').expect(['cran', 'cpan'])
given('[github appveyor ]').expect(['github', 'appveyor'])

View File

@@ -36,7 +36,7 @@ describe('SQL token persistence', function () {
beforeEach('Create temporary table', async function () {
await pool.query(
`CREATE TEMPORARY TABLE ${tableName} (LIKE github_user_tokens INCLUDING ALL);`,
`CREATE TEMPORARY TABLE ${tableName} (LIKE github_user_tokens INCLUDING ALL);`
)
})
afterEach('Drop temporary table', async function () {
@@ -57,7 +57,7 @@ describe('SQL token persistence', function () {
initialTokens.forEach(async token => {
await pool.query(
`INSERT INTO pg_temp.${tableName} (token) VALUES ($1::text);`,
[token],
[token]
)
})
})
@@ -77,7 +77,7 @@ describe('SQL token persistence', function () {
await persistence.noteTokenAdded(newToken)
const result = await pool.query(
`SELECT token FROM pg_temp.${tableName};`,
`SELECT token FROM pg_temp.${tableName};`
)
const savedTokens = result.rows.map(row => row.token)
expect(savedTokens.sort()).to.deep.equal(expected)
@@ -93,7 +93,7 @@ describe('SQL token persistence', function () {
await persistence.noteTokenRemoved(toRemove)
const result = await pool.query(
`SELECT token FROM pg_temp.${tableName};`,
`SELECT token FROM pg_temp.${tableName};`
)
const savedTokens = result.rows.map(row => row.token)
expect(savedTokens.sort()).to.deep.equal(expected)

View File

@@ -28,14 +28,14 @@ export default class SqlTokenPersistence {
async onTokenAdded(token) {
return await this.pool.query(
`INSERT INTO ${this.table} (token) VALUES ($1::text) ON CONFLICT (token) DO NOTHING;`,
[token],
[token]
)
}
async onTokenRemoved(token) {
return await this.pool.query(
`DELETE FROM ${this.table} WHERE token=$1::text;`,
[token],
[token]
)
}

View File

@@ -21,16 +21,16 @@ describe('The token pool', function () {
it('should yield the expected tokens', function () {
ids.forEach(id =>
times(batchSize, () => expect(tokenPool.next().id).to.equal(id)),
times(batchSize, () => expect(tokenPool.next().id).to.equal(id))
)
})
it('should repeat when reaching the end', function () {
ids.forEach(id =>
times(batchSize, () => expect(tokenPool.next().id).to.equal(id)),
times(batchSize, () => expect(tokenPool.next().id).to.equal(id))
)
ids.forEach(id =>
times(batchSize, () => expect(tokenPool.next().id).to.equal(id)),
times(batchSize, () => expect(tokenPool.next().id).to.equal(id))
)
})

View File

@@ -16,7 +16,7 @@ const { fileMatch } = danger.git
const documentation = fileMatch(
'**/*.md',
'frontend/docs/**',
'frontend/src/**',
'frontend/src/**'
)
const server = fileMatch('core/server/**.js', '!*.spec.js')
const serverTests = fileMatch('core/server/**.spec.js')
@@ -33,7 +33,7 @@ message(
[
':sparkles: Thanks for your contribution to Shields, ',
`@${danger.github.pr.user.login}!`,
].join(''),
].join('')
)
const targetBranch = danger.github.pr.base.ref
@@ -48,7 +48,7 @@ if (documentation.edited) {
[
'Thanks for contributing to our documentation. ',
'We :heart: our [documentarians](http://www.writethedocs.org/)!',
].join(''),
].join('')
)
}
@@ -63,7 +63,7 @@ if (server.modified && !serverTests.modified) {
[
'This PR modified the server but none of its tests. <br>',
"That's okay so long as it's refactoring existing code.",
].join(''),
].join('')
)
}
@@ -74,7 +74,7 @@ if (legacyHelpers.created) {
[
'This PR modified helper functions in `lib/` but not accompanying tests. <br>',
"That's okay so long as it's refactoring existing code.",
].join(''),
].join('')
)
}
@@ -85,7 +85,7 @@ if (logos.created) {
'Please ensure your contribution follows our ',
'[guidance](https://github.com/badges/shields/blob/master/doc/logos.md#contributing-logos) ',
'for logo submissions.',
].join(''),
].join('')
)
}
@@ -94,7 +94,7 @@ if (capitals.created || underscores.created) {
[
'JavaScript source files should be named with `kebab-case` ',
'(dash-separated lowercase).',
].join(''),
].join('')
)
}
@@ -116,7 +116,7 @@ if (allFiles.length > 100) {
':books: Remember to ensure any changes to `config.private` ',
`in \`${file}\` are reflected in the [server secrets documentation]`,
'(https://github.com/badges/shields/blob/master/doc/server-secrets.md)',
].join(''),
].join('')
)
}
@@ -126,7 +126,7 @@ if (allFiles.length > 100) {
`Found 'assert' statement added in \`${file}\`. <br>`,
'Please ensure tests are written using Chai ',
'[expect syntax](http://chaijs.com/guide/styles/#expect)',
].join(''),
].join('')
)
}
@@ -135,7 +135,7 @@ if (allFiles.length > 100) {
[
`Found import of '@hapi/joi' in \`${file}\`. <br>`,
"Joi must be imported as 'joi'.",
].join(''),
].join('')
)
}
})
@@ -168,7 +168,7 @@ affectedServices.forEach(service => {
[
`This PR modified service code for <kbd>${service}</kbd> but not its test code. <br>`,
"That's okay so long as it's refactoring existing code.",
].join(''),
].join('')
)
}
})

View File

@@ -25,7 +25,7 @@ and learn about the [GitHub workflow](http://try.github.io/).
#### Node, NPM
Node >=16 and NPM 9.x is required. If you don't already have them,
Node >=16 and NPM >=8 is required. If you don't already have them,
install node and npm: https://nodejs.org/en/download/
### Setup a dev install

View File

@@ -1,6 +1,38 @@
# Logos
For documentation on using logos, see https://shields.io/docs/logos
## Using Logos
### SimpleIcons
We support a wide range of logos via [SimpleIcons][]. They should be referenced by the logo slug e.g:
![](https://img.shields.io/npm/v/npm.svg?logo=nodedotjs) - https://img.shields.io/npm/v/npm.svg?logo=nodedotjs
The set of Simple Icon slugs can be found in the [slugs.md](https://github.com/simple-icons/simple-icons/blob/develop/slugs.md) file in the Simple Icons repository. NB - the Simple Icons site and that slugs.md page may at times contain new icons that haven't yet been pulled into the Shields.io runtime. More information on how and when we incorporate icon updates can be found [here](https://github.com/badges/shields/discussions/5369).
### Shields logos
We also maintain a small number of custom logos for some services: https://github.com/badges/shields/tree/master/logo They can also be referenced by name and take preference to SimpleIcons e.g:
![](https://img.shields.io/npm/v/npm.svg?logo=npm) - https://img.shields.io/npm/v/npm.svg?logo=npm
### Custom Logos
Any custom logo can be passed in a URL parameter by base64 encoding it. e.g:
![](https://img.shields.io/badge/play-station-blue.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIj48cGF0aCBkPSJNMTI5IDExMWMtNTUgNC05MyA2Ni05MyA3OEwwIDM5OGMtMiA3MCAzNiA5MiA2OSA5MWgxYzc5IDAgODctNTcgMTMwLTEyOGgyMDFjNDMgNzEgNTAgMTI4IDEyOSAxMjhoMWMzMyAxIDcxLTIxIDY5LTkxbC0zNi0yMDljMC0xMi00MC03OC05OC03OGgtMTBjLTYzIDAtOTIgMzUtOTIgNDJIMjM2YzAtNy0yOS00Mi05Mi00MmgtMTV6IiBmaWxsPSIjZmZmIi8+PC9zdmc+) - https://img.shields.io/badge/play-station-blue.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIj48cGF0aCBkPSJNMTI5IDExMWMtNTUgNC05MyA2Ni05MyA3OEwwIDM5OGMtMiA3MCAzNiA5MiA2OSA5MWgxYzc5IDAgODctNTcgMTMwLTEyOGgyMDFjNDMgNzEgNTAgMTI4IDEyOSAxMjhoMWMzMyAxIDcxLTIxIDY5LTkxbC0zNi0yMDljMC0xMi00MC03OC05OC03OGgtMTBjLTYzIDAtOTIgMzUtOTIgNDJIMjM2YzAtNy0yOS00Mi05Mi00MmgtMTV6IiBmaWxsPSIjZmZmIi8+PC9zdmc+
### logoColor parameter
The `logoColor` param can be used to set the color of the logo. Hex, rgb, rgba, hsl, hsla and css named colors can all be used. For SimpleIcons named logos (which are monochrome), the color will be applied to the SimpleIcons logo.
- ![](https://img.shields.io/badge/logo-javascript-blue?logo=javascript) - https://img.shields.io/badge/logo-javascript-blue?logo=javascript
- ![](https://img.shields.io/badge/logo-javascript-blue?logo=javascript&logoColor=f5f5f5) - https://img.shields.io/badge/logo-javascript-blue?logo=javascript&logoColor=f5f5f5
In the case where Shields hosts a custom multi-colored logo, if the `logoColor` param is passed, the corresponding SimpleIcons logo will be substituted and colored.
- ![](https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab) - https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab
- ![](https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab&logoColor=white) - https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab&logoColor=white
## Contributing Logos
@@ -37,6 +69,7 @@ If you are submitting a pull request for a custom logo, please:
We try to ensure our logos are compliant with brand guidelines. If one of our custom logos does not conform to the necessary brand guidelines, please open an issue on the [shields.io tracker](https://github.com/badges/shields/issues) and we'll work with you to resolve it. If a logo from the simple-icons set does not conform to the relevant brand guidelines, please open an issue on the [simple-icons tracker](https://github.com/simple-icons/simple-icons/issues) first.
[simpleicons]: https://simpleicons.org/
[simple-icons github]: https://github.com/simple-icons/simple-icons
[svgo]: https://github.com/svg/svgo
[svgomg]: https://jakearchibald.github.io/svgomg/

View File

@@ -186,7 +186,7 @@ t.create('Build status (private application)')
.intercept(nock =>
nock('https://app.wercker.com/api/v3/applications/')
.get('/wercker/go-wercker-api/builds?limit=1')
.reply(401),
.reply(401)
)
.expectBadge({ label: 'build', message: 'private application not supported' })
```
@@ -227,7 +227,7 @@ t.create('Build passed')
.intercept(nock =>
nock('https://app.wercker.com/api/v3/applications/')
.get('/wercker/go-wercker-api/builds?limit=1')
.reply(200, [{ status: 'finished', result: 'passed' }]),
.reply(200, [{ status: 'finished', result: 'passed' }])
)
.expectBadge({
label: 'build',
@@ -240,7 +240,7 @@ t.create('Build failed')
.intercept(nock =>
nock('https://app.wercker.com/api/v3/applications/')
.get('/wercker/go-wercker-api/builds?limit=1')
.reply(200, [{ status: 'finished', result: 'failed' }]),
.reply(200, [{ status: 'finished', result: 'failed' }])
)
.expectBadge({ label: 'build', message: 'failed', color: 'red' })
```

View File

@@ -1 +1,12 @@
This documentation has moved to https://shields.io/docs/static-badges
# Static Badges
It is possible to use shields.io to make a wide variety of badges displaying static text and/or logos. For example:
- ![any text you like](https://img.shields.io/badge/any%20text-you%20like-blue) - https://img.shields.io/badge/any%20text-you%20like-blue
- ![just the message](https://img.shields.io/badge/just%20the%20message-8A2BE2) - https://img.shields.io/badge/just%20the%20message-8A2BE2
- !['for the badge' style](https://img.shields.io/badge/%27for%20the%20badge%27%20style-20B2AA?style=for-the-badge) - https://img.shields.io/badge/%27for%20the%20badge%27%20style-20B2AA?style=for-the-badge
- ![with a logo](https://img.shields.io/badge/with%20a%20logo-grey?style=for-the-badge&logo=javascript) - https://img.shields.io/badge/with%20a%20logo-grey?style=for-the-badge&logo=javascript
Full documentation of styles and parameters: https://shields.io/#styles
More documentation on logos: https://github.com/badges/shields/blob/master/doc/logos.md

View File

@@ -18,7 +18,7 @@ after('shut down the server', async function () {
it('should render a badge', async function () {
this.timeout('30s')
const { statusCode, body } = await got(
'http://localhost:1111/badge/fruit-apple-green.svg',
'http://localhost:1111/badge/fruit-apple-green.svg'
)
expect(statusCode).to.equal(200)
expect(body).to.satisfy(isSvg).and.to.include('fruit').and.to.include('apple')

View File

@@ -1,21 +0,0 @@
---
slug: new-frontend
title: We launched a new frontend
authors:
name: chris48s
title: Shields.io Core Team
url: https://github.com/chris48s
image_url: https://avatars.githubusercontent.com/u/6025893
tags: []
---
Alongside the general visual refresh and improvements to look and feel, our new frontend has allowed us to address a number of long-standing feature requests and enhancements:
- Clearer and more discoverable documentation for our [static](https://shields.io/badges/static-badge), dynamic [json](https://shields.io/badges/dynamic-json-badge)/[xml](https://shields.io/badges/dynamic-xml-badge)/[yaml](https://shields.io/badges/dynamic-yaml-badge) and [endpoint](https://shields.io/badges/endpoint-badge) badges
- Improved badge builder interface, with all optional query parameters included in the builder for each badge
- Each badge now has its own documentation page, which we can link to. e.g: [https://shields.io/badges/discord](https://shields.io/badges/discord)
- Light/dark mode themes
- Improved search
- Documentation for individual path and query parameters
The new site also comes with big maintenance benefits for the core team. We rely heavily on [docusaurus](https://docusaurus.io/), [docusaurus-openapi](https://github.com/cloud-annotations/docusaurus-openapi), and [docusaurus-search-local](https://github.com/easyops-cn/docusaurus-search-local). This moves us to a mostly declarative setup, massively reducing the amount of custom frontend code we maintain ourselves.

View File

@@ -1,19 +0,0 @@
---
slug: tag-filter
title: Applying filters to GitHub Tag and Release badges
authors:
name: chris48s
title: Shields.io Core Team
url: https://github.com/chris48s
image_url: https://avatars.githubusercontent.com/u/6025893
tags: []
---
We recently shipped a feature which allows you to pass an arbitrary filter to the GitHub tag and release badges. The `filter` param can be used to apply a filter to the project's tag or release names before selecting the latest from the list. Two constructs are available: `*` is a wildcard matching zero or more characters, and if the pattern starts with a `!`, the whole pattern is negated.
To give an example of how this might be useful, we create two types of tags on our GitHub repo: https://github.com/badges/shields/tags There are tags in the format `major.minor.patch` which correspond to our [NPM package releases](https://www.npmjs.com/package/badge-maker?activeTab=versions) and tags in the format `server-YYYY-MM-DD` that correspond to our [docker snapshot releases](https://registry.hub.docker.com/r/shieldsio/shields/tags?page=1&ordering=last_updated).
In our case, this would allow us to make a badge that applies the filter `!server-*` to filter out the snapshot tags and just select the latest package tag.
- ![tag badge without filter](https://img.shields.io/github/v/tag/badges/shields) - https://img.shields.io/github/v/tag/badges/shields
- ![tag badge with filter](https://img.shields.io/github/v/tag/badges/shields?filter=%21server-%2A) - https://img.shields.io/github/v/tag/badges/shields?filter=%21server-%2A

View File

@@ -1,13 +0,0 @@
---
sidebar_position: 1
---
# Intro
Shields.io is a service for concise, consistent, and legible badges, which can easily be included in GitHub readmes or any other web page. The service supports dozens of continuous integration services, package registries, distributions, app stores, social networks, code coverage services, and code analysis services. It is used by some of the world's most popular open-source projects.
Browse a [complete list of badges](/badges) and locate a particular badge by using the search bar or by browsing the categories.
Use the builder to fill in required path parameters for that badge type (like your username or repo) and optionally customize (label, colors etc.). And it's ready for use! Copy your badge url or code snippet, which can then be added to places like your GitHub readme files or other web pages.
![screenshot of the badge builder](/img/builder.png)

5
frontend/docs/intro.md Normal file
View File

@@ -0,0 +1,5 @@
---
sidebar_position: 1
---
# TODO

View File

@@ -1,37 +0,0 @@
---
sidebar_position: 2
---
# Logos
## SimpleIcons
We support a wide range of logos via [SimpleIcons](https://simpleicons.org/). All simple-icons are referenced using icon slugs. e.g:
![](https://img.shields.io/npm/v/npm.svg?logo=nodedotjs) - https://img.shields.io/npm/v/npm.svg?logo=nodedotjs
You can click the icon title on <a href="https://simpleicons.org/" rel="noopener noreferrer" target="_blank">simple-icons</a> to copy the slug or they can be found in the <a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">slugs.md file</a> in the simple-icons repository. NB - the Simple Icons site and slugs.md page may at times contain new icons that haven't yet been pulled into Shields.io yet. More information on how and when we incorporate icon updates can be found [here](https://github.com/badges/shields/discussions/5369).
## Shields logos
We also maintain a small number of custom logos for a handful of services: https://github.com/badges/shields/tree/master/logo They can also be referenced by name and take preference to SimpleIcons e.g:
![](https://img.shields.io/npm/v/npm.svg?logo=npm) - https://img.shields.io/npm/v/npm.svg?logo=npm
## Custom Logos
Any custom logo can be passed in a URL parameter by base64 encoding it. e.g:
![](https://img.shields.io/badge/play-station-blue.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIj48cGF0aCBkPSJNMTI5IDExMWMtNTUgNC05MyA2Ni05MyA3OEwwIDM5OGMtMiA3MCAzNiA5MiA2OSA5MWgxYzc5IDAgODctNTcgMTMwLTEyOGgyMDFjNDMgNzEgNTAgMTI4IDEyOSAxMjhoMWMzMyAxIDcxLTIxIDY5LTkxbC0zNi0yMDljMC0xMi00MC03OC05OC03OGgtMTBjLTYzIDAtOTIgMzUtOTIgNDJIMjM2YzAtNy0yOS00Mi05Mi00MmgtMTV6IiBmaWxsPSIjZmZmIi8+PC9zdmc+) - https://img.shields.io/badge/play-station-blue.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIj48cGF0aCBkPSJNMTI5IDExMWMtNTUgNC05MyA2Ni05MyA3OEwwIDM5OGMtMiA3MCAzNiA5MiA2OSA5MWgxYzc5IDAgODctNTcgMTMwLTEyOGgyMDFjNDMgNzEgNTAgMTI4IDEyOSAxMjhoMWMzMyAxIDcxLTIxIDY5LTkxbC0zNi0yMDljMC0xMi00MC03OC05OC03OGgtMTBjLTYzIDAtOTIgMzUtOTIgNDJIMjM2YzAtNy0yOS00Mi05Mi00MmgtMTV6IiBmaWxsPSIjZmZmIi8+PC9zdmc+
## logoColor parameter
The `logoColor` param can be used to set the color of the logo. Hex, rgb, rgba, hsl, hsla and css named colors can all be used. For SimpleIcons named logos (which are monochrome), the color will be applied to the SimpleIcons logo.
- ![](https://img.shields.io/badge/logo-javascript-blue?logo=javascript) - https://img.shields.io/badge/logo-javascript-blue?logo=javascript
- ![](https://img.shields.io/badge/logo-javascript-blue?logo=javascript&logoColor=f5f5f5) - https://img.shields.io/badge/logo-javascript-blue?logo=javascript&logoColor=f5f5f5
In the case where Shields hosts a custom multi-colored logo, if the `logoColor` param is passed, the corresponding SimpleIcons logo will be substituted and colored.
- ![](https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab) - https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab
- ![](https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab&logoColor=white) - https://img.shields.io/badge/logo-gitlab-blue?logo=gitlab&logoColor=white

View File

@@ -1,13 +0,0 @@
# Static Badges
It is possible to use shields.io to make a wide variety of badges displaying static text and/or logos. For example:
- ![any text you like](https://img.shields.io/badge/any%20text-you%20like-blue) - https://img.shields.io/badge/any%20text-you%20like-blue
- ![just the message](https://img.shields.io/badge/just%20the%20message-8A2BE2) - https://img.shields.io/badge/just%20the%20message-8A2BE2
- !['for the badge' style](https://img.shields.io/badge/%27for%20the%20badge%27%20style-20B2AA?style=for-the-badge) - https://img.shields.io/badge/%27for%20the%20badge%27%20style-20B2AA?style=for-the-badge
- ![with a logo](https://img.shields.io/badge/with%20a%20logo-grey?style=for-the-badge&logo=javascript) - https://img.shields.io/badge/with%20a%20logo-grey?style=for-the-badge&logo=javascript
For more info, see:
- [Static badge builder](/badges/static-badge), including full documentation of styles and parameters
- [Logos](/docs/logos)

View File

@@ -1,5 +1,5 @@
const lightCodeTheme = require('prism-react-renderer').themes.github
const darkCodeTheme = require('prism-react-renderer').themes.dracula
const lightCodeTheme = require('prism-react-renderer/themes/github')
const darkCodeTheme = require('prism-react-renderer/themes/dracula')
/** @type {import('@docusaurus/types').Config} */
const config = {
@@ -31,11 +31,11 @@ const config = {
({
docs: {
sidebarPath: require.resolve('./sidebars.cjs'),
editUrl: 'https://github.com/badges/shields/tree/master/frontend',
editUrl: 'https://github.com/badges/shields/',
},
blog: {
showReadingTime: true,
editUrl: 'https://github.com/badges/shields/tree/master/frontend',
editUrl: 'https://github.com/badges/shields/',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
@@ -60,13 +60,7 @@ const config = {
},
items: [
{ to: '/badges', label: 'Badges', position: 'left' },
{
to: '/docs',
label: 'Documentation',
position: 'left',
},
{ to: '/community', label: 'Community', position: 'left' },
{ to: '/blog', label: 'Blog', position: 'left' },
{
href: 'https://github.com/badges/shields',
label: 'GitHub',

View File

@@ -35,13 +35,7 @@ const FeatureList = [
description: (
<>
Render badges in your own application using our{' '}
<a
href="https://www.npmjs.com/package/badge-maker"
rel="noreferrer"
target="_blank"
>
NPM library
</a>
<a href="https://www.npmjs.com/package/badge-maker">NPM library</a>
<br />
<code>npm install badge-maker</code>
</>
@@ -52,11 +46,7 @@ const FeatureList = [
description: (
<>
Host a shields instance behind your firewall with our{' '}
<a
href="https://registry.hub.docker.com/r/shieldsio/shields/"
rel="noreferrer"
target="_blank"
>
<a href="https://registry.hub.docker.com/r/shieldsio/shields/">
docker image
</a>
<br />
@@ -69,14 +59,8 @@ const FeatureList = [
description: (
<>
Please consider{' '}
<a
href="https://opencollective.com/shields"
rel="noreferrer"
target="_blank"
>
donating
</a>{' '}
to sustain our activities
<a href="https://opencollective.com/shields">donating</a> to sustain our
activities
</>
),
},

View File

@@ -26,27 +26,3 @@
html[data-theme="dark"] .docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.3);
}
.opencollective-image {
color-scheme: initial;
}
/*
TODO: remove these three styles when
we can upgrade to docusaurus-theme-openapi@0.6.5
*/
input[type="text"], :not(#fakeID#fakeId#fakeID) select {
border-color: var(--ifm-color-primary-lightest);
border-style: solid;
border-width: 1px;
}
div.api-code-tab-group {
justify-content: center;
flex-wrap: wrap;
}
div.api-code-tab-group button.api-code-tab {
width: unset;
}

View File

@@ -22,10 +22,7 @@ Shields.io is possible thanks to the people and companies who donate money, serv
💵 These organisations help keep shields running by donating on OpenCollective. Your organisation can support this project by <a href="https://opencollective.com/shields#sponsor">becoming a sponsor </a>. Your logo will show up here with a link to your website.
<p>
<object
data="https://opencollective.com/shields/sponsors.svg?avatarHeight=80&width=600"
class="opencollective-image"
></object>
<object data="https://opencollective.com/shields/sponsors.svg?avatarHeight=80&width=600" />
</p>
## Backers
@@ -33,10 +30,7 @@ Shields.io is possible thanks to the people and companies who donate money, serv
💵 Thank you to all our backers who help keep shields running by donating on OpenCollective. You can support this project by <a href="https://opencollective.com/shields#backer">becoming a backer</a>.
<p>
<object
data="https://opencollective.com/shields/backers.svg?width=600"
class="opencollective-image">
</object>
<object data="https://opencollective.com/shields/backers.svg?width=600" />
</p>
## Contributors
@@ -44,10 +38,7 @@ Shields.io is possible thanks to the people and companies who donate money, serv
🙏 This project exists thanks to all the nice people who contribute their time to work on the project.
<p>
<object
data="https://opencollective.com/shields/contributors.svg?width=600"
class="opencollective-image"
></object>
<object data="https://opencollective.com/shields/contributors.svg?width=600" />
</p>
✨ Shields is helped by these companies which provide a free plan for their product or service:

View File

@@ -27,8 +27,8 @@ export default function Home() {
const { siteConfig } = useDocusaurusContext()
return (
<Layout
description="Concise, consistent, and legible badges"
title={siteConfig.title}
description="Description will go into a meta tag in <head />"
title={`${siteConfig.title}`}
>
<HomepageHeader />
<main>

View File

@@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect } from 'react'
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
import clsx from 'clsx'
import codegen from 'postman-code-generators'
import { Highlight } from 'prism-react-renderer'
import Highlight, { defaultProps } from 'prism-react-renderer'
import { useTypedSelector } from '@theme/ApiDemoPanel/hooks'
import buildPostmanRequest from '@theme/ApiDemoPanel/buildPostmanRequest'
import FloatingButton from '@theme/ApiDemoPanel/FloatingButton'
@@ -183,11 +183,11 @@ function Curl({ postman, codeSamples }) {
}
setCodeText(snippet)
},
}
)
} else if (language && !!language.source) {
setCodeText(
language.source.replace('$url', postmanRequest.url.toString()),
language.source.replace('$url', postmanRequest.url.toString())
)
} else {
setCodeText('')
@@ -230,7 +230,7 @@ function Curl({ postman, codeSamples }) {
className={clsx(
language === lang ? styles.selected : undefined,
language === lang ? 'api-code-tab--active' : undefined,
'api-code-tab',
'api-code-tab'
)}
key={lang.tabName || lang.label}
onClick={() => setLanguage(lang)}
@@ -241,6 +241,7 @@ function Curl({ postman, codeSamples }) {
</div>
<Highlight
{...defaultProps}
code={codeText}
language={language.highlight || language.lang}
theme={languageTheme}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -9,7 +9,7 @@ function loadLogos() {
const logoDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'logo',
'logo'
)
const logoFiles = fs.readdirSync(logoDir)
logoFiles.forEach(filename => {

Some files were not shown because too many files have changed in this diff Show More