Compare commits
1 Commits
server-202
...
dangertest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
027323d4b3 |
77
.circleci/config.yml
Normal file
77
.circleci/config.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
version: 2
|
||||
|
||||
services_steps: &services_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
- run:
|
||||
name: Identify services tagged in the PR title
|
||||
command: npm run test:services:pr:prepare
|
||||
|
||||
- run:
|
||||
name: Run tests for tagged services
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/services/results.xml
|
||||
command: RETRY_COUNT=3 npm run test:services:pr:run
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
jobs:
|
||||
services:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
services@node-17:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
on-commit:
|
||||
jobs:
|
||||
- services:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- services@node-17:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
# on-commit-with-cache:
|
||||
# jobs:
|
||||
# - npm-install:
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: gh-pages
|
||||
# - services:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: master
|
||||
# - services@node-latest:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: master
|
||||
@@ -24,7 +24,6 @@ plugins:
|
||||
- chai-friendly
|
||||
- jsdoc
|
||||
- mocha
|
||||
- icedfrisby
|
||||
- no-extension-in-require
|
||||
- sort-class-members
|
||||
- import
|
||||
@@ -114,16 +113,9 @@ overrides:
|
||||
mocha: true
|
||||
rules:
|
||||
mocha/no-exclusive-tests: 'error'
|
||||
mocha/no-skipped-tests: 'error'
|
||||
mocha/no-mocha-arrows: 'error'
|
||||
mocha/prefer-arrow-callback: 'error'
|
||||
|
||||
- files:
|
||||
- 'services/**/*.tester.js'
|
||||
rules:
|
||||
icedfrisby/no-exclusive-tests: 'error'
|
||||
icedfrisby/no-skipped-tests: 'error'
|
||||
|
||||
rules:
|
||||
# Disable some rules from eslint:recommended.
|
||||
no-empty: ['error', { 'allowEmptyCatch': true }]
|
||||
|
||||
86
.github/actions/service-tests/action.yml
vendored
86
.github/actions/service-tests/action.yml
vendored
@@ -1,86 +0,0 @@
|
||||
name: 'Service tests'
|
||||
description: 'Run tests for selected services'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
librariesio-tokens:
|
||||
description: 'The SERVICETESTS_LIBRARIESIO_TOKENS secret'
|
||||
required: false
|
||||
default: ''
|
||||
obs-user:
|
||||
description: 'The SERVICETESTS_OBS_USER secret'
|
||||
required: false
|
||||
default: ''
|
||||
obs-pass:
|
||||
description: 'The SERVICETESTS_OBS_PASS secret'
|
||||
required: false
|
||||
default: ''
|
||||
sl-insight-user-uuid:
|
||||
description: 'The SERVICETESTS_SL_INSIGHT_USER_UUID secret'
|
||||
required: false
|
||||
default: ''
|
||||
sl-insight-api-token:
|
||||
description: 'The SERVICETESTS_SL_INSIGHT_API_TOKEN secret'
|
||||
required: false
|
||||
default: ''
|
||||
twitch-client-id:
|
||||
description: 'The SERVICETESTS_TWITCH_CLIENT_ID secret'
|
||||
required: false
|
||||
default: ''
|
||||
twitch-client-secret:
|
||||
description: 'The SERVICETESTS_TWITCH_CLIENT_SECRET secret'
|
||||
required: false
|
||||
default: ''
|
||||
wheelmap-token:
|
||||
description: 'The SERVICETESTS_WHEELMAP_TOKEN secret'
|
||||
required: false
|
||||
default: ''
|
||||
youtube-api-key:
|
||||
description: 'The SERVICETESTS_YOUTUBE_API_KEY secret'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Derive list of service tests to run
|
||||
# Note: In this step we are using an intermediate env var instead of
|
||||
# passing github.event.pull_request.title as an argument
|
||||
# to prevent a shell injection attack. Further reading:
|
||||
# https://securitylab.github.com/research/github-actions-untrusted-input/#exploitability-and-impact
|
||||
# https://securitylab.github.com/research/github-actions-untrusted-input/#remediation
|
||||
if: always()
|
||||
env:
|
||||
TITLE: ${{ github.event.pull_request.title }}
|
||||
run: npm run test:services:pr:prepare "$TITLE"
|
||||
shell: bash
|
||||
|
||||
- name: Run service tests
|
||||
if: always()
|
||||
run: npm run test:services:pr:run -- --reporter json --reporter-option 'output=reports/service-tests.json'
|
||||
shell: bash
|
||||
env:
|
||||
RETRY_COUNT: 3
|
||||
GH_TOKEN: '${{ inputs.github-token }}'
|
||||
LIBRARIESIO_TOKENS: '${{ inputs.librariesio-tokens }}'
|
||||
OBS_USER: '${{ inputs.obs-user }}'
|
||||
OBS_PASS: '${{ inputs.obs-pass }}'
|
||||
SL_INSIGHT_USER_UUID: '${{ inputs.sl-insight-user-uuid }}'
|
||||
SL_INSIGHT_API_TOKEN: '${{ inputs.sl-insight-api-token }}'
|
||||
TWITCH_CLIENT_ID: '${{ inputs.twitch-client-id }}'
|
||||
TWITCH_CLIENT_SECRET: '${{ inputs.twitch-client-secret }}'
|
||||
WHEELMAP_TOKEN: '${{ inputs.wheelmap-token }}'
|
||||
YOUTUBE_API_KEY: '${{ inputs.youtube-api-key }}'
|
||||
|
||||
- name: Write Markdown Summary
|
||||
if: always()
|
||||
run: |
|
||||
if test -f 'reports/service-tests.json'; then
|
||||
echo '# Services' >> $GITHUB_STEP_SUMMARY
|
||||
sed -e 's/^/- /' pull-request-services.log >> $GITHUB_STEP_SUMMARY
|
||||
node scripts/mocha2md.js Report reports/service-tests.json >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo 'No services found. Nothing to do.' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
shell: bash
|
||||
2
.github/workflows/build-docker-image.yml
vendored
2
.github/workflows/build-docker-image.yml
vendored
@@ -11,8 +11,6 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Set Git Short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
@@ -35,8 +35,6 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -11,7 +11,7 @@ permissions:
|
||||
jobs:
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'dependabot[bot]' && github.actor != 'repo-ranger[bot]'
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/publish-docker-next.yml
vendored
2
.github/workflows/publish-docker-next.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: v0.9.1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
40
.github/workflows/test-services-17.yml
vendored
40
.github/workflows/test-services-17.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Services@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
test-services-17:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Service tests (triggered from local branch)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
twitch-client-secret: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
|
||||
wheelmap-token: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
|
||||
youtube-api-key: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
|
||||
|
||||
- name: Service tests (triggered from fork)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
38
.github/workflows/test-services.yml
vendored
38
.github/workflows/test-services.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Services
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
test-services:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Service tests (triggered from local branch)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
twitch-client-secret: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
|
||||
wheelmap-token: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
|
||||
youtube-api-key: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
|
||||
|
||||
- name: Service tests (triggered from fork)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
@@ -4,15 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2023-02-01
|
||||
|
||||
- replace [twitter] badge with static fallback [#8842](https://github.com/badges/shields/issues/8842)
|
||||
- Add various [Polymart] badges [#8811](https://github.com/badges/shields/issues/8811)
|
||||
- update [githubpipenv] tests/examples [#8797](https://github.com/badges/shields/issues/8797)
|
||||
- deprecate [apm] service [#8773](https://github.com/badges/shields/issues/8773)
|
||||
- deprecate lgtm [#8771](https://github.com/badges/shields/issues/8771)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-01-01
|
||||
|
||||
- Breaking change: Routes for GitHub workflows badge have changed. See https://github.com/badges/shields/issues/8671 for more details
|
||||
|
||||
102
core/service-test-runner/infer-pull-request.js
Normal file
102
core/service-test-runner/infer-pull-request.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { URL, format as urlFormat } from 'url'
|
||||
|
||||
function formatSlug(owner, repo, pullRequest) {
|
||||
return `${owner}/${repo}#${pullRequest}`
|
||||
}
|
||||
|
||||
function parseGithubPullRequestUrl(url, options = {}) {
|
||||
const { verifyBaseUrl } = options
|
||||
|
||||
const parsed = new URL(url)
|
||||
const components = parsed.pathname.substr(1).split('/')
|
||||
if (components[2] !== 'pull' || components.length !== 4) {
|
||||
throw Error(`Invalid GitHub pull request URL: ${url}`)
|
||||
}
|
||||
const [owner, repo, , pullRequest] = components
|
||||
|
||||
parsed.pathname = ''
|
||||
const baseUrl = urlFormat(parsed, {
|
||||
auth: false,
|
||||
fragment: false,
|
||||
search: false,
|
||||
}).replace(/\/$/, '')
|
||||
|
||||
if (verifyBaseUrl && baseUrl !== verifyBaseUrl) {
|
||||
throw Error(`Expected base URL to be ${verifyBaseUrl} but got ${baseUrl}`)
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl,
|
||||
owner,
|
||||
repo,
|
||||
pullRequest: +pullRequest,
|
||||
slug: formatSlug(owner, repo, pullRequest),
|
||||
}
|
||||
}
|
||||
|
||||
function parseGithubRepoSlug(slug) {
|
||||
const components = slug.split('/')
|
||||
if (components.length !== 2) {
|
||||
throw Error(`Invalid GitHub repo slug: ${slug}`)
|
||||
}
|
||||
const [owner, repo] = components
|
||||
return { owner, repo }
|
||||
}
|
||||
|
||||
function _inferPullRequestFromTravisEnv(env) {
|
||||
const { owner, repo } = parseGithubRepoSlug(env.TRAVIS_REPO_SLUG)
|
||||
const pullRequest = +env.TRAVIS_PULL_REQUEST
|
||||
return {
|
||||
owner,
|
||||
repo,
|
||||
pullRequest,
|
||||
slug: formatSlug(owner, repo, pullRequest),
|
||||
}
|
||||
}
|
||||
|
||||
function _inferPullRequestFromCircleEnv(env) {
|
||||
return parseGithubPullRequestUrl(
|
||||
env.CI_PULL_REQUEST || env.CIRCLE_PULL_REQUEST
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When called inside a CI build, infer the details
|
||||
* of a pull request from the environment variables.
|
||||
*
|
||||
* @param {object} [env=process.env] Environment variables
|
||||
* @returns {module:core/service-test-runner/infer-pull-request~PullRequest}
|
||||
* Pull Request
|
||||
*/
|
||||
function inferPullRequest(env = process.env) {
|
||||
if (env.TRAVIS) {
|
||||
return _inferPullRequestFromTravisEnv(env)
|
||||
} else if (env.CIRCLECI) {
|
||||
return _inferPullRequestFromCircleEnv(env)
|
||||
} else if (env.CI) {
|
||||
throw Error(
|
||||
'Unsupported CI system. Unable to obtain pull request information from the environment.'
|
||||
)
|
||||
} else {
|
||||
throw Error(
|
||||
'Unable to obtain pull request information from the environment. Is this running in CI?'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull Request
|
||||
*
|
||||
* @typedef PullRequest
|
||||
* @property {string} pr.baseUrl (returned for travis CI only)
|
||||
* @property {string} owner
|
||||
* @property {string} repo
|
||||
* @property {string} pullRequest PR/issue number
|
||||
* @property {string} slug owner/repo/#pullRequest
|
||||
*/
|
||||
|
||||
export { parseGithubPullRequestUrl, parseGithubRepoSlug, inferPullRequest }
|
||||
48
core/service-test-runner/infer-pull-request.spec.js
Normal file
48
core/service-test-runner/infer-pull-request.spec.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { test, given, forCases } from 'sazerac'
|
||||
import {
|
||||
parseGithubPullRequestUrl,
|
||||
inferPullRequest,
|
||||
} from './infer-pull-request.js'
|
||||
|
||||
describe('Pull request inference', function () {
|
||||
test(parseGithubPullRequestUrl, () => {
|
||||
forCases([
|
||||
given('https://github.com/badges/shields/pull/1234'),
|
||||
given('https://github.com/badges/shields/pull/1234', {
|
||||
verifyBaseUrl: 'https://github.com',
|
||||
}),
|
||||
]).expect({
|
||||
baseUrl: 'https://github.com',
|
||||
owner: 'badges',
|
||||
repo: 'shields',
|
||||
pullRequest: 1234,
|
||||
slug: 'badges/shields#1234',
|
||||
})
|
||||
|
||||
given('https://github.com/badges/shields/pull/1234', {
|
||||
verifyBaseUrl: 'https://example.com',
|
||||
}).expectError(
|
||||
'Expected base URL to be https://example.com but got https://github.com'
|
||||
)
|
||||
})
|
||||
|
||||
test(inferPullRequest, () => {
|
||||
const expected = {
|
||||
owner: 'badges',
|
||||
repo: 'shields',
|
||||
pullRequest: 1234,
|
||||
slug: 'badges/shields#1234',
|
||||
}
|
||||
|
||||
given({
|
||||
CIRCLECI: '1',
|
||||
CI_PULL_REQUEST: 'https://github.com/badges/shields/pull/1234',
|
||||
}).expect(Object.assign({ baseUrl: 'https://github.com' }, expected))
|
||||
|
||||
given({
|
||||
TRAVIS: '1',
|
||||
TRAVIS_REPO_SLUG: 'badges/shields',
|
||||
TRAVIS_PULL_REQUEST: '1234',
|
||||
}).expect(expected)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
// Derive a list of service tests to run based on
|
||||
// space-separated service names in the PR title.
|
||||
// Infer the current PR from the Travis environment, and look for bracketed,
|
||||
// space-separated service names in the pull request title.
|
||||
//
|
||||
// Output the list of services.
|
||||
//
|
||||
@@ -8,26 +8,54 @@
|
||||
// Output:
|
||||
// travis
|
||||
// sonar
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// TRAVIS=1 TRAVIS_REPO_SLUG=badges/shields TRAVIS_PULL_REQUEST=1108 npm run test:services:pr:prepare
|
||||
|
||||
import got from 'got'
|
||||
import { inferPullRequest } from './infer-pull-request.js'
|
||||
import servicesForTitle from './services-for-title.js'
|
||||
|
||||
let title
|
||||
async function getTitle(owner, repo, pullRequest) {
|
||||
const {
|
||||
body: { title },
|
||||
} = await got(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullRequest}`,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': 'badges/shields',
|
||||
Authorization: `token ${process.env.GITHUB_TOKEN}`,
|
||||
},
|
||||
responseType: 'json',
|
||||
}
|
||||
)
|
||||
return title
|
||||
}
|
||||
|
||||
try {
|
||||
if (process.argv.length < 3) {
|
||||
throw new Error()
|
||||
async function main() {
|
||||
const { owner, repo, pullRequest, slug } = inferPullRequest()
|
||||
console.error(`PR: ${slug}`)
|
||||
|
||||
const title = await getTitle(owner, repo, pullRequest)
|
||||
|
||||
console.error(`Title: ${title}\n`)
|
||||
const services = servicesForTitle(title)
|
||||
if (services.length === 0) {
|
||||
console.error('No services found. Nothing to do.')
|
||||
} else {
|
||||
console.error(
|
||||
`Services: (${services.length} found) ${services.join(', ')}\n`
|
||||
)
|
||||
console.log(services.join('\n'))
|
||||
}
|
||||
title = process.argv[2]
|
||||
} catch (e) {
|
||||
console.error('Error processing arguments')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.error(`Title: ${title}\n`)
|
||||
const services = servicesForTitle(title)
|
||||
if (services.length === 0) {
|
||||
console.error('No services found. Nothing to do.')
|
||||
} else {
|
||||
console.error(`Services: (${services.length} found) ${services.join(', ')}\n`)
|
||||
console.log(services.join('\n'))
|
||||
}
|
||||
;(async () => {
|
||||
try {
|
||||
await main()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -125,17 +125,11 @@ Because of GitHub rate limits, you will need to provide a token, or else badges
|
||||
will stop working once you hit 60 requests per hour, the
|
||||
[unauthenticated rate limit][github rate limit].
|
||||
|
||||
You can [create a personal access token][personal access tokens] (PATs) through the
|
||||
You can [create a personal access token][personal access tokens] through the
|
||||
GitHub website. When you create the token, you can choose to give read access
|
||||
to your repositories. If you do that, your self-hosted Shields installation
|
||||
will have access to your private repositories.
|
||||
|
||||
For most users we recommend using a classic PAT as opposed to a [fine-grained PAT][fine-grained pat].
|
||||
It is possible to request a fairly large subset of the GitHub badge suite using a
|
||||
fine-grained PAT for authentication but there are also some badges that won't work.
|
||||
This is because some of our badges make use of GitHub's v4 GraphQL API and the
|
||||
GraphQL API only supports authentication with a classic PAT.
|
||||
|
||||
When a `gh_token` is specified, it is used in place of the Shields token
|
||||
rotation logic.
|
||||
|
||||
@@ -145,7 +139,6 @@ token, though it's not required.
|
||||
|
||||
[github rate limit]: https://developer.github.com/v3/#rate-limiting
|
||||
[personal access tokens]: https://github.com/settings/tokens
|
||||
[fine-grained pat]: https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/
|
||||
|
||||
- `GH_CLIENT_ID` (yml: `private.gh_client_id`)
|
||||
- `GH_CLIENT_SECRET` (yml: `private.gh_client_secret`)
|
||||
|
||||
695
package-lock.json
generated
695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -24,8 +24,8 @@
|
||||
"@fontsource/lato": "^4.5.10",
|
||||
"@fontsource/lekton": "^4.5.11",
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@renovatebot/ruby-semver": "^2.1.4",
|
||||
"@sentry/node": "^7.34.0",
|
||||
"@renovatebot/ruby-semver": "^1.1.8",
|
||||
"@sentry/node": "^7.30.0",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -33,19 +33,19 @@
|
||||
"chalk": "^5.2.0",
|
||||
"check-node-version": "^4.2.1",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.9",
|
||||
"config": "^3.3.8",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.7",
|
||||
"decamelize": "^3.2.0",
|
||||
"emojic": "^1.1.17",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^4.0.15",
|
||||
"glob": "^8.1.0",
|
||||
"fast-xml-parser": "^4.0.13",
|
||||
"glob": "^8.0.3",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^12.5.3",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"ioredis": "5.3.0",
|
||||
"ioredis": "5.2.4",
|
||||
"joi": "17.7.0",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -62,7 +62,7 @@
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^8.1.0",
|
||||
"semver": "~7.3.8",
|
||||
"simple-icons": "8.3.0",
|
||||
"simple-icons": "8.2.0",
|
||||
"webextension-store-meta": "^1.0.5",
|
||||
"xmldom": "~0.6.0",
|
||||
"xpath": "~0.0.32"
|
||||
@@ -156,7 +156,7 @@
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-select": "^4.0.17",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.46.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-preset-gatsby": "^2.22.0",
|
||||
@@ -169,9 +169,10 @@
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^7.6.0",
|
||||
"cypress": "^12.4.1",
|
||||
"cypress": "^12.3.0",
|
||||
"cypress-wait-for-stable-dom": "^0.1.0",
|
||||
"danger": "^11.2.3",
|
||||
"danger": "^11.2.1",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
@@ -180,14 +181,13 @@
|
||||
"eslint-config-standard-react": "^11.0.1",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-icedfrisby": "^0.1.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsdoc": "^39.6.9",
|
||||
"eslint-plugin-import": "^2.27.4",
|
||||
"eslint-plugin-jsdoc": "^39.6.4",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-react": "^7.32.1",
|
||||
"eslint-plugin-react": "^7.32.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sort-class-members": "^1.16.0",
|
||||
"fetch-ponyfill": "^7.1.0",
|
||||
@@ -219,7 +219,7 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"open-cli": "^7.1.0",
|
||||
"portfinder": "^1.0.32",
|
||||
"prettier": "2.8.3",
|
||||
"prettier": "2.8.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-overlay": "^6.0.11",
|
||||
@@ -229,13 +229,13 @@
|
||||
"react-select": "^4.3.1",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"redis-server": "^1.2.2",
|
||||
"rimraf": "^4.1.2",
|
||||
"rimraf": "^4.0.4",
|
||||
"sazerac": "^2.0.0",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"sinon": "^15.0.1",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"snap-shot-it": "^7.9.10",
|
||||
"start-server-and-test": "1.15.3",
|
||||
"start-server-and-test": "1.15.2",
|
||||
"styled-components": "^5.3.6",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"tsd": "^0.25.0",
|
||||
|
||||
@@ -22,10 +22,6 @@ if (data.stats.passes > 0) {
|
||||
if (data.stats.failures > 0) {
|
||||
process.stdout.write(`✖ ${data.stats.failures} failed\n\n`)
|
||||
}
|
||||
if (data.stats.pending > 0) {
|
||||
process.stdout.write(`● ${data.stats.pending} pending\n\n`)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
if (data.stats.failures > 0) {
|
||||
for (const test of data.tests) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ServiceTester } from '../../tester.js'
|
||||
|
||||
export const t = new ServiceTester({
|
||||
id: 'GistLastCommitRedirect',
|
||||
id: 'GithubGistLastCommitRedirect',
|
||||
title: 'Github Gist Last Commit Redirect',
|
||||
pathPrefix: '/github-gist',
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ const schema = Joi.object({
|
||||
updated_at: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
export default class GistLastCommit extends GithubAuthV3Service {
|
||||
export default class GithubGistLastCommit extends GithubAuthV3Service {
|
||||
static category = 'activity'
|
||||
static route = { base: 'github/gist/last-commit', pattern: ':gistId' }
|
||||
static examples = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ServiceTester } from '../../tester.js'
|
||||
export const t = new ServiceTester({
|
||||
id: 'GistStarsRedirect',
|
||||
id: 'GithubGistStarsRedirect',
|
||||
title: 'Github Gist Stars Redirect',
|
||||
pathPrefix: '/github',
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ const documentation = `${commonDocumentation}
|
||||
<p>This badge shows the number of stargazers for a gist. Gist id is accepted as input and 'gist not found' is returned if the gist is not found for the given gist id.
|
||||
</p>`
|
||||
|
||||
export default class GistStars extends GithubAuthV4Service {
|
||||
export default class GithubGistStars extends GithubAuthV4Service {
|
||||
static category = 'social'
|
||||
|
||||
static route = {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import Joi from 'joi'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
const resourceSchema = Joi.object({
|
||||
response: Joi.object({
|
||||
resource: Joi.object({
|
||||
price: Joi.number().required(),
|
||||
downloads: Joi.string().required(),
|
||||
reviews: Joi.object({
|
||||
count: Joi.number().required(),
|
||||
stars: Joi.number().required(),
|
||||
}).required(),
|
||||
updates: Joi.object({
|
||||
latest: Joi.object({
|
||||
version: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required()
|
||||
|
||||
const notFoundResourceSchema = Joi.object({
|
||||
response: Joi.object({
|
||||
success: Joi.boolean().required(),
|
||||
errors: Joi.object().required(),
|
||||
}).required(),
|
||||
})
|
||||
|
||||
const resourceFoundOrNotSchema = Joi.alternatives(
|
||||
resourceSchema,
|
||||
notFoundResourceSchema
|
||||
)
|
||||
|
||||
const documentation = `
|
||||
<p>You can find your resource ID in the url for your resource page.</p>
|
||||
<p>Example: <code>https://polymart.org/resource/polymart-plugin.323</code> - Here the Resource ID is 323.</p>`
|
||||
|
||||
class BasePolymartService extends BaseJsonService {
|
||||
async fetch({
|
||||
resourceId,
|
||||
schema = resourceFoundOrNotSchema,
|
||||
url = `https://api.polymart.org/v1/getResourceInfo/?resource_id=${resourceId}`,
|
||||
}) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { documentation, BasePolymartService }
|
||||
@@ -1,35 +0,0 @@
|
||||
import { NotFound } from '../../core/base-service/errors.js'
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { BasePolymartService, documentation } from './polymart-base.js'
|
||||
|
||||
export default class PolymartDownloads extends BasePolymartService {
|
||||
static category = 'downloads'
|
||||
|
||||
static route = {
|
||||
base: 'polymart/downloads',
|
||||
pattern: ':resourceId',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Polymart Downloads',
|
||||
namedParams: {
|
||||
resourceId: '323',
|
||||
},
|
||||
staticPreview: renderDownloadsBadge({ downloads: 655 }),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
label: 'downloads',
|
||||
}
|
||||
|
||||
async handle({ resourceId }) {
|
||||
const { response } = await this.fetch({ resourceId })
|
||||
if (!response.resource) {
|
||||
throw new NotFound()
|
||||
}
|
||||
return renderDownloadsBadge({ downloads: response.resource.downloads })
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { isMetric } from '../test-validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('Polymart Plugin (id 323)').get('/323.json').expectBadge({
|
||||
label: 'downloads',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('Invalid Resource (id 0)').get('/0.json').expectBadge({
|
||||
label: 'downloads',
|
||||
message: 'not found',
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
import { NotFound } from '../../core/base-service/errors.js'
|
||||
import { renderVersionBadge } from '../version.js'
|
||||
import { BasePolymartService, documentation } from './polymart-base.js'
|
||||
export default class PolymartLatestVersion extends BasePolymartService {
|
||||
static category = 'version'
|
||||
|
||||
static route = {
|
||||
base: 'polymart/version',
|
||||
pattern: ':resourceId',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Polymart Version',
|
||||
namedParams: {
|
||||
resourceId: '323',
|
||||
},
|
||||
staticPreview: renderVersionBadge({
|
||||
version: 'v1.2.9',
|
||||
}),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
label: 'polymart',
|
||||
}
|
||||
|
||||
async handle({ resourceId }) {
|
||||
const { response } = await this.fetch({ resourceId })
|
||||
if (!response.resource) {
|
||||
throw new NotFound()
|
||||
}
|
||||
return renderVersionBadge({
|
||||
version: response.resource.updates.latest.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { isVPlusDottedVersionNClauses } from '../test-validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('Polymart Plugin (id 323)').get('/323.json').expectBadge({
|
||||
label: 'polymart',
|
||||
message: isVPlusDottedVersionNClauses,
|
||||
})
|
||||
|
||||
t.create('Invalid Resource (id 0)').get('/0.json').expectBadge({
|
||||
label: 'polymart',
|
||||
message: 'not found',
|
||||
})
|
||||
@@ -1,65 +0,0 @@
|
||||
import { starRating, metric } from '../text-formatters.js'
|
||||
import { floorCount } from '../color-formatters.js'
|
||||
import { NotFound } from '../../core/base-service/errors.js'
|
||||
import { BasePolymartService, documentation } from './polymart-base.js'
|
||||
|
||||
export default class PolymartRatings extends BasePolymartService {
|
||||
static category = 'rating'
|
||||
|
||||
static route = {
|
||||
base: 'polymart',
|
||||
pattern: ':format(rating|stars)/:resourceId',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Polymart Stars',
|
||||
pattern: 'stars/:resourceId',
|
||||
namedParams: {
|
||||
resourceId: '323',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
format: 'stars',
|
||||
total: 14,
|
||||
average: 5,
|
||||
}),
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Polymart Rating',
|
||||
pattern: 'rating/:resourceId',
|
||||
namedParams: {
|
||||
resourceId: '323',
|
||||
},
|
||||
staticPreview: this.render({ total: 14, average: 5 }),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
label: 'rating',
|
||||
}
|
||||
|
||||
static render({ format, total, average }) {
|
||||
const message =
|
||||
format === 'stars'
|
||||
? starRating(average)
|
||||
: `${average}/5 (${metric(total)})`
|
||||
return {
|
||||
message,
|
||||
color: floorCount(average, 2, 3, 4),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ format, resourceId }) {
|
||||
const { response } = await this.fetch({ resourceId })
|
||||
if (!response.resource) {
|
||||
throw new NotFound()
|
||||
}
|
||||
return this.constructor.render({
|
||||
format,
|
||||
total: response.resource.reviews.count,
|
||||
average: response.resource.reviews.stars.toFixed(2),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { isStarRating, withRegex } from '../test-validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('Stars - Polymart Plugin (id 323)')
|
||||
.get('/stars/323.json')
|
||||
.expectBadge({
|
||||
label: 'rating',
|
||||
message: isStarRating,
|
||||
})
|
||||
|
||||
t.create('Stars - Invalid Resource (id 0)').get('/stars/0.json').expectBadge({
|
||||
label: 'rating',
|
||||
message: 'not found',
|
||||
})
|
||||
|
||||
t.create('Rating - Polymart Plugin (id 323)')
|
||||
.get('/rating/323.json')
|
||||
.expectBadge({
|
||||
label: 'rating',
|
||||
message: withRegex(/^(\d*\.\d+)(\/5 \()(\d+)(\))$/),
|
||||
})
|
||||
|
||||
t.create('Rating - Invalid Resource (id 0)').get('/rating/0.json').expectBadge({
|
||||
label: 'rating',
|
||||
message: 'not found',
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { BaseService, BaseJsonService } from '../index.js'
|
||||
import { BaseService, BaseJsonService, NotFound } from '../index.js'
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
url: optionalUrl.required(),
|
||||
@@ -32,8 +33,6 @@ class TwitterUrl extends BaseService {
|
||||
},
|
||||
]
|
||||
|
||||
static _cacheLength = 86400
|
||||
|
||||
static defaultBadgeData = {
|
||||
namedLogo: 'twitter',
|
||||
}
|
||||
@@ -52,19 +51,8 @@ class TwitterUrl extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This badge is unusual.
|
||||
const schema = Joi.any()
|
||||
|
||||
We don't usually host badges that don't show any dynamic information.
|
||||
Also when an upstream API is removed, we usually deprecate/remove badges
|
||||
according to the process in
|
||||
https://github.com/badges/shields/blob/master/doc/deprecating-badges.md
|
||||
|
||||
In the case of twitter, we decided to provide a static fallback instead
|
||||
due to how widely used the badge was. See
|
||||
https://github.com/badges/shields/issues/8837
|
||||
for related discussion.
|
||||
*/
|
||||
class TwitterFollow extends BaseJsonService {
|
||||
static category = 'social'
|
||||
|
||||
@@ -77,40 +65,51 @@ class TwitterFollow extends BaseJsonService {
|
||||
{
|
||||
title: 'Twitter Follow',
|
||||
namedParams: {
|
||||
user: 'shields_io',
|
||||
user: 'espadrine',
|
||||
},
|
||||
queryParams: { label: 'Follow' },
|
||||
// hard code the static preview
|
||||
// because link[] is not allowed in examples
|
||||
staticPreview: {
|
||||
label: 'Follow @shields_io',
|
||||
message: '',
|
||||
label: 'Follow',
|
||||
message: '393',
|
||||
style: 'social',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
static _cacheLength = 86400
|
||||
|
||||
static defaultBadgeData = {
|
||||
namedLogo: 'twitter',
|
||||
}
|
||||
|
||||
static render({ user }) {
|
||||
static render({ user, followers }) {
|
||||
return {
|
||||
label: `follow @${user}`,
|
||||
message: '',
|
||||
message: metric(followers),
|
||||
style: 'social',
|
||||
link: [
|
||||
`https://twitter.com/intent/follow?screen_name=${encodeURIComponent(
|
||||
user
|
||||
)}`,
|
||||
`https://twitter.com/${encodeURIComponent(user)}/followers`,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ user }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: 'http://cdn.syndication.twimg.com/widgets/followbutton/info.json',
|
||||
options: { searchParams: { screen_names: user } },
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ user }) {
|
||||
return this.constructor.render({ user })
|
||||
const data = await this.fetch({ user })
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
throw new NotFound({ prettyMessage: 'invalid user' })
|
||||
}
|
||||
return this.constructor.render({ user, followers: data[0].followers_count })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isMetric } from '../test-validators.js'
|
||||
import { ServiceTester } from '../tester.js'
|
||||
|
||||
export const t = new ServiceTester({
|
||||
@@ -9,8 +10,25 @@ t.create('Followers')
|
||||
.get('/follow/shields_io.json')
|
||||
.expectBadge({
|
||||
label: 'follow @shields_io',
|
||||
message: '',
|
||||
link: ['https://twitter.com/intent/follow?screen_name=shields_io'],
|
||||
message: isMetric,
|
||||
link: [
|
||||
'https://twitter.com/intent/follow?screen_name=shields_io',
|
||||
'https://twitter.com/shields_io/followers',
|
||||
],
|
||||
})
|
||||
|
||||
t.create('Invalid Username Specified (non-existent user)')
|
||||
.get('/follow/invalidusernamethatshouldnotexist.json?label=Follow')
|
||||
.expectBadge({
|
||||
label: 'Follow',
|
||||
message: 'invalid user',
|
||||
})
|
||||
|
||||
t.create('Invalid Username Specified (only spaces)')
|
||||
.get('/follow/%20%20.json?label=Follow')
|
||||
.expectBadge({
|
||||
label: 'Follow',
|
||||
message: 'invalid user',
|
||||
})
|
||||
|
||||
t.create('URL')
|
||||
|
||||
Reference in New Issue
Block a user