Compare commits
93 Commits
server-202
...
integratio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57036a34b0 | ||
|
|
185fdfe001 | ||
|
|
1d29e66ad2 | ||
|
|
82ff65089a | ||
|
|
00494aad20 | ||
|
|
2822d134c1 | ||
|
|
6ff9760319 | ||
|
|
681a6a672b | ||
|
|
ac4324c0bb | ||
|
|
d34b1e81a1 | ||
|
|
f2e6183e01 | ||
|
|
e8f151eafa | ||
|
|
93fa955dde | ||
|
|
e3c938b4d7 | ||
|
|
ae018cdddc | ||
|
|
f6b09e9ca3 | ||
|
|
d8c825a1ad | ||
|
|
3a07437bea | ||
|
|
0c21dce592 | ||
|
|
9d92a2a7ab | ||
|
|
9af1bdfa1a | ||
|
|
457aa5e0e5 | ||
|
|
971e6da72d | ||
|
|
c470607998 | ||
|
|
67fabf28a7 | ||
|
|
35dfc260b4 | ||
|
|
1f6e17099f | ||
|
|
ef5f03e41c | ||
|
|
cf83fb3235 | ||
|
|
d9842c0ceb | ||
|
|
a73026faa9 | ||
|
|
bf9412b8b1 | ||
|
|
4f8501aceb | ||
|
|
42fcf035d4 | ||
|
|
4e6b662fa4 | ||
|
|
8bf429d0de | ||
|
|
daca76726c | ||
|
|
aa345aa9d4 | ||
|
|
9642729e05 | ||
|
|
d6bb882a4b | ||
|
|
f23d16a6fe | ||
|
|
b6163941d7 | ||
|
|
e039a2fd43 | ||
|
|
b624179b8d | ||
|
|
8267758ca0 | ||
|
|
6cf6bac22a | ||
|
|
1dc9e0e4c0 | ||
|
|
a199610fcd | ||
|
|
3adea534e5 | ||
|
|
7a5b28fffe | ||
|
|
665d8317e2 | ||
|
|
ab10cdbaa4 | ||
|
|
bb326a0f93 | ||
|
|
73d8390703 | ||
|
|
acf189eaf7 | ||
|
|
08af9eadbd | ||
|
|
9daae0ce94 | ||
|
|
104aa30709 | ||
|
|
0534192ead | ||
|
|
d29cb90a3e | ||
|
|
0b8370df49 | ||
|
|
f5e671a64b | ||
|
|
a41403593f | ||
|
|
4641fe62b0 | ||
|
|
a2db48616e | ||
|
|
35700bad5f | ||
|
|
1321bcfb16 | ||
|
|
e95189c180 | ||
|
|
aa646e01f4 | ||
|
|
e3d5046d00 | ||
|
|
70b05454b2 | ||
|
|
4284468d7d | ||
|
|
9ffc3673c4 | ||
|
|
7359296e6e | ||
|
|
52bedb0881 | ||
|
|
19663c6c84 | ||
|
|
cab062fc64 | ||
|
|
812c322ef6 | ||
|
|
5409f29167 | ||
|
|
84829853b9 | ||
|
|
af47032cb5 | ||
|
|
dbf94df115 | ||
|
|
f985d2cc02 | ||
|
|
547e2b8bd4 | ||
|
|
febaf7e87a | ||
|
|
0779426502 | ||
|
|
d1ddb3ec88 | ||
|
|
0029ea04e5 | ||
|
|
538b429941 | ||
|
|
39b015164e | ||
|
|
fa3839cdab | ||
|
|
1954963cd3 | ||
|
|
fbb2ff3619 |
@@ -1,69 +1,5 @@
|
||||
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
|
||||
@@ -89,87 +25,7 @@ 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
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
<<: *main_steps
|
||||
|
||||
integration:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
integration@node-17:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
- image: redis
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
danger:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
@@ -228,12 +84,6 @@ jobs:
|
||||
when: always
|
||||
command: npm run build
|
||||
|
||||
package:
|
||||
machine:
|
||||
image: 'ubuntu-2004:202111-02'
|
||||
|
||||
<<: *package_steps
|
||||
|
||||
services:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
@@ -297,26 +147,10 @@ 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:
|
||||
@@ -346,12 +180,6 @@ workflows:
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: gh-pages
|
||||
# - main:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# - main@node-latest:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# - frontend:
|
||||
# requires:
|
||||
# - npm-install
|
||||
|
||||
@@ -144,6 +144,8 @@ 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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/3_Badge_request.md
vendored
2
.github/ISSUE_TEMPLATE/3_Badge_request.md
vendored
@@ -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 requires an API key?
|
||||
- Does the API require an API key?
|
||||
- Link to the API documentation.
|
||||
-->
|
||||
|
||||
|
||||
2
.github/actions/close-bot/index.js
vendored
2
.github/actions/close-bot/index.js
vendored
@@ -27,7 +27,7 @@ async function run() {
|
||||
state: 'closed',
|
||||
})
|
||||
|
||||
core.debug(`Done.`)
|
||||
core.debug('Done.')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
33
.github/actions/close-bot/package-lock.json
generated
vendored
33
.github/actions/close-bot/package-lock.json
generated
vendored
@@ -9,16 +9,17 @@
|
||||
"version": "0.0.0",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.9.0",
|
||||
"@actions/core": "^1.9.1",
|
||||
"@actions/github": "^5.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1"
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
@@ -204,6 +205,14 @@
|
||||
"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",
|
||||
@@ -226,11 +235,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1"
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"@actions/github": {
|
||||
@@ -393,6 +403,11 @@
|
||||
"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",
|
||||
|
||||
2
.github/actions/close-bot/package.json
vendored
2
.github/actions/close-bot/package.json
vendored
@@ -10,7 +10,7 @@
|
||||
"author": "chris48s",
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.9.0",
|
||||
"@actions/core": "^1.9.1",
|
||||
"@actions/github": "^5.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
21
.github/actions/core-tests/action.yml
vendored
Normal file
21
.github/actions/core-tests/action.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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
|
||||
20
.github/actions/integration-tests/action.yml
vendored
Normal file
20
.github/actions/integration-tests/action.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
26
.github/actions/package-tests/action.yml
vendored
Normal file
26
.github/actions/package-tests/action.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
25
.github/actions/setup/action.yml
vendored
Normal file
25
.github/actions/setup/action.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
2
.github/workflows/auto-close.yml
vendored
2
.github/workflows/auto-close.yml
vendored
@@ -5,7 +5,7 @@ permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
auto-close:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
|
||||
2
.github/workflows/build-docker-image.yml
vendored
2
.github/workflows/build-docker-image.yml
vendored
@@ -3,7 +3,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
2
.github/workflows/deploy-docs.yml
vendored
2
.github/workflows/deploy-docs.yml
vendored
@@ -8,7 +8,7 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
2
.github/workflows/draft-release.yml
vendored
2
.github/workflows/draft-release.yml
vendored
@@ -10,7 +10,7 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -2,7 +2,7 @@ name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
enforce-dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
|
||||
2
.github/workflows/publish-docker-next.yml
vendored
2
.github/workflows/publish-docker-next.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
publish-docker-next:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
37
.github/workflows/test-integration-17.yml
vendored
Normal file
37
.github/workflows/test-integration-17.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Integration@node 17
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
jobs:
|
||||
test-integration-17:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Integration Tests
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
35
.github/workflows/test-integration.yml
vendored
Normal file
35
.github/workflows/test-integration.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Integration
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
jobs:
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Integration Tests
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
26
.github/workflows/test-lint.yml
vendored
Normal file
26
.github/workflows/test-lint.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
jobs:
|
||||
test-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: ESLint
|
||||
if: always()
|
||||
run: npm run lint
|
||||
|
||||
- name: 'Prettier check (quick fix: `npm run prettier`)'
|
||||
if: always()
|
||||
run: npm run prettier:check
|
||||
23
.github/workflows/test-main-17.yml
vendored
Normal file
23
.github/workflows/test-main-17.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Main@node 17
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
jobs:
|
||||
test-main-17:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
26
.github/workflows/test-main.yml
vendored
Normal file
26
.github/workflows/test-main.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Main
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
jobs:
|
||||
test-main:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['ubuntu-latest', 'windows-latest']
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
44
.github/workflows/test-package-cli.yml
vendored
Normal file
44
.github/workflows/test-package-cli.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Package CLI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
# Smoke test (render a badge with the CLI) with only the package
|
||||
# dependencies installed.
|
||||
|
||||
jobs:
|
||||
test-package-cli:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- node: '14'
|
||||
engine-strict: 'false'
|
||||
- node: '16'
|
||||
engine-strict: 'false'
|
||||
- node: '18'
|
||||
engine-strict: 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
run: |
|
||||
cd badge-maker
|
||||
npm install
|
||||
npm link
|
||||
|
||||
- name: Render a badge with the CLI
|
||||
run: |
|
||||
cd badge-maker
|
||||
badge cactus grown :green @flat
|
||||
32
.github/workflows/test-package-lib.yml
vendored
Normal file
32
.github/workflows/test-package-lib.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Package Library
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
|
||||
jobs:
|
||||
test-package-lib:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- node: '14'
|
||||
engine-strict: 'false'
|
||||
- node: '16'
|
||||
engine-strict: 'true'
|
||||
- node: '18'
|
||||
engine-strict: 'false'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
|
||||
- name: Package tests
|
||||
uses: ./.github/actions/package-tests
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -4,6 +4,35 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2022-09-04
|
||||
|
||||
- fix frontend compile for users running on Windows [#8350](https://github.com/badges/shields/issues/8350)
|
||||
- [DockerSize] Docker image size multi arch [#8290](https://github.com/badges/shields/issues/8290)
|
||||
- upgrade gatsby [#8334](https://github.com/badges/shields/issues/8334)
|
||||
- Custom domains for [JitPack] artifacts [#8333](https://github.com/badges/shields/issues/8333)
|
||||
- fix [dockerstars] service [#8316](https://github.com/badges/shields/issues/8316)
|
||||
- [BountySource] Fix: Broken Badge generation for decimal activity values [#8315](https://github.com/badges/shields/issues/8315)
|
||||
- feat: add [gitlabmergerequests] service [#8166](https://github.com/badges/shields/issues/8166)
|
||||
- Fix terminology for [ROS] version service [#8292](https://github.com/badges/shields/issues/8292)
|
||||
- feat: add [GitlabStars] service [#8209](https://github.com/badges/shields/issues/8209)
|
||||
- Fix invalid `rst` format when `alt` or `target` is present [#8275](https://github.com/badges/shields/issues/8275)
|
||||
- [GithubGistLastCommit] GitHub gist last commit [#8272](https://github.com/badges/shields/issues/8272)
|
||||
- [GitHub] GitHub file size for a specific branch [#8262](https://github.com/badges/shields/issues/8262)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-08-01
|
||||
|
||||
- [pypi] Add Framework Version Badges support [#8261](https://github.com/badges/shields/issues/8261)
|
||||
- feat: add [GitlabForks] server [#8208](https://github.com/badges/shields/issues/8208)
|
||||
- Update PyPI api according to https://warehouse.pypa.io/api-reference/json.html [#8251](https://github.com/badges/shields/issues/8251)
|
||||
- Add [galaxytoolshed] Activity [#8164](https://github.com/badges/shields/issues/8164)
|
||||
- [greasyfork] Add Greasy Fork rating badges [#8087](https://github.com/badges/shields/issues/8087)
|
||||
- refactor(deps): Replace moment with dayjs [#8192](https://github.com/badges/shields/issues/8192)
|
||||
- add spaces round pipe in [conda] badge [#8189](https://github.com/badges/shields/issues/8189)
|
||||
- Add [ROS] version service [#8169](https://github.com/badges/shields/issues/8169)
|
||||
- feat: add [gitlabissues] service [#8108](https://github.com/badges/shields/issues/8108)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-07-03
|
||||
|
||||
- Add [galaxytoolshed] services [#8114](https://github.com/badges/shields/issues/8114)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 4.0.0 [WIP]
|
||||
|
||||
- Drop compatibility with Node 10
|
||||
- Drop compatibility with Node < 14
|
||||
|
||||
## 3.3.1
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12",
|
||||
"node": ">= 14",
|
||||
"npm": ">= 6"
|
||||
},
|
||||
"collective": {
|
||||
|
||||
@@ -13,6 +13,13 @@ 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)
|
||||
@@ -22,7 +29,9 @@ class InvalidService extends Error {
|
||||
|
||||
async function loadServiceClasses(servicePaths) {
|
||||
if (!servicePaths) {
|
||||
servicePaths = glob.sync(path.join(serviceDir, '**', '*.service.js'))
|
||||
servicePaths = glob.sync(
|
||||
toUnixPath(path.join(serviceDir, '**', '*.service.js'))
|
||||
)
|
||||
}
|
||||
|
||||
const serviceClasses = []
|
||||
@@ -42,8 +51,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(serviceDir, '')
|
||||
.split(path.sep)[1]
|
||||
.replace(toUnixPath(serviceDir), '')
|
||||
.split('/')[1]
|
||||
serviceClass.validateDefinition()
|
||||
return serviceClasses.push(serviceClass)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
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"]'
|
||||
@@ -9,8 +13,13 @@ 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 () {
|
||||
cy.visit('/')
|
||||
visitAndWait('/')
|
||||
|
||||
cy.get(SEARCH_INPUT).type('pypi')
|
||||
|
||||
@@ -18,7 +27,7 @@ describe('Main page', function () {
|
||||
})
|
||||
|
||||
it('Shows badge from category', function () {
|
||||
cy.visit('/category/chat')
|
||||
visitAndWait('/category/chat')
|
||||
|
||||
expectBadgeExample(
|
||||
'Discourse status',
|
||||
@@ -29,7 +38,7 @@ describe('Main page', function () {
|
||||
|
||||
it('Suggest badges', function () {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
|
||||
cy.visit('/')
|
||||
visitAndWait('/')
|
||||
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
@@ -39,7 +48,7 @@ describe('Main page', function () {
|
||||
|
||||
it('Customization form is filled with suggested badge details', function () {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
|
||||
cy.visit('/')
|
||||
visitAndWait('/')
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
|
||||
@@ -51,7 +60,7 @@ describe('Main page', function () {
|
||||
|
||||
it('Customizate suggested badge', function () {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
|
||||
cy.visit('/')
|
||||
visitAndWait('/')
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
cy.contains(badgeUrl).click()
|
||||
@@ -62,7 +71,7 @@ describe('Main page', function () {
|
||||
})
|
||||
|
||||
it('Do not duplicate example parameters', function () {
|
||||
cy.visit('/category/funding')
|
||||
visitAndWait('/category/funding')
|
||||
|
||||
cy.contains('GitHub Sponsors').click()
|
||||
cy.get('[name="style"]').should($style => {
|
||||
|
||||
@@ -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('')
|
||||
|
||||
@@ -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 :alt: Example'
|
||||
'.. image:: https://img.shields.io/badge\n :alt: Example'
|
||||
)
|
||||
given(
|
||||
'https://img.shields.io/badge',
|
||||
'https://example.com/example',
|
||||
'Example'
|
||||
).expect(
|
||||
'.. image:: https://img.shields.io/badge :alt: Example :target: https://example.com/example'
|
||||
'.. image:: https://img.shields.io/badge\n :alt: Example\n :target: https://example.com/example'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ export function reStructuredText(
|
||||
): string {
|
||||
let result = `.. image:: ${badgeUrl}`
|
||||
if (title) {
|
||||
result += ` :alt: ${title}`
|
||||
result += `\n :alt: ${title}`
|
||||
}
|
||||
if (link) {
|
||||
result += ` :target: ${link}`
|
||||
result += `\n :target: ${link}`
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPgo='
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZD0iTTAgMGg0MHY0MEgwVjB6IiBmaWxsPSIjY2IwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPg=='
|
||||
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+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPgo='
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZD0iTTAgMGg0MHY0MEgwVjB6IiBmaWxsPSIjY2IwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcgN2gyNnYyNmgtN1YxNGgtNnYxOUg3eiIvPjwvc3ZnPg=='
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
function svg2base64(svg) {
|
||||
return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`
|
||||
return `data:image/svg+xml;base64,${Buffer.from(svg.trim()).toString(
|
||||
'base64'
|
||||
)}`
|
||||
}
|
||||
|
||||
export { svg2base64 }
|
||||
|
||||
16517
package-lock.json
generated
16517
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@@ -21,10 +21,10 @@
|
||||
"url": "https://github.com/badges/shields"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/lato": "^4.5.8",
|
||||
"@fontsource/lekton": "^4.5.9",
|
||||
"@fontsource/lato": "^4.5.9",
|
||||
"@fontsource/lekton": "^4.5.10",
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@sentry/node": "^7.8.0",
|
||||
"@sentry/node": "^7.13.0",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -32,19 +32,19 @@
|
||||
"chalk": "^5.0.1",
|
||||
"check-node-version": "^4.2.1",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.7",
|
||||
"config": "^3.3.8",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.4",
|
||||
"dayjs": "^1.11.5",
|
||||
"decamelize": "^3.2.0",
|
||||
"emojic": "^1.1.17",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^4.0.9",
|
||||
"fast-xml-parser": "^4.0.10",
|
||||
"glob": "^8.0.3",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^12.3.0",
|
||||
"got": "^12.4.1",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"ioredis": "5.2.2",
|
||||
"ioredis": "5.2.3",
|
||||
"joi": "17.6.0",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -57,11 +57,11 @@
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^14.0.1",
|
||||
"prom-client": "^14.1.0",
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^7.1.1",
|
||||
"semver": "~7.3.7",
|
||||
"simple-icons": "7.5.0",
|
||||
"simple-icons": "7.11.0",
|
||||
"webextension-store-meta": "^1.0.5",
|
||||
"xmldom": "~0.6.0",
|
||||
"xpath": "~0.0.32"
|
||||
@@ -141,12 +141,12 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.9",
|
||||
"@babel/core": "^7.19.1",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.18.9",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"@mapbox/react-click-to-select": "^2.2.1",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.groupby": "^4.6.7",
|
||||
"@types/mocha": "^9.1.1",
|
||||
@@ -154,11 +154,11 @@
|
||||
"@types/react-helmet": "^6.1.5",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-select": "^4.0.17",
|
||||
"@types/styled-components": "5.1.25",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-preset-gatsby": "^2.19.0",
|
||||
"babel-preset-gatsby": "^2.22.0",
|
||||
"c8": "^7.12.0",
|
||||
"caller": "^1.1.0",
|
||||
"chai": "^4.3.6",
|
||||
@@ -167,9 +167,10 @@
|
||||
"chai-string": "^1.4.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^7.3.0",
|
||||
"cypress": "^10.3.1",
|
||||
"danger": "^11.1.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"cypress": "^10.8.0",
|
||||
"cypress-wait-for-stable-dom": "^0.0.3",
|
||||
"danger": "^11.1.2",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
@@ -180,23 +181,23 @@
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsdoc": "^39.3.3",
|
||||
"eslint-plugin-jsdoc": "^39.3.6",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sort-class-members": "^1.14.1",
|
||||
"eslint-plugin-sort-class-members": "^1.15.2",
|
||||
"fetch-ponyfill": "^7.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"gatsby": "4.6.2",
|
||||
"gatsby": "4.23.0",
|
||||
"gatsby-plugin-catch-links": "^4.19.0",
|
||||
"gatsby-plugin-page-creator": "^4.7.0",
|
||||
"gatsby-plugin-react-helmet": "^5.10.0",
|
||||
"gatsby-plugin-page-creator": "^4.22.0",
|
||||
"gatsby-plugin-react-helmet": "^5.22.0",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^4.9.0",
|
||||
"gatsby-plugin-styled-components": "^5.19.0",
|
||||
"gatsby-plugin-typescript": "^4.11.1",
|
||||
"gatsby-plugin-typescript": "^4.22.0",
|
||||
"humanize-string": "^2.1.0",
|
||||
"icedfrisby": "4.0.0",
|
||||
"icedfrisby-nock": "^2.1.0",
|
||||
@@ -213,10 +214,10 @@
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.2.9",
|
||||
"node-mocks-http": "^1.11.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"nodemon": "^2.0.20",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"open-cli": "^7.0.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"portfinder": "^1.0.32",
|
||||
"prettier": "2.7.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -236,8 +237,8 @@
|
||||
"start-server-and-test": "1.14.0",
|
||||
"styled-components": "^5.3.5",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"tsd": "^0.22.0",
|
||||
"typescript": "^4.7.4",
|
||||
"tsd": "^0.24.1",
|
||||
"typescript": "^4.8.3",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -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
|
||||
|
||||
36
scripts/mocha2md.js
Normal file
36
scripts/mocha2md.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/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
|
||||
@@ -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')
|
||||
|
||||
@@ -27,7 +27,7 @@ function pullRequestClassGenerator(raw) {
|
||||
static category = 'issue-tracking'
|
||||
static route = {
|
||||
base: `bitbucket/${routePrefix}`,
|
||||
pattern: `:user/:repo`,
|
||||
pattern: ':user/:repo',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
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: nonNegativeInteger })
|
||||
const schema = Joi.object({ activity_total: Joi.number().required() })
|
||||
|
||||
export default class Bountysource extends BaseJsonService {
|
||||
static category = 'funding'
|
||||
|
||||
@@ -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 {object}
|
||||
* @type {Joi}
|
||||
*/
|
||||
const isBuildStatus = Joi.equal(...allStatuses)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -28,6 +28,12 @@ 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,
|
||||
})
|
||||
|
||||
@@ -5,13 +5,29 @@ export const t = await createServiceTester()
|
||||
|
||||
const isAutomatedBuildStatus = Joi.string().valid('automated', 'manual')
|
||||
|
||||
t.create('docker cloud automated build (valid, user)')
|
||||
.get('/jrottenberg/ffmpeg.json')
|
||||
t.create('docker cloud automated build (valid user)')
|
||||
.get('/pavics/magpie.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 =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, NotFound } from '../index.js'
|
||||
import { dockerBlue, buildDockerUrl } from './docker-helpers.js'
|
||||
import { fetchBuild } from './docker-cloud-common-fetch.js'
|
||||
|
||||
@@ -31,6 +31,12 @@ 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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,29 @@ 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('/jrottenberg/ffmpeg.json')
|
||||
t.create('docker cloud build status (valid user)')
|
||||
.get('/pavics/magpie.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 =>
|
||||
|
||||
@@ -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' },
|
||||
})
|
||||
|
||||
@@ -1,71 +1,36 @@
|
||||
const sizeDataNoTagSemVerSort = [
|
||||
{ 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 },
|
||||
{
|
||||
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 },
|
||||
],
|
||||
},
|
||||
]
|
||||
const versionDataNoTagDateSort = {
|
||||
count: 4,
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
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 {
|
||||
@@ -55,6 +76,7 @@ function getDigestSemVerMatches({ data, digest }) {
|
||||
}
|
||||
|
||||
export {
|
||||
archSchema,
|
||||
dockerBlue,
|
||||
buildDockerUrl,
|
||||
getDockerHubUser,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { nonNegativeInteger } from '../validators.js'
|
||||
import { latest } from '../version.js'
|
||||
import { BaseJsonService, NotFound } from '../index.js'
|
||||
import {
|
||||
archSchema,
|
||||
buildDockerUrl,
|
||||
getDockerHubUser,
|
||||
getMultiPageData,
|
||||
@@ -12,6 +13,12 @@ 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({
|
||||
@@ -20,14 +27,37 @@ 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 }
|
||||
@@ -46,6 +76,14 @@ 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',
|
||||
@@ -73,30 +111,83 @@ export default class DockerSize extends BaseJsonService {
|
||||
})
|
||||
}
|
||||
|
||||
transform({ tag, sort, data }) {
|
||||
if (!tag && sort === 'date') {
|
||||
if (data.count === 0) {
|
||||
throw new NotFound({ prettyMessage: 'repository not found' })
|
||||
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) }
|
||||
} else {
|
||||
return { size: data.results[0].full_size }
|
||||
return { size: latestEntry.full_size }
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ user, repo, tag }, { sort }) {
|
||||
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 }) {
|
||||
let data
|
||||
|
||||
if (!tag && sort === 'date') {
|
||||
@@ -111,7 +202,7 @@ export default class DockerSize extends BaseJsonService {
|
||||
data = await this.fetch({ user, repo, tag })
|
||||
}
|
||||
|
||||
const { size } = await this.transform({ tag, sort, data })
|
||||
const { size } = await this.transform({ tag, sort, data, arch })
|
||||
return this.constructor.render({ size })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,40 +3,99 @@ import DockerSize from './docker-size.service.js'
|
||||
import { sizeDataNoTagSemVerSort } from './docker-fixtures.js'
|
||||
|
||||
describe('DockerSize', function () {
|
||||
test(DockerSize.prototype.transform, () => {
|
||||
given({
|
||||
tag: '',
|
||||
sort: 'date',
|
||||
data: { results: [{ name: 'next', full_size: 219939484 }] },
|
||||
}).expect({
|
||||
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({
|
||||
size: 219939484,
|
||||
})
|
||||
given({
|
||||
tag: '',
|
||||
sort: 'date',
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
full_size: 300000000,
|
||||
name: 'next',
|
||||
images: [
|
||||
{ architecture: 'amd64', size: 219939484 },
|
||||
{ architecture: 'arm64', size: 200000000 },
|
||||
],
|
||||
},
|
||||
],
|
||||
}).expect({
|
||||
size: 300000000,
|
||||
})
|
||||
given(
|
||||
{
|
||||
count: 1,
|
||||
results: [
|
||||
{ name: 'latest', full_size: 74661264 },
|
||||
{ name: 'arm64v8-latest', full_size: 76310416 },
|
||||
{ name: 'arm32v7-latest', full_size: 68001970 },
|
||||
{ name: 'amd64-latest', full_size: 74661264 },
|
||||
{
|
||||
full_size: 300000000,
|
||||
name: 'next',
|
||||
images: [
|
||||
{ architecture: 'amd64', size: 219939484 },
|
||||
{ architecture: 'arm64', size: 200000000 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}).expect({
|
||||
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,
|
||||
})
|
||||
given({
|
||||
tag: '',
|
||||
sort: 'semver',
|
||||
data: sizeDataNoTagSemVerSort,
|
||||
full_size: 300000000,
|
||||
name: 'next',
|
||||
images: [{ architecture: 'amd64', size: 219939484 }],
|
||||
}).expect({
|
||||
size: 13448411,
|
||||
size: 300000000,
|
||||
})
|
||||
given({
|
||||
tag: 'latest',
|
||||
data: { name: 'latest', full_size: 13448411 },
|
||||
}).expect({
|
||||
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(sizeDataNoTagSemVerSort).expect({
|
||||
size: 400000000,
|
||||
})
|
||||
given(sizeDataNoTagSemVerSort, 'nonexistentArch').expectError(
|
||||
'Not Found: architecture not found'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,13 @@ 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({
|
||||
@@ -41,5 +48,19 @@ t.create('docker image size (invalid, unknown repository)')
|
||||
.get('/_/not-a-real-repo.json')
|
||||
.expectBadge({
|
||||
label: 'image size',
|
||||
message: 'repository not found',
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import Joi from 'joi'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseService } from '../index.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import {
|
||||
dockerBlue,
|
||||
buildDockerUrl,
|
||||
getDockerHubUser,
|
||||
} from './docker-helpers.js'
|
||||
|
||||
export default class DockerStars extends BaseService {
|
||||
const schema = Joi.object({
|
||||
star_count: nonNegativeInteger.required(),
|
||||
}).required()
|
||||
|
||||
export default class DockerStars extends BaseJsonService {
|
||||
static category = 'rating'
|
||||
static route = buildDockerUrl('stars')
|
||||
static examples = [
|
||||
@@ -31,18 +36,17 @@ export default class DockerStars extends BaseService {
|
||||
}
|
||||
|
||||
async fetch({ user, repo }) {
|
||||
const url = `https://hub.docker.com/v2/repositories/${getDockerHubUser(
|
||||
user
|
||||
)}/${repo}/stars/count/`
|
||||
const { buffer } = await this._request({
|
||||
url,
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://hub.docker.com/v2/repositories/${getDockerHubUser(
|
||||
user
|
||||
)}/${repo}/`,
|
||||
errorMessages: { 404: 'repo not found' },
|
||||
})
|
||||
return this.constructor._validate(buffer, nonNegativeInteger)
|
||||
}
|
||||
|
||||
async handle({ user, repo }) {
|
||||
const stars = await this.fetch({ user, repo })
|
||||
return this.constructor.render({ stars })
|
||||
const { star_count } = await this.fetch({ user, repo })
|
||||
return this.constructor.render({ stars: star_count })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { nonNegativeInteger } from '../validators.js'
|
||||
import { latest, renderVersionBadge } from '../version.js'
|
||||
import { BaseJsonService, NotFound, InvalidResponse } from '../index.js'
|
||||
import {
|
||||
archSchema,
|
||||
buildDockerUrl,
|
||||
getDockerHubUser,
|
||||
getMultiPageData,
|
||||
@@ -26,23 +27,7 @@ const buildSchema = Joi.object({
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
sort: Joi.string().valid('date', 'semver').default('date'),
|
||||
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'),
|
||||
arch: archSchema.default('amd64'),
|
||||
}).required()
|
||||
|
||||
export default class DockerVersion extends BaseJsonService {
|
||||
|
||||
@@ -39,5 +39,5 @@ t.create('docker version (invalid, unknown repository)')
|
||||
.get('/_/not-a-real-repo.json')
|
||||
.expectBadge({
|
||||
label: 'version',
|
||||
message: 'repository not found',
|
||||
message: 'repository or tag not found',
|
||||
})
|
||||
|
||||
@@ -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' })
|
||||
|
||||
@@ -22,7 +22,7 @@ const errorMessages = {
|
||||
* Joi schema for validating individual value.
|
||||
* Checks if the individual value is of type string or number.
|
||||
*
|
||||
* @type {object}
|
||||
* @type {Joi}
|
||||
*/
|
||||
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 {object}
|
||||
* @type {Joi}
|
||||
*/
|
||||
const compoundValueSchema = Joi.alternatives().try(
|
||||
individualValueSchema,
|
||||
|
||||
@@ -23,7 +23,7 @@ const optionalNumberWhenAnyLogoPresent = Joi.alternatives()
|
||||
/**
|
||||
* Joi schema for validating endpoint.
|
||||
*
|
||||
* @type {object}
|
||||
* @type {Joi}
|
||||
*/
|
||||
const endpointSchema = Joi.object({
|
||||
schemaVersion: 1,
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class FreeCodeCampPoints extends BaseJsonService {
|
||||
async fetch({ username }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://api.freecodecamp.org/api/users/get-public-profile`,
|
||||
url: 'https://api.freecodecamp.org/api/users/get-public-profile',
|
||||
options: {
|
||||
searchParams: {
|
||||
username,
|
||||
|
||||
@@ -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
|
||||
|
||||
59
services/github/github-commits-difference.service.js
Normal file
59
services/github/github-commits-difference.service.js
Normal file
@@ -0,0 +1,59 @@
|
||||
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 })
|
||||
}
|
||||
}
|
||||
43
services/github/github-commits-difference.tester.js
Normal file
43
services/github/github-commits-difference.tester.js
Normal file
@@ -0,0 +1,43 @@
|
||||
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',
|
||||
})
|
||||
47
services/github/github-gist-last-commit.service.js
Normal file
47
services/github/github-gist-last-commit.service.js
Normal file
@@ -0,0 +1,47 @@
|
||||
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 })
|
||||
}
|
||||
}
|
||||
24
services/github/github-gist-last-commit.tester.js
Normal file
24
services/github/github-gist-last-commit.tester.js
Normal file
@@ -0,0 +1,24 @@
|
||||
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',
|
||||
})
|
||||
@@ -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(' ')
|
||||
|
||||
@@ -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'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ class GithubPackageJsonDependencyVersion extends ConditionalGithubAuthV3Service
|
||||
})
|
||||
|
||||
const wantedDependency = scope ? `${scope}/${packageName}` : packageName
|
||||
const { range } = getDependencyVersion({
|
||||
const range = getDependencyVersion({
|
||||
kind,
|
||||
wantedDependency,
|
||||
dependencies,
|
||||
|
||||
@@ -5,6 +5,10 @@ import { NotFound } from '../index.js'
|
||||
import { GithubAuthV3Service } from './github-auth-service.js'
|
||||
import { documentation, errorMessagesFor } from './github-helpers.js'
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
branch: Joi.string(),
|
||||
}).required()
|
||||
|
||||
const schema = Joi.alternatives(
|
||||
Joi.object({
|
||||
size: nonNegativeInteger,
|
||||
@@ -18,6 +22,7 @@ export default class GithubSize extends GithubAuthV3Service {
|
||||
static route = {
|
||||
base: 'github/size',
|
||||
pattern: ':user/:repo/:path*',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
@@ -32,6 +37,20 @@ export default class GithubSize extends GithubAuthV3Service {
|
||||
keywords: ['repo'],
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'GitHub file size in bytes on a specified ref (branch/commit/tag)',
|
||||
namedParams: {
|
||||
user: 'webcaetano',
|
||||
repo: 'craft',
|
||||
path: 'build/phaser-craft.min.js',
|
||||
},
|
||||
staticPreview: this.render({ size: 9170 }),
|
||||
keywords: ['repo'],
|
||||
documentation,
|
||||
queryParams: {
|
||||
branch: 'master',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
static render({ size }) {
|
||||
@@ -41,16 +60,25 @@ export default class GithubSize extends GithubAuthV3Service {
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ user, repo, path }) {
|
||||
return this._requestJson({
|
||||
url: `/repos/${user}/${repo}/contents/${path}`,
|
||||
schema,
|
||||
errorMessages: errorMessagesFor('repo or file not found'),
|
||||
})
|
||||
async fetch({ user, repo, path, branch }) {
|
||||
if (branch) {
|
||||
return this._requestJson({
|
||||
url: `/repos/${user}/${repo}/contents/${path}?ref=${branch}`,
|
||||
schema,
|
||||
errorMessages: errorMessagesFor('repo, branch or file not found'),
|
||||
})
|
||||
} else {
|
||||
return this._requestJson({
|
||||
url: `/repos/${user}/${repo}/contents/${path}`,
|
||||
schema,
|
||||
errorMessages: errorMessagesFor('repo or file not found'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ user, repo, path }) {
|
||||
const body = await this.fetch({ user, repo, path })
|
||||
async handle({ user, repo, path }, queryParams) {
|
||||
const branch = queryParams.branch
|
||||
const body = await this.fetch({ user, repo, path, branch })
|
||||
if (Array.isArray(body)) {
|
||||
throw new NotFound({ prettyMessage: 'not a regular file' })
|
||||
}
|
||||
|
||||
@@ -10,6 +10,22 @@ t.create('File size 404')
|
||||
.get('/webcaetano/craft/build/does-not-exist.min.js.json')
|
||||
.expectBadge({ label: 'size', message: 'repo or file not found' })
|
||||
|
||||
t.create('File size for nonexisting branch')
|
||||
.get('/webcaetano/craft/build/phaser-craft.min.js.json?branch=notARealBranch')
|
||||
.expectBadge({ label: 'size', message: 'repo, branch or file not found' })
|
||||
|
||||
t.create('File size for "not a regular file"')
|
||||
.get('/webcaetano/craft/build.json')
|
||||
.expectBadge({ label: 'size', message: 'not a regular file' })
|
||||
|
||||
t.create('File size for a specified branch')
|
||||
.get('/webcaetano/craft/build/craft.min.js.json?branch=version-2')
|
||||
.expectBadge({ label: 'size', message: isFileSize })
|
||||
|
||||
t.create('File size for a specified tag')
|
||||
.get('/webcaetano/craft/build/phaser-craft.min.js.json?branch=2.1.2')
|
||||
.expectBadge({ label: 'size', message: isFileSize })
|
||||
|
||||
t.create('File size for a specified commit')
|
||||
.get('/webcaetano/craft/build/phaser-craft.min.js.json?branch=b848dbb')
|
||||
.expectBadge({ label: 'size', message: isFileSize })
|
||||
|
||||
@@ -108,7 +108,7 @@ const query = gql`
|
||||
|
||||
const affiliationsAllowedValues = [
|
||||
'OWNER',
|
||||
`COLLABORATOR`,
|
||||
'COLLABORATOR',
|
||||
'ORGANIZATION_MEMBER',
|
||||
]
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl, nonNegativeInteger } from '../validators.js'
|
||||
import { renderContributorBadge } from '../contributor-count.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.object({ 'x-total': nonNegativeInteger }).required()
|
||||
@@ -9,19 +10,6 @@ const queryParamSchema = Joi.object({
|
||||
gitlab_url: optionalUrl,
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g. gitlab-org/gitlab )
|
||||
</p>
|
||||
`
|
||||
|
||||
const customDocumentation = `
|
||||
<p>
|
||||
Note that only network-accessible jihulab.com and other self-managed GitLab instances are supported.
|
||||
You may use your GitLab Project Id (e.g. 13953) or your Project Path (e.g. gitlab-cn/gitlab ) in <a href="https://jihulab.com">https://jihulab.com</a>
|
||||
</p>
|
||||
`
|
||||
|
||||
export default class GitlabContributors extends GitLabBase {
|
||||
static category = 'activity'
|
||||
static route = {
|
||||
@@ -46,7 +34,7 @@ export default class GitlabContributors extends GitLabBase {
|
||||
project: 'gitlab-cn/gitlab',
|
||||
},
|
||||
staticPreview: this.render({ contributorCount: 415 }),
|
||||
documentation: customDocumentation,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -64,9 +52,7 @@ export default class GitlabContributors extends GitLabBase {
|
||||
project
|
||||
)}/repository/contributors`,
|
||||
options: { searchParams: { page: '1', per_page: '1' } },
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
)
|
||||
const data = this.constructor._validate(res.headers, schema)
|
||||
|
||||
@@ -2,6 +2,7 @@ import Joi from 'joi'
|
||||
import { coveragePercentage } from '../color-formatters.js'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { BaseSvgScrapingService, NotFound } from '../index.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.string()
|
||||
@@ -14,7 +15,7 @@ const queryParamSchema = Joi.object({
|
||||
job_name: Joi.string(),
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
const moreDocs = `
|
||||
<p>
|
||||
Important: If your project is publicly visible, but the badge is like this:
|
||||
<img src="https://img.shields.io/badge/coverage-not set up-red" alt="coverage not set up"/>
|
||||
@@ -54,7 +55,7 @@ export default class GitlabCoverage extends BaseSvgScrapingService {
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: this.render({ coverage: 67 }),
|
||||
documentation,
|
||||
documentation: documentation + moreDocs,
|
||||
},
|
||||
{
|
||||
title: 'Gitlab code coverage (specific job)',
|
||||
@@ -65,14 +66,14 @@ export default class GitlabCoverage extends BaseSvgScrapingService {
|
||||
},
|
||||
queryParams: { job_name: 'test coverage report' },
|
||||
staticPreview: this.render({ coverage: 96 }),
|
||||
documentation,
|
||||
documentation: documentation + moreDocs,
|
||||
},
|
||||
{
|
||||
title: 'Gitlab code coverage (self-managed)',
|
||||
namedParams: { user: 'GNOME', repo: 'at-spi2-core', branch: 'master' },
|
||||
queryParams: { gitlab_url: 'https://gitlab.gnome.org' },
|
||||
staticPreview: this.render({ coverage: 93 }),
|
||||
documentation,
|
||||
documentation: documentation + moreDocs,
|
||||
},
|
||||
{
|
||||
title: 'Gitlab code coverage (self-managed, specific job)',
|
||||
@@ -82,7 +83,7 @@ export default class GitlabCoverage extends BaseSvgScrapingService {
|
||||
job_name: 'unit-test',
|
||||
},
|
||||
staticPreview: this.render({ coverage: 93 }),
|
||||
documentation,
|
||||
documentation: documentation + moreDocs,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -100,10 +101,7 @@ export default class GitlabCoverage extends BaseSvgScrapingService {
|
||||
// it is recommended to not use the query param at all if not required
|
||||
jobName = jobName ? `?job=${jobName}` : ''
|
||||
const url = `${baseUrl}/${user}/${repo}/badges/${branch}/coverage.svg${jobName}`
|
||||
const errorMessages = {
|
||||
401: 'repo not found',
|
||||
404: 'repo not found',
|
||||
}
|
||||
const errorMessages = errorMessagesFor('project not found')
|
||||
return this._requestSvg({
|
||||
schema,
|
||||
url,
|
||||
|
||||
19
services/gitlab/gitlab-helper.js
Normal file
19
services/gitlab/gitlab-helper.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g.
|
||||
<a href="https://gitlab.com/gitlab-org/gitlab" target="_blank" >gitlab-org/gitlab</a> ).
|
||||
Note that only internet-accessible GitLab instances are supported, for example
|
||||
<a href="https://jihulab.com" target="_blank" >https://jihulab.com</a>,
|
||||
<a href="https://gitlab.gnome.org" target="_blank" >https://gitlab.gnome.org</a>, or
|
||||
<a href="https://gitlab.com" target="_blank" >https://gitlab.com</a>.
|
||||
</p>
|
||||
`
|
||||
|
||||
function errorMessagesFor(notFoundMessage = 'project not found') {
|
||||
return {
|
||||
401: notFoundMessage,
|
||||
404: notFoundMessage,
|
||||
}
|
||||
}
|
||||
|
||||
export { documentation, errorMessagesFor }
|
||||
@@ -1,6 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl, nonNegativeInteger } from '../validators.js'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
@@ -18,13 +19,6 @@ const queryParamSchema = Joi.object({
|
||||
gitlab_url: optionalUrl,
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g. gitlab-org/gitlab ).
|
||||
Note that only internet-accessible GitLab instances are supported, for example https://jihulab.com, https://gitlab.gnome.org, or https://gitlab.com/.
|
||||
</p>
|
||||
`
|
||||
|
||||
const labelDocumentation = `
|
||||
<p>
|
||||
If you want to use multiple labels then please use commas (<code>,</code>) to separate them, e.g. <code>foo,bar</code>.
|
||||
@@ -243,9 +237,7 @@ export default class GitlabIssues extends GitLabBase {
|
||||
project
|
||||
)}/issues_statistics`,
|
||||
options: labels ? { searchParams: { labels } } : undefined,
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { renderLicenseBadge } from '../licenses.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
@@ -13,19 +14,6 @@ const queryParamSchema = Joi.object({
|
||||
gitlab_url: optionalUrl,
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g. gitlab-org/gitlab )
|
||||
</p>
|
||||
`
|
||||
|
||||
const customDocumentation = `
|
||||
<p>
|
||||
Note that only internet-accessible GitLab instances are supported, for example https://jihulab.com, https://gitlab.gnome.org, or https://gitlab.com/.
|
||||
You may use your GitLab Project Id (e.g. 13953) or your Project Path (e.g. gitlab-cn/gitlab ) in <a href="https://jihulab.com">https://jihulab.com</a>
|
||||
</p>
|
||||
`
|
||||
|
||||
export default class GitlabLicense extends GitLabBase {
|
||||
static category = 'license'
|
||||
|
||||
@@ -59,7 +47,7 @@ export default class GitlabLicense extends GitLabBase {
|
||||
message: 'MIT License',
|
||||
color: 'green',
|
||||
},
|
||||
documentation: customDocumentation,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -79,9 +67,7 @@ export default class GitlabLicense extends GitLabBase {
|
||||
schema,
|
||||
url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}`,
|
||||
options: { searchParams: { license: '1' } },
|
||||
errorMessages: {
|
||||
404: 'repo not found',
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ t.create('License for unknown repo')
|
||||
.get('/user1/gitlab-does-not-have-this-repo.json')
|
||||
.expectBadge({
|
||||
label: 'license',
|
||||
message: 'repo not found',
|
||||
message: 'project not found',
|
||||
color: 'red',
|
||||
})
|
||||
|
||||
|
||||
352
services/gitlab/gitlab-merge-requests.service.js
Normal file
352
services/gitlab/gitlab-merge-requests.service.js
Normal file
@@ -0,0 +1,352 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl, nonNegativeInteger } from '../validators.js'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
// The total number of MR is in the `x-total` field in the headers.
|
||||
// https://docs.gitlab.com/ee/api/index.html#other-pagination-headers
|
||||
const schema = Joi.object({
|
||||
'x-total': Joi.number().integer(),
|
||||
'x-page': nonNegativeInteger,
|
||||
})
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
labels: Joi.string(),
|
||||
gitlab_url: optionalUrl,
|
||||
}).required()
|
||||
|
||||
const more = `
|
||||
<p>
|
||||
<a href="https://docs.gitlab.com/ee/user/gitlab_com/index.html#pagination-response-headers">GitLab's API </a> only reports up to 10k Merge Requests, so badges for projects that have more than 10k will not have an exact count.
|
||||
</p>
|
||||
`
|
||||
|
||||
const labelText = `
|
||||
<p>
|
||||
If you want to use multiple labels then please use commas (<code>,</code>) to separate them, e.g. <code>foo,bar</code>.
|
||||
</p>
|
||||
`
|
||||
|
||||
const defaultDocumentation = documentation + more
|
||||
|
||||
const labelDocumentation = documentation + labelText + more
|
||||
|
||||
export default class GitlabMergeRequests extends GitLabBase {
|
||||
static category = 'issue-tracking'
|
||||
|
||||
static route = {
|
||||
base: 'gitlab/merge-requests',
|
||||
pattern: ':variant(all|open|closed|locked|merged):raw(-raw)?/:project+',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'GitLab merge requests',
|
||||
pattern: 'open/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'merge requests',
|
||||
message: '1.4k open',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab merge requests',
|
||||
pattern: 'open-raw/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'open merge requests',
|
||||
message: '1.4k',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab merge requests by-label',
|
||||
pattern: 'open/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'test,type::feature',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'test,failure::new merge requests',
|
||||
message: '3 open',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab merge requests by-label',
|
||||
pattern: 'open-raw/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'gitlab-org/gitlab',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'open test,failure::new merge requests',
|
||||
message: '3',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab closed merge requests',
|
||||
pattern: 'closed/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'merge requests',
|
||||
message: 'more than 10k closed',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab closed merge requests',
|
||||
pattern: 'closed-raw/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'closed merge requests',
|
||||
message: 'more than 10k',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab closed merge requests by-label',
|
||||
pattern: 'closed/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'test,type::feature',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'test,failure::new merge requests',
|
||||
message: '32 closed',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab closed merge requests by-label',
|
||||
pattern: 'closed-raw/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'test,type::feature',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'closed test,failure::new merge requests',
|
||||
message: '32',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab all merge requests',
|
||||
pattern: 'all/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'merge requests',
|
||||
message: 'more than 10k all',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab all merge requests',
|
||||
pattern: 'all-raw/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'all merge requests',
|
||||
message: 'more than 10k',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab all merge requests by-label',
|
||||
pattern: 'all-raw/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'test,failure::new',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'all test,failure::new merge requests',
|
||||
message: '12',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab locked merge requests',
|
||||
pattern: 'locked/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'merge requests',
|
||||
message: '0 locked',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab locked merge requests by-label',
|
||||
pattern: 'closed/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'test,type::feature',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'test,failure::new merge requests',
|
||||
message: '0 locked',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab merged merge requests',
|
||||
pattern: 'merged/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'merge requests',
|
||||
message: 'more than 10k merged',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
{
|
||||
title: 'GitLab merged merge requests by-label',
|
||||
pattern: 'merged/:project+',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: {
|
||||
labels: 'test,type::feature',
|
||||
gitlab_url: 'https://gitlab.com',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'test,failure::new merge requests',
|
||||
message: '68 merged',
|
||||
color: 'blue',
|
||||
},
|
||||
documentation: labelDocumentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'merge requests' }
|
||||
|
||||
static render({ variant, raw, labels, mergeRequestCount }) {
|
||||
const state = variant
|
||||
const isMultiLabel = labels && labels.includes(',')
|
||||
const labelText = labels ? `${isMultiLabel ? `${labels}` : labels} ` : ''
|
||||
|
||||
let labelPrefix = ''
|
||||
let messageSuffix = ''
|
||||
if (raw !== undefined) {
|
||||
labelPrefix = `${state} `
|
||||
} else {
|
||||
messageSuffix = state
|
||||
}
|
||||
const message = `${mergeRequestCount > 10000 ? 'more than ' : ''}${metric(
|
||||
mergeRequestCount
|
||||
)}${messageSuffix ? ' ' : ''}${messageSuffix}`
|
||||
return {
|
||||
label: `${labelPrefix}${labelText}merge requests`,
|
||||
message,
|
||||
color: 'blue',
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ project, baseUrl, variant, labels }) {
|
||||
// https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests
|
||||
const { res } = await this._request(
|
||||
this.authHelper.withBearerAuthHeader({
|
||||
url: `${baseUrl}/api/v4/projects/${encodeURIComponent(
|
||||
project
|
||||
)}/merge_requests`,
|
||||
options: {
|
||||
searchParams: {
|
||||
state: variant === 'open' ? 'opened' : variant,
|
||||
page: '1',
|
||||
per_page: '1',
|
||||
labels,
|
||||
},
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
)
|
||||
return this.constructor._validate(res.headers, schema)
|
||||
}
|
||||
|
||||
static transform(data) {
|
||||
if (data['x-total'] !== undefined) {
|
||||
return data['x-total']
|
||||
} else {
|
||||
// https://docs.gitlab.com/ee/api/index.html#pagination-response-headers
|
||||
// For performance reasons, if a query returns more than 10,000 records, GitLab doesn’t return `x-total` header.
|
||||
// Displayed on the page as "more than 10k".
|
||||
return 10001
|
||||
}
|
||||
}
|
||||
|
||||
async handle(
|
||||
{ variant, raw, project },
|
||||
{ gitlab_url: baseUrl = 'https://gitlab.com', labels }
|
||||
) {
|
||||
const data = await this.fetch({
|
||||
project,
|
||||
baseUrl,
|
||||
variant,
|
||||
labels,
|
||||
})
|
||||
const mergeRequestCount = this.constructor.transform(data)
|
||||
return this.constructor.render({
|
||||
variant,
|
||||
raw,
|
||||
labels,
|
||||
mergeRequestCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
92
services/gitlab/gitlab-merge-requests.spec.js
Normal file
92
services/gitlab/gitlab-merge-requests.spec.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import nock from 'nock'
|
||||
import { expect } from 'chai'
|
||||
import { cleanUpNockAfterEach, defaultContext } from '../test-helpers.js'
|
||||
import GitlabMergeRequests from './gitlab-merge-requests.service.js'
|
||||
|
||||
describe('GitlabMergeRequests', function () {
|
||||
test(GitlabMergeRequests.render, () => {
|
||||
given({ variant: 'open', mergeRequestCount: 1399 }).expect({
|
||||
label: 'merge requests',
|
||||
message: '1.4k open',
|
||||
color: 'blue',
|
||||
})
|
||||
given({ variant: 'open', raw: '-raw', mergeRequestCount: 1399 }).expect({
|
||||
label: 'open merge requests',
|
||||
message: '1.4k',
|
||||
color: 'blue',
|
||||
})
|
||||
given({
|
||||
variant: 'open',
|
||||
labels: 'discussion,enhancement',
|
||||
mergeRequestCount: 15,
|
||||
}).expect({
|
||||
label: 'discussion,enhancement merge requests',
|
||||
message: '15 open',
|
||||
color: 'blue',
|
||||
})
|
||||
given({
|
||||
variant: 'open',
|
||||
raw: '-raw',
|
||||
labels: 'discussion,enhancement',
|
||||
mergeRequestCount: 15,
|
||||
}).expect({
|
||||
label: 'open discussion,enhancement merge requests',
|
||||
message: '15',
|
||||
color: 'blue',
|
||||
})
|
||||
given({ variant: 'open', mergeRequestCount: 0 }).expect({
|
||||
label: 'merge requests',
|
||||
message: '0 open',
|
||||
color: 'blue',
|
||||
})
|
||||
given({ variant: 'open', mergeRequestCount: 10001 }).expect({
|
||||
label: 'merge requests',
|
||||
message: 'more than 10k open',
|
||||
color: 'blue',
|
||||
})
|
||||
})
|
||||
describe('auth', function () {
|
||||
cleanUpNockAfterEach()
|
||||
|
||||
const fakeToken = 'abc123'
|
||||
const config = {
|
||||
public: {
|
||||
services: {
|
||||
gitlab: {
|
||||
authorizedOrigins: ['https://gitlab.com'],
|
||||
},
|
||||
},
|
||||
},
|
||||
private: {
|
||||
gitlab_token: fakeToken,
|
||||
},
|
||||
}
|
||||
|
||||
it('sends the auth information as configured', async function () {
|
||||
const scope = nock('https://gitlab.com/')
|
||||
.get(
|
||||
'/api/v4/projects/foo%2Fbar/merge_requests?state=opened&page=1&per_page=1'
|
||||
)
|
||||
// This ensures that the expected credentials are actually being sent with the HTTP request.
|
||||
// Without this the request wouldn't match and the test would fail.
|
||||
.matchHeader('Authorization', `Bearer ${fakeToken}`)
|
||||
.reply(200, {}, { 'x-total': '100', 'x-page': '1' })
|
||||
|
||||
expect(
|
||||
await GitlabMergeRequests.invoke(
|
||||
defaultContext,
|
||||
config,
|
||||
{ project: 'foo/bar', variant: 'open' },
|
||||
{}
|
||||
)
|
||||
).to.deep.equal({
|
||||
label: 'merge requests',
|
||||
message: '100 open',
|
||||
color: 'blue',
|
||||
})
|
||||
|
||||
scope.done()
|
||||
})
|
||||
})
|
||||
})
|
||||
172
services/gitlab/gitlab-merge-requests.tester.js
Normal file
172
services/gitlab/gitlab-merge-requests.tester.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import Joi from 'joi'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
import {
|
||||
isMetric,
|
||||
isMetricOpenIssues,
|
||||
isMetricClosedIssues,
|
||||
} from '../test-validators.js'
|
||||
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('Merge Requests (project not found)')
|
||||
.get('/open/guoxudong.io/shields-test/do-not-exist.json')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: 'project not found',
|
||||
})
|
||||
|
||||
/**
|
||||
* Opened issue number case
|
||||
*/
|
||||
t.create('Opened merge requests')
|
||||
.get('/open/guoxudong.io/shields-test/issue-test.json')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: isMetricOpenIssues,
|
||||
})
|
||||
|
||||
t.create('Open merge requests raw')
|
||||
.get('/open-raw/guoxudong.io/shields-test/issue-test.json')
|
||||
.expectBadge({
|
||||
label: 'open merge requests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('Open merge requests by label is > zero')
|
||||
.get('/open/guoxudong.io/shields-test/issue-test.json?labels=discussion')
|
||||
.expectBadge({
|
||||
label: 'discussion merge requests',
|
||||
message: isMetricOpenIssues,
|
||||
})
|
||||
|
||||
t.create('Open merge requests by multi-word label is > zero')
|
||||
.get(
|
||||
'/open/guoxudong.io/shields-test/issue-test.json?labels=discussion,enhancement'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'discussion,enhancement merge requests',
|
||||
message: isMetricOpenIssues,
|
||||
})
|
||||
|
||||
t.create('Open merge requests by label (raw)')
|
||||
.get('/open-raw/guoxudong.io/shields-test/issue-test.json?labels=discussion')
|
||||
.expectBadge({
|
||||
label: 'open discussion merge requests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('Opened merge requests by Scoped labels')
|
||||
.get('/open/gitlab-org%2Fgitlab.json?labels=test,failure::new')
|
||||
.expectBadge({
|
||||
label: 'test,failure::new merge requests',
|
||||
message: Joi.alternatives(isMetricOpenIssues, Joi.equal('0 open')),
|
||||
})
|
||||
|
||||
/**
|
||||
* Closed issue number case
|
||||
*/
|
||||
t.create('Closed merge requests')
|
||||
.get('/closed/guoxudong.io/shields-test/issue-test.json')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: isMetricClosedIssues,
|
||||
})
|
||||
|
||||
t.create('Closed merge requests raw')
|
||||
.get('/closed-raw/guoxudong.io/shields-test/issue-test.json')
|
||||
.expectBadge({
|
||||
label: 'closed merge requests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('Closed merge requests by label is > zero')
|
||||
.get('/closed/guoxudong.io/shields-test/issue-test.json?labels=bug')
|
||||
.expectBadge({
|
||||
label: 'bug merge requests',
|
||||
message: Joi.alternatives(isMetricClosedIssues, Joi.equal('0 closed')),
|
||||
})
|
||||
|
||||
t.create('Closed merge requests by multi-word label is > zero')
|
||||
.get('/closed/guoxudong.io/shields-test/issue-test.json?labels=bug,critical')
|
||||
.expectBadge({
|
||||
label: 'bug,critical merge requests',
|
||||
message: Joi.alternatives(isMetricClosedIssues, Joi.equal('0 closed')),
|
||||
})
|
||||
|
||||
t.create('Closed merge requests by label (raw)')
|
||||
.get(
|
||||
'/closed-raw/guoxudong.io/shields-test/issue-test.json?labels=enhancement'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'closed enhancement merge requests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
/**
|
||||
* All issue number case
|
||||
*/
|
||||
t.create('All merge requests')
|
||||
.get('/all/guoxudong.io/shields-test/issue-test.json')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: Joi.string().regex(
|
||||
/^([0-9]+[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) all$/
|
||||
),
|
||||
})
|
||||
|
||||
t.create('All merge requests raw')
|
||||
.get('/all-raw/guoxudong.io/shields-test/issue-test.json')
|
||||
.expectBadge({
|
||||
label: 'all merge requests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('All merge requests by label is > zero')
|
||||
.get('/all/guoxudong.io/shields-test/issue-test.json?labels=discussion')
|
||||
.expectBadge({
|
||||
label: 'discussion merge requests',
|
||||
message: Joi.string().regex(
|
||||
/^([0-9]+[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) all$/
|
||||
),
|
||||
})
|
||||
|
||||
t.create('All merge requests by multi-word label is > zero')
|
||||
.get(
|
||||
'/all/guoxudong.io/shields-test/issue-test.json?labels=discussion,enhancement'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'discussion,enhancement merge requests',
|
||||
message: Joi.string().regex(
|
||||
/^([0-9]+[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) all$/
|
||||
),
|
||||
})
|
||||
|
||||
t.create('All merge requests by label (raw)')
|
||||
.get('/all-raw/guoxudong.io/shields-test/issue-test.json?labels=discussion')
|
||||
.expectBadge({
|
||||
label: 'all discussion merge requests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('more than 10k merge requests')
|
||||
.get('/all/gitlab-org%2Fgitlab.json')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: 'more than 10k all',
|
||||
})
|
||||
|
||||
t.create('locked merge requests')
|
||||
.get('/locked/gitlab-org%2Fgitlab.json')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: Joi.string().regex(
|
||||
/^([0-9]+[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) locked$/
|
||||
),
|
||||
})
|
||||
|
||||
t.create('Opened merge requests (self-managed)')
|
||||
.get('/open/gitlab-cn/gitlab.json?gitlab_url=https://jihulab.com')
|
||||
.expectBadge({
|
||||
label: 'merge requests',
|
||||
message: isMetricOpenIssues,
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import Joi from 'joi'
|
||||
import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { BaseSvgScrapingService, NotFound, redirector } from '../index.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
|
||||
const badgeSchema = Joi.object({
|
||||
message: Joi.alternatives()
|
||||
@@ -14,7 +15,7 @@ const queryParamSchema = Joi.object({
|
||||
branch: Joi.string(),
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
const moreDocs = `
|
||||
<p>
|
||||
Important: You must use the Project Path, not the Project Id. Additionally, if your project is publicly visible, but the badge is like this:
|
||||
<img src="https://img.shields.io/badge/build-not found-red" alt="build not found"/>
|
||||
@@ -51,14 +52,14 @@ class GitlabPipelineStatus extends BaseSvgScrapingService {
|
||||
namedParams: { project: 'gitlab-org/gitlab' },
|
||||
queryParams: { branch: 'master' },
|
||||
staticPreview: this.render({ status: 'passed' }),
|
||||
documentation,
|
||||
documentation: documentation + moreDocs,
|
||||
},
|
||||
{
|
||||
title: 'Gitlab pipeline status (self-managed)',
|
||||
namedParams: { project: 'GNOME/pango' },
|
||||
queryParams: { gitlab_url: 'https://gitlab.gnome.org', branch: 'master' },
|
||||
staticPreview: this.render({ status: 'passed' }),
|
||||
documentation,
|
||||
documentation: documentation + moreDocs,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -72,10 +73,7 @@ class GitlabPipelineStatus extends BaseSvgScrapingService {
|
||||
url: `${baseUrl}/${decodeURIComponent(
|
||||
project
|
||||
)}/badges/${branch}/pipeline.svg`,
|
||||
errorMessages: {
|
||||
401: 'repo not found',
|
||||
404: 'repo not found',
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import Joi from 'joi'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { latest, renderVersionBadge } from '../version.js'
|
||||
import { NotFound } from '../index.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.array().items(
|
||||
@@ -21,11 +22,6 @@ const queryParamSchema = Joi.object({
|
||||
.default('created_at'),
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 25813592) or your Project Path (e.g. megabyte-labs/dockerfile/ci-pipeline/ansible-lint)
|
||||
</p>
|
||||
`
|
||||
const commonProps = {
|
||||
namedParams: {
|
||||
project: 'shields-ops-group/tag-test',
|
||||
@@ -102,9 +98,7 @@ export default class GitLabRelease extends GitLabBase {
|
||||
return this.fetchPaginatedArrayData({
|
||||
schema,
|
||||
url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}/releases`,
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
options: {
|
||||
searchParams: { order_by: orderBy },
|
||||
},
|
||||
|
||||
74
services/gitlab/gitlab-stars.service.js
Normal file
74
services/gitlab/gitlab-stars.service.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl, nonNegativeInteger } from '../validators.js'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
star_count: nonNegativeInteger,
|
||||
}).required()
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
gitlab_url: optionalUrl,
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g. gitlab-org/gitlab ).
|
||||
Note that only internet-accessible GitLab instances are supported, for example https://jihulab.com, https://gitlab.gnome.org, or https://gitlab.com/.
|
||||
</p>
|
||||
`
|
||||
|
||||
export default class GitlabStars extends GitLabBase {
|
||||
static category = 'social'
|
||||
|
||||
static route = {
|
||||
base: 'gitlab/stars',
|
||||
pattern: ':project+',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'GitLab stars',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: {
|
||||
label: 'stars',
|
||||
message: '3.9k',
|
||||
style: 'social',
|
||||
},
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'stars', namedLogo: 'gitlab' }
|
||||
|
||||
static render({ baseUrl, project, starCount }) {
|
||||
return {
|
||||
message: metric(starCount),
|
||||
color: 'blue',
|
||||
link: [`${baseUrl}/${project}`, `${baseUrl}/${project}/-/starrers`],
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ project, baseUrl }) {
|
||||
// https://docs.gitlab.com/ee/api/projects.html#get-single-project
|
||||
return super.fetch({
|
||||
schema,
|
||||
url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}`,
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ project }, { gitlab_url: baseUrl = 'https://gitlab.com' }) {
|
||||
const { star_count: starCount } = await this.fetch({
|
||||
project,
|
||||
baseUrl,
|
||||
})
|
||||
return this.constructor.render({ baseUrl, project, starCount })
|
||||
}
|
||||
}
|
||||
35
services/gitlab/gitlab-stars.tester.js
Normal file
35
services/gitlab/gitlab-stars.tester.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { isMetric } from '../test-validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('Stars')
|
||||
.get('/gitlab-org/gitlab.json')
|
||||
.expectBadge({
|
||||
label: 'stars',
|
||||
message: isMetric,
|
||||
color: 'blue',
|
||||
link: [
|
||||
'https://gitlab.com/gitlab-org/gitlab',
|
||||
'https://gitlab.com/gitlab-org/gitlab/-/starrers',
|
||||
],
|
||||
})
|
||||
|
||||
t.create('Stars (self-managed)')
|
||||
.get('/gitlab-cn/gitlab.json?gitlab_url=https://jihulab.com')
|
||||
.expectBadge({
|
||||
label: 'stars',
|
||||
message: isMetric,
|
||||
color: 'blue',
|
||||
link: [
|
||||
'https://jihulab.com/gitlab-cn/gitlab',
|
||||
'https://jihulab.com/gitlab-cn/gitlab/-/starrers',
|
||||
],
|
||||
})
|
||||
|
||||
t.create('Stars (project not found)')
|
||||
.get('/user1/gitlab-does-not-have-this-repo.json')
|
||||
.expectBadge({
|
||||
label: 'stars',
|
||||
message: 'project not found',
|
||||
})
|
||||
@@ -4,6 +4,7 @@ import { optionalUrl } from '../validators.js'
|
||||
import { latest } from '../version.js'
|
||||
import { addv } from '../text-formatters.js'
|
||||
import { NotFound } from '../index.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.array().items(
|
||||
@@ -18,11 +19,6 @@ const queryParamSchema = Joi.object({
|
||||
sort: Joi.string().valid('date', 'semver').default('date'),
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
You may use your GitLab Project Id (e.g. 25813592) or your Project Path (e.g. megabyte-labs/dockerfile/ci-pipeline/ansible-lint)
|
||||
</p>
|
||||
`
|
||||
const commonProps = {
|
||||
namedParams: {
|
||||
project: 'shields-ops-group/tag-test',
|
||||
@@ -96,9 +92,7 @@ export default class GitlabTag extends GitLabBase {
|
||||
project
|
||||
)}/repository/tags`,
|
||||
options: { searchParams: { order_by: 'updated' } },
|
||||
errorMessages: {
|
||||
404: 'repo not found',
|
||||
},
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ t.create('Tag (custom instance)')
|
||||
|
||||
t.create('Tag (repo not found)')
|
||||
.get('/fdroid/nonexistant.json')
|
||||
.expectBadge({ label: 'tag', message: 'repo not found' })
|
||||
.expectBadge({ label: 'tag', message: 'project not found' })
|
||||
|
||||
t.create('Tag (no tags)')
|
||||
.get('/fdroid/fdroiddata.json')
|
||||
|
||||
@@ -26,7 +26,7 @@ export default redirector({
|
||||
documentation,
|
||||
},
|
||||
],
|
||||
transformPath: () => `/maven-metadata/v`,
|
||||
transformPath: () => '/maven-metadata/v',
|
||||
transformQueryParams: ({ pluginId }) => {
|
||||
const groupPath = pluginId.replace(/\./g, '/')
|
||||
const artifactId = `${pluginId}.gradle.plugin`
|
||||
|
||||
@@ -55,7 +55,7 @@ export default class HSTS extends BaseJsonService {
|
||||
async fetch({ domain }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://hstspreload.org/api/v2/status`,
|
||||
url: 'https://hstspreload.org/api/v2/status',
|
||||
options: { searchParams: { domain } },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,12 +3,24 @@ import { redirector } from '../index.js'
|
||||
export default [
|
||||
redirector({
|
||||
category: 'version',
|
||||
name: 'JitpackVersionGitHubRedirect',
|
||||
route: {
|
||||
base: 'jitpack/v',
|
||||
pattern: ':groupId/:artifactId',
|
||||
pattern: ':user/:repo',
|
||||
},
|
||||
transformPath: ({ groupId, artifactId }) =>
|
||||
`/jitpack/v/github/${groupId}/${artifactId}`,
|
||||
dateAdded: new Date('2019-03-31'),
|
||||
transformPath: ({ user, repo }) =>
|
||||
`/jitpack/version/com.github.${user}/${repo}`,
|
||||
dateAdded: new Date('2022-08-21'),
|
||||
}),
|
||||
redirector({
|
||||
category: 'version',
|
||||
name: 'JitpackVersionVcsRedirect',
|
||||
route: {
|
||||
base: 'jitpack/v',
|
||||
pattern: ':vcs(github|bitbucket|gitlab|gitee)/:user/:repo',
|
||||
},
|
||||
transformPath: ({ vcs, user, repo }) =>
|
||||
`/jitpack/version/com.${vcs}.${user}/${repo}`,
|
||||
dateAdded: new Date('2022-08-21'),
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -6,6 +6,10 @@ export const t = new ServiceTester({
|
||||
pathPrefix: '/jitpack/v',
|
||||
})
|
||||
|
||||
t.create('jitpack version redirect')
|
||||
t.create('jitpack version redirect (no vcs)')
|
||||
.get('/jitpack/maven-simple.svg')
|
||||
.expectRedirect('/jitpack/v/github/jitpack/maven-simple.svg')
|
||||
.expectRedirect('/jitpack/version/com.github.jitpack/maven-simple.svg')
|
||||
|
||||
t.create('jitpack version redirect (github)')
|
||||
.get('/github/jitpack/maven-simple.svg')
|
||||
.expectRedirect('/jitpack/version/com.github.jitpack/maven-simple.svg')
|
||||
|
||||
@@ -10,18 +10,19 @@ const schema = Joi.object({
|
||||
export default class JitPackVersion extends BaseJsonService {
|
||||
static category = 'version'
|
||||
|
||||
// Changed endpoint to allow any groupId, custom domains included
|
||||
// See: https://github.com/badges/shields/issues/8312
|
||||
static route = {
|
||||
base: 'jitpack/v',
|
||||
pattern: ':vcs(github|bitbucket|gitlab|gitee)/:user/:repo',
|
||||
base: 'jitpack/version',
|
||||
pattern: ':groupId/:artifactId',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'JitPack',
|
||||
namedParams: {
|
||||
vcs: 'github',
|
||||
user: 'jitpack',
|
||||
repo: 'maven-simple',
|
||||
groupId: 'com.github.jitpack',
|
||||
artifactId: 'maven-simple',
|
||||
},
|
||||
staticPreview: renderVersionBadge({ version: 'v1.1' }),
|
||||
keywords: ['java', 'maven'],
|
||||
@@ -30,8 +31,8 @@ export default class JitPackVersion extends BaseJsonService {
|
||||
|
||||
static defaultBadgeData = { label: 'jitpack' }
|
||||
|
||||
async fetch({ vcs, user, repo }) {
|
||||
const url = `https://jitpack.io/api/builds/com.${vcs}.${user}/${repo}/latestOk`
|
||||
async fetch({ groupId, artifactId }) {
|
||||
const url = `https://jitpack.io/api/builds/${groupId}/${artifactId}/latestOk`
|
||||
|
||||
return this._requestJson({
|
||||
schema,
|
||||
@@ -40,8 +41,8 @@ export default class JitPackVersion extends BaseJsonService {
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ vcs, user, repo }) {
|
||||
const { version } = await this.fetch({ vcs, user, repo })
|
||||
async handle({ groupId, artifactId }) {
|
||||
const { version } = await this.fetch({ groupId, artifactId })
|
||||
return renderVersionBadge({ version })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ export const t = await createServiceTester()
|
||||
const isAnyV = Joi.string().regex(/^v.+$/)
|
||||
|
||||
t.create('version (groupId)')
|
||||
.get('/github/erayerdin/kappdirs.json')
|
||||
.get('/com.github.erayerdin/kappdirs.json')
|
||||
.expectBadge({ label: 'jitpack', message: isAnyV })
|
||||
|
||||
t.create('unknown package')
|
||||
.get('/github/some-bogus-user/project.json')
|
||||
.get('/com.github.some-bogus-user/project.json')
|
||||
.expectBadge({ label: 'jitpack', message: 'project not found or private' })
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class Maintenance extends BaseService {
|
||||
}
|
||||
|
||||
return {
|
||||
message: `${isStale ? `stale` : 'no!'} (as of ${targetYear})`,
|
||||
message: `${isStale ? 'stale' : 'no!'} (as of ${targetYear})`,
|
||||
color: isStale ? undefined : 'red',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default redirector({
|
||||
documentation,
|
||||
},
|
||||
],
|
||||
transformPath: () => `/maven-metadata/v`,
|
||||
transformPath: () => '/maven-metadata/v',
|
||||
transformQueryParams: ({ groupId, artifactId, versionPrefix }) => {
|
||||
const group = encodeURIComponent(groupId).replace(/\./g, '/')
|
||||
const artifact = encodeURIComponent(artifactId)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user