Compare commits
42 Commits
server-202
...
services-g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0ef5046d0 | ||
|
|
f60c2058fa | ||
|
|
39e4d9bcbc | ||
|
|
2bf863fb09 | ||
|
|
ed277d4e79 | ||
|
|
fdc81c2e3a | ||
|
|
6ef9dcaba5 | ||
|
|
541cb9acf2 | ||
|
|
2492ff79f7 | ||
|
|
0c73b35915 | ||
|
|
f1d151e963 | ||
|
|
a0149a8f8f | ||
|
|
e843d4eac1 | ||
|
|
830b5d8a1f | ||
|
|
8afb034a58 | ||
|
|
2f915a7b45 | ||
|
|
7dbfd0d049 | ||
|
|
493fdb76af | ||
|
|
abb1bbf8d4 | ||
|
|
a4911dac33 | ||
|
|
5e6583c530 | ||
|
|
bb1fda2aa7 | ||
|
|
9c692cd53a | ||
|
|
7410bf2e97 | ||
|
|
a83cfa4fb6 | ||
|
|
4d64969738 | ||
|
|
ade213c6d3 | ||
|
|
1135fba9f6 | ||
|
|
0234fb077f | ||
|
|
ed86c1de21 | ||
|
|
a47f770c82 | ||
|
|
3176d6f7f3 | ||
|
|
0763d8ec66 | ||
|
|
3fcb959ed2 | ||
|
|
f562dfe868 | ||
|
|
22aee48544 | ||
|
|
098a24cae5 | ||
|
|
a2c8ed27b8 | ||
|
|
2dccd3d040 | ||
|
|
b8ce38a041 | ||
|
|
0784a14153 | ||
|
|
afc7b283bc |
@@ -1,110 +1,3 @@
|
||||
version: 2
|
||||
|
||||
services_steps: &services_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
- run:
|
||||
name: Identify services tagged in the PR title
|
||||
command: npm run test:services:pr:prepare
|
||||
|
||||
- run:
|
||||
name: Run tests for tagged services
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/services/results.xml
|
||||
command: RETRY_COUNT=3 npm run test:services:pr:run
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
- run:
|
||||
name: Danger
|
||||
when: always
|
||||
environment:
|
||||
# https://github.com/gatsbyjs/gatsby/pull/11555
|
||||
NODE_ENV: test
|
||||
command: npm run danger ci
|
||||
|
||||
services:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
services@node-17:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
<<: *services_steps
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
on-commit:
|
||||
jobs:
|
||||
- services:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- services@node-17:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- danger:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- /dependabot\/.*/
|
||||
# on-commit-with-cache:
|
||||
# jobs:
|
||||
# - npm-install:
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: gh-pages
|
||||
# - services:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: master
|
||||
# - services@node-latest:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: master
|
||||
# - danger:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: /dependabot\/.*/
|
||||
# Do nothing
|
||||
# TODO: disable Circle
|
||||
|
||||
86
.github/actions/service-tests/action.yml
vendored
Normal file
86
.github/actions/service-tests/action.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: 'Service tests'
|
||||
description: 'Run tests for selected services'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
librariesio-tokens:
|
||||
description: 'The SERVICETESTS_LIBRARIESIO_TOKENS secret'
|
||||
required: false
|
||||
default: ''
|
||||
obs-user:
|
||||
description: 'The SERVICETESTS_OBS_USER secret'
|
||||
required: false
|
||||
default: ''
|
||||
obs-pass:
|
||||
description: 'The SERVICETESTS_OBS_PASS secret'
|
||||
required: false
|
||||
default: ''
|
||||
sl-insight-user-uuid:
|
||||
description: 'The SERVICETESTS_SL_INSIGHT_USER_UUID secret'
|
||||
required: false
|
||||
default: ''
|
||||
sl-insight-api-token:
|
||||
description: 'The SERVICETESTS_SL_INSIGHT_API_TOKEN secret'
|
||||
required: false
|
||||
default: ''
|
||||
twitch-client-id:
|
||||
description: 'The SERVICETESTS_TWITCH_CLIENT_ID secret'
|
||||
required: false
|
||||
default: ''
|
||||
twitch-client-secret:
|
||||
description: 'The SERVICETESTS_TWITCH_CLIENT_SECRET secret'
|
||||
required: false
|
||||
default: ''
|
||||
wheelmap-token:
|
||||
description: 'The SERVICETESTS_WHEELMAP_TOKEN secret'
|
||||
required: false
|
||||
default: ''
|
||||
youtube-api-key:
|
||||
description: 'The SERVICETESTS_YOUTUBE_API_KEY secret'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Derive list of service tests to run
|
||||
# Note: In this step we are using an intermediate env var instead of
|
||||
# passing github.event.pull_request.title as an argument
|
||||
# to prevent a shell injection attack. Further reading:
|
||||
# https://securitylab.github.com/research/github-actions-untrusted-input/#exploitability-and-impact
|
||||
# https://securitylab.github.com/research/github-actions-untrusted-input/#remediation
|
||||
if: always()
|
||||
env:
|
||||
TITLE: ${{ github.event.pull_request.title }}
|
||||
run: npm run test:services:pr:prepare "$TITLE"
|
||||
shell: bash
|
||||
|
||||
- name: Run service tests
|
||||
if: always()
|
||||
run: npm run test:services:pr:run -- --reporter json --reporter-option 'output=reports/service-tests.json'
|
||||
shell: bash
|
||||
env:
|
||||
RETRY_COUNT: 3
|
||||
GH_TOKEN: '${{ inputs.github-token }}'
|
||||
LIBRARIESIO_TOKENS: '${{ inputs.librariesio-tokens }}'
|
||||
OBS_USER: '${{ inputs.obs-user }}'
|
||||
OBS_PASS: '${{ inputs.obs-pass }}'
|
||||
SL_INSIGHT_USER_UUID: '${{ inputs.sl-insight-user-uuid }}'
|
||||
SL_INSIGHT_API_TOKEN: '${{ inputs.sl-insight-api-token }}'
|
||||
TWITCH_CLIENT_ID: '${{ inputs.twitch-client-id }}'
|
||||
TWITCH_CLIENT_SECRET: '${{ inputs.twitch-client-secret }}'
|
||||
WHEELMAP_TOKEN: '${{ inputs.wheelmap-token }}'
|
||||
YOUTUBE_API_KEY: '${{ inputs.youtube-api-key }}'
|
||||
|
||||
- name: Write Markdown Summary
|
||||
if: always()
|
||||
run: |
|
||||
if test -f 'reports/service-tests.json'; then
|
||||
echo '# Services' >> $GITHUB_STEP_SUMMARY
|
||||
sed -e 's/^/- /' pull-request-services.log >> $GITHUB_STEP_SUMMARY
|
||||
node scripts/mocha2md.js Report reports/service-tests.json >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo 'No services found. Nothing to do.' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
shell: bash
|
||||
29
.github/workflows/danger.yml
vendored
Normal file
29
.github/workflows/danger.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Danger
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Danger
|
||||
run: npm run danger ci
|
||||
env:
|
||||
# https://github.com/gatsbyjs/gatsby/pull/11555
|
||||
NODE_ENV: test
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
40
.github/workflows/test-services-17.yml
vendored
Normal file
40
.github/workflows/test-services-17.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Services@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
test-services-17:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Service tests (triggered from local branch)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
twitch-client-secret: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
|
||||
wheelmap-token: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
|
||||
youtube-api-key: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
|
||||
|
||||
- name: Service tests (triggered from fork)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
38
.github/workflows/test-services.yml
vendored
Normal file
38
.github/workflows/test-services.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Services
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
test-services:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Service tests (triggered from local branch)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
twitch-client-secret: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_SECRET }}'
|
||||
wheelmap-token: '${{ secrets.SERVICETESTS_WHEELMAP_TOKEN }}'
|
||||
youtube-api-key: '${{ secrets.SERVICETESTS_YOUTUBE_API_KEY }}'
|
||||
|
||||
- name: Service tests (triggered from fork)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
uses: ./.github/actions/service-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
@@ -19,9 +19,6 @@
|
||||
<a href="https://coveralls.io/github/badges/shields">
|
||||
<img src="https://img.shields.io/coveralls/github/badges/shields"
|
||||
alt="coverage"></a>
|
||||
<a href="https://lgtm.com/projects/g/badges/shields/alerts/">
|
||||
<img src="https://img.shields.io/lgtm/alerts/g/badges/shields"
|
||||
alt="Total alerts"/></a>
|
||||
<a href="https://discord.gg/HjJCwm5">
|
||||
<img src="https://img.shields.io/discord/308323056592486420?logo=discord"
|
||||
alt="chat on Discord"></a>
|
||||
|
||||
@@ -14,14 +14,16 @@ let resourceCache = Object.create(null)
|
||||
/**
|
||||
* Make a HTTP request using an in-memory cache
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.url URL to request
|
||||
* @param {number} attrs.ttl Number of milliseconds to keep cached value for
|
||||
* @param {boolean} [attrs.json=true] True if we expect to parse the response as JSON
|
||||
* @param {Function} [attrs.scraper=buffer => buffer] Function to extract value from the response
|
||||
* @param {object} [attrs.options={}] Options to pass to got
|
||||
* @param {Function} [attrs.requestFetcher=fetch] Custom fetch function
|
||||
* @returns {*} Parsed response
|
||||
* @async
|
||||
* @param {object} attrs - Refer to individual attrs
|
||||
* @param {string} attrs.url - URL to request
|
||||
* @param {number} attrs.ttl - Number of milliseconds to keep cached value for
|
||||
* @param {boolean} [attrs.json=true] - True if we expect to parse the response as JSON
|
||||
* @param {Function} [attrs.scraper=buffer => buffer] - Function to extract value from the response
|
||||
* @param {object} [attrs.options={}] - Options to pass to got
|
||||
* @param {Function} [attrs.requestFetcher=fetch] - Custom fetch function
|
||||
* @throws {InvalidResponse} - Error if unable to parse response
|
||||
* @returns {Promise<*>} Promise that resolves to parsed response
|
||||
*/
|
||||
async function getCachedResource({
|
||||
url,
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { URL, format as urlFormat } from 'url'
|
||||
|
||||
function formatSlug(owner, repo, pullRequest) {
|
||||
return `${owner}/${repo}#${pullRequest}`
|
||||
}
|
||||
|
||||
function parseGithubPullRequestUrl(url, options = {}) {
|
||||
const { verifyBaseUrl } = options
|
||||
|
||||
const parsed = new URL(url)
|
||||
const components = parsed.pathname.substr(1).split('/')
|
||||
if (components[2] !== 'pull' || components.length !== 4) {
|
||||
throw Error(`Invalid GitHub pull request URL: ${url}`)
|
||||
}
|
||||
const [owner, repo, , pullRequest] = components
|
||||
|
||||
parsed.pathname = ''
|
||||
const baseUrl = urlFormat(parsed, {
|
||||
auth: false,
|
||||
fragment: false,
|
||||
search: false,
|
||||
}).replace(/\/$/, '')
|
||||
|
||||
if (verifyBaseUrl && baseUrl !== verifyBaseUrl) {
|
||||
throw Error(`Expected base URL to be ${verifyBaseUrl} but got ${baseUrl}`)
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl,
|
||||
owner,
|
||||
repo,
|
||||
pullRequest: +pullRequest,
|
||||
slug: formatSlug(owner, repo, pullRequest),
|
||||
}
|
||||
}
|
||||
|
||||
function parseGithubRepoSlug(slug) {
|
||||
const components = slug.split('/')
|
||||
if (components.length !== 2) {
|
||||
throw Error(`Invalid GitHub repo slug: ${slug}`)
|
||||
}
|
||||
const [owner, repo] = components
|
||||
return { owner, repo }
|
||||
}
|
||||
|
||||
function _inferPullRequestFromTravisEnv(env) {
|
||||
const { owner, repo } = parseGithubRepoSlug(env.TRAVIS_REPO_SLUG)
|
||||
const pullRequest = +env.TRAVIS_PULL_REQUEST
|
||||
return {
|
||||
owner,
|
||||
repo,
|
||||
pullRequest,
|
||||
slug: formatSlug(owner, repo, pullRequest),
|
||||
}
|
||||
}
|
||||
|
||||
function _inferPullRequestFromCircleEnv(env) {
|
||||
return parseGithubPullRequestUrl(
|
||||
env.CI_PULL_REQUEST || env.CIRCLE_PULL_REQUEST
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When called inside a CI build, infer the details
|
||||
* of a pull request from the environment variables.
|
||||
*
|
||||
* @param {object} [env=process.env] Environment variables
|
||||
* @returns {module:core/service-test-runner/infer-pull-request~PullRequest}
|
||||
* Pull Request
|
||||
*/
|
||||
function inferPullRequest(env = process.env) {
|
||||
if (env.TRAVIS) {
|
||||
return _inferPullRequestFromTravisEnv(env)
|
||||
} else if (env.CIRCLECI) {
|
||||
return _inferPullRequestFromCircleEnv(env)
|
||||
} else if (env.CI) {
|
||||
throw Error(
|
||||
'Unsupported CI system. Unable to obtain pull request information from the environment.'
|
||||
)
|
||||
} else {
|
||||
throw Error(
|
||||
'Unable to obtain pull request information from the environment. Is this running in CI?'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull Request
|
||||
*
|
||||
* @typedef PullRequest
|
||||
* @property {string} pr.baseUrl (returned for travis CI only)
|
||||
* @property {string} owner
|
||||
* @property {string} repo
|
||||
* @property {string} pullRequest PR/issue number
|
||||
* @property {string} slug owner/repo/#pullRequest
|
||||
*/
|
||||
|
||||
export { parseGithubPullRequestUrl, parseGithubRepoSlug, inferPullRequest }
|
||||
@@ -1,48 +0,0 @@
|
||||
import { test, given, forCases } from 'sazerac'
|
||||
import {
|
||||
parseGithubPullRequestUrl,
|
||||
inferPullRequest,
|
||||
} from './infer-pull-request.js'
|
||||
|
||||
describe('Pull request inference', function () {
|
||||
test(parseGithubPullRequestUrl, () => {
|
||||
forCases([
|
||||
given('https://github.com/badges/shields/pull/1234'),
|
||||
given('https://github.com/badges/shields/pull/1234', {
|
||||
verifyBaseUrl: 'https://github.com',
|
||||
}),
|
||||
]).expect({
|
||||
baseUrl: 'https://github.com',
|
||||
owner: 'badges',
|
||||
repo: 'shields',
|
||||
pullRequest: 1234,
|
||||
slug: 'badges/shields#1234',
|
||||
})
|
||||
|
||||
given('https://github.com/badges/shields/pull/1234', {
|
||||
verifyBaseUrl: 'https://example.com',
|
||||
}).expectError(
|
||||
'Expected base URL to be https://example.com but got https://github.com'
|
||||
)
|
||||
})
|
||||
|
||||
test(inferPullRequest, () => {
|
||||
const expected = {
|
||||
owner: 'badges',
|
||||
repo: 'shields',
|
||||
pullRequest: 1234,
|
||||
slug: 'badges/shields#1234',
|
||||
}
|
||||
|
||||
given({
|
||||
CIRCLECI: '1',
|
||||
CI_PULL_REQUEST: 'https://github.com/badges/shields/pull/1234',
|
||||
}).expect(Object.assign({ baseUrl: 'https://github.com' }, expected))
|
||||
|
||||
given({
|
||||
TRAVIS: '1',
|
||||
TRAVIS_REPO_SLUG: 'badges/shields',
|
||||
TRAVIS_PULL_REQUEST: '1234',
|
||||
}).expect(expected)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
// Infer the current PR from the Travis environment, and look for bracketed,
|
||||
// space-separated service names in the pull request title.
|
||||
// Derive a list of service tests to run based on
|
||||
// space-separated service names in the PR title.
|
||||
//
|
||||
// Output the list of services.
|
||||
//
|
||||
@@ -8,54 +8,26 @@
|
||||
// Output:
|
||||
// travis
|
||||
// sonar
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// TRAVIS=1 TRAVIS_REPO_SLUG=badges/shields TRAVIS_PULL_REQUEST=1108 npm run test:services:pr:prepare
|
||||
|
||||
import got from 'got'
|
||||
import { inferPullRequest } from './infer-pull-request.js'
|
||||
import servicesForTitle from './services-for-title.js'
|
||||
|
||||
async function getTitle(owner, repo, pullRequest) {
|
||||
const {
|
||||
body: { title },
|
||||
} = await got(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullRequest}`,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': 'badges/shields',
|
||||
Authorization: `token ${process.env.GITHUB_TOKEN}`,
|
||||
},
|
||||
responseType: 'json',
|
||||
}
|
||||
)
|
||||
return title
|
||||
let title
|
||||
|
||||
try {
|
||||
if (process.argv.length < 3) {
|
||||
throw new Error()
|
||||
}
|
||||
title = process.argv[2]
|
||||
} catch (e) {
|
||||
console.error('Error processing arguments')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { owner, repo, pullRequest, slug } = inferPullRequest()
|
||||
console.error(`PR: ${slug}`)
|
||||
|
||||
const title = await getTitle(owner, repo, pullRequest)
|
||||
|
||||
console.error(`Title: ${title}\n`)
|
||||
const services = servicesForTitle(title)
|
||||
if (services.length === 0) {
|
||||
console.error('No services found. Nothing to do.')
|
||||
} else {
|
||||
console.error(
|
||||
`Services: (${services.length} found) ${services.join(', ')}\n`
|
||||
)
|
||||
console.log(services.join('\n'))
|
||||
}
|
||||
console.error(`Title: ${title}\n`)
|
||||
const services = servicesForTitle(title)
|
||||
if (services.length === 0) {
|
||||
console.error('No services found. Nothing to do.')
|
||||
} else {
|
||||
console.error(`Services: (${services.length} found) ${services.join(', ')}\n`)
|
||||
console.log(services.join('\n'))
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
await main()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -106,9 +106,6 @@ export default function SponsorsPage(): JSX.Element {
|
||||
<li>
|
||||
<a href="https://github.com/">GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://lgtm.com/">LGTM</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://uptimerobot.com/">Uptime Robot</a>
|
||||
</li>
|
||||
|
||||
761
package-lock.json
generated
761
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -25,7 +25,7 @@
|
||||
"@fontsource/lekton": "^4.5.11",
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@renovatebot/ruby-semver": "^1.1.7",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/node": "^7.29.0",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -58,7 +58,7 @@
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^14.1.0",
|
||||
"prom-client": "^14.1.1",
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^8.1.0",
|
||||
"semver": "~7.3.8",
|
||||
@@ -142,7 +142,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.18.9",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
@@ -156,7 +156,7 @@
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-select": "^4.0.17",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.46.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-preset-gatsby": "^2.22.0",
|
||||
@@ -169,13 +169,13 @@
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^7.6.0",
|
||||
"cypress": "^12.2.0",
|
||||
"cypress": "^12.3.0",
|
||||
"cypress-wait-for-stable-dom": "^0.1.0",
|
||||
"danger": "^11.2.0",
|
||||
"danger": "^11.2.1",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-config-standard-jsx": "^10.0.0",
|
||||
"eslint-config-standard-react": "^11.0.1",
|
||||
|
||||
@@ -22,6 +22,10 @@ if (data.stats.passes > 0) {
|
||||
if (data.stats.failures > 0) {
|
||||
process.stdout.write(`✖ ${data.stats.failures} failed\n\n`)
|
||||
}
|
||||
if (data.stats.pending > 0) {
|
||||
process.stdout.write(`● ${data.stats.pending} pending\n\n`)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
if (data.stats.failures > 0) {
|
||||
for (const test of data.tests) {
|
||||
|
||||
@@ -1,117 +1,33 @@
|
||||
import Joi from 'joi'
|
||||
import { renderLicenseBadge } from '../licenses.js'
|
||||
import { renderVersionBadge } from '../version.js'
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService, InvalidResponse } from '../index.js'
|
||||
import { deprecatedService } from '../index.js'
|
||||
|
||||
const keywords = ['atom']
|
||||
|
||||
const schema = Joi.object({
|
||||
downloads: nonNegativeInteger,
|
||||
releases: Joi.object({
|
||||
latest: Joi.string().required(),
|
||||
}),
|
||||
metadata: Joi.object({
|
||||
license: Joi.string().required(),
|
||||
}),
|
||||
const APMDownloads = deprecatedService({
|
||||
category: 'downloads',
|
||||
route: {
|
||||
base: 'apm/dm',
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'downloads',
|
||||
dateAdded: new Date('2023-01-04'),
|
||||
})
|
||||
|
||||
class BaseAPMService extends BaseJsonService {
|
||||
static defaultBadgeData = { label: 'apm' }
|
||||
const APMVersion = deprecatedService({
|
||||
category: 'version',
|
||||
route: {
|
||||
base: 'apm/v',
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'apm',
|
||||
dateAdded: new Date('2023-01-04'),
|
||||
})
|
||||
|
||||
async fetch({ packageName }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://atom.io/api/packages/${packageName}`,
|
||||
errorMessages: { 404: 'package not found' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class APMDownloads extends BaseAPMService {
|
||||
static category = 'downloads'
|
||||
static route = { base: 'apm/dm', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'APM',
|
||||
namedParams: { packageName: 'vim-mode' },
|
||||
staticPreview: this.render({ downloads: '60043' }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'downloads' }
|
||||
|
||||
static render({ downloads }) {
|
||||
return renderDownloadsBadge({ downloads, colorOverride: 'green' })
|
||||
}
|
||||
|
||||
async handle({ packageName }) {
|
||||
const json = await this.fetch({ packageName })
|
||||
return this.constructor.render({ downloads: json.downloads })
|
||||
}
|
||||
}
|
||||
|
||||
class APMVersion extends BaseAPMService {
|
||||
static category = 'version'
|
||||
static route = { base: 'apm/v', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'APM',
|
||||
namedParams: { packageName: 'vim-mode' },
|
||||
staticPreview: this.render({ version: '0.6.0' }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
|
||||
static render({ version }) {
|
||||
return renderVersionBadge({ version })
|
||||
}
|
||||
|
||||
async handle({ packageName }) {
|
||||
const json = await this.fetch({ packageName })
|
||||
|
||||
const version = json.releases.latest
|
||||
if (!version)
|
||||
throw new InvalidResponse({
|
||||
underlyingError: new Error('version is invalid'),
|
||||
})
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
}
|
||||
|
||||
class APMLicense extends BaseAPMService {
|
||||
static category = 'license'
|
||||
static route = { base: 'apm/l', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'APM',
|
||||
namedParams: { packageName: 'vim-mode' },
|
||||
staticPreview: this.render({ license: 'MIT' }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'license' }
|
||||
|
||||
static render({ license }) {
|
||||
return renderLicenseBadge({ license })
|
||||
}
|
||||
|
||||
async handle({ packageName }) {
|
||||
const json = await this.fetch({ packageName })
|
||||
|
||||
const license = json.metadata.license
|
||||
if (!license)
|
||||
throw new InvalidResponse({
|
||||
underlyingError: new Error('licence is invalid'),
|
||||
})
|
||||
return this.constructor.render({ license })
|
||||
}
|
||||
}
|
||||
const APMLicense = deprecatedService({
|
||||
category: 'license',
|
||||
route: {
|
||||
base: 'apm/l',
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'license',
|
||||
dateAdded: new Date('2023-01-04'),
|
||||
})
|
||||
|
||||
export { APMDownloads, APMVersion, APMLicense }
|
||||
|
||||
@@ -1,57 +1,19 @@
|
||||
import { ServiceTester } from '../tester.js'
|
||||
import { invalidJSON } from '../response-fixtures.js'
|
||||
import { isMetric, isVPlusTripleDottedVersion } from '../test-validators.js'
|
||||
|
||||
export const t = new ServiceTester({
|
||||
id: 'apm',
|
||||
title: 'Atom Package Manager',
|
||||
pathPrefix: '/apm',
|
||||
})
|
||||
|
||||
t.create('Downloads')
|
||||
.get('/dm/vim-mode.json')
|
||||
.expectBadge({ label: 'downloads', message: isMetric })
|
||||
.expectBadge({ label: 'downloads', message: 'no longer available' })
|
||||
|
||||
t.create('Version')
|
||||
.get('/v/vim-mode.json')
|
||||
.expectBadge({ label: 'apm', message: isVPlusTripleDottedVersion })
|
||||
.expectBadge({ label: 'apm', message: 'no longer available' })
|
||||
|
||||
t.create('License')
|
||||
.get('/l/vim-mode.json')
|
||||
.expectBadge({ label: 'license', message: 'MIT' })
|
||||
|
||||
t.create('Downloads | Package not found')
|
||||
.get('/dm/notapackage.json')
|
||||
.expectBadge({ label: 'downloads', message: 'package not found' })
|
||||
|
||||
t.create('Version | Package not found')
|
||||
.get('/v/notapackage.json')
|
||||
.expectBadge({ label: 'apm', message: 'package not found' })
|
||||
|
||||
t.create('License | Package not found')
|
||||
.get('/l/notapackage.json')
|
||||
.expectBadge({ label: 'license', message: 'package not found' })
|
||||
|
||||
t.create('Invalid version')
|
||||
.get('/dm/vim-mode.json')
|
||||
.intercept(nock =>
|
||||
nock('https://atom.io')
|
||||
.get('/api/packages/vim-mode')
|
||||
.reply(200, '{"releases":{}}')
|
||||
)
|
||||
.expectBadge({ label: 'downloads', message: 'invalid response data' })
|
||||
|
||||
t.create('Invalid License')
|
||||
.get('/l/vim-mode.json')
|
||||
.intercept(nock =>
|
||||
nock('https://atom.io')
|
||||
.get('/api/packages/vim-mode')
|
||||
.reply(200, '{"metadata":{}}')
|
||||
)
|
||||
.expectBadge({ label: 'license', message: 'invalid response data' })
|
||||
|
||||
t.create('Unexpected response')
|
||||
.get('/dm/vim-mode.json')
|
||||
.intercept(nock =>
|
||||
nock('https://atom.io').get('/api/packages/vim-mode').reply(invalidJSON)
|
||||
)
|
||||
.expectBadge({ label: 'downloads', message: 'unparseable json response' })
|
||||
.expectBadge({ label: 'license', message: 'no longer available' })
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ServiceTester } from '../../tester.js'
|
||||
|
||||
export const t = new ServiceTester({
|
||||
id: 'GithubGistLastCommitRedirect',
|
||||
id: 'GistLastCommitRedirect',
|
||||
title: 'Github Gist Last Commit Redirect',
|
||||
pathPrefix: '/github-gist',
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ const schema = Joi.object({
|
||||
updated_at: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
export default class GithubGistLastCommit extends GithubAuthV3Service {
|
||||
export default class GistLastCommit extends GithubAuthV3Service {
|
||||
static category = 'activity'
|
||||
static route = { base: 'github/gist/last-commit', pattern: ':gistId' }
|
||||
static examples = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ServiceTester } from '../../tester.js'
|
||||
export const t = new ServiceTester({
|
||||
id: 'GithubGistStarsRedirect',
|
||||
id: 'GistStarsRedirect',
|
||||
title: 'Github Gist Stars Redirect',
|
||||
pathPrefix: '/github',
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ const documentation = `${commonDocumentation}
|
||||
<p>This badge shows the number of stargazers for a gist. Gist id is accepted as input and 'gist not found' is returned if the gist is not found for the given gist id.
|
||||
</p>`
|
||||
|
||||
export default class GithubGistStars extends GithubAuthV4Service {
|
||||
export default class GistStars extends GithubAuthV4Service {
|
||||
static category = 'social'
|
||||
|
||||
static route = {
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
import { metric } from '../text-formatters.js'
|
||||
import LgtmBaseService from './lgtm-base.js'
|
||||
import { deprecatedService } from '../index.js'
|
||||
|
||||
export default class LgtmAlerts extends LgtmBaseService {
|
||||
static route = {
|
||||
export default deprecatedService({
|
||||
category: 'analysis',
|
||||
route: {
|
||||
base: 'lgtm/alerts',
|
||||
pattern: this.pattern,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'LGTM Alerts',
|
||||
namedParams: {
|
||||
host: 'github',
|
||||
user: 'apache',
|
||||
repo: 'cloudstack',
|
||||
},
|
||||
staticPreview: this.render({ alerts: 2488 }),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
label: 'lgtm alerts',
|
||||
}
|
||||
|
||||
static getColor({ alerts }) {
|
||||
let color = 'yellow'
|
||||
if (alerts === 0) {
|
||||
color = 'brightgreen'
|
||||
}
|
||||
return color
|
||||
}
|
||||
|
||||
static render({ alerts }) {
|
||||
return {
|
||||
message: metric(alerts),
|
||||
color: this.getColor({ alerts }),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ host, user, repo }) {
|
||||
const { alerts } = await this.fetch({ host, user, repo })
|
||||
return this.constructor.render({ alerts })
|
||||
}
|
||||
}
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'lgtm alerts',
|
||||
dateAdded: new Date('2023-01-03'),
|
||||
})
|
||||
|
||||
@@ -1,61 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
import { data } from './lgtm-test-helpers.js'
|
||||
export const t = await createServiceTester()
|
||||
import { ServiceTester } from '../tester.js'
|
||||
|
||||
t.create('alerts: total alerts for a project')
|
||||
export const t = new ServiceTester({
|
||||
id: 'lgtmAlerts',
|
||||
title: 'LgtmAlerts',
|
||||
pathPrefix: '/lgtm/alerts',
|
||||
})
|
||||
|
||||
t.create('Lgtm')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.expectBadge({
|
||||
label: 'lgtm alerts',
|
||||
message: Joi.string().regex(/^[0-9kM.]+$/),
|
||||
})
|
||||
|
||||
t.create('alerts: missing project')
|
||||
.get('/github/some-org/this-project-doesnt-exist.json')
|
||||
.expectBadge({
|
||||
label: 'lgtm alerts',
|
||||
message: 'project not found',
|
||||
})
|
||||
|
||||
t.create('alerts: no alerts')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, { alerts: 0, languages: data.languages })
|
||||
)
|
||||
.expectBadge({ label: 'lgtm alerts', message: '0' })
|
||||
|
||||
t.create('alerts: single alert')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, { alerts: 1, languages: data.languages })
|
||||
)
|
||||
.expectBadge({ label: 'lgtm alerts', message: '1' })
|
||||
|
||||
t.create('alerts: multiple alerts')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, { alerts: 123, languages: data.languages })
|
||||
)
|
||||
.expectBadge({ label: 'lgtm alerts', message: '123' })
|
||||
|
||||
t.create('alerts: json missing alerts')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, {})
|
||||
)
|
||||
.expectBadge({ label: 'lgtm alerts', message: 'invalid response data' })
|
||||
|
||||
t.create('alerts: total alerts for a project with a github mapped host')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.expectBadge({
|
||||
label: 'lgtm alerts',
|
||||
message: Joi.string().regex(/^[0-9kM.]+$/),
|
||||
})
|
||||
.expectBadge({ label: 'lgtm alerts', message: 'no longer available' })
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import Joi from 'joi'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
alerts: Joi.number().required(),
|
||||
|
||||
languages: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
lang: Joi.string().required(),
|
||||
grade: Joi.string(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
const hostMappings = {
|
||||
github: 'g',
|
||||
bitbucket: 'b',
|
||||
gitlab: 'gl',
|
||||
}
|
||||
|
||||
export default class LgtmBaseService extends BaseJsonService {
|
||||
static category = 'analysis'
|
||||
|
||||
static defaultBadgeData = { label: 'lgtm' }
|
||||
|
||||
static pattern = `:host(${Object.keys(hostMappings).join('|')})/:user/:repo`
|
||||
|
||||
async fetch({ host, user, repo }) {
|
||||
const mappedHost = hostMappings[host]
|
||||
const url = `https://lgtm.com/api/v0.1/project/${mappedHost}/${user}/${repo}/details`
|
||||
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url,
|
||||
errorMessages: {
|
||||
404: 'project not found',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,11 @@
|
||||
import LgtmBaseService from './lgtm-base.js'
|
||||
import { deprecatedService } from '../index.js'
|
||||
|
||||
export default class LgtmGrade extends LgtmBaseService {
|
||||
static route = {
|
||||
export default deprecatedService({
|
||||
category: 'analysis',
|
||||
route: {
|
||||
base: 'lgtm/grade',
|
||||
pattern: `:language/${this.pattern}`,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'LGTM Grade',
|
||||
namedParams: {
|
||||
language: 'java',
|
||||
host: 'github',
|
||||
user: 'apache',
|
||||
repo: 'cloudstack',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
language: 'java',
|
||||
data: {
|
||||
languages: [
|
||||
{
|
||||
lang: 'java',
|
||||
grade: 'C',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
static getLabel({ language }) {
|
||||
const languageLabel = (() => {
|
||||
switch (language) {
|
||||
case 'cpp':
|
||||
return 'c/c++'
|
||||
case 'csharp':
|
||||
return 'c#'
|
||||
// Javascript analysis on LGTM also includes TypeScript
|
||||
case 'javascript':
|
||||
return 'js/ts'
|
||||
default:
|
||||
return language
|
||||
}
|
||||
})()
|
||||
return languageLabel
|
||||
}
|
||||
|
||||
static getGradeAndColor({ language, data }) {
|
||||
let grade = 'no language data'
|
||||
let color = 'red'
|
||||
|
||||
for (const languageData of data.languages) {
|
||||
if (languageData.lang === language && 'grade' in languageData) {
|
||||
// Pretty label for the language
|
||||
grade = languageData.grade
|
||||
// Pick colour based on grade
|
||||
if (languageData.grade === 'A+') {
|
||||
color = 'brightgreen'
|
||||
} else if (languageData.grade === 'A') {
|
||||
color = 'green'
|
||||
} else if (languageData.grade === 'B') {
|
||||
color = 'yellowgreen'
|
||||
} else if (languageData.grade === 'C') {
|
||||
color = 'yellow'
|
||||
} else if (languageData.grade === 'D') {
|
||||
color = 'orange'
|
||||
}
|
||||
}
|
||||
}
|
||||
return { grade, color }
|
||||
}
|
||||
|
||||
static render({ language, data }) {
|
||||
const { grade, color } = this.getGradeAndColor({ language, data })
|
||||
|
||||
return {
|
||||
label: `code quality: ${this.getLabel({ language })}`,
|
||||
message: grade,
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ language, host, user, repo }) {
|
||||
const data = await this.fetch({ host, user, repo })
|
||||
return this.constructor.render({ language, data })
|
||||
}
|
||||
}
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'lgtm grade',
|
||||
dateAdded: new Date('2023-01-03'),
|
||||
})
|
||||
|
||||
@@ -1,106 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
import { data } from './lgtm-test-helpers.js'
|
||||
export const t = await createServiceTester()
|
||||
import { ServiceTester } from '../tester.js'
|
||||
|
||||
t.create('grade: missing project')
|
||||
.get('/java/github/some-org/this-project-doesnt-exist.json')
|
||||
.expectBadge({
|
||||
label: 'lgtm',
|
||||
message: 'project not found',
|
||||
})
|
||||
export const t = new ServiceTester({
|
||||
id: 'lgtmGrade',
|
||||
title: 'LgtmGrade',
|
||||
pathPrefix: '/lgtm/grade',
|
||||
})
|
||||
|
||||
t.create('grade: json missing languages')
|
||||
.get('/java/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, {})
|
||||
)
|
||||
.expectBadge({ label: 'lgtm', message: 'invalid response data' })
|
||||
|
||||
t.create('grade: grade for a project (java)')
|
||||
.get('/java/github/apache/cloudstack.json')
|
||||
.expectBadge({
|
||||
label: 'code quality: java',
|
||||
message: Joi.string().regex(/^(?:A\+)|A|B|C|D|E$/),
|
||||
})
|
||||
|
||||
t.create('grade: grade for missing language')
|
||||
.get('/foo/github/apache/cloudstack.json')
|
||||
.expectBadge({
|
||||
label: 'code quality: foo',
|
||||
message: 'no language data',
|
||||
})
|
||||
|
||||
t.create('grade: grade for a project with a mapped host')
|
||||
.get('/java/github/apache/cloudstack.json')
|
||||
.expectBadge({
|
||||
label: 'code quality: java',
|
||||
message: Joi.string().regex(/^(?:A\+)|A|B|C|D|E$/),
|
||||
})
|
||||
|
||||
// Test display of languages
|
||||
|
||||
t.create('grade: cpp')
|
||||
.get('/cpp/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: c/c++', message: 'A+' })
|
||||
|
||||
t.create('grade: javascript')
|
||||
.get('/javascript/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: js/ts', message: 'A' })
|
||||
|
||||
t.create('grade: java')
|
||||
.get('/java/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: java', message: 'B' })
|
||||
|
||||
t.create('grade: python')
|
||||
.get('/python/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: python', message: 'C' })
|
||||
|
||||
t.create('grade: csharp')
|
||||
.get('/csharp/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: c#', message: 'D' })
|
||||
|
||||
t.create('grade: other')
|
||||
.get('/other/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: other', message: 'E' })
|
||||
|
||||
t.create('grade: foo (no grade for valid language)')
|
||||
.get('/foo/github/apache/cloudstack.json')
|
||||
.intercept(nock =>
|
||||
nock('https://lgtm.com')
|
||||
.get('/api/v0.1/project/g/apache/cloudstack/details')
|
||||
.reply(200, data)
|
||||
)
|
||||
.expectBadge({ label: 'code quality: foo', message: 'no language data' })
|
||||
t.create('Lgtm')
|
||||
.get('/github/apache/cloudstack.json')
|
||||
.expectBadge({ label: 'lgtm grade', message: 'no longer available' })
|
||||
|
||||
@@ -7,9 +7,9 @@ export const t = new ServiceTester({
|
||||
})
|
||||
|
||||
t.create('alerts')
|
||||
.get('/alerts/g/badges/shields.svg')
|
||||
.expectRedirect('/lgtm/alerts/github/badges/shields.svg')
|
||||
.get('/alerts/g/badges/shields.json')
|
||||
.expectBadge({ label: 'lgtm alerts', message: 'no longer available' })
|
||||
|
||||
t.create('grade')
|
||||
.get('/grade/java/g/apache/cloudstack.svg')
|
||||
.expectRedirect('/lgtm/grade/java/github/apache/cloudstack.svg')
|
||||
.get('/grade/java/g/apache/cloudstack.json')
|
||||
.expectBadge({ label: 'lgtm grade', message: 'no longer available' })
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
const data = {
|
||||
alerts: 0,
|
||||
languages: [
|
||||
{ lang: 'cpp', grade: 'A+' },
|
||||
{ lang: 'javascript', grade: 'A' },
|
||||
{ lang: 'java', grade: 'B' },
|
||||
{ lang: 'python', grade: 'C' },
|
||||
{ lang: 'csharp', grade: 'D' },
|
||||
{ lang: 'other', grade: 'E' },
|
||||
{ lang: 'foo' },
|
||||
],
|
||||
}
|
||||
|
||||
export { data }
|
||||
@@ -2,14 +2,23 @@
|
||||
* Utilities relating to PHP version numbers. This compares version numbers
|
||||
* using the algorithm followed by Composer (see
|
||||
* https://getcomposer.org/doc/04-schema.md#version).
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { fetch } from '../core/base-service/got.js'
|
||||
import { getCachedResource } from '../core/base-service/resource-cache.js'
|
||||
import { listCompare } from './version.js'
|
||||
import { omitv } from './text-formatters.js'
|
||||
|
||||
// Return a negative value if v1 < v2,
|
||||
// zero if v1 = v2, a positive value otherwise.
|
||||
/**
|
||||
* Return a negative value if v1 < v2,
|
||||
* zero if v1 = v2, a positive value otherwise.
|
||||
*
|
||||
* @param {string} v1 - First version for comparison
|
||||
* @param {string} v2 - Second version for comparison
|
||||
* @returns {number} Comparison result (-1, 0 or 1)
|
||||
*/
|
||||
function asciiVersionCompare(v1, v2) {
|
||||
if (v1 < v2) {
|
||||
return -1
|
||||
@@ -20,9 +29,14 @@ function asciiVersionCompare(v1, v2) {
|
||||
}
|
||||
}
|
||||
|
||||
// Take a version without the starting v.
|
||||
// eg, '1.0.x-beta'
|
||||
// Return { numbers: [1,0,something big], modifier: 2, modifierCount: 1 }
|
||||
/**
|
||||
* Take a version without the starting v.
|
||||
* eg, '1.0.x-beta'
|
||||
* Return { numbers: [1,0,something big], modifier: 2, modifierCount: 1 }
|
||||
*
|
||||
* @param {string} version - Version number string
|
||||
* @returns {object} Object containing version details
|
||||
*/
|
||||
function numberedVersionData(version) {
|
||||
// A version has a numbered part and a modifier part
|
||||
// (eg, 1.0.0-patch, 2.0.x-dev).
|
||||
@@ -96,7 +110,12 @@ function numberedVersionData(version) {
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to a list of numbers.
|
||||
/**
|
||||
* Try to convert to a list of numbers.
|
||||
*
|
||||
* @param {string} s - Version number string
|
||||
* @returns {number} Version number interger
|
||||
*/
|
||||
function toNum(s) {
|
||||
let n = +s
|
||||
if (Number.isNaN(n)) {
|
||||
@@ -113,12 +132,15 @@ function numberedVersionData(version) {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a negative value if v1 < v2,
|
||||
// zero if v1 = v2,
|
||||
// a positive value otherwise.
|
||||
//
|
||||
// See https://getcomposer.org/doc/04-schema.md#version
|
||||
// and https://github.com/badges/shields/issues/319#issuecomment-74411045
|
||||
/**
|
||||
* Compares two versions and return an interger based on the result.
|
||||
* See https://getcomposer.org/doc/04-schema.md#version
|
||||
* and https://github.com/badges/shields/issues/319#issuecomment-74411045
|
||||
*
|
||||
* @param {string} v1 - First version
|
||||
* @param {string} v2 - Second version
|
||||
* @returns {number} Negative value if v1 < v2, zero if v1 = v2, else a positive value
|
||||
*/
|
||||
function compare(v1, v2) {
|
||||
// Omit the starting `v`.
|
||||
const rawv1 = omitv(v1)
|
||||
@@ -154,6 +176,12 @@ function compare(v1, v2) {
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the latest version from a list of versions.
|
||||
*
|
||||
* @param {string[]} versions - List of versions
|
||||
* @returns {string} Latest version
|
||||
*/
|
||||
function latest(versions) {
|
||||
let latest = versions[0]
|
||||
for (let i = 1; i < versions.length; i++) {
|
||||
@@ -164,6 +192,12 @@ function latest(versions) {
|
||||
return latest
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a version is stable or not.
|
||||
*
|
||||
* @param {string} version - Version number
|
||||
* @returns {boolean} true if version is stable, else false
|
||||
*/
|
||||
function isStable(version) {
|
||||
const rawVersion = omitv(version)
|
||||
let versionData
|
||||
@@ -176,6 +210,12 @@ function isStable(version) {
|
||||
return versionData.modifier === 3 || versionData.modifier === 4
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a version is valid and returns the minor version.
|
||||
*
|
||||
* @param {string} version - Version number
|
||||
* @returns {string} Minor version
|
||||
*/
|
||||
function minorVersion(version) {
|
||||
const result = version.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
|
||||
|
||||
@@ -186,6 +226,13 @@ function minorVersion(version) {
|
||||
return `${result[1]}.${result[2] ? result[2] : '0'}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the list of php versions that intersect with release versions to a version range (for eg. '5.4 - 7.1', '>= 5.5').
|
||||
*
|
||||
* @param {string[]} versions - List of php versions
|
||||
* @param {string[]} phpReleases - List of php release versions
|
||||
* @returns {string[]} Reduced Version Range (for eg. ['5.4 - 7.1'], ['>= 5.5'])
|
||||
*/
|
||||
function versionReduction(versions, phpReleases) {
|
||||
if (!versions.length) {
|
||||
return []
|
||||
@@ -216,6 +263,13 @@ function versionReduction(versions, phpReleases) {
|
||||
return versions
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the PHP release versions from cache if exists, else fetch from the souce url and save in cache.
|
||||
*
|
||||
* @async
|
||||
* @param {object} githubApiProvider - Github API provider
|
||||
* @returns {Promise<*>} Promise that resolves to parsed response
|
||||
*/
|
||||
async function getPhpReleases(githubApiProvider) {
|
||||
return getCachedResource({
|
||||
url: '/repos/php/php-src/git/refs/tags',
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
/**
|
||||
* Common functions and utilities for tasks related to pipenv
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import Joi from 'joi'
|
||||
import { InvalidParameter } from './index.js'
|
||||
|
||||
/**
|
||||
* Joi schema for validating dependency.
|
||||
*
|
||||
* @type {Joi}
|
||||
*/
|
||||
const isDependency = Joi.object({
|
||||
version: Joi.string(),
|
||||
ref: Joi.string(),
|
||||
}).required()
|
||||
|
||||
/**
|
||||
* Joi schema for validating lock file object.
|
||||
* Checks if the lock file object has required properties and the properties are valid.
|
||||
*
|
||||
* @type {Joi}
|
||||
*/
|
||||
const isLockfile = Joi.object({
|
||||
_meta: Joi.object({
|
||||
requires: Joi.object({
|
||||
@@ -16,6 +33,18 @@ const isLockfile = Joi.object({
|
||||
develop: Joi.object().pattern(Joi.string(), isDependency),
|
||||
}).required()
|
||||
|
||||
/**
|
||||
* Determines the dependency version based on the dependency type.
|
||||
*
|
||||
* @param {object} attrs - Refer to individual attributes
|
||||
* @param {string} attrs.kind - Wanted dependency type ('dev' or 'default'), defaults to 'default'
|
||||
* @param {string} attrs.wantedDependency - Name of the wanted dependency
|
||||
* @param {object} attrs.lockfileData - Object containing lock file data
|
||||
* @throws {Error} - Error if unknown dependency type provided
|
||||
* @throws {InvalidParameter} - Error if wanted dependency is not present in lock file data
|
||||
* @throws {InvalidParameter} - Error if version or ref is not present for the wanted dependency
|
||||
* @returns {object} Object containing wanted dependency version or ref
|
||||
*/
|
||||
function getDependencyVersion({
|
||||
kind = 'default',
|
||||
wantedDependency,
|
||||
|
||||
Reference in New Issue
Block a user