Compare commits

..

1 Commits

Author SHA1 Message Date
release[bot]
29a8899767 Update Changelog 2022-05-01 01:39:44 +00:00
236 changed files with 10944 additions and 14367 deletions

View File

@@ -1,5 +1,71 @@
version: 2
main_steps: &main_steps
steps:
- checkout
- run:
name: Install dependencies
command: |
npm install --dry-run
npm ci
environment:
# https://docs.cypress.io/guides/getting-started/installing-cypress.html#Skipping-installation
# We don't need to install the Cypress binary in jobs that aren't actually running Cypress.
CYPRESS_INSTALL_BINARY: 0
- run:
name: Linter
when: always
command: npm run lint
- run:
name: Core tests
when: always
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/core/results.xml
command: npm run test:core
- run:
name: Entrypoint tests
when: always
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/entrypoint/results.xml
command: npm run test:entrypoint
- store_test_results:
path: junit
- run:
name: 'Prettier check (quick fix: `npm run prettier`)'
when: always
command: npm run prettier:check
integration_steps: &integration_steps
steps:
- checkout
- run:
name: Install dependencies
command: |
npm install --dry-run
npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
- run:
name: Integration tests
when: always
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/integration/results.xml
command: npm run test:integration
- store_test_results:
path: junit
services_steps: &services_steps
steps:
- checkout
@@ -7,6 +73,7 @@ services_steps: &services_steps
- run:
name: Install dependencies
command: |
npm install --dry-run
npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
@@ -25,10 +92,90 @@ services_steps: &services_steps
- store_test_results:
path: junit
package_steps: &package_steps
steps:
- checkout
- run:
name: Install node and npm
command: |
set +e
export NVM_DIR="/opt/circleci/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install v14
nvm use v14
npm install -g npm
# Run the package tests on each currently supported node version. See:
# https://github.com/badges/shields/blob/master/badge-maker/README.md#node-version-support
# https://nodejs.org/en/about/releases/
- run:
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/badge-maker/v12/results.xml
NODE_VERSION: v12
CYPRESS_INSTALL_BINARY: 0
name: Run package tests on Node 12
command: scripts/run_package_tests.sh
- run:
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/badge-maker/v14/results.xml
NODE_VERSION: v14
CYPRESS_INSTALL_BINARY: 0
name: Run package tests on Node 14
command: scripts/run_package_tests.sh
- run:
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/badge-maker/v16/results.xml
NODE_VERSION: v16
CYPRESS_INSTALL_BINARY: 0
name: Run package tests on Node 16
command: scripts/run_package_tests.sh
- store_test_results:
path: junit
jobs:
main:
docker:
- image: cimg/node:16.14
environment:
NPM_CONFIG_ENGINE_STRICT: 'true'
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
<<: *main_steps
main@node-17:
docker:
- image: cimg/node:17.7
<<: *main_steps
integration:
docker:
- image: cimg/node:16.14
environment:
NPM_CONFIG_ENGINE_STRICT: 'true'
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
- image: redis
<<: *integration_steps
integration@node-17:
docker:
- image: cimg/node:17.7
- image: redis
<<: *integration_steps
danger:
docker:
- image: cimg/node:16.15
- image: cimg/node:16.14
steps:
- checkout
@@ -48,14 +195,17 @@ jobs:
frontend:
docker:
- image: cimg/node:16.15
- image: cimg/node:16.14
environment:
NPM_CONFIG_ENGINE_STRICT: 'true'
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
steps:
- checkout
- run:
name: Install dependencies
command: |
npm install --dry-run
npm ci
environment:
CYPRESS_INSTALL_BINARY: 0
@@ -84,24 +234,33 @@ jobs:
when: always
command: npm run build
package:
machine:
image: 'ubuntu-2004:202111-02'
<<: *package_steps
services:
docker:
- image: cimg/node:16.15
- image: cimg/node:16.14
environment:
NPM_CONFIG_ENGINE_STRICT: 'true'
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
<<: *services_steps
services@node-17:
docker:
- image: cimg/node:17.9
environment:
NPM_CONFIG_ENGINE_STRICT: 'false'
- image: cimg/node:17.7
<<: *services_steps
e2e:
docker:
- image: cypress/base:16.14.0
- image: cypress/base:16.13.0
environment:
NPM_CONFIG_ENGINE_STRICT: 'true'
NPM_CONFIG_STRICT_PEER_DEPS: 'true'
steps:
- checkout
@@ -113,6 +272,7 @@ jobs:
- run:
name: Install dependencies
command: |
npm install --dry-run
npm ci
- run:
@@ -147,10 +307,26 @@ workflows:
on-commit:
jobs:
- main:
filters:
branches:
ignore: gh-pages
- main@node-17:
filters:
branches:
ignore: gh-pages
- integration@node-17:
filters:
branches:
ignore: gh-pages
- frontend:
filters:
branches:
ignore: gh-pages
- package:
filters:
branches:
ignore: gh-pages
- services:
filters:
branches:
@@ -180,6 +356,12 @@ workflows:
# filters:
# branches:
# ignore: gh-pages
# - main:
# requires:
# - npm-install
# - main@node-latest:
# requires:
# - npm-install
# - frontend:
# requires:
# - npm-install

View File

@@ -144,8 +144,6 @@ rules:
func-style: ['error', 'declaration', { 'allowArrowFunctions': true }]
new-cap: ['error', { 'capIsNew': true }]
import/order: ['error', { 'newlines-between': 'never' }]
quotes:
['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }]
# Account for destructuring responses from upstream services,
# many of which do not follow camelcase

View File

@@ -21,7 +21,7 @@ A clear and concise description of the new badge.
Where can we get the data from?
- Is there a public API?
- Does the API require an API key?
- Does the API requires an API key?
- Link to the API documentation.
-->

View File

@@ -27,7 +27,7 @@ async function run() {
state: 'closed',
})
core.debug('Done.')
core.debug(`Done.`)
}
}
} catch (error) {

View File

@@ -9,36 +9,35 @@
"version": "0.0.0",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/github": "^5.0.3"
"@actions/core": "^1.6.0",
"@actions/github": "^5.0.1"
}
},
"node_modules/@actions/core": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/http-client": "^1.0.11"
}
},
"node_modules/@actions/github": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.1.tgz",
"integrity": "sha512-JZGyPM9ektb8NVTTI/2gfJ9DL7Rk98tQ7OVyTlgTuaQroariRBsOnzjy0I2EarX4xUZpK88YyO503fhmjFdyAg==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@actions/http-client": "^1.0.11",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"dependencies": {
"tunnel": "^0.0.6"
"tunnel": "0.0.6"
}
},
"node_modules/@octokit/auth-token": {
@@ -205,14 +204,6 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -235,31 +226,30 @@
},
"dependencies": {
"@actions/core": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/http-client": "^1.0.11"
}
},
"@actions/github": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.1.tgz",
"integrity": "sha512-JZGyPM9ektb8NVTTI/2gfJ9DL7Rk98tQ7OVyTlgTuaQroariRBsOnzjy0I2EarX4xUZpK88YyO503fhmjFdyAg==",
"requires": {
"@actions/http-client": "^2.0.1",
"@actions/http-client": "^1.0.11",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "^0.0.6"
"tunnel": "0.0.6"
}
},
"@octokit/auth-token": {
@@ -403,11 +393,6 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@@ -10,7 +10,7 @@
"author": "chris48s",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/github": "^5.0.3"
"@actions/core": "^1.6.0",
"@actions/github": "^5.0.1"
}
}

View File

@@ -1,21 +0,0 @@
name: 'Core tests'
description: 'Run core and entrypoint tests'
runs:
using: 'composite'
steps:
- name: Core tests
if: always()
run: npm run test:core -- --reporter json --reporter-option 'output=reports/core.json'
shell: bash
- name: Entrypoint tests
if: always()
run: npm run test:entrypoint -- --reporter json --reporter-option 'output=reports/entrypoint.json'
shell: bash
- name: Write Markdown Summary
if: always()
run: |
node scripts/mocha2md.js Core reports/core.json >> $GITHUB_STEP_SUMMARY
node scripts/mocha2md.js Entrypoint reports/entrypoint.json >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -1,20 +0,0 @@
name: 'Integration tests'
description: 'Run integration tests'
inputs:
github-token:
description: 'The GITHUB_TOKEN secret'
required: true
runs:
using: 'composite'
steps:
- name: Integration Tests
if: always()
run: npm run test:integration -- --reporter json --reporter-option 'output=reports/integration-tests.json'
env:
GH_TOKEN: '${{ inputs.github-token }}'
shell: bash
- name: Write Markdown Summary
if: always()
run: node scripts/mocha2md.js Integration reports/integration-tests.json >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -1,26 +0,0 @@
name: 'Package tests'
description: 'Run package tests and check types'
runs:
using: 'composite'
steps:
- name: Tests
if: always()
run: npm run test:package -- --reporter json --reporter-option 'output=reports/package-tests.json'
shell: bash
- name: Type Checks
if: always()
run: |
set -o pipefail
npm run check-types:package 2>&1 | tee reports/package-types.txt
shell: bash
- name: Write Markdown Summary
if: always()
run: |
node scripts/mocha2md.js 'Package Tests' reports/package-tests.json >> $GITHUB_STEP_SUMMARY
echo '# Package Types' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat reports/package-types.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -1,25 +0,0 @@
name: 'Set up project'
description: 'Set up project'
inputs:
node-version:
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
required: true
cypress:
description: 'Install Cypress binary: 0 or 1'
# https://docs.cypress.io/guides/getting-started/installing-cypress.html#Skipping-installation
# We don't need to install the Cypress binary in jobs that aren't actually running Cypress.
required: false
default: 0
runs:
using: 'composite'
steps:
- name: Install Node JS ${{ inputs.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node-version }}
- name: Install dependencies
env:
CYPRESS_INSTALL_BINARY: ${{ inputs.cypress }}
run: npm ci
shell: bash

View File

@@ -36,8 +36,3 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99

View File

@@ -5,12 +5,12 @@ permissions:
pull-requests: write
jobs:
auto-close:
build:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Install action dependencies
run: cd .github/actions/close-bot && npm ci

View File

@@ -3,20 +3,20 @@ on:
pull_request:
jobs:
build-docker-image:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1
- name: Set Git Short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Build
uses: docker/build-push-action@v3
uses: docker/build-push-action@v2
with:
context: .
push: false

View File

@@ -4,9 +4,6 @@ on:
pull_request:
types: [closed]
permissions:
contents: write
jobs:
create-release:
if: |
@@ -23,7 +20,7 @@ jobs:
run: echo "::set-output name=date::$(date --rfc-3339=date)"
- name: Checkout branch "master"
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
ref: 'master'
@@ -34,16 +31,16 @@ jobs:
tag: server-${{ steps.date.outputs.date }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push snapshot release to DockerHub
uses: docker/build-push-action@v3
uses: docker/build-push-action@v2
with:
context: .
push: true

View File

@@ -3,16 +3,12 @@ on:
push:
branches:
- master
permissions:
contents: write
jobs:
deploy-docs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
@@ -22,8 +18,9 @@ jobs:
npm run build-docs
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
branch: gh-pages
folder: api-docs
clean: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: api-docs
CLEAN: true

View File

@@ -5,16 +5,12 @@ on:
# At 01:00 on the first day of every month
workflow_dispatch:
permissions:
pull-requests: write
contents: write
jobs:
draft-release:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Draft Release
uses: ./.github/actions/draft-release

View File

@@ -1,11 +1,14 @@
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
enforce-dependency-review:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v2
uses: actions/dependency-review-action@v1

View File

@@ -5,17 +5,17 @@ on:
- master
jobs:
publish-docker-next:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -24,7 +24,7 @@ jobs:
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v2
with:
context: .
push: true

View File

@@ -1,37 +0,0 @@
name: Integration@node 17
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
jobs:
test-integration-17:
runs-on: ubuntu-latest
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 17
env:
NPM_CONFIG_ENGINE_STRICT: 'false'
- name: Integration Tests
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,35 +0,0 @@
name: Integration
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
jobs:
test-integration:
runs-on: ubuntu-latest
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Integration Tests
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,26 +0,0 @@
name: Lint
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
jobs:
test-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: ESLint
if: always()
run: npm run lint
- name: 'Prettier check (quick fix: `npm run prettier`)'
if: always()
run: npm run prettier:check

View File

@@ -1,23 +0,0 @@
name: Main@node 17
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
jobs:
test-main-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: Core tests
uses: ./.github/actions/core-tests

View File

@@ -1,26 +0,0 @@
name: Main
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
jobs:
test-main:
strategy:
matrix:
os: ['ubuntu-latest', 'windows-latest']
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Core tests
uses: ./.github/actions/core-tests

View File

@@ -1,44 +0,0 @@
name: Package CLI
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
# Smoke test (render a badge with the CLI) with only the package
# dependencies installed.
jobs:
test-package-cli:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
engine-strict: 'false'
- node: '18'
engine-strict: 'true'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node JS ${{ inputs.node-version }}
uses: actions/setup-node@v3
with:
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
npm link
- name: Render a badge with the CLI
run: |
cd badge-maker
badge cactus grown :green @flat

View File

@@ -1,32 +0,0 @@
name: Package Library
on:
pull_request:
push:
branches-ignore:
- 'gh-pages'
jobs:
test-package-lib:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- node: '14'
engine-strict: 'false'
- node: '16'
engine-strict: 'true'
- node: '18'
engine-strict: 'false'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ matrix.node }}
env:
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
- name: Package tests
uses: ./.github/actions/package-tests

2
.npmrc
View File

@@ -1,2 +0,0 @@
engine-strict=true
strict-peer-deps=true

View File

@@ -4,68 +4,20 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2022-09-04
- fix frontend compile for users running on Windows [#8350](https://github.com/badges/shields/issues/8350)
- [DockerSize] Docker image size multi arch [#8290](https://github.com/badges/shields/issues/8290)
- upgrade gatsby [#8334](https://github.com/badges/shields/issues/8334)
- Custom domains for [JitPack] artifacts [#8333](https://github.com/badges/shields/issues/8333)
- fix [dockerstars] service [#8316](https://github.com/badges/shields/issues/8316)
- [BountySource] Fix: Broken Badge generation for decimal activity values [#8315](https://github.com/badges/shields/issues/8315)
- feat: add [gitlabmergerequests] service [#8166](https://github.com/badges/shields/issues/8166)
- Fix terminology for [ROS] version service [#8292](https://github.com/badges/shields/issues/8292)
- feat: add [GitlabStars] service [#8209](https://github.com/badges/shields/issues/8209)
- Fix invalid `rst` format when `alt` or `target` is present [#8275](https://github.com/badges/shields/issues/8275)
- [GithubGistLastCommit] GitHub gist last commit [#8272](https://github.com/badges/shields/issues/8272)
- [GitHub] GitHub file size for a specific branch [#8262](https://github.com/badges/shields/issues/8262)
- Dependency updates
## server-2022-08-01
- [pypi] Add Framework Version Badges support [#8261](https://github.com/badges/shields/issues/8261)
- feat: add [GitlabForks] server [#8208](https://github.com/badges/shields/issues/8208)
- Update PyPI api according to https://warehouse.pypa.io/api-reference/json.html [#8251](https://github.com/badges/shields/issues/8251)
- Add [galaxytoolshed] Activity [#8164](https://github.com/badges/shields/issues/8164)
- [greasyfork] Add Greasy Fork rating badges [#8087](https://github.com/badges/shields/issues/8087)
- refactor(deps): Replace moment with dayjs [#8192](https://github.com/badges/shields/issues/8192)
- add spaces round pipe in [conda] badge [#8189](https://github.com/badges/shields/issues/8189)
- Add [ROS] version service [#8169](https://github.com/badges/shields/issues/8169)
- feat: add [gitlabissues] service [#8108](https://github.com/badges/shields/issues/8108)
- Dependency updates
## server-2022-07-03
- Add [galaxytoolshed] services [#8114](https://github.com/badges/shields/issues/8114)
- fix [gitlab] auth [#8145](https://github.com/badges/shields/issues/8145) [#8162](https://github.com/badges/shields/issues/8162)
- increase cache length on AUR version badge, run [AUR] [#8110](https://github.com/badges/shields/issues/8110)
- Use GraphQL to fix GitHub file count badges [github] [#8112](https://github.com/badges/shields/issues/8112)
- feat: add [gitlab] contributors service [#8084](https://github.com/badges/shields/issues/8084)
- [greasyfork] Add Greasy Fork service badges [#8080](https://github.com/badges/shields/issues/8080)
- Add [gitlablicense] services [#8024](https://github.com/badges/shields/issues/8024)
- [Spack] Package Manager: Update Domain [#8046](https://github.com/badges/shields/issues/8046)
- switch [jitpack] to use latestOk endpoint [#8041](https://github.com/badges/shields/issues/8041)
- Dependency updates
## server-2022-06-01
- Update GitLab logo (2022) [#7984](https://github.com/badges/shields/issues/7984)
- [GitHub] Added milestone property to GitHub issue details service [#7864](https://github.com/badges/shields/issues/7864)
- [Spack] Package Manager: Update Endpoint [#7957](https://github.com/badges/shields/issues/7957)
- Update Chocolatey API endpoint URL [#7952](https://github.com/badges/shields/issues/7952)
- [Flathub]Add downloads badge [#7724](https://github.com/badges/shields/issues/7724)
- replace the outdated Telegram logo with the newest [#7831](https://github.com/badges/shields/issues/7831)
- add [PUB] points badge [#7918](https://github.com/badges/shields/issues/7918)
- add [PUB] popularity badge [#7920](https://github.com/badges/shields/issues/7920)
- add [PUB] likes badge [#7916](https://github.com/badges/shields/issues/7916)
- Dependency updates
## server-2022-05-03
## server-2022-05-01
- move all public production config into yaml [#7885](https://github.com/badges/shields/issues/7885)
- [OSSFScorecard] Create scorecard badge service [#7687](https://github.com/badges/shields/issues/7687)
- Stringify [githublanguagecount] message [#7881](https://github.com/badges/shields/issues/7881)
- Stringify and trim whitespace from a few services [#7880](https://github.com/badges/shields/issues/7880)
- Reclassify test for versionColorForWordpressVersion as an integration test [#7879](https://github.com/badges/shields/issues/7879)
- Remove obsolete monitor.html [#7878](https://github.com/badges/shields/issues/7878)
- add labels to Dockerfile [#7862](https://github.com/badges/shields/issues/7862)
- fix: various sonar service tests [#7836](https://github.com/badges/shields/issues/7836)
- fix: bitrise example & service test [#7837](https://github.com/badges/shields/issues/7837)
- add dependency review action [#7816](https://github.com/badges/shields/issues/7816)
- handle missing 'fly-client-ip' [#7814](https://github.com/badges/shields/issues/7814)
- note ioredis breaking change in changelog [#7813](https://github.com/badges/shields/issues/7813)
- Dependency updates
## server-2022-04-03

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@>=8"
RUN npm install -g "npm@>=7"
# We need dev deps to build the front end. We don't need Cypress, though.
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci

View File

@@ -35,16 +35,6 @@
"WEBLATE_API_KEY": {
"description": "Configure the API key to be used for the Weblate service.",
"required": false
},
"METRICS_INFLUX_ENABLED": {
"description": "Disable influx metrics",
"value": "false",
"required": false
},
"REQUIRE_CLOUDFLARE": {
"description": "Allow direct traffic",
"value": "false",
"required": false
}
},
"formation": {

View File

@@ -2,7 +2,7 @@
## 4.0.0 [WIP]
- Drop compatibility with Node < 14
- Drop compatibility with Node 10
## 3.3.1

View File

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

View File

@@ -13,13 +13,6 @@ const serviceDir = path.join(
'services'
)
function toUnixPath(path) {
// glob does not allow \ as a path separator
// see https://github.com/isaacs/node-glob/blob/main/changelog.md#80
// so we need to convert to use / for use with glob
return path.replace(/\\/g, '/')
}
class InvalidService extends Error {
constructor(message) {
super(message)
@@ -29,9 +22,7 @@ class InvalidService extends Error {
async function loadServiceClasses(servicePaths) {
if (!servicePaths) {
servicePaths = glob.sync(
toUnixPath(path.join(serviceDir, '**', '*.service.js'))
)
servicePaths = glob.sync(path.join(serviceDir, '**', '*.service.js'))
}
const serviceClasses = []
@@ -51,8 +42,8 @@ async function loadServiceClasses(servicePaths) {
if (serviceClass && serviceClass.prototype instanceof BaseService) {
// Decorate each service class with the directory that contains it.
serviceClass.serviceFamily = servicePath
.replace(toUnixPath(serviceDir), '')
.split('/')[1]
.replace(serviceDir, '')
.split(path.sep)[1]
serviceClass.validateDefinition()
return serviceClasses.push(serviceClass)
}

View File

@@ -59,9 +59,7 @@ function _inferPullRequestFromTravisEnv(env) {
}
function _inferPullRequestFromCircleEnv(env) {
return parseGithubPullRequestUrl(
env.CI_PULL_REQUEST || env.CIRCLE_PULL_REQUEST
)
return parseGithubPullRequestUrl(env.CI_PULL_REQUEST)
}
/**

View File

@@ -1,13 +0,0 @@
import { defineConfig } from 'cypress'
export default defineConfig({
fixturesFolder: false,
env: {
backend_url: 'http://localhost:8080',
},
e2e: {
setupNodeEvents(on, config) {},
baseUrl: 'http://localhost:3000',
supportFile: false,
},
})

9
cypress.json Normal file
View File

@@ -0,0 +1,9 @@
{
"baseUrl": "http://localhost:3000",
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false,
"env": {
"backend_url": "http://localhost:8080"
}
}

View File

@@ -1,7 +1,3 @@
import { registerCommand } from 'cypress-wait-for-stable-dom'
registerCommand()
describe('Main page', function () {
const backendUrl = Cypress.env('backend_url')
const SEARCH_INPUT = 'input[placeholder="search / project URL"]'
@@ -13,13 +9,8 @@ describe('Main page', function () {
.should('have.attr', 'src', previewUrl)
}
function visitAndWait(page) {
cy.visit(page)
cy.waitForStableDOM({ pollInterval: 1000, timeout: 10000 })
}
it('Search for badges', function () {
visitAndWait('/')
cy.visit('/')
cy.get(SEARCH_INPUT).type('pypi')
@@ -27,7 +18,7 @@ describe('Main page', function () {
})
it('Shows badge from category', function () {
visitAndWait('/category/chat')
cy.visit('/category/chat')
expectBadgeExample(
'Discourse status',
@@ -38,7 +29,7 @@ describe('Main page', function () {
it('Suggest badges', function () {
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
visitAndWait('/')
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
cy.contains('Suggest badges').click()
@@ -48,7 +39,7 @@ describe('Main page', function () {
it('Customization form is filled with suggested badge details', function () {
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
visitAndWait('/')
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
cy.contains('Suggest badges').click()
@@ -60,7 +51,7 @@ describe('Main page', function () {
it('Customizate suggested badge', function () {
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
visitAndWait('/')
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
cy.contains('Suggest badges').click()
cy.contains(badgeUrl).click()
@@ -71,7 +62,7 @@ describe('Main page', function () {
})
it('Do not duplicate example parameters', function () {
visitAndWait('/category/funding')
cy.visit('/category/funding')
cy.contains('GitHub Sponsors').click()
cy.get('[name="style"]').should($style => {

View File

@@ -114,7 +114,7 @@ if (allFiles.length > 100) {
if (diff.includes('authHelper') && !secretsDocs.modified) {
warn(
[
':books: Remember to ensure any changes to `config.private` ',
`: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('')

View File

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

View File

@@ -3,9 +3,6 @@
- The format of new badges should be of the form `/SERVICE/NOUN/PARAMETERS?QUERYSTRING` e.g:
`/github/issues/:user/:repo`. The service is github, the
badge is for issues, and the parameters are `:user/:repo`.
- The `NOUN` part of the route is:
- singular if the badge message represents a single entity, such as the current status of a build (e.g: `/build`), or a more abstract or aggregate representation of the thing (e.g.: `/coverage`, `/quality`)
- plural if there are (or may) be many of the thing (e.g: `/dependencies`, `/stars`)
- Parameters should always be part of the route if they are required to display a badge e.g: `:packageName`.
- Common optional params like, `:branch` or `:tag` should also be passed as part of the route.
- Query string parameters should be used when:

View File

@@ -58,7 +58,7 @@ The tests are also divided into several parts:
[redis-token-persistence.integration]: https://github.com/badges/shields/blob/master/core/token-pooling/redis-token-persistence.integration.js
[github-api-provider.integration]: https://github.com/badges/shields/blob/master/services/github/github-api-provider.integration.js
Our goal is to reach 100% coverage of the code in the
Our goal is for the core code is to reach 100% coverage of the code in the
frontend, core, and service helper functions when the unit and functional
tests are run.
@@ -95,7 +95,7 @@ test this kind of logic through unit tests (e.g. of `render()` and
callback with the four parameters `( queryParams, match, end, ask )` which
is created in a legacy helper function in
[`legacy-request-handler.js`][legacy-request-handler]. This callback
delegates to a callback in `BaseService.register` with three different
delegates to a callback in `BaseService.register` with four different
parameters `( queryParams, match, sendBadge )`, which
then runs `BaseService.invoke`. `BaseService.invoke` instantiates the
service and runs `BaseService#handle`.

View File

@@ -40,7 +40,7 @@ If you are submitting a pull request for a custom logo, please:
- Install SVGO
- With npm: `npm install -g svgo`
- With Homebrew: `brew install svgo`
- Run the following command `svgo --precision=3 icon.svg -o icon.min.svg`
- Run the following command `svgo --precision=3 icon.svg icon.min.svg`
- Check if there is a loss of quality in the output, if so increase the precision.
- The [SVGOMG Online Tool][svgomg]
- Click "Open SVG" and select an SVG file.

View File

@@ -16,8 +16,9 @@ Production hosting is managed by the Shields ops team:
| Component | Subcomponent | People with access |
| ----------------------------- | ------------------------------- | --------------------------------------------------------------- |
| shields-io-production | Full access | @calebcartwright, @chris48s, @paulmelnikow |
| shields-io-production | Access management | @calebcartwright, @chris48s, @paulmelnikow |
| shields-production-us | Account owner | @calebcartwright, @paulmelnikow |
| shields-production-us | Full access | @calebcartwright, @chris48s, @paulmelnikow, @pyvesb |
| shields-production-us | Access management | @calebcartwright, @chris48s, @paulmelnikow, @pyvesb |
| Compose.io Redis | Account owner | @paulmelnikow |
| Compose.io Redis | Account access | @paulmelnikow |
| Compose.io Redis | Database connection credentials | @calebcartwright, @chris48s, @paulmelnikow, @pyvesb |
@@ -94,10 +95,13 @@ The raster server `raster.shields.io` (a.k.a. the rasterizing proxy) is
hosted on Heroku. It's managed in the
[squint](https://github.com/badges/squint/) repo.
### Fly.io Deployment
### Heroku Deployment
Both the badge server and frontend are served from Fly.io. Deployments are
triggered using GitHub actions in a private repo.
Both the badge server and frontend are served from Heroku.
After merging a commit to master, heroku should create a staging deploy. Check this has deployed correctly in the `shields-staging` pipeline and review https://shields-staging.herokuapp.com/
If we're happy with it, "promote to production". This will deploy what's on staging to the `shields-production-eu` and `shields-production-us` pieplines.
## DNS
@@ -105,15 +109,19 @@ DNS is registered with [DNSimple][].
[dnsimple]: https://dnsimple.com/
## Logs
Logs can be retrieved [from heroku](https://devcenter.heroku.com/articles/logging#log-retrieval).
## Error reporting
[Error reporting][sentry] is one of the most useful tools we have for monitoring
the server. It's generously donated by [Sentry][sentry home]. We bundle
[`@sentry/node`][sentry-node] into the application, and the Sentry DSN is configured
via `local-shields-io-production.yml` (see [documentation][sentry configuration]).
[`raven`][raven] into the application, and the Sentry DSN is configured via
`local-shields-io-production.yml` (see [documentation][sentry configuration]).
[sentry]: https://sentry.io/shields/
[sentry-node]: https://www.npmjs.com/package/@sentry/node
[raven]: https://www.npmjs.com/package/raven
[sentry home]: https://sentry.io/shields/
[sentry configuration]: https://github.com/badges/shields/blob/master/doc/self-hosting.md#sentry

View File

@@ -119,7 +119,7 @@ machine.
If you want to host PNG badges, you can also self-host a [raster server][]
which points to your badge server. It's a docker container. We host it on
Fly.io but should be possible to host on a wide variety of platforms.
Heroku but should be possible to host on a wide variety of platforms.
- In your raster instance, set `BASE_URL` to your Shields instance, e.g.
`https://shields.example.co`.

View File

@@ -67,7 +67,7 @@ t.create('Build status')
- All badges on shields can be requested in a number of formats. As well as calling https://img.shields.io/wercker/build/wercker/go-wercker-api.svg to generate ![](https://img.shields.io/wercker/build/wercker/go-wercker-api.svg) we can also call https://img.shields.io/wercker/build/wercker/go-wercker-api.json to request the same content as JSON. When writing service tests, we request the badge in JSON format so it is easier to make assertions about the content.
- We don't need to explicitly call `/wercker/build/wercker/go-wercker-api.json` here, only `/build/wercker/go-wercker-api.json`. When we create a tester object with `createServiceTester()` the URL base defined in our service class (in this case `/wercker`) is used as the base URL for any requests made by the tester object.
3. `expectBadge()` is a helper function which accepts either a string literal, a [RegExp][] or a [Joi][] schema for the different fields.
Joi is a validation library that is built into IcedFrisby which you can use to
Joi is a validation library that is build into IcedFrisby which you can use to
match based on a set of allowed strings, regexes, or specific values. You can
refer to their [API reference][joi api].
4. We expect `label` to be a string literal `"build"`.

View File

@@ -51,14 +51,14 @@ test(reStructuredText, () => {
'.. image:: https://img.shields.io/badge'
)
given('https://img.shields.io/badge', undefined, 'Example').expect(
'.. image:: https://img.shields.io/badge\n :alt: Example'
'.. image:: https://img.shields.io/badge :alt: Example'
)
given(
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect(
'.. image:: https://img.shields.io/badge\n :alt: Example\n :target: https://example.com/example'
'.. image:: https://img.shields.io/badge :alt: Example :target: https://example.com/example'
)
})

View File

@@ -33,10 +33,10 @@ export function reStructuredText(
): string {
let result = `.. image:: ${badgeUrl}`
if (title) {
result += `\n :alt: ${title}`
result += ` :alt: ${title}`
}
if (link) {
result += `\n :target: ${link}`
result += ` :target: ${link}`
}
return result
}

View File

@@ -134,7 +134,7 @@ export default function EndpointPage(): JSX.Element {
</p>
<p>
The endpoint badge is a better alternative than redirecting to the
static badge endpoint or generating SVG on your server:
static badge enpoint or generating SVG on your server:
</p>
<ol>
<li>
@@ -142,7 +142,7 @@ export default function EndpointPage(): JSX.Element {
Content and presentation are separate.
</a>{' '}
The service provider authors the badge, and Shields takes input from
the user to format it. As a service provider, you author the badge
the user to format it. As a service provider you author the badge
but don't have to concern yourself with styling. You don't even have
to pass the formatting options through to Shields.
</li>
@@ -152,12 +152,12 @@ export default function EndpointPage(): JSX.Element {
</li>
<li>
A JSON response is easy to implement; easier than an HTTP redirect.
It is trivial in almost any framework and is more compatible with
It is trivial in almost any framework, and is more compatible with
hosting environments such as{' '}
<a href="https://runkit.com/docs/endpoint">RunKit endpoints</a>.
</li>
<li>
As a service provider, you can rely on the Shields CDN. There's no
As a service provider you can rely on the Shields CDN. There's no
need to study the HTTP headers. Adjusting cache behavior is as
simple as setting a property in the JSON response.
</li>
@@ -197,7 +197,7 @@ export default function EndpointPage(): JSX.Element {
<dd>
Default: <code>false</code>. <code>true</code> to treat this as an
error badge. This prevents the user from overriding the color. In the
future, it may affect cache behavior.
future it may affect cache behavior.
</dd>
<dt>namedLogo</dt>
<dd>

View File

@@ -1,4 +1,4 @@
import * as originalSimpleIcons from 'simple-icons/icons'
import originalSimpleIcons from 'simple-icons'
import { svg2base64 } from './svg-helpers.js'
function loadSimpleIcons() {
@@ -14,29 +14,26 @@ function loadSimpleIcons() {
// https://github.com/badges/shields/issues/4273
Object.keys(originalSimpleIcons).forEach(key => {
const icon = originalSimpleIcons[key]
const { title, slug, hex } = icon
const title = icon.title.toLowerCase()
const legacyTitle = title.replace(/ /g, '-')
icon.base64 = {
default: svg2base64(icon.svg.replace('<svg', `<svg fill="#${hex}"`)),
light: svg2base64(icon.svg.replace('<svg', '<svg fill="whitesmoke"')),
dark: svg2base64(icon.svg.replace('<svg', '<svg fill="#333"')),
default: svg2base64(icon.svg.replace('<svg', `<svg fill="#${icon.hex}"`)),
light: svg2base64(icon.svg.replace('<svg', `<svg fill="whitesmoke"`)),
dark: svg2base64(icon.svg.replace('<svg', `<svg fill="#333"`)),
}
// There are a few instances where multiple icons have the same title
// (e.g. 'Hive'). If a by-title reference we generate for
// backwards compatibility collides with a proper slug from Simple Icons
// then do nothing, so that the proper slug will always map to the correct icon.
// Starting in v7, the exported object with the full icon set has updated the keys
// to include a lowercase `si` prefix, and utilizes proper case naming conventions.
if (!(`si${title}` in originalSimpleIcons)) {
simpleIcons[title.toLowerCase()] = icon
if (!(title in originalSimpleIcons)) {
simpleIcons[title] = icon
}
const legacyTitle = title.replace(/ /g, '-')
if (!(`si${legacyTitle}` in originalSimpleIcons)) {
simpleIcons[legacyTitle.toLowerCase()] = icon
if (!(legacyTitle in originalSimpleIcons)) {
simpleIcons[legacyTitle] = icon
}
simpleIcons[slug] = icon
simpleIcons[key] = icon
})
return simpleIcons
}

View File

@@ -33,7 +33,7 @@ describe('Logo helpers', function () {
test(prepareNamedLogo, () => {
// NPM uses multiple colors so the color param should be ignored
const npmLogo =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZD0iTTAgMGg0MHY0MEgwVjB6IiBmaWxsPSIjY2IwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPg=='
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZD0iTTAgMGg0MHY0MEgwVjB6IiBmaWxsPSIjY2IwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPgo='
given({ name: 'npm' }).expect(npmLogo)
given({ name: 'npm', color: 'blue' }).expect(npmLogo)
@@ -115,7 +115,7 @@ describe('Logo helpers', function () {
undefined
)
given('npm', {}).expect(
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZD0iTTAgMGg0MHY0MEgwVjB6IiBmaWxsPSIjY2IwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPg=='
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZD0iTTAgMGg0MHY0MEgwVjB6IiBmaWxsPSIjY2IwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPgo='
)
})
})

View File

@@ -1,7 +1,5 @@
function svg2base64(svg) {
return `data:image/svg+xml;base64,${Buffer.from(svg.trim()).toString(
'base64'
)}`
return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`
}
export { svg2base64 }

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="93 93 194 194"><defs><style>.b{fill:#fc6d26}</style></defs><path style="fill:#e24329" d="m282.83 170.73-.27-.69-26.14-68.22a6.81 6.81 0 0 0-2.69-3.24 7 7 0 0 0-8 .43 7 7 0 0 0-2.32 3.52l-17.65 54h-71.47l-17.65-54a6.86 6.86 0 0 0-2.32-3.53 7 7 0 0 0-8-.43 6.87 6.87 0 0 0-2.69 3.24L97.44 170l-.26.69a48.54 48.54 0 0 0 16.1 56.1l.09.07.24.17 39.82 29.82 19.7 14.91 12 9.06a8.07 8.07 0 0 0 9.76 0l12-9.06 19.7-14.91 40.06-30 .1-.08a48.56 48.56 0 0 0 16.08-56.04Z"/><path class="b" d="m282.83 170.73-.27-.69a88.3 88.3 0 0 0-35.15 15.8L190 229.25c19.55 14.79 36.57 27.64 36.57 27.64l40.06-30 .1-.08a48.56 48.56 0 0 0 16.1-56.08Z"/><path style="fill:#fca326" d="m153.43 256.89 19.7 14.91 12 9.06a8.07 8.07 0 0 0 9.76 0l12-9.06 19.7-14.91S209.55 244 190 229.25c-19.55 14.75-36.57 27.64-36.57 27.64Z"/><path class="b" d="M132.58 185.84A88.19 88.19 0 0 0 97.44 170l-.26.69a48.54 48.54 0 0 0 16.1 56.1l.09.07.24.17 39.82 29.82L190 229.21Z"/></svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.956 13.587l-1.344-4.133a4549.814 4549.814 0 0 0-2.663-8.189.456.456 0 0 0-.87 0l-2.658 8.189H7.585L4.92 1.265a.456.456 0 0 0-.87 0A4549.814 4549.814 0 0 0 .044 13.587a.908.908 0 0 0 .336 1.02L12 23.054l11.62-8.447a.908.908 0 0 0 .336-1.02" fill="#fc6d26"/><path d="M12 23.054l4.421-13.6H7.58z" fill="#e24329"/><path d="M7.579 9.454H1.388L12 23.054z" fill="#fc6d26"/><path d="M1.388 9.454L.044 13.587a.908.908 0 0 0 .336 1.02L12 23.054z" fill="#fca326"/><path d="M7.579 9.454L4.92 1.265a.456.456 0 0 0-.87 0L1.388 9.454z" fill="#e24329"/><path d="M16.421 9.454h6.191L12 23.054z" fill="#fc6d26"/><path d="M22.612 9.454l1.344 4.133a.908.908 0 0 1-.336 1.02L12 23.054z" fill="#fca326"/><path d="M16.421 9.454l2.658-8.189a.456.456 0 0 1 .87 0l2.663 8.189z" fill="#e24329"/></svg>

Before

Width:  |  Height:  |  Size: 986 B

After

Width:  |  Height:  |  Size: 847 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12Z" fill="url(#a)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.425 11.871a796.414 796.414 0 0 1 6.994-3.018c3.328-1.388 4.027-1.628 4.477-1.638.1 0 .32.02.47.14.12.1.15.23.17.33.02.1.04.31.02.47-.18 1.898-.96 6.504-1.36 8.622-.17.9-.5 1.199-.819 1.229-.7.06-1.229-.46-1.898-.9-1.06-.689-1.649-1.119-2.678-1.798-1.19-.78-.42-1.209.26-1.908.18-.18 3.247-2.978 3.307-3.228.01-.03.01-.15-.06-.21-.07-.06-.17-.04-.25-.02-.11.02-1.788 1.14-5.056 3.348-.48.33-.909.49-1.299.48-.43-.01-1.248-.24-1.868-.44-.75-.24-1.349-.37-1.299-.79.03-.22.33-.44.89-.669Z" fill="#fff"/><defs><linearGradient id="a" x1="11.99" y1="0" x2="11.99" y2="23.81" gradientUnits="userSpaceOnUse"><stop stop-color="#2AABEE"/><stop offset="1" stop-color="#229ED9"/></linearGradient></defs></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="12" fill="#2ca5e0"/><path d="M9.8 17.5c-.389 0-.323-.147-.457-.517L8.2 13.221 17 8" fill="#a9c9dd"/><path d="M9.8 17.5c.3 0 .433-.137.6-.3l1.6-1.556-1.996-1.203" fill="#c8daea"/><path d="M10.004 14.441l4.836 3.573c.552.304.95.147 1.088-.512l1.968-9.277c.202-.808-.308-1.174-.836-.935L5.501 11.748c-.789.316-.784.756-.144.952l2.967.926 6.867-4.332c.324-.197.622-.091.377.125" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 481 B

18895
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,30 +21,29 @@
"url": "https://github.com/badges/shields"
},
"dependencies": {
"@fontsource/lato": "^4.5.9",
"@fontsource/lekton": "^4.5.10",
"@fontsource/lato": "^4.5.5",
"@fontsource/lekton": "^4.5.6",
"@renovate/pep440": "^1.0.0",
"@sentry/node": "^7.13.0",
"@sentry/node": "^6.19.6",
"@shields_io/camp": "^18.1.1",
"badge-maker": "file:badge-maker",
"bytes": "^3.1.2",
"camelcase": "^7.0.0",
"camelcase": "^6.3.0",
"chalk": "^5.0.1",
"check-node-version": "^4.2.1",
"cloudflare-middleware": "^1.0.4",
"config": "^3.3.8",
"config": "^3.3.7",
"cross-env": "^7.0.3",
"dayjs": "^1.11.5",
"decamelize": "^3.2.0",
"emojic": "^1.1.17",
"escape-string-regexp": "^4.0.0",
"fast-xml-parser": "^4.0.10",
"glob": "^8.0.3",
"fast-xml-parser": "^4.0.7",
"glob": "^8.0.1",
"global-agent": "^3.0.0",
"got": "^12.4.1",
"got": "^12.0.3",
"graphql": "^15.6.1",
"graphql-tag": "^2.12.6",
"ioredis": "5.2.3",
"ioredis": "5.0.4",
"joi": "17.6.0",
"joi-extension-semver": "5.0.0",
"js-yaml": "^4.1.0",
@@ -52,16 +51,17 @@
"lodash.countby": "^4.6.0",
"lodash.groupby": "^4.6.0",
"lodash.times": "^4.3.2",
"moment": "^2.29.3",
"node-env-flag": "^0.1.0",
"parse-link-header": "^2.0.0",
"path-to-regexp": "^6.2.1",
"path-to-regexp": "^6.2.0",
"pretty-bytes": "^6.0.0",
"priorityqueuejs": "^2.0.0",
"prom-client": "^14.1.0",
"qs": "^6.11.0",
"prom-client": "^14.0.1",
"qs": "^6.10.3",
"query-string": "^7.1.1",
"semver": "~7.3.7",
"simple-icons": "7.11.0",
"simple-icons": "6.19.0",
"webextension-store-meta": "^1.0.5",
"xmldom": "~0.6.0",
"xpath": "~0.0.32"
@@ -141,12 +141,12 @@
]
},
"devDependencies": {
"@babel/core": "^7.19.1",
"@babel/core": "^7.17.9",
"@babel/polyfill": "^7.12.1",
"@babel/register": "7.18.9",
"@babel/register": "7.17.7",
"@istanbuljs/schema": "^0.1.3",
"@mapbox/react-click-to-select": "^2.2.1",
"@types/chai": "^4.3.3",
"@types/chai": "^4.3.1",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.groupby": "^4.6.7",
"@types/mocha": "^9.1.1",
@@ -154,12 +154,12 @@
"@types/react-helmet": "^6.1.5",
"@types/react-modal": "^3.13.1",
"@types/react-select": "^4.0.17",
"@types/styled-components": "5.1.26",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.30.7",
"@types/styled-components": "5.1.25",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.15.0",
"babel-plugin-inline-react-svg": "^2.0.1",
"babel-preset-gatsby": "^2.22.0",
"c8": "^7.12.0",
"babel-preset-gatsby": "^2.11.1",
"c8": "^7.11.2",
"caller": "^1.1.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
@@ -167,10 +167,9 @@
"chai-string": "^1.4.0",
"child-process-promise": "^2.2.1",
"clipboard-copy": "^4.0.1",
"concurrently": "^7.4.0",
"cypress": "^10.8.0",
"cypress-wait-for-stable-dom": "^0.0.3",
"danger": "^11.1.2",
"concurrently": "^7.1.0",
"cypress": "^9.5.4",
"danger": "^11.0.5",
"danger-plugin-no-test-shortcuts": "^2.0.0",
"deepmerge": "^4.2.2",
"eslint": "^7.32.0",
@@ -181,30 +180,30 @@
"eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.3.6",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-jsdoc": "^39.2.7",
"eslint-plugin-mocha": "^10.0.3",
"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.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-sort-class-members": "^1.15.2",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"eslint-plugin-sort-class-members": "^1.14.1",
"fetch-ponyfill": "^7.1.0",
"form-data": "^4.0.0",
"gatsby": "4.23.0",
"gatsby-plugin-catch-links": "^4.19.0",
"gatsby-plugin-page-creator": "^4.22.0",
"gatsby-plugin-react-helmet": "^5.22.0",
"gatsby": "4.6.2",
"gatsby-plugin-catch-links": "^4.11.0",
"gatsby-plugin-page-creator": "^4.7.0",
"gatsby-plugin-react-helmet": "^5.10.0",
"gatsby-plugin-remove-trailing-slashes": "^4.9.0",
"gatsby-plugin-styled-components": "^5.19.0",
"gatsby-plugin-typescript": "^4.22.0",
"gatsby-plugin-styled-components": "^5.11.0",
"gatsby-plugin-typescript": "^4.11.1",
"humanize-string": "^2.1.0",
"icedfrisby": "4.0.0",
"icedfrisby-nock": "^2.1.0",
"is-svg": "^4.3.2",
"js-yaml-loader": "^1.2.2",
"jsdoc": "^3.6.11",
"lint-staged": "^13.0.3",
"jsdoc": "^3.6.10",
"lint-staged": "^12.4.1",
"lodash.debounce": "^4.0.8",
"lodash.difference": "^4.5.0",
"minimist": "^1.2.6",
@@ -212,38 +211,38 @@
"mocha-env-reporter": "^4.0.0",
"mocha-junit-reporter": "^2.0.2",
"mocha-yaml-loader": "^1.0.3",
"nock": "13.2.9",
"nock": "13.2.4",
"node-mocks-http": "^1.11.0",
"nodemon": "^2.0.20",
"nodemon": "^2.0.15",
"npm-run-all": "^4.1.5",
"open-cli": "^7.0.1",
"portfinder": "^1.0.32",
"prettier": "2.7.1",
"portfinder": "^1.0.28",
"prettier": "2.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-overlay": "^6.0.11",
"react-helmet": "^6.1.0",
"react-modal": "^3.15.1",
"react-modal": "^3.14.4",
"react-pose": "^4.0.10",
"react-select": "^4.3.1",
"read-all-stdin-sync": "^1.0.5",
"redis-server": "^1.2.2",
"rimraf": "^3.0.2",
"sazerac": "^2.0.0",
"simple-git-hooks": "^2.8.0",
"sinon": "^14.0.0",
"simple-git-hooks": "^2.7.0",
"sinon": "^13.0.2",
"sinon-chai": "^3.7.0",
"snap-shot-it": "^7.9.6",
"start-server-and-test": "1.14.0",
"styled-components": "^5.3.5",
"ts-mocha": "^10.0.0",
"tsd": "^0.24.1",
"typescript": "^4.8.3",
"ts-mocha": "^9.0.2",
"tsd": "^0.20.0",
"typescript": "^4.6.4",
"url": "^0.11.0"
},
"engines": {
"node": "^16.13.0",
"npm": ">=8.0.0"
"npm": ">=7.0.0"
},
"type": "module",
"collective": {

View File

@@ -29,7 +29,7 @@ async function captureTimings(warmupIterations) {
function logResults({ times, iterations, warmupIterations }) {
if (isNaN(iterations)) {
console.log(
'No timings captured. Have you included console.time statements in the badge creation code path?'
`No timings captured. Have you included console.time statements in the badge creation code path?`
)
} else {
const timedIterations = iterations - warmupIterations

View File

@@ -1,36 +0,0 @@
import fs from 'fs'
let data
let title
try {
if (process.argv.length < 4) {
throw new Error()
}
title = process.argv[2]
data = JSON.parse(fs.readFileSync(process.argv[3]))
} catch (e) {
process.stdout.write('failed to write summary\n')
process.exit(1)
}
process.stdout.write(`# ${title}\n\n`)
if (data.stats.passes > 0) {
process.stdout.write(`${data.stats.passes} passed\n`)
}
if (data.stats.failures > 0) {
process.stdout.write(`${data.stats.failures} failed\n\n`)
}
if (data.stats.failures > 0) {
for (const test of data.tests) {
if (test.err && Object.keys(test.err).length > 0) {
process.stdout.write(`### ${test.title}\n\n`)
process.stdout.write(`${test.fullTitle}\n\n`)
process.stdout.write('```\n')
process.stdout.write(`${test.err.stack}\n`)
process.stdout.write('```\n\n')
}
}
}

34
scripts/run_package_tests.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
# https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/3
# Start off less strict to work around various nvm errors.
set -e
export NVM_DIR="/opt/circleci/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"
nvm install $NODE_VERSION
nvm use $NODE_VERSION
# Stricter.
set -euo pipefail
node --version
# Install the shields.io dependencies.
npm ci
# Run the package tests.
npm run test:package
npm run check-types:package
# Delete the full shields.io dependency tree
rm -rf node_modules/
# Run a smoke test (render a badge with the CLI) with only the package
# dependencies installed.
cd badge-maker
npm install # install only the package dependencies for this test
npm link
badge cactus grown :green @flat
rm package-lock.json && rm -rf node_modules/ # clean up package dependencies

View File

@@ -108,7 +108,9 @@ class AurVotes extends BaseAurService {
class AurVersion extends BaseAurService {
static category = 'version'
static route = { base: 'aur/version', pattern: ':packageName' }
static examples = [
{
title: 'AUR version',
@@ -117,8 +119,6 @@ class AurVersion extends BaseAurService {
},
]
static _cacheLength = 3600
static render({ version, outOfDate }) {
const color = outOfDate === null ? 'blue' : 'orange'
return { message: addv(version), color }

View File

@@ -8,7 +8,7 @@ import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('unknown build definition')
.get('/swellaby/opensource/99999999.json')
.get(`/swellaby/opensource/99999999.json`)
.expectBadge({ label: 'tests', message: 'build pipeline not found' })
t.create('404 latest build error response')
@@ -51,7 +51,7 @@ t.create('no test result summary response')
})
t.create('no build response')
.get('/swellaby/opensource/174.json')
.get(`/swellaby/opensource/174.json`)
.expectBadge({ label: 'tests', message: 'build pipeline not found' })
t.create('no tests in test result summary response')

View File

@@ -27,7 +27,7 @@ function pullRequestClassGenerator(raw) {
static category = 'issue-tracking'
static route = {
base: `bitbucket/${routePrefix}`,
pattern: ':user/:repo',
pattern: `:user/:repo`,
queryParamSchema,
}

View File

@@ -1,8 +1,9 @@
import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { metric } from '../text-formatters.js'
import { BaseJsonService } from '../index.js'
const schema = Joi.object({ activity_total: Joi.number().required() })
const schema = Joi.object({ activity_total: nonNegativeInteger })
export default class Bountysource extends BaseJsonService {
static category = 'funding'

View File

@@ -1,7 +1,5 @@
import {
isVPlusDottedVersionAtLeastOne,
isVPlusDottedVersionNClausesWithOptionalSuffix,
} from '../test-validators.js'
import Joi from 'joi'
import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js'
import { ServiceTester } from '../tester.js'
export const t = new ServiceTester({
id: 'BowerVersion',
@@ -9,17 +7,21 @@ export const t = new ServiceTester({
pathPrefix: '/bower',
})
t.create('version').timeout(10000).get('/v/angular.json').expectBadge({
const isBowerPrereleaseVersion = Joi.string().regex(
/^v\d+(\.\d+)?(\.\d+)?(-?[.\w\d])+?$/
)
t.create('version').timeout(10000).get('/v/bootstrap.json').expectBadge({
label: 'bower',
message: isVPlusDottedVersionAtLeastOne,
})
t.create('pre version')
t.create('pre version') // e.g. bower|v0.2.5-alpha-rc-pre
.timeout(10000)
.get('/v/angular.json?include_prereleases')
.get('/v/bootstrap.json?include_prereleases')
.expectBadge({
label: 'bower',
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
message: isBowerPrereleaseVersion,
})
t.create('Version for Invalid Package')

View File

@@ -1,9 +1,3 @@
/**
* Common functions and schemas for tasks related to build status.
*
* @module
*/
import Joi from 'joi'
const greenStatuses = [
@@ -56,23 +50,8 @@ const allStatuses = greenStatuses
.concat(redStatuses)
.concat(otherStatuses)
/**
* Joi schema for validating Build Status.
* Checks if the build status is present in the list of allowed build status.
*
* @type {Joi}
*/
const isBuildStatus = Joi.equal(...allStatuses)
/**
* Handles rendering concerns of badges that display build status.
* Determines the message and color of the badge according to the build status.
*
* @param {object} attrs Refer to individual attributes
* @param {string} [attrs.label] If provided then badge label is set to this value
* @param {string} attrs.status Build status
* @returns {object} Badge with label, message and color properties
*/
function renderBuildStatusBadge({ label, status }) {
let message
let color

View File

@@ -3,7 +3,7 @@ import { createServiceFamily } from '../nuget/nuget-v2-service-family.js'
export default createServiceFamily({
defaultLabel: 'chocolatey',
serviceBaseUrl: 'chocolatey',
apiBaseUrl: 'https://community.chocolatey.org/api/v2',
apiBaseUrl: 'https://www.chocolatey.org/api/v2',
odataFormat: 'json',
title: 'Chocolatey',
examplePackageName: 'git',

View File

@@ -3,32 +3,32 @@ import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('level known project')
.get('/level/1.json')
.get(`/level/1.json`)
.expectBadge({
label: 'cii',
message: withRegex(/in progress|passing|silver|gold/),
})
t.create('percentage known project')
.get('/percentage/29.json')
.get(`/percentage/29.json`)
.expectBadge({
label: 'cii',
message: withRegex(/([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-9][0-9]|300)%/),
})
t.create('summary known project')
.get('/summary/33.json')
.get(`/summary/33.json`)
.expectBadge({
label: 'cii',
message: withRegex(/(in progress [0-9]|[1-9][0-9]%)|passing|silver|gold/),
})
t.create('unknown project')
.get('/level/abc.json')
.get(`/level/abc.json`)
.expectBadge({ label: 'cii', message: 'project not found' })
t.create('level: gold project')
.get('/level/1.json')
.get(`/level/1.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/1/badge.json')
@@ -43,7 +43,7 @@ t.create('level: gold project')
})
t.create('level: silver project')
.get('/level/34.json')
.get(`/level/34.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/34/badge.json')
@@ -58,7 +58,7 @@ t.create('level: silver project')
})
t.create('level: passing project')
.get('/level/29.json')
.get(`/level/29.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/29/badge.json')
@@ -73,7 +73,7 @@ t.create('level: passing project')
})
t.create('level: in progress project')
.get('/level/33.json')
.get(`/level/33.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/33/badge.json')
@@ -88,7 +88,7 @@ t.create('level: in progress project')
})
t.create('percentage: gold project')
.get('/percentage/1.json')
.get(`/percentage/1.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/1/badge.json')
@@ -103,7 +103,7 @@ t.create('percentage: gold project')
})
t.create('percentage: silver project')
.get('/percentage/34.json')
.get(`/percentage/34.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/34/badge.json')
@@ -118,7 +118,7 @@ t.create('percentage: silver project')
})
t.create('percentage: passing project')
.get('/percentage/29.json')
.get(`/percentage/29.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/29/badge.json')
@@ -133,7 +133,7 @@ t.create('percentage: passing project')
})
t.create('percentage: in progress project')
.get('/percentage/33.json')
.get(`/percentage/33.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/33/badge.json')
@@ -148,7 +148,7 @@ t.create('percentage: in progress project')
})
t.create('summary: gold project')
.get('/summary/1.json')
.get(`/summary/1.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/1/badge.json')
@@ -163,7 +163,7 @@ t.create('summary: gold project')
})
t.create('summary: silver project')
.get('/summary/34.json')
.get(`/summary/34.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/34/badge.json')
@@ -178,7 +178,7 @@ t.create('summary: silver project')
})
t.create('summary: passing project')
.get('/summary/29.json')
.get(`/summary/29.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/29/badge.json')
@@ -193,7 +193,7 @@ t.create('summary: passing project')
})
t.create('summary: in progress project')
.get('/summary/33.json')
.get(`/summary/33.json`)
.intercept(nock =>
nock('https://bestpractices.coreinfrastructure.org/projects')
.get('/33/badge.json')

View File

@@ -60,7 +60,7 @@ t.create('handles unauthorized private repository')
.intercept(nock =>
nock('https://codecov.io')
.get('/github/codecov/private-example-python/graph/badge.svg')
.reply(200, '<g><text x="105.5" y="14">unknown</text></g>', {
.reply(200, `<g><text x="105.5" y="14">unknown</text></g>`, {
'Content-Type': 'image/svg+xml',
})
)
@@ -110,7 +110,7 @@ t.create('gets coverage for private repository')
.get(
'/gh/codecov/private-example-python/graph/badge.svg?token=a1b2c3d4e5'
)
.reply(200, '<g><text x="105.5" y="14">100%</text></g>', {
.reply(200, `<g><text x="105.5" y="14">100%</text></g>`, {
'Content-Type': 'image/svg+xml',
})
)

View File

@@ -1,19 +1,10 @@
/**
* Commonly-used functions for determining the colour to use for a badge,
* including colours based off download count, version number, etc.
*
* @module
*/
import dayjs from 'dayjs'
import moment from 'moment'
import pep440 from '@renovate/pep440'
/**
* Determines the color used for a badge based on version.
*
* @param {string|number} version Version used for determining badge color
* @returns {string} Badge color
*/
function version(version) {
if (typeof version !== 'string' && typeof version !== 'number') {
throw new Error(`Can't generate a version color for ${version}`)
@@ -30,12 +21,6 @@ function version(version) {
}
}
/**
* Determines the color used for a badge based on PEP440 versioning.
*
* @param {string|number} version Version used for determining badge color
* @returns {string} Badge color
*/
function pep440VersionColor(version) {
if (!pep440.valid(version)) {
return 'lightgrey'
@@ -47,18 +32,6 @@ function pep440VersionColor(version) {
return 'blue'
}
/**
* Determines the color used for a badge by comparing the value and floor count values.
* The color can vary from red to bright green depending on the range the value lies in.
* Decreasing the value will shift the color towards red.
* Increasing the value will shift the color towards bright green.
*
* @param {number} value Current value
* @param {number} yellow Yellow color threshold, should be greater than 0
* @param {number} yellowgreen Yellowgreen color threshold, should be greater than yellow
* @param {number} green Green color threshold, should be greater than yellowgreen
* @returns {string} Badge color
*/
function floorCount(value, yellow, yellowgreen, green) {
if (value <= 0) {
return 'red'
@@ -73,37 +46,14 @@ function floorCount(value, yellow, yellowgreen, green) {
}
}
/**
* Determines the color used for a badge by comparing the download count and floor values.
* The color varies from red to bright green as the download count increases.
*
* @param {number} downloads Download count
* @returns {string} Badge color
*/
function downloadCount(downloads) {
return floorCount(downloads, 10, 100, 1000)
}
/**
* Determines the color used for a badge by comparing percentage and floor values.
* The color varies from red to bright green as the percentage increases.
*
* @param {number} percentage Percentage value
* @returns {string} Badge color
*/
function coveragePercentage(percentage) {
return floorCount(percentage, 80, 90, 100)
}
/**
* Determines the color used for a badge by matching score with grade values.
* The color varies from bright green to red as the score decreases.
* The score can be one of the following grade value: ['A', 'B', 'C', 'D', 'E'].
* The color defaults to red if the score does not matches with any of the grade values.
*
* @param {string} score Score value
* @returns {string} Badge color
*/
function letterScore(score) {
if (score === 'A') {
return 'brightgreen'
@@ -120,18 +70,6 @@ function letterScore(score) {
}
}
/**
* Creates a callback function that determines badge color from the colors array.
* If the colors array is provided then for n steps, there should be n + 1 color.
* If the colors array is not provided then it is chosen from the default colors array
* according to the size of the steps array.
*
* @param {number[]} steps Steps array
* @param {string[]} colors Colors array. If provided, should be of length steps.length + 1
* @param {boolean} reversed If true then the colors array will be considered in reverse order
* @returns {function(number): string} Function that finds the step index by comparing value
* with steps array and returns color from colors array for the corresponding step index
*/
function colorScale(steps, colors, reversed) {
if (steps === undefined) {
throw Error('When invoking colorScale, steps should be provided.')
@@ -172,17 +110,9 @@ function colorScale(steps, colors, reversed) {
}
}
/**
* Determines the color used for a badge according to the age.
* Age is calculated as days elapsed till current date.
* The color varies from bright green to red as the age increases.
*
* @param {string} date Date string
* @returns {string} Badge color
*/
function age(date) {
const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, true)
const daysElapsed = dayjs().diff(dayjs(date), 'days')
const daysElapsed = moment().diff(moment(date), 'days')
return colorByAge(daysElapsed)
}

View File

@@ -31,7 +31,7 @@ export default class CondaVersion extends BaseCondaService {
static render({ variant, channel, version }) {
return {
label: variant === 'vn' ? channel : `conda | ${channel}`,
label: variant === 'vn' ? channel : `conda|${channel}`,
message: versionText(version),
color: versionColor(version),
}

View File

@@ -3,7 +3,7 @@ import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('version').get('/v/conda-forge/zlib.json').expectBadge({
label: 'conda | conda-forge',
label: 'conda|conda-forge',
message: isVPlusTripleDottedVersion,
})

View File

@@ -1,18 +1,5 @@
/**
* Common functions and utilities for tasks related to contributor count.
*
* @module
*/
import { metric } from './text-formatters.js'
/**
* Determines the color used for a badge based on the contributor count.
* The color varies from red to bright green as the contributor count increases.
*
* @param {number} contributorCount Contributor count
* @returns {string} Badge color
*/
function contributorColor(contributorCount) {
if (contributorCount > 2) {
return 'brightgreen'
@@ -23,15 +10,6 @@ function contributorColor(contributorCount) {
}
}
/**
* Handles rendering concerns of badges that display contributor count.
* Determines the message and color of the badge according to the contributor count.
*
* @param {object} attrs Refer to individual attributes
* @param {string} [attrs.label] If provided then badge label is set to this value
* @param {number} attrs.contributorCount Contributor count
* @returns {object} Badge with label, message and color properties
*/
function renderContributorBadge({ label, contributorCount }) {
return {
label,

View File

@@ -0,0 +1,9 @@
import { Deprecated } from './index.js'
function enforceDeprecation(effectiveDate) {
if (Date.now() >= effectiveDate.getTime()) {
throw new Deprecated()
}
}
export { enforceDeprecation }

View File

@@ -0,0 +1,15 @@
import { expect } from 'chai'
import { Deprecated } from '../core/base-service/errors.js'
import { enforceDeprecation } from './deprecation-helpers.js'
describe('enforceDeprecation', function () {
it('throws Deprecated for a date in the past', function () {
expect(() => enforceDeprecation(new Date())).to.throw(Deprecated)
})
it('does not throw for a date in the future', function () {
expect(() =>
enforceDeprecation(new Date(Date.now() + 10000))
).not.to.throw()
})
})

View File

@@ -14,12 +14,12 @@ describe('Discord', function () {
},
}
const scope = nock('https://discord.com', {
const scope = nock(`https://discord.com`, {
// This ensures that the expected credential is actually being sent with the HTTP request.
// Without this the request wouldn't match and the test would fail.
reqheaders: { Authorization: 'Bot password' },
reqheaders: { Authorization: `Bot password` },
})
.get('/api/v6/guilds/12345/widget.json')
.get(`/api/v6/guilds/12345/widget.json`)
.reply(200, {
presence_count: 125,
})

View File

@@ -72,7 +72,7 @@ class DiscourseStatus extends DiscourseBase {
static route = this.buildRoute('status')
static examples = [
{
title: 'Discourse status',
title: `Discourse status`,
namedParams: {},
queryParams: {
server: 'https://meta.discourse.org',

View File

@@ -28,12 +28,6 @@ export default class DockerCloudAutomatedBuild extends BaseJsonService {
async handle({ user, repo }) {
const data = await fetchBuild(this, { user, repo })
if (data.objects.length === 0) {
return this.constructor.render({
buildSettings: [],
})
}
return this.constructor.render({
buildSettings: data.objects[0].build_settings,
})

View File

@@ -5,29 +5,13 @@ export const t = await createServiceTester()
const isAutomatedBuildStatus = Joi.string().valid('automated', 'manual')
t.create('docker cloud automated build (valid user)')
.get('/pavics/magpie.json')
t.create('docker cloud automated build (valid, user)')
.get('/jrottenberg/ffmpeg.json')
.expectBadge({
label: 'docker build',
message: isAutomatedBuildStatus,
})
t.create('docker cloud automated build status (invalid, nonexisting user)')
.get('/pavicsssss/magpie.json')
.expectBadge({
label: 'docker build',
message: 'manual',
})
t.create(
"docker cloud build status (valid user, but the 'objects' array from the response is empty)"
)
.get('/pavics/weaver.json')
.expectBadge({
label: 'docker build',
message: 'manual',
})
t.create('docker cloud automated build (not found)')
.get('/badges/not-a-real-repo.json')
.intercept(nock =>

View File

@@ -1,4 +1,4 @@
import { BaseJsonService, NotFound } from '../index.js'
import { BaseJsonService } from '../index.js'
import { dockerBlue, buildDockerUrl } from './docker-helpers.js'
import { fetchBuild } from './docker-cloud-common-fetch.js'
@@ -31,12 +31,6 @@ export default class DockerCloudBuild extends BaseJsonService {
async handle({ user, repo }) {
const data = await fetchBuild(this, { user, repo })
if (data.objects.length === 0) {
throw new NotFound({
prettyMessage: 'automated builds not set up',
})
}
return this.constructor.render({ state: data.objects[0].state })
}
}

View File

@@ -3,29 +3,13 @@ import { createServiceTester } from '../tester.js'
import { dockerBlue } from './docker-helpers.js'
export const t = await createServiceTester()
t.create('docker cloud build status (valid user)')
.get('/pavics/magpie.json')
t.create('docker cloud build status (valid, user)')
.get('/jrottenberg/ffmpeg.json')
.expectBadge({
label: 'docker build',
message: isBuildStatus,
})
t.create('docker cloud build status (invalid, nonexisting user)')
.get('/pavicsssss/magpie.json')
.expectBadge({
label: 'docker build',
message: 'automated builds not set up',
})
t.create(
"docker cloud build status (valid user, but the 'objects' array from the response is empty)"
)
.get('/pavics/weaver.json')
.expectBadge({
label: 'docker build',
message: 'automated builds not set up',
})
t.create('docker cloud build status (not found)')
.get('/badges/not-a-real-repo.json')
.intercept(nock =>

View File

@@ -6,7 +6,7 @@ const cloudBuildSchema = Joi.object({
Joi.object({
state: Joi.string(),
build_settings: Joi.array(),
})
}).required()
)
.required(),
}).required()
@@ -14,7 +14,7 @@ const cloudBuildSchema = Joi.object({
async function fetchBuild(serviceInstance, { user, repo }) {
return serviceInstance._requestJson({
schema: cloudBuildSchema,
url: 'https://cloud.docker.com/api/build/v1/source',
url: `https://cloud.docker.com/api/build/v1/source`,
options: { searchParams: { image: `${user}/${repo}` } },
errorMessages: { 404: 'repo not found' },
})

View File

@@ -1,36 +1,71 @@
const sizeDataNoTagSemVerSort = [
{
full_size: 300000000,
name: 'v4.0.0-alpha2',
images: [
{ architecture: 'amd64', size: 219939484 },
{ architecture: 'arm64', size: 200000000 },
],
},
{
full_size: 400000000,
name: 'v4.2.4',
images: [
{ architecture: 'amd64', size: 220000000 },
{ architecture: 'arm64', size: 210000000 },
],
},
{
full_size: 100000000,
name: 'v3.9.7',
images: [
{ architecture: 'amd64', size: 120000000 },
{ architecture: 'arm64', size: 110000000 },
],
},
{
full_size: 500000000,
name: 'latest',
images: [
{ architecture: 'amd64', size: 560000000 },
{ architecture: 'arm64', size: 460000000 },
],
},
{ name: 'master', full_size: 13449470 },
{ name: 'feature-smtps-support', full_size: 13449638 },
{ name: 'latest', full_size: 13448411 },
{ name: '4', full_size: 13448411 },
{ name: '4.3', full_size: 13448411 },
{ name: '4.3.0', full_size: 13448411 },
{ name: '4.2', full_size: 13443674 },
{ name: '4.2.0', full_size: 13443674 },
{ name: '4.1', full_size: 19244435 },
{ name: '4.1.0', full_size: 19244435 },
{ name: 'v4.0.0-alpha2', full_size: 10933605 },
{ name: 'v4.0.0-alpha1', full_size: 10933644 },
{ name: '4.0.0', full_size: 11512227 },
{ name: '4.0', full_size: 11512227 },
{ name: 'v2.1.9', full_size: 29739490 },
{ name: 'v2.1.10', full_size: 29739842 },
{ name: 'v3.0.0', full_size: 32882980 },
{ name: 'v3.0.1', full_size: 32880923 },
{ name: 'v3.1.0', full_size: 32441549 },
{ name: 'v3.1.1', full_size: 32441767 },
{ name: 'v3.1.2', full_size: 32442741 },
{ name: 'v3.1.3', full_size: 32442629 },
{ name: 'v3.1.4', full_size: 32478607 },
{ name: 'v3.2.0', full_size: 33489914 },
{ name: 'v3.3.0', full_size: 33628545 },
{ name: 'v3.3.1', full_size: 33629018 },
{ name: 'v3.3.3', full_size: 33628988 },
{ name: 'v3.3.4', full_size: 33629019 },
{ name: 'v3.3.6', full_size: 33628753 },
{ name: 'v3.3.7', full_size: 33629556 },
{ name: 'v3.3.8', full_size: 33644261 },
{ name: 'v3.3.9', full_size: 33644175 },
{ name: 'v3.3.10', full_size: 33644406 },
{ name: 'v3.3.11', full_size: 33644430 },
{ name: 'v3.3.12', full_size: 33644703 },
{ name: 'v3.3.13', full_size: 33644377 },
{ name: 'v3.3.15', full_size: 33644581 },
{ name: 'v3.3.16', full_size: 33644663 },
{ name: 'v3.3.17', full_size: 33644228 },
{ name: 'v3.3.18', full_size: 33644466 },
{ name: 'v3.3.19', full_size: 33644724 },
{ name: 'v3.4.0', full_size: 34918552 },
{ name: 'v3.4.2', full_size: 33605129 },
{ name: 'v3.5.0', full_size: 33582915 },
{ name: 'v3.6.0', full_size: 34789944 },
{ name: 'develop', full_size: 38129308 },
{ name: 'v3.7.0', full_size: 38179583 },
{ name: 'v3.7.1', full_size: 38614944 },
{ name: 'v3.8.0', full_size: 42962384 },
{ name: 'v3.8.1', full_size: 40000713 },
{ name: 'v3.8.2', full_size: 40000567 },
{ name: 'v3.8.3', full_size: 40040963 },
{ name: 'v3.9.0', full_size: 40044357 },
{ name: 'v3.9.1', full_size: 40048123 },
{ name: 'v3.9.2', full_size: 40047663 },
{ name: 'v3.9.3', full_size: 40048204 },
{ name: 'v3.9.4', full_size: 40049571 },
{ name: 'v3.9.5', full_size: 40049695 },
{ name: 'v3.10.0', full_size: 39940736 },
{ name: 'v3.11.0', full_size: 39928170 },
{ name: 'v3.12.0', full_size: 39966770 },
{ name: 'v3.13.0', full_size: 38556045 },
{ name: 'v3.14.0', full_size: 38574008 },
{ name: 'v3.15.0', full_size: 38578507 },
{ name: 'v3.16.0', full_size: 38852598 },
{ name: 'v3.16.1', full_size: 38851702 },
{ name: 'v3.16.2', full_size: 38969822 },
]
const versionDataNoTagDateSort = {
count: 4,

View File

@@ -1,28 +1,7 @@
import Joi from 'joi'
// see https://github.com/badges/shields/pull/1690
import { NotFound } from '../index.js'
const dockerBlue = '066da5'
// Valid architecture values: https://golang.org/doc/install/source#environment (GOARCH)
const archSchema = Joi.alternatives(
Joi.string().valid(
'amd64',
'arm',
'arm64',
's390x',
'386',
'ppc64',
'ppc64le',
'wasm',
'mips',
'mipsle',
'mips64',
'mips64le',
'riscv64'
),
Joi.number().valid(386).cast('string')
)
function buildDockerUrl(badgeName, includeTagRoute) {
if (includeTagRoute) {
return {
@@ -76,7 +55,6 @@ function getDigestSemVerMatches({ data, digest }) {
}
export {
archSchema,
dockerBlue,
buildDockerUrl,
getDockerHubUser,

View File

@@ -4,7 +4,6 @@ import { nonNegativeInteger } from '../validators.js'
import { latest } from '../version.js'
import { BaseJsonService, NotFound } from '../index.js'
import {
archSchema,
buildDockerUrl,
getDockerHubUser,
getMultiPageData,
@@ -13,12 +12,6 @@ import {
const buildSchema = Joi.object({
name: Joi.string().required(),
full_size: nonNegativeInteger.required(),
images: Joi.array().items(
Joi.object({
size: nonNegativeInteger.required(),
architecture: Joi.string().required(),
})
),
}).required()
const pagedSchema = Joi.object({
@@ -27,37 +20,14 @@ const pagedSchema = Joi.object({
Joi.object({
name: Joi.string().required(),
full_size: nonNegativeInteger.required(),
images: Joi.array().items(
Joi.object({
size: nonNegativeInteger.required(),
architecture: Joi.string().required(),
})
),
})
),
}).required()
const queryParamSchema = Joi.object({
sort: Joi.string().valid('date', 'semver').default('date'),
arch: archSchema,
}).required()
// If user provided the arch parameter,
// check if any of the returned images has an architecture matching the arch parameter provided.
// If yes, return the size of the image with this arch.
// If not, throw the `NotFound` error.
// For details see: https://github.com/badges/shields/issues/8238
function getImageSizeForArch(images, arch) {
const imgWithArch = Object.values(images).find(
img => img.architecture === arch
)
if (!imgWithArch) {
throw new NotFound({ prettyMessage: 'architecture not found' })
}
return imgWithArch.size
}
export default class DockerSize extends BaseJsonService {
static category = 'size'
static route = { ...buildDockerUrl('image-size', true), queryParamSchema }
@@ -76,14 +46,6 @@ export default class DockerSize extends BaseJsonService {
queryParams: { sort: 'semver' },
staticPreview: this.render({ size: 136000000 }),
},
{
title:
'Docker Image Size with architecture (latest by date/latest semver)',
pattern: ':user/:repo',
namedParams: { user: 'library', repo: 'mysql' },
queryParams: { sort: 'date', arch: 'amd64' },
staticPreview: this.render({ size: 146000000 }),
},
{
title: 'Docker Image Size (tag)',
pattern: ':user/:repo/:tag',
@@ -111,83 +73,30 @@ export default class DockerSize extends BaseJsonService {
})
}
getSizeFromImageByLatestDate(data, arch) {
if (data.count === 0) {
throw new NotFound({ prettyMessage: 'repository not found' })
} else {
const latestEntry = data.results[0]
if (arch) {
return { size: getImageSizeForArch(latestEntry.images, arch) }
transform({ tag, sort, data }) {
if (!tag && sort === 'date') {
if (data.count === 0) {
throw new NotFound({ prettyMessage: 'repository not found' })
} else {
return { size: latestEntry.full_size }
return { size: data.results[0].full_size }
}
}
}
getSizeFromImageByLatestSemver(data, arch) {
// If no tag is specified, and sorting is by semver, first filter out the entry containing the latest semver from the response with Docker images.
// If no architecture is supplied by the user, return `full_size` from this entry.
// If the architecture is supplied by the user, check if any of the returned images for this entry has an architecture matching the arch parameter supplied by the user.
// If yes, return the size of the image with this arch.
// If not, throw the `NotFound` error.
const [matches, versions, images] = data.reduce(
([m, v, i], d) => {
m[d.name] = d.full_size
v.push(d.name)
i[d.name] = d.images
return [m, v, i]
},
[{}, [], {}]
)
const version = latest(versions)
let sizeOfImgWithArch
if (arch) {
Object.keys(images).forEach(ver => {
if (ver === version) {
sizeOfImgWithArch = getImageSizeForArch(images[ver], arch)
return { size: sizeOfImgWithArch }
}
})
if (sizeOfImgWithArch) {
return { size: sizeOfImgWithArch }
} else {
throw new NotFound({ prettyMessage: 'architecture not found' })
}
} else {
} else if (!tag && sort === 'semver') {
const [matches, versions] = data.reduce(
([m, v], d) => {
m[d.name] = d.full_size
v.push(d.name)
return [m, v]
},
[{}, []]
)
const version = latest(versions)
return { size: matches[version] }
}
}
getSizeFromTag(data, arch) {
// If the tag is specified, and the architecture is supplied by the user,
// check if any of the returned images has an architecture matching the arch parameter supplied by the user.
// If yes, return the size of the image with this arch.
// If no, throw the `NotFound` error.
// If no architecture is supplied by the user, return the value of the `full_size` from the response (the image with the `latest` tag).
if (arch) {
return { size: getImageSizeForArch(data.images, arch) }
} else {
return { size: data.full_size }
}
}
transform({ tag, sort, data, arch }) {
if (!tag && sort === 'date') {
return this.getSizeFromImageByLatestDate(data, arch)
} else if (!tag && sort === 'semver') {
return this.getSizeFromImageByLatestSemver(data, arch)
} else {
return this.getSizeFromTag(data, arch)
}
}
async handle({ user, repo, tag }, { sort, arch }) {
async handle({ user, repo, tag }, { sort }) {
let data
if (!tag && sort === 'date') {
@@ -202,7 +111,7 @@ export default class DockerSize extends BaseJsonService {
data = await this.fetch({ user, repo, tag })
}
const { size } = await this.transform({ tag, sort, data, arch })
const { size } = await this.transform({ tag, sort, data })
return this.constructor.render({ size })
}
}

View File

@@ -3,99 +3,40 @@ import DockerSize from './docker-size.service.js'
import { sizeDataNoTagSemVerSort } from './docker-fixtures.js'
describe('DockerSize', function () {
test(DockerSize.prototype.getSizeFromImageByLatestDate, () => {
given(
{
count: 0,
results: [],
},
'amd64'
).expectError('Not Found: repository not found')
given(
{
count: 1,
results: [
{
full_size: 300000000,
name: 'next',
images: [{ architecture: 'amd64', size: 219939484 }],
},
],
},
'amd64'
).expect({
test(DockerSize.prototype.transform, () => {
given({
tag: '',
sort: 'date',
data: { results: [{ name: 'next', full_size: 219939484 }] },
}).expect({
size: 219939484,
})
given({
count: 1,
results: [
{
full_size: 300000000,
name: 'next',
images: [
{ architecture: 'amd64', size: 219939484 },
{ architecture: 'arm64', size: 200000000 },
],
},
],
}).expect({
size: 300000000,
})
given(
{
count: 1,
tag: '',
sort: 'date',
data: {
results: [
{
full_size: 300000000,
name: 'next',
images: [
{ architecture: 'amd64', size: 219939484 },
{ architecture: 'arm64', size: 200000000 },
],
},
{ name: 'latest', full_size: 74661264 },
{ name: 'arm64v8-latest', full_size: 76310416 },
{ name: 'arm32v7-latest', full_size: 68001970 },
{ name: 'amd64-latest', full_size: 74661264 },
],
},
'arm64777'
).expectError('Not Found: architecture not found')
})
test(DockerSize.prototype.getSizeFromTag, () => {
given(
{
full_size: 300000000,
name: 'next',
images: [{ architecture: 'amd64', size: 219939484 }],
},
'amd64'
).expect({
size: 219939484,
}).expect({
size: 74661264,
})
given({
full_size: 300000000,
name: 'next',
images: [{ architecture: 'amd64', size: 219939484 }],
tag: '',
sort: 'semver',
data: sizeDataNoTagSemVerSort,
}).expect({
size: 300000000,
size: 13448411,
})
given(
{
full_size: 300000000,
name: 'next',
images: [{ architecture: 'amd64', size: 219939484 }],
},
'arm64777'
).expectError('Not Found: architecture not found')
})
test(DockerSize.prototype.getSizeFromImageByLatestSemver, () => {
given(sizeDataNoTagSemVerSort, 'amd64').expect({
size: 220000000,
given({
tag: 'latest',
data: { name: 'latest', full_size: 13448411 },
}).expect({
size: 13448411,
})
given(sizeDataNoTagSemVerSort).expect({
size: 400000000,
})
given(sizeDataNoTagSemVerSort, 'nonexistentArch').expectError(
'Not Found: architecture not found'
)
})
})

View File

@@ -9,13 +9,6 @@ t.create('docker image size (valid, library)')
message: isFileSize,
})
t.create('docker image size (valid, library, arch parameter )')
.get('/_/mysql.json?arch=amd64')
.expectBadge({
label: 'image size',
message: isFileSize,
})
t.create('docker image size (valid, library with tag)')
.get('/_/alpine/latest.json')
.expectBadge({
@@ -48,19 +41,5 @@ t.create('docker image size (invalid, unknown repository)')
.get('/_/not-a-real-repo.json')
.expectBadge({
label: 'image size',
message: 'repository or tag not found',
})
t.create('docker image size (invalid, wrong sorting method)')
.get('/jrottenberg/ffmpeg/3.2-alpine.json?sort=daterrr')
.expectBadge({
label: 'image size',
message: 'invalid query parameter: sort',
})
t.create('docker image size (invalid, nonexisting arch)')
.get('/jrottenberg/ffmpeg/3.2-alpine.json?arch=nonexistingArch')
.expectBadge({
label: 'image size',
message: 'invalid query parameter: arch',
message: 'repository not found',
})

View File

@@ -1,18 +1,13 @@
import Joi from 'joi'
import { metric } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { BaseJsonService } from '../index.js'
import { BaseService } from '../index.js'
import {
dockerBlue,
buildDockerUrl,
getDockerHubUser,
} from './docker-helpers.js'
const schema = Joi.object({
star_count: nonNegativeInteger.required(),
}).required()
export default class DockerStars extends BaseJsonService {
export default class DockerStars extends BaseService {
static category = 'rating'
static route = buildDockerUrl('stars')
static examples = [
@@ -36,17 +31,18 @@ export default class DockerStars extends BaseJsonService {
}
async fetch({ user, repo }) {
return this._requestJson({
schema,
url: `https://hub.docker.com/v2/repositories/${getDockerHubUser(
user
)}/${repo}/`,
const url = `https://hub.docker.com/v2/repositories/${getDockerHubUser(
user
)}/${repo}/stars/count/`
const { buffer } = await this._request({
url,
errorMessages: { 404: 'repo not found' },
})
return this.constructor._validate(buffer, nonNegativeInteger)
}
async handle({ user, repo }) {
const { star_count } = await this.fetch({ user, repo })
return this.constructor.render({ stars: star_count })
const stars = await this.fetch({ user, repo })
return this.constructor.render({ stars })
}
}

View File

@@ -3,7 +3,6 @@ import { nonNegativeInteger } from '../validators.js'
import { latest, renderVersionBadge } from '../version.js'
import { BaseJsonService, NotFound, InvalidResponse } from '../index.js'
import {
archSchema,
buildDockerUrl,
getDockerHubUser,
getMultiPageData,
@@ -27,7 +26,23 @@ const buildSchema = Joi.object({
const queryParamSchema = Joi.object({
sort: Joi.string().valid('date', 'semver').default('date'),
arch: archSchema.default('amd64'),
arch: Joi.string()
// Valid architecture values: https://golang.org/doc/install/source#environment (GOARCH)
.valid(
'amd64',
'arm',
'arm64',
's390x',
'386',
'ppc64',
'ppc64le',
'wasm',
'mips',
'mipsle',
'mips64',
'mips64le'
)
.default('amd64'),
}).required()
export default class DockerVersion extends BaseJsonService {

View File

@@ -39,5 +39,5 @@ t.create('docker version (invalid, unknown repository)')
.get('/_/not-a-real-repo.json')
.expectBadge({
label: 'version',
message: 'repository or tag not found',
message: 'repository not found',
})

View File

@@ -10,7 +10,7 @@ describe('DroneBuild', function () {
const token = 'abc123'
const scope = nock('https://cloud.drone.io', {
reqheaders: { Authorization: 'Bearer abc123' },
reqheaders: { Authorization: `Bearer abc123` },
})
.get(/.*/)
.reply(200, { status: 'passing' })

View File

@@ -1,54 +1,22 @@
/**
* Common functions and utilities for tasks related to dynamic badges.
*
* @module
*/
import Joi from 'joi'
import toArray from '../core/base-service/to-array.js'
import validate from '../core/base-service/validate.js'
import { InvalidResponse } from './index.js'
/**
* Map of error codes and their corresponding error messages.
*
* @type {object}
*/
const errorMessages = {
404: 'resource not found',
}
/**
* Joi schema for validating individual value.
* Checks if the individual value is of type string or number.
*
* @type {Joi}
*/
const individualValueSchema = Joi.alternatives()
.try(Joi.string(), Joi.number())
.required()
/**
* Joi schema for validating compound value.
* Checks if the compound value is of type individualValueSchema, array of individualValueSchema or empty array.
*
* @type {Joi}
*/
const compoundValueSchema = Joi.alternatives().try(
individualValueSchema,
Joi.array().items(individualValueSchema).required(),
Joi.array().length(0)
)
/**
* Look up the value in the data object by key and validate the value against compoundValueSchema.
*
* @param {object} attrs Refer to individual attributes
* @param {object} attrs.data Object containing the data for validation
* @param {string} attrs.key Key to retrieve the data from object for validation
* @throws {InvalidResponse|Error} Error if Joi validation fails due to invalid or no schema
* @returns {object} Value if Joi validation is success
*/
function transformAndValidate({ data, key }) {
return validate(
{
@@ -62,20 +30,6 @@ function transformAndValidate({ data, key }) {
)
}
/**
* Handles rendering concerns of dynamic badges.
* Determines the label of the badge according to the tag and defaultLabel.
* Determines the message of the badge according to the prefix, suffix and value.
* Sets the color of the badge to blue.
*
* @param {object} attrs Refer to individual attributes
* @param {string} attrs.defaultLabel default badge label
* @param {string} [attrs.tag] If provided then this value will be appended to the badge label, e.g. `foobar@v1.23`
* @param {any} attrs.value Value or array of value to be used for the badge message
* @param {string} [attrs.prefix] If provided then the badge message will use this value as a prefix
* @param {string} [attrs.suffix] If provided then the badge message will use this value as a suffix
* @returns {object} Badge with label, message and color properties
*/
function renderDynamicBadge({
defaultLabel,
tag,

View File

@@ -1,9 +1,3 @@
/**
* Common functions and utilities for tasks related to endpoint badges.
*
* @module
*/
import Joi from 'joi'
import validate from '../core/base-service/validate.js'
import { InvalidResponse } from './index.js'
@@ -20,11 +14,6 @@ const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
.conditional('namedLogo', { is: Joi.string().required(), then: Joi.number() })
.conditional('logoSvg', { is: Joi.string().required(), then: Joi.number() })
/**
* Joi schema for validating endpoint.
*
* @type {Joi}
*/
const endpointSchema = Joi.object({
schemaVersion: 1,
label: Joi.string().allow('').required(),
@@ -44,18 +33,9 @@ const endpointSchema = Joi.object({
.oxor('namedLogo', 'logoSvg')
.required()
/**
* Strictly validate the data according to the endpoint schema.
* This rejects unknown/invalid keys.
* Optionally it prints those keys in the message to provide detailed feedback.
*
* @param {object} data Object containing the data for validation
* @param {object} attrs Refer to individual attributes
* @param {string} [attrs.prettyErrorMessage] If provided then error message is set to this value
* @param {boolean} [attrs.includeKeys] If true then includes error details in error message, defaults to false
* @throws {InvalidResponse|Error} Error if Joi validation fails due to invalid or no schema
* @returns {object} Value if Joi validation is success
*/
// Strictly validate according to the endpoint schema. This rejects unknown /
// invalid keys. Optionally it prints those keys in the message in order to
// provide detailed feedback.
function validateEndpointData(
data,
{ prettyErrorMessage = 'invalid response data', includeKeys = false } = {}
@@ -76,17 +56,6 @@ function validateEndpointData(
const anySchema = Joi.any()
/**
* Fetches data from the endpoint and validates the data.
*
* @param {object} serviceInstance Instance of Endpoint class
* @param {object} attrs Refer to individual attributes
* @param {string} attrs.url Endpoint URL
* @param {object} attrs.errorMessages Object containing error messages for different error codes
* @param {string} attrs.validationPrettyErrorMessage If provided then the error message is set to this value
* @param {boolean} attrs.includeKeys If true then includes error details in error message
* @returns {object} Data fetched from endpoint
*/
async function fetchEndpointData(
serviceInstance,
{ url, errorMessages, validationPrettyErrorMessage, includeKeys }

View File

@@ -1,33 +0,0 @@
import Joi from 'joi'
import { BaseJsonService } from '../index.js'
import { renderDownloadsBadge } from '../downloads.js'
const schema = Joi.object({
installs_total: Joi.number().integer().required(),
}).required()
export default class FlathubDownloads extends BaseJsonService {
static category = 'downloads'
static route = { base: 'flathub/downloads', pattern: ':packageName' }
static examples = [
{
title: 'Flathub',
namedParams: {
packageName: 'org.mozilla.firefox',
},
staticPreview: renderDownloadsBadge({ downloads: '277136' }),
},
]
static defaultBadgeData = { label: 'installs' }
async handle({ packageName }) {
const data = await this._requestJson({
schema,
url: `https://flathub.org/api/v2/stats/${encodeURIComponent(
packageName
)}`,
})
return renderDownloadsBadge({ downloads: data.installs_total })
}
}

View File

@@ -1,14 +0,0 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Flathub Downloads (valid)')
.get('/org.mozilla.firefox.json')
.expectBadge({
label: 'installs',
message: isMetric,
})
t.create('Flathub Downloads (not found)')
.get('/not.a.package.json')
.expectBadge({ label: 'installs', message: 'not found' })

View File

@@ -46,7 +46,7 @@ export default class FreeCodeCampPoints extends BaseJsonService {
async fetch({ username }) {
return this._requestJson({
schema,
url: 'https://api.freecodecamp.org/api/users/get-public-profile',
url: `https://api.freecodecamp.org/api/users/get-public-profile`,
options: {
searchParams: {
username,

View File

@@ -1,41 +0,0 @@
import { formatDate } from '../text-formatters.js'
import BaseGalaxyToolshedService from './galaxytoolshed-base.js'
export default class GalaxyToolshedCreatedDate extends BaseGalaxyToolshedService {
static category = 'activity'
static route = {
base: 'galaxytoolshed/created-date',
pattern: ':repository/:owner',
}
static examples = [
{
title: 'Galaxy Toolshed (created date)',
namedParams: {
repository: 'sra_tools',
owner: 'iuc',
},
staticPreview: this.render({
date: this.render({ date: '2022-01-01' }),
}),
},
]
static defaultBadgeData = {
label: 'created date',
color: 'blue',
}
static render({ date }) {
return { message: formatDate(date) }
}
async handle({ repository, owner }) {
const response = await this.fetchLastOrderedInstallableRevisionsSchema({
repository,
owner,
})
const { create_time: date } = response[0]
return this.constructor.render({ date })
}
}

View File

@@ -1,23 +0,0 @@
import { isFormattedDate } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Created Date')
.get('/sra_tools/iuc.json')
.expectBadge({ label: 'created date', message: isFormattedDate })
t.create('Created Date - repository not found')
.get('/sra_tool/iuc.json')
.expectBadge({ label: 'created date', message: 'not found' })
t.create('Created Date - owner not found')
.get('/sra_tools/iu.json')
.expectBadge({ label: 'created date', message: 'not found' })
t.create('Created Date - changesetRevision not found')
.get('/bioqc/badilla.json')
.expectBadge({
label: 'created date',
message: 'changesetRevision not found',
})

View File

@@ -1,55 +0,0 @@
import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { NotFound, BaseJsonService } from '../index.js'
const orderedInstallableRevisionsSchema = Joi.array()
.items(Joi.string())
.required()
const repositoryRevisionInstallInfoSchema = Joi.array()
.ordered(
Joi.object({
create_time: Joi.date().required(),
times_downloaded: nonNegativeInteger,
}).required()
)
.items(Joi.any())
export default class BaseGalaxyToolshedService extends BaseJsonService {
static defaultBadgeData = { label: 'galaxytoolshed' }
static baseUrl = 'https://toolshed.g2.bx.psu.edu'
async fetchOrderedInstallableRevisionsSchema({ repository, owner }) {
return this._requestJson({
schema: orderedInstallableRevisionsSchema,
url: `${this.constructor.baseUrl}/api/repositories/get_ordered_installable_revisions?name=${repository}&owner=${owner}`,
})
}
async fetchRepositoryRevisionInstallInfoSchema({
repository,
owner,
changesetRevision,
}) {
return this._requestJson({
schema: repositoryRevisionInstallInfoSchema,
url: `${this.constructor.baseUrl}/api/repositories/get_repository_revision_install_info?name=${repository}&owner=${owner}&changeset_revision=${changesetRevision}`,
})
}
async fetchLastOrderedInstallableRevisionsSchema({ repository, owner }) {
const changesetRevisions =
await this.fetchOrderedInstallableRevisionsSchema({
repository,
owner,
})
if (!Array.isArray(changesetRevisions) || !changesetRevisions.length) {
throw new NotFound({ prettyMessage: 'changesetRevision not found' })
}
return this.fetchRepositoryRevisionInstallInfoSchema({
repository,
owner,
changesetRevision: changesetRevisions[0],
})
}
}

View File

@@ -1,34 +0,0 @@
import { renderDownloadsBadge } from '../downloads.js'
import BaseGalaxyToolshedService from './galaxytoolshed-base.js'
export default class GalaxyToolshedDownloads extends BaseGalaxyToolshedService {
static category = 'downloads'
static route = {
base: 'galaxytoolshed/downloads',
pattern: ':repository/:owner',
}
static examples = [
{
title: 'Galaxy Toolshed - Downloads',
namedParams: {
repository: 'sra_tools',
owner: 'iuc',
},
staticPreview: renderDownloadsBadge({ downloads: 10000 }),
},
]
static defaultBadgeData = {
label: 'downloads',
}
async handle({ repository, owner }) {
const response = await this.fetchLastOrderedInstallableRevisionsSchema({
repository,
owner,
})
const { times_downloaded: downloads } = response[0]
return renderDownloadsBadge({ downloads })
}
}

View File

@@ -1,28 +0,0 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('downloads - raw').get('/sra_tools/iuc.json').expectBadge({
label: 'downloads',
message: isMetric,
})
t.create('downloads - repository not found')
.get('/sra_tool/iuc.json')
.expectBadge({
label: 'downloads',
message: 'not found',
})
t.create('downloads - owner not found').get('/sra_tools/iu.json').expectBadge({
label: 'downloads',
message: 'not found',
})
t.create('downloads - changesetRevision not found')
.get('/bioqc/badilla.json')
.expectBadge({
label: 'downloads',
message: 'changesetRevision not found',
})

View File

@@ -40,7 +40,7 @@ class GithubAuthV4Service extends BaseGraphqlService {
}
async _requestGraphql(attrs) {
const url = '/graphql'
const url = `/graphql`
/*
The Github v4 API requires us to query the rateLimit object to return

View File

@@ -1,59 +0,0 @@
import Joi from 'joi'
import { metric } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, errorMessagesFor } from './github-helpers.js'
const schema = Joi.object({ total_commits: nonNegativeInteger }).required()
const queryParamSchema = Joi.object({
base: Joi.string().required(),
head: Joi.string().required(),
}).required()
export default class GithubCommitsDifference extends GithubAuthV3Service {
static category = 'activity'
static route = {
base: 'github/commits-difference',
pattern: ':user/:repo',
queryParamSchema,
}
static examples = [
{
title: 'GitHub commits difference between two branches/tags/commits',
namedParams: {
user: 'microsoft',
repo: 'vscode',
},
queryParams: {
base: '1.60.0',
head: '82f2db7',
},
staticPreview: this.render({
commitCount: 9227,
}),
documentation,
},
]
static defaultBadgeData = { label: 'commits difference', namedLogo: 'github' }
static render({ commitCount }) {
return {
message: metric(commitCount),
color: 'blue',
}
}
async handle({ user, repo }, { base, head }) {
const notFoundMessage = 'could not establish commit difference between refs'
const { total_commits: commitCount } = await this._requestJson({
schema,
url: `/repos/${user}/${repo}/compare/${base}...${head}`,
errorMessages: errorMessagesFor(notFoundMessage),
})
return this.constructor.render({ commitCount })
}
}

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