Compare commits

..

1 Commits

Author SHA1 Message Date
Caleb Cartwright
7da38f8354 docs: start collating info on integration patterns 2022-07-14 21:11:16 -05:00
223 changed files with 12288 additions and 13884 deletions

View File

@@ -1,5 +1,69 @@
version: 2
main_steps: &main_steps
steps:
- checkout
- run:
name: Install dependencies
command: |
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 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
@@ -25,7 +89,83 @@ 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
NPM_CONFIG_ENGINE_STRICT: 'false'
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
NPM_CONFIG_ENGINE_STRICT: 'false'
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.15
<<: *main_steps
main@node-17:
docker:
- image: cimg/node:17.9
<<: *main_steps
integration:
docker:
- image: cimg/node:16.15
- image: redis
<<: *integration_steps
integration@node-17:
docker:
- image: cimg/node:17.9
- image: redis
<<: *integration_steps
danger:
docker:
- image: cimg/node:16.15
@@ -84,6 +224,12 @@ jobs:
when: always
command: npm run build
package:
machine:
image: 'ubuntu-2004:202111-02'
<<: *package_steps
services:
docker:
- image: cimg/node:16.15
@@ -93,8 +239,6 @@ jobs:
services@node-17:
docker:
- image: cimg/node:17.9
environment:
NPM_CONFIG_ENGINE_STRICT: 'false'
<<: *services_steps
@@ -147,10 +291,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 +340,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,23 +9,22 @@
"version": "0.0.0",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
"@actions/core": "^1.9.0",
"@actions/github": "^5.0.3"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
@@ -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,18 +226,17 @@
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/http-client": "^2.0.1"
}
},
"@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
"requires": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
@@ -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.10.0",
"@actions/github": "^5.1.1"
"@actions/core": "^1.9.0",
"@actions/github": "^5.0.3"
}
}

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

@@ -1,13 +1,11 @@
name: Auto close
on:
pull_request_target:
types: [opened]
on: pull_request_target
permissions:
pull-requests: write
jobs:
auto-close:
build:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:

View File

@@ -3,7 +3,7 @@ on:
pull_request:
jobs:
build-docker-image:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -8,7 +8,7 @@ permissions:
contents: write
jobs:
deploy-docs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -10,7 +10,7 @@ permissions:
contents: write
jobs:
draft-release:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -1,10 +1,8 @@
name: 'Dependency Review'
on:
pull_request:
types: [opened, edited, reopened, synchronize]
on: [pull_request]
jobs:
enforce-dependency-review:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'

View File

@@ -5,7 +5,7 @@ on:
- master
jobs:
publish-docker-next:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -1,48 +0,0 @@
name: Integration@node 17
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-integration-17:
runs-on: ubuntu-latest
env:
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
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 (with PAT)
if: ${{ env.PAT_EXISTS == 'true' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GH_PAT }}'
- name: Integration Tests (with workflow token)
if: ${{ env.PAT_EXISTS == 'false' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,46 +0,0 @@
name: Integration
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-integration:
runs-on: ubuntu-latest
env:
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
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 (with PAT)
if: ${{ env.PAT_EXISTS == 'true' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GH_PAT }}'
- name: Integration Tests (with workflow token)
if: ${{ env.PAT_EXISTS == 'false' }}
uses: ./.github/actions/integration-tests
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,28 +0,0 @@
name: Lint
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
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,25 +0,0 @@
name: Main@node 17
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
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,28 +0,0 @@
name: Main
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
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,46 +0,0 @@
name: Package CLI
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
# 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,34 +0,0 @@
name: Package Library
on:
pull_request:
types: [opened, edited, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
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

View File

@@ -4,77 +4,6 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2022-11-01
- fix [githubpullrequestcheckstate] service tests [#8548](https://github.com/badges/shields/issues/8548)
- require array to have at least 1 item in [githublastcommit] [#8563](https://github.com/badges/shields/issues/8563)
- [VisualStudioMarketplace] Add support to prerelease extensions version (Issue #8207) [#8561](https://github.com/badges/shields/issues/8561)
- feat: add [GitlabLastCommit] service [#8508](https://github.com/badges/shields/issues/8508)
- fix [swagger] service tests (allow 0 items in array) [#8564](https://github.com/badges/shields/issues/8564)
- fix codecov badge for non-default branch [#8565](https://github.com/badges/shields/issues/8565)
- fixes for integration test rate limit issues [#8538](https://github.com/badges/shields/issues/8538)
- remove failing [lgtm] test case [#8547](https://github.com/badges/shields/issues/8547)
- Add [GitHubLastCommit] by committer badge [#8537](https://github.com/badges/shields/issues/8537)
- [GitHubReleaseDate] - published_at field [#8543](https://github.com/badges/shields/issues/8543)
- Fix [Testspace] with new "untested" value in case_counts array [#8544](https://github.com/badges/shields/issues/8544)
- fix: Support WAITING status for GitHub deployments [#8521](https://github.com/badges/shields/issues/8521)
- update [bitbucket] private repo test [#8520](https://github.com/badges/shields/issues/8520)
- update [powershellgallery] platform example [#8518](https://github.com/badges/shields/issues/8518)
- fix [myget] downloads (tenant) test [#8513](https://github.com/badges/shields/issues/8513)
- [Whatpulse] badge for a user and for a team [#8466](https://github.com/badges/shields/issues/8466)
- increase timeout for [reuse] not found test [#8519](https://github.com/badges/shields/issues/8519)
- update [JenkinsPluginInstalls] test example [#8511](https://github.com/badges/shields/issues/8511)
- migrate integration tests to GH actions [#8423](https://github.com/badges/shields/issues/8423)
- deprecate [pkgreview] service [#8499](https://github.com/badges/shields/issues/8499)
- Dependency updates
## server-2022-10-08
- deprecate [criterion] service [#8501](https://github.com/badges/shields/issues/8501)
- fix formatRelativeDate error handling; run [date] [#8497](https://github.com/badges/shields/issues/8497)
- allow/validate bitbucket_username / bitbucket_password in private config schema [#8472](https://github.com/badges/shields/issues/8472)
- fix [pub] points badge test and example [#8498](https://github.com/badges/shields/issues/8498)
- feat: add [GitlabLanguageCount] service [#8377](https://github.com/badges/shields/issues/8377)
- [GitHubGistStars] add GitHub Gist Stars [#8471](https://github.com/badges/shields/issues/8471)
- fix display/search of CII badge examples [#8473](https://github.com/badges/shields/issues/8473)
- feat: add 2022 support to GitHub Hacktoberfest [#8468](https://github.com/badges/shields/issues/8468)
- fix [GitLabCoverage] subgroup bug [#8401](https://github.com/badges/shields/issues/8401)
- implement ruby gems-specific version sort/color functions [#8434](https://github.com/badges/shields/issues/8434)
- Add `rc` to pre-release identifiers [#8435](https://github.com/badges/shields/issues/8435)
- add [GitHub] Number of commits between branches/tags/commits [#8394](https://github.com/badges/shields/issues/8394)
- add [Packagist] dependency version [#8371](https://github.com/badges/shields/issues/8371)
- fix Docker build status invalid response data bug [#8392](https://github.com/badges/shields/issues/8392)
- Dependency updates
## 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)

View File

@@ -134,7 +134,7 @@ Prettier before a commit by default.
When adding or changing a service [please write tests][service-tests], and ensure the [title of your Pull Requests follows the required conventions](#running-service-tests-in-pull-requests) to ensure your tests are executed.
When changing other code, please add unit tests.
To run the integration tests, you must have Redis installed and in your PATH.
To run the integration tests, you must have redis installed and in your PATH.
Use `brew install redis`, `yum install redis`, etc. The test runner will
start the server automatically.

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

@@ -169,8 +169,6 @@ const privateConfigSchema = Joi.object({
jenkins_pass: Joi.string(),
jira_user: Joi.string(),
jira_pass: Joi.string(),
bitbucket_username: Joi.string(),
bitbucket_password: Joi.string(),
bitbucket_server_username: Joi.string(),
bitbucket_server_password: Joi.string(),
librariesio_tokens: Joi.arrayFromString().items(Joi.string()),

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

@@ -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

@@ -0,0 +1,75 @@
# Integration with upstream services
## Overview
In a nutshell, the Shields Badge Server handles the responsibilities of accepting requests for badges, and then serving those badges back to users.
A grossly oversimplified visualization would probably look something like this:
```mermaid
sequenceDiagram
actor User
participant B as Badge Server
participant P as Data Provider
User->>+B: I'd like a badge for my project please!
B->>+P: Get data for project
P-->>-B: Data
B->>B: Make badge with data
B-->>-User: Here's your badge!
```
Shields is not a system of record (we're not the package registry, pipeline tool, etc.) so when Shields receives a request for a badge, the badge server will first have to reach out to the system of record in order to get the data points it needs to create your badge.
For example, if you ask Shields for a build status badge for your CircleCI pipeline, then Shields has to reach out to CircleCI to figure out what the status of your pipeline is (CircleCI would be the "Data Provider" actor in the above diagram). Similarly if you want a badge that shows the count of downloads of your npm package, Shields has to reach out to the npm.js registry.
That covers the gist, but the actual story is a bit more involved and complicated than that of course. There's a number of other components along the way, ranging from the browser on your local machine to extra services and actors we have deployed as part of the Shields.io runtime ecosystem which help ensure we can provide a stable and reliable deployment of the badge server-as-a-service. Additionally, badges rendered in GitHub have some additional factors at play that impose some additional constraints, detailed in the next section.
### GitHub Badge Rendering
A common usage pattern for badges is to embed them in your project's README files so relevant information is conveyed to the project's users. This means badges are often utilized and rendered in source control management platforms, like GitHub.
[GitHub utilizes a proxy service, called camo][camo], for handling the images that you see when you browse project pages on github.com, and this is utilized for badges too (both svg and png formats). GitHub does this for a number of reasons, including to anonymize requests and protect your privacy. However, this also requires the upstream images (including badges) to be returned quickly in order for those images to show up on your screen, with a rough ceiling of 3-4 seconds. If the upstream image provider is too slow to respond, then camo will timeout and the image won't be displayed.
This imposes constraints on Shields, as we need to ensure that the badge server completes the entire request/response workflow and returns the final badge within a few seconds.
[camo]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-anonymized-urls
## Interaction patterns
Because of the time limits around the full badge flow discussed above, the badge server is somewhat constrained in terms of patterns it can employ to fetch data from the upstream provider. When we incorporate new badges we typically need to ensure when we receive a badge request and need to get data to serve that badge request, that we can get that data by making a single call to the upstream provider to get the data points needed for the badge.
The nature of the call Shields makes to upstream data providers is obviously dependent upon the nature of the upstream endpoints. The overwhelming majority of the time this occurs via a GET request, though there are a few occasions where the upstream endpoint requires a POST in order for us to retrieve data, and a few others where we issue a HEAD request because the data points we need for the badge actually reside in the response headers.
There are a couple other exceptions, but as a general pattern we strive to integrate with services via a stateless, single call manner.
### Authentication
Shields typically integrates with upstream data providers anonymously, largely because the data targets we need are anonymously available (open source packages, repositories, pipelines, etc.)
The badge server can be configured to make authenticated requests for certain supported services. This exists so that users who are [self-hosting] their own instance of the badge server can get badges for their private content, and also as part of agreements we've made with certain upstream data providers for the main Shields.io deployment.
[self-hosting]: ./self-hosting.md
### Rate Limits
Many upstream data providers employ common techniques to protect the availability and integrity of their service, and one common technique is [rate limiting].
Typically, when clients/consumers of rate limited endpoints will employ client-side techniques to avoid running afoul of those limits and potentially getting blocked or having their requests throttled. Unfortunately, those techniques aren't really viable for the Shields.io environment due to the workflow and constraints discussed above.
As such, we instead try to ensure that Shields.io never makes more calls to an upstream provider than their rate limits allow.
In cases where Shields.io may run close to or exceed those limits, we typically consider:
- increasing the cache periods we set (to reduce the number of badge requests we receive)
- collaborating with the vendor or maintainers of the upstream data provider to explore options for an increased rate limit for Shields.io
- decline to provide badges for that upstream data provider
[rate limiting]: https://en.wikipedia.org/wiki/Rate_limiting
### Denial of Service
coming soon...
### Considerations for new upstream integrations
coming soon...

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

@@ -18,8 +18,8 @@ function loadSimpleIcons() {
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"')),
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

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 }

20416
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,48 +21,47 @@
"url": "https://github.com/badges/shields"
},
"dependencies": {
"@fontsource/lato": "^4.5.10",
"@fontsource/lekton": "^4.5.11",
"@fontsource/lato": "^4.5.8",
"@fontsource/lekton": "^4.5.9",
"@renovate/pep440": "^1.0.0",
"@renovatebot/ruby-semver": "^1.1.6",
"@sentry/node": "^7.17.2",
"@sentry/node": "^7.5.1",
"@shields_io/camp": "^18.1.1",
"badge-maker": "file:badge-maker",
"bytes": "^3.1.2",
"camelcase": "^7.0.0",
"chalk": "^5.1.2",
"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.6",
"decamelize": "^3.2.0",
"emojic": "^1.1.17",
"escape-string-regexp": "^4.0.0",
"fast-xml-parser": "^4.0.11",
"fast-xml-parser": "^4.0.8",
"glob": "^8.0.3",
"global-agent": "^3.0.0",
"got": "^12.5.2",
"got": "^12.1.0",
"graphql": "^15.6.1",
"graphql-tag": "^2.12.6",
"ioredis": "5.2.3",
"joi": "17.6.4",
"ioredis": "5.1.0",
"joi": "17.6.0",
"joi-extension-semver": "5.0.0",
"js-yaml": "^4.1.0",
"jsonpath": "~1.1.1",
"lodash.countby": "^4.6.0",
"lodash.groupby": "^4.6.0",
"lodash.times": "^4.3.2",
"moment": "^2.29.4",
"node-env-flag": "^0.1.0",
"parse-link-header": "^2.0.0",
"path-to-regexp": "^6.2.1",
"pretty-bytes": "^6.0.0",
"priorityqueuejs": "^2.0.0",
"prom-client": "^14.1.0",
"prom-client": "^14.0.1",
"qs": "^6.11.0",
"query-string": "^7.1.1",
"semver": "~7.3.8",
"simple-icons": "7.17.0",
"semver": "~7.3.7",
"simple-icons": "7.4.0",
"webextension-store-meta": "^1.0.5",
"xmldom": "~0.6.0",
"xpath": "~0.0.32"
@@ -142,25 +141,25 @@
]
},
"devDependencies": {
"@babel/core": "^7.19.6",
"@babel/core": "^7.18.6",
"@babel/polyfill": "^7.12.1",
"@babel/register": "7.18.9",
"@babel/register": "7.18.6",
"@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": "^10.0.0",
"@types/mocha": "^9.1.1",
"@types/node": "^16.7.10",
"@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.41.0",
"@typescript-eslint/parser": "^5.30.7",
"@types/styled-components": "5.1.25",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.27.0",
"babel-plugin-inline-react-svg": "^2.0.1",
"babel-preset-gatsby": "^2.22.0",
"c8": "^7.12.0",
"babel-preset-gatsby": "^2.14.0",
"c8": "^7.11.3",
"caller": "^1.1.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
@@ -168,10 +167,9 @@
"chai-string": "^1.4.0",
"child-process-promise": "^2.2.1",
"clipboard-copy": "^4.0.1",
"concurrently": "^7.5.0",
"cypress": "^10.11.0",
"cypress-wait-for-stable-dom": "^0.1.0",
"danger": "^11.1.4",
"concurrently": "^7.2.2",
"cypress": "^10.3.0",
"danger": "^11.1.1",
"danger-plugin-no-test-shortcuts": "^2.0.0",
"deepmerge": "^4.2.2",
"eslint": "^7.32.0",
@@ -182,68 +180,68 @@
"eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.3.25",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-jsdoc": "^39.3.3",
"eslint-plugin-mocha": "^10.0.5",
"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.10",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-sort-class-members": "^1.15.2",
"eslint-plugin-sort-class-members": "^1.14.1",
"fetch-ponyfill": "^7.1.0",
"form-data": "^4.0.0",
"gatsby": "4.23.1",
"gatsby-plugin-catch-links": "^4.19.0",
"gatsby-plugin-page-creator": "^4.24.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.24.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",
"jsdoc": "^3.6.10",
"lint-staged": "^13.0.3",
"lodash.debounce": "^4.0.8",
"lodash.difference": "^4.5.0",
"minimist": "^1.2.7",
"mocha": "^10.1.0",
"minimist": "^1.2.6",
"mocha": "^9.2.2",
"mocha-env-reporter": "^4.0.0",
"mocha-junit-reporter": "^2.1.1",
"mocha-junit-reporter": "^2.0.2",
"mocha-yaml-loader": "^1.0.3",
"nock": "13.2.9",
"nock": "13.2.8",
"node-mocks-http": "^1.11.0",
"nodemon": "^2.0.20",
"nodemon": "^2.0.19",
"npm-run-all": "^4.1.5",
"open-cli": "^7.1.0",
"portfinder": "^1.0.32",
"open-cli": "^7.0.1",
"portfinder": "^1.0.28",
"prettier": "2.7.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-overlay": "^6.0.11",
"react-helmet": "^6.1.0",
"react-modal": "^3.16.1",
"react-modal": "^3.15.1",
"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.1",
"sinon": "^14.0.1",
"simple-git-hooks": "^2.8.0",
"sinon": "^14.0.0",
"sinon-chai": "^3.7.0",
"snap-shot-it": "^7.9.6",
"start-server-and-test": "1.14.0",
"styled-components": "^5.3.6",
"styled-components": "^5.3.5",
"ts-mocha": "^10.0.0",
"tsd": "^0.24.1",
"typescript": "^4.8.4",
"tsd": "^0.22.0",
"typescript": "^4.7.4",
"url": "^0.11.0"
},
"engines": {
"node": "^16.13.0",
"node": ">=16.13.0",
"npm": ">=8.0.0"
},
"type": "module",

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

@@ -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

@@ -20,7 +20,7 @@ t.create('pr-raw (not found)')
t.create('pr-raw (private repo)')
.get('/pr-raw/chris48s/example-private-repo.json')
.expectBadge({ label: 'pull requests', message: 'not found' })
.expectBadge({ label: 'pull requests', message: 'private repo' })
t.create('pr (valid)').get('/pr/atlassian/python-bitbucket.json').expectBadge({
label: 'pull requests',
@@ -33,7 +33,7 @@ t.create('pr (not found)')
t.create('pr (private repo)')
.get('/pr/chris48s/example-private-repo.json')
.expectBadge({ label: 'pull requests', message: 'not found' })
.expectBadge({ label: 'pull requests', message: 'private repo' })
t.create('pr (server)')
.get('/pr/project/repo.json?server=https://bitbucket.mydomain.net')

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

@@ -60,7 +60,7 @@ const allStatuses = greenStatuses
* Joi schema for validating Build Status.
* Checks if the build status is present in the list of allowed build status.
*
* @type {Joi}
* @type {object}
*/
const isBuildStatus = Joi.equal(...allStatuses)

View File

@@ -35,7 +35,7 @@ export default class CIIBestPracticesService extends BaseJsonService {
pattern: ':metric(level|percentage|summary)/:projectId',
}
static examples = [
static exampless = [
{
title: 'CII Best Practices Level',
pattern: 'level/:projectId',

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

@@ -107,7 +107,7 @@ export default class Codecov extends BaseSvgScrapingService {
async legacyFetch({ vcsName, user, repo, branch, token }) {
// Codecov Docs: https://docs.codecov.io/reference#section-get-a-single-repository
const url = `https://codecov.io/api/${vcsName}/${user}/${repo}${
branch ? `/branch/${branch}` : ''
branch ? `/branches/${branch}` : ''
}`
const { buffer } = await this._request({
url,

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

@@ -5,7 +5,7 @@
* @module
*/
import dayjs from 'dayjs'
import moment from 'moment'
import pep440 from '@renovate/pep440'
/**
@@ -23,7 +23,7 @@ function version(version) {
if (first === 'v') {
first = version[1]
}
if (first === '0' || /alpha|beta|snapshot|dev|pre|rc/i.test(version)) {
if (first === '0' || /alpha|beta|snapshot|dev|pre/i.test(version)) {
return 'orange'
} else {
return 'blue'
@@ -182,7 +182,7 @@ function colorScale(steps, colors, reversed) {
*/
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

@@ -88,7 +88,6 @@ describe('Color formatters', function () {
given('6.0-SNAPSHOT'),
given('1.0.1-dev'),
given('2.1.6-prerelease'),
given('2.1.6-RC1'),
]).expect('orange')
expect(() => version(null)).to.throw(

View File

@@ -1,11 +1,66 @@
import { deprecatedService } from '../index.js'
import Joi from 'joi'
import { BaseJsonService } from '../index.js'
import {
IMPROVED_STATUS,
NOT_FOUND_STATUS,
REGRESSED_STATUS,
NO_CHANGE_STATUS,
} from './constants.js'
export default deprecatedService({
category: 'analysis',
route: {
base: 'criterion',
pattern: ':various*',
},
label: 'criterion',
dateAdded: new Date('2022-10-07'),
})
const schema = Joi.string()
.allow(IMPROVED_STATUS, REGRESSED_STATUS, NO_CHANGE_STATUS)
.required()
/**
* Criterion Badge Service
*
* Support and Contact:
* - https://github.com/chmoder/api.criterion.dev
*
* API Documentation:
* - https://app.swaggerhub.com/apis-docs/chmoder/Criterion.dev
*/
export default class Criterion extends BaseJsonService {
static category = 'analysis'
static route = { base: 'criterion', pattern: ':user/:repo' }
static examples = [
{
title: 'Criterion',
namedParams: {
user: 'chmoder',
repo: 'data_vault',
},
staticPreview: this.render({ status: IMPROVED_STATUS }),
},
]
static defaultBadgeData = { label: 'criterion' }
static render({ status }) {
let statusColor = 'lightgrey'
if (status === IMPROVED_STATUS) {
statusColor = 'brightgreen'
} else if (status === NO_CHANGE_STATUS) {
statusColor = 'green'
} else if (statusColor === REGRESSED_STATUS) {
statusColor = 'red'
}
return {
message: `${status}`,
color: statusColor,
}
}
async handle({ user, repo }) {
const status = await this._requestJson({
url: `https://api.criterion.dev/v1/${user}/${repo}/status`,
errorMessages: { 404: NOT_FOUND_STATUS },
schema,
})
return this.constructor.render({ status })
}
}

View File

@@ -1,11 +1,21 @@
import { ServiceTester } from '../tester.js'
import Joi from 'joi'
import { createServiceTester } from '../tester.js'
import {
IMPROVED_STATUS,
REGRESSED_STATUS,
NO_CHANGE_STATUS,
NOT_FOUND_STATUS,
} from './constants.js'
export const t = await createServiceTester()
export const t = new ServiceTester({
id: 'criterion',
title: 'Criterion',
pathPrefix: '/criterion',
})
const isStatus = Joi.string()
.allow(IMPROVED_STATUS, REGRESSED_STATUS, NOT_FOUND_STATUS, NO_CHANGE_STATUS)
.required()
t.create('Criterion')
t.create('Criterion (valid repo)')
.get('/chmoder/credit_card.json')
.expectBadge({ label: 'criterion', message: 'no longer available' })
.expectBadge({ label: 'criterion', message: isStatus })
t.create('Criterion (not found)')
.get('/chmoder/not-a-repo.json')
.expectBadge({ label: 'criterion', message: NOT_FOUND_STATUS })

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

@@ -22,7 +22,7 @@ const errorMessages = {
* Joi schema for validating individual value.
* Checks if the individual value is of type string or number.
*
* @type {Joi}
* @type {object}
*/
const individualValueSchema = Joi.alternatives()
.try(Joi.string(), Joi.number())
@@ -32,7 +32,7 @@ const individualValueSchema = Joi.alternatives()
* Joi schema for validating compound value.
* Checks if the compound value is of type individualValueSchema, array of individualValueSchema or empty array.
*
* @type {Joi}
* @type {object}
*/
const compoundValueSchema = Joi.alternatives().try(
individualValueSchema,

View File

@@ -23,7 +23,7 @@ const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
/**
* Joi schema for validating endpoint.
*
* @type {Joi}
* @type {object}
*/
const endpointSchema = Joi.object({
schemaVersion: 1,

View File

@@ -4,10 +4,9 @@ import { BaseJsonService, InvalidResponse, NotFound } from '../index.js'
/**
* Validates that the schema response is what we're expecting.
* The username pattern should match the requirements in the freeCodeCamp
* repository.
* The username pattern should match the freeCodeCamp repository.
*
* @see https://github.com/freeCodeCamp/freeCodeCamp/blob/main/utils/validate.js
* @see https://github.com/freeCodeCamp/freeCodeCamp/blob/main/utils/validate.js#L14
*/
const schema = Joi.object({
entities: Joi.object({
@@ -47,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

@@ -9,7 +9,6 @@ const orderedInstallableRevisionsSchema = Joi.array()
const repositoryRevisionInstallInfoSchema = Joi.array()
.ordered(
Joi.object({
create_time: Joi.date().required(),
times_downloaded: nonNegativeInteger,
}).required()
)

View File

@@ -1,25 +0,0 @@
import { valid, maxSatisfying, prerelease } from '@renovatebot/ruby-semver'
function latest(versions) {
// latest Ruby Gems version, including pre-releases
return maxSatisfying(versions, '>0')
}
function versionColor(version) {
if (!valid(version)) {
return 'lightgrey'
}
version = `${version}`
let first = version[0]
if (first === 'v') {
first = version[1]
}
if (first === '0' || prerelease(version)) {
return 'orange'
}
return 'blue'
}
export { latest, versionColor }

View File

@@ -1,17 +0,0 @@
import { test, given } from 'sazerac'
import { latest, versionColor } from './gem-helpers.js'
describe('Gem helpers', function () {
test(latest, () => {
given(['2.0.0', '2.0.0.beta1']).expect('2.0.0')
given(['2.0.0.beta1', '1.9.0']).expect('2.0.0.beta1')
given(['0.0.1', '0.0.2']).expect('0.0.2')
})
test(versionColor, () => {
given('1.9.0').expect('blue')
given('2.0.0.beta1').expect('orange')
given('0.0.1').expect('orange')
given('v1').expect('lightgrey')
})
})

View File

@@ -1,7 +1,12 @@
import Joi from 'joi'
import { createServiceTester } from '../tester.js'
import { isOrdinalNumber, isOrdinalNumberDaily } from '../test-validators.js'
export const t = await createServiceTester()
const isOrdinalNumber = Joi.string().regex(/^[1-9][0-9]+(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ)$/)
const isOrdinalNumberDaily = Joi.string().regex(
/^[1-9][0-9]*(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ) daily$/
)
t.create('total rank (valid)').get('/rt/rspec-puppet-facts.json').expectBadge({
label: 'rank',
message: isOrdinalNumber,

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { renderVersionBadge } from '../version.js'
import { renderVersionBadge, latest } from '../version.js'
import { BaseJsonService } from '../index.js'
import { latest, versionColor } from './gem-helpers.js'
const schema = Joi.object({
// In most cases `version` will be a SemVer but the registry doesn't
@@ -46,7 +45,7 @@ export default class GemVersion extends BaseJsonService {
static defaultBadgeData = { label: 'gem' }
static render({ version }) {
return renderVersionBadge({ version, versionFormatter: versionColor })
return renderVersionBadge({ version })
}
async fetch({ gem }) {

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 })
}
}

View File

@@ -1,43 +0,0 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Commits difference - correct, between branches')
.get('/microsoft/vscode.json?base=standalone/0.1.x&head=release/1.21')
.expectBadge({
label: 'commits difference',
message: isMetric,
color: 'blue',
})
t.create('Commits difference - correct, between tags')
.get('/microsoft/vscode.json?base=1.58.0&head=1.59.0')
.expectBadge({
label: 'commits difference',
message: isMetric,
color: 'blue',
})
t.create('Commits difference - correct, between commits')
.get('/microsoft/vscode.json?base=3d82ef7&head=82f2db7')
.expectBadge({
label: 'commits difference',
message: isMetric,
color: 'blue',
})
t.create('Commits difference - incorrect, between commits')
.get('/microsoft/vscode.json?base=fffffff&head=82f2db7')
.expectBadge({
label: 'commits difference',
message: 'could not establish commit difference between refs',
color: 'red',
})
t.create('Commits difference - incorrect, missing head')
.get('/microsoft/vscode.json?base=fffffff')
.expectBadge({
label: 'commits difference',
message: 'invalid query parameter: head',
color: 'red',
})

View File

@@ -7,7 +7,7 @@ import { documentation, transformErrors } from './github-helpers.js'
const greenStates = ['SUCCESS']
const redStates = ['ERROR', 'FAILURE']
const blueStates = ['INACTIVE']
const otherStates = ['IN_PROGRESS', 'QUEUED', 'PENDING', 'NO_STATUS', 'WAITING']
const otherStates = ['IN_PROGRESS', 'QUEUED', 'PENDING', 'NO_STATUS']
const stateToMessageMappings = {
IN_PROGRESS: 'in progress',

View File

@@ -21,12 +21,6 @@ describe('GithubDeployments', function () {
message: 'in progress',
color: undefined,
})
given({
state: 'WAITING',
}).expect({
message: 'waiting',
color: undefined,
})
given({
state: 'NO_STATUS',
}).expect({

View File

@@ -1,47 +0,0 @@
import Joi from 'joi'
import { formatDate } from '../text-formatters.js'
import { age as ageColor } from '../color-formatters.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, errorMessagesFor } from './github-helpers.js'
const schema = Joi.object({
updated_at: Joi.string().required(),
}).required()
export default class GithubGistLastCommit extends GithubAuthV3Service {
static category = 'activity'
static route = { base: 'github-gist/last-commit', pattern: ':gistId' }
static examples = [
{
title: 'GitHub Gist last commit',
namedParams: {
gistId: '8710649',
},
staticPreview: this.render({ commitDate: '2022-07-29T20:01:41Z' }),
keywords: ['latest'],
documentation,
},
]
static defaultBadgeData = { label: 'last commit' }
static render({ commitDate }) {
return {
message: formatDate(commitDate),
color: ageColor(Date.parse(commitDate)),
}
}
async fetch({ gistId }) {
return this._requestJson({
url: `/gists/${gistId}`,
schema,
errorMessages: errorMessagesFor('gist not found'),
})
}
async handle({ gistId }) {
const { updated_at: commitDate } = await this.fetch({ gistId })
return this.constructor.render({ commitDate })
}
}

View File

@@ -1,24 +0,0 @@
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('last commit in gist (ancient)').get('/871064.json').expectBadge({
label: 'last commit',
message: 'september 2015',
color: 'red',
})
// not checking the color badge, since in August 2022 it is orange but later it will become red
t.create('last commit in gist (still ancient but slightly less so)')
.get('/870071abadfd66a28bf539677332f12b.json')
.expectBadge({
label: 'last commit',
message: 'october 2020',
})
t.create('last commit in gist (gist not found)')
.get('/55555555555555.json')
.expectBadge({
label: 'last commit',
message: 'gist not found',
color: 'red',
})

View File

@@ -1,113 +0,0 @@
import gql from 'graphql-tag'
import Joi from 'joi'
import { metric } from '../text-formatters.js'
import { NotFound } from '../index.js'
import { GithubAuthV4Service } from './github-auth-service.js'
import { documentation as commonDocumentation } from './github-helpers.js'
const schema = Joi.object({
data: Joi.object({
viewer: Joi.object({
gist: Joi.object({
stargazerCount: Joi.number().required(),
url: Joi.string().required(),
owner: Joi.object({
login: Joi.string().required(),
}).required(),
name: Joi.string().required(),
}).allow(null),
}).required(),
}).required(),
}).required()
const documentation = `${commonDocumentation}
<p>This badge shows the number of stargazers for a gist. Gist id is accepted as input and 'gist not found' is returned if the gist is not found for the given gist id.
</p>`
export default class GithubGistStars extends GithubAuthV4Service {
static category = 'social'
static route = {
base: 'github/stars/gists',
pattern: ':gistId',
}
static examples = [
{
title: 'Github Gist stars',
namedParams: { gistId: '47a4d00457a92aa426dbd48a18776322' },
staticPreview: {
label: this.defaultBadgeData.label,
message: metric(29),
style: 'social',
},
documentation,
},
]
static defaultBadgeData = {
label: 'Stars',
color: 'blue',
namedLogo: 'github',
}
static render({ stargazerCount, url, stargazers }) {
return { message: metric(stargazerCount), link: [url, stargazers] }
}
async fetch({ gistId }) {
const data = await this._requestGraphql({
query: gql`
query ($gistId: String!) {
viewer {
gist(name: $gistId) {
stargazerCount
url
name
owner {
login
}
}
}
}
`,
variables: {
gistId,
},
schema,
})
return data
}
static transform({ data }) {
const {
data: {
viewer: { gist },
},
} = data
if (!gist) {
throw new NotFound({ prettyMessage: 'gist not found' })
}
const {
stargazerCount,
url,
name,
owner: { login },
} = gist
const stargazers = `https://gist.github.com/${login}/${name}/stargazers`
return { stargazerCount, url, stargazers }
}
async handle({ gistId }) {
const data = await this.fetch({ gistId })
const { stargazerCount, url, stargazers } =
await this.constructor.transform({
data,
})
return this.constructor.render({ stargazerCount, url, stargazers })
}
}

View File

@@ -1,25 +0,0 @@
import { createServiceTester } from '../tester.js'
import { isMetric } from '../test-validators.js'
export const t = await createServiceTester()
t.create('Gist Total Stars')
.get('/47a4d00457a92aa426dbd48a18776322.json')
.expectBadge({
label: 'Stars',
message: isMetric,
color: 'blue',
link: [
'https://gist.github.com/47a4d00457a92aa426dbd48a18776322',
'https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322/stargazers',
],
})
t.create('Gist Total Stars (Not Found)')
.get('/invalid-gist-id.json')
.expectBadge({
label: 'Stars',
message: 'gist not found',
color: 'red',
link: [],
})

View File

@@ -1,6 +1,6 @@
import gql from 'graphql-tag'
import Joi from 'joi'
import dayjs from 'dayjs'
import moment from 'moment'
import { metric, maybePluralize } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { GithubAuthV4Service } from './github-auth-service.js'
@@ -60,7 +60,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
static category = 'issue-tracking'
static route = {
base: 'github/hacktoberfest',
pattern: ':year(2019|2020|2021|2022)/:user/:repo',
pattern: ':year(2019|2020|2021)/:user/:repo',
queryParamSchema,
}
@@ -68,7 +68,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
{
title: 'GitHub Hacktoberfest combined status',
namedParams: {
year: '2022',
year: '2021',
user: 'snyk',
repo: 'snyk',
},
@@ -82,7 +82,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
{
title: 'GitHub Hacktoberfest combined status (suggestion label override)',
namedParams: {
year: '2022',
year: '2021',
user: 'tmrowco',
repo: 'tmrowapp-contrib',
},
@@ -90,7 +90,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
suggestion_label: 'help wanted',
},
staticPreview: this.render({
year: '2022',
year: '2021',
suggestedIssueCount: 12,
contributionCount: 8,
daysLeft: 15,
@@ -121,7 +121,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
// The global cutoff time is 11/1 noon UTC.
// https://github.com/badges/shields/pull/4109#discussion_r330782093
// We want to show "1 day left" on the last day so we add 1.
daysLeft = dayjs(`${year}-11-01 12:00:00 Z`).diff(dayjs(), 'days') + 1
daysLeft = moment(`${year}-11-01 12:00:00 Z`).diff(moment(), 'days') + 1
}
if (daysLeft < 0) {
return {
@@ -160,7 +160,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
`repo:${user}/${repo}`,
'is:pr',
`created:${year}-10-01..${year}-10-31`,
'-label:invalid',
`-label:invalid`,
]
.filter(Boolean)
.join(' ')
@@ -205,7 +205,10 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
}
static getCalendarPosition(year) {
const daysToStart = dayjs(`${year}-10-01 00:00:00 Z`).diff(dayjs(), 'days')
const daysToStart = moment(`${year}-10-01 00:00:00 Z`).diff(
moment(),
'days'
)
const isBefore = daysToStart > 0
return { daysToStart, isBefore }
}

View File

@@ -35,7 +35,7 @@ export default class GithubLabels extends GithubAuthV3Service {
return this._requestJson({
url: `/repos/${user}/${repo}/labels/${name}`,
schema,
errorMessages: errorMessagesFor('repo or label not found'),
errorMessages: errorMessagesFor(`repo or label not found`),
})
}

View File

@@ -15,29 +15,14 @@ const schema = Joi.array()
author: Joi.object({
date: Joi.string().required(),
}).required(),
committer: Joi.object({
date: Joi.string().required(),
}).required(),
}).required(),
}).required()
)
.required()
.min(1)
const queryParamSchema = Joi.object({
display_timestamp: Joi.string()
.valid('author', 'committer')
.default('author'),
}).required()
export default class GithubLastCommit extends GithubAuthV3Service {
static category = 'activity'
static route = {
base: 'github/last-commit',
pattern: ':user/:repo/:branch*',
queryParamSchema,
}
static route = { base: 'github/last-commit', pattern: ':user/:repo/:branch*' }
static examples = [
{
title: 'GitHub last commit',
@@ -60,17 +45,6 @@ export default class GithubLastCommit extends GithubAuthV3Service {
staticPreview: this.render({ commitDate: '2013-07-31T20:01:41Z' }),
...commonExampleAttrs,
},
{
title: 'GitHub last commit (by committer)',
pattern: ':user/:repo',
namedParams: {
user: 'google',
repo: 'skia',
},
queryParams: { display_timestamp: 'committer' },
staticPreview: this.render({ commitDate: '2022-10-15T20:01:41Z' }),
...commonExampleAttrs,
},
]
static defaultBadgeData = { label: 'last commit' }
@@ -91,11 +65,8 @@ export default class GithubLastCommit extends GithubAuthV3Service {
})
}
async handle({ user, repo, branch }, queryParams) {
async handle({ user, repo, branch }) {
const body = await this.fetch({ user, repo, branch })
return this.constructor.render({
commitDate: body[0].commit[queryParams.display_timestamp].date,
})
return this.constructor.render({ commitDate: body[0].commit.author.date })
}
}

View File

@@ -14,10 +14,6 @@ t.create('last commit (on branch)')
.get('/badges/badgr.co/shielded.json')
.expectBadge({ label: 'last commit', message: 'july 2013' })
t.create('last commit (by committer)')
.get('/badges/badgr.co/shielded.json?display_timestamp=committer')
.expectBadge({ label: 'last commit', message: 'july 2013' })
t.create('last commit (repo not found)')
.get('/badges/helmets.json')
.expectBadge({ label: 'last commit', message: 'repo not found' })

View File

@@ -85,7 +85,7 @@ export default class GithubMilestoneDetail extends GithubAuthV3Service {
return this._requestJson({
url: `/repos/${user}/${repo}/milestones/${number}`,
schema,
errorMessages: errorMessagesFor('repo or milestone not found'),
errorMessages: errorMessagesFor(`repo or milestone not found`),
})
}

View File

@@ -70,7 +70,7 @@ export default class GithubMilestone extends GithubAuthV3Service {
return this._requestJson({
url: `/repos/${user}/${repo}/milestones?state=${variant}`,
schema,
errorMessages: errorMessagesFor('repo not found'),
errorMessages: errorMessagesFor(`repo not found`),
})
}

View File

@@ -175,7 +175,7 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthV3Service
})
const wantedDependency = scope ? `${scope}/${packageName}` : packageName
const range = getDependencyVersion({
const { range } = getDependencyVersion({
kind,
wantedDependency,
dependencies,

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