Compare commits
24 Commits
server-202
...
dangertest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
027323d4b3 | ||
|
|
2fb9889742 | ||
|
|
5edec9967f | ||
|
|
47d67b4b19 | ||
|
|
2afcdf4ebd | ||
|
|
91d85368bf | ||
|
|
1fe60b49e1 | ||
|
|
d6c3459803 | ||
|
|
0d4fcfe880 | ||
|
|
f67fe525c2 | ||
|
|
dad6ce554a | ||
|
|
0c73b35915 | ||
|
|
f1d151e963 | ||
|
|
a0149a8f8f | ||
|
|
e843d4eac1 | ||
|
|
830b5d8a1f | ||
|
|
8afb034a58 | ||
|
|
2f915a7b45 | ||
|
|
7dbfd0d049 | ||
|
|
493fdb76af | ||
|
|
abb1bbf8d4 | ||
|
|
a4911dac33 | ||
|
|
5e6583c530 | ||
|
|
bb1fda2aa7 |
@@ -26,26 +26,6 @@ services_steps: &services_steps
|
||||
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
|
||||
@@ -77,13 +57,6 @@ workflows:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- danger:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- gh-pages
|
||||
- /dependabot\/.*/
|
||||
# on-commit-with-cache:
|
||||
# jobs:
|
||||
# - npm-install:
|
||||
@@ -102,9 +75,3 @@ workflows:
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: master
|
||||
# - danger:
|
||||
# requires:
|
||||
# - npm-install
|
||||
# filters:
|
||||
# branches:
|
||||
# ignore: /dependabot\/.*/
|
||||
|
||||
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 }}'
|
||||
@@ -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>
|
||||
|
||||
@@ -27,11 +27,13 @@ class InvalidService extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function getServicePaths(pattern) {
|
||||
return glob.sync(toUnixPath(path.join(serviceDir, '**', pattern)))
|
||||
}
|
||||
|
||||
async function loadServiceClasses(servicePaths) {
|
||||
if (!servicePaths) {
|
||||
servicePaths = glob.sync(
|
||||
toUnixPath(path.join(serviceDir, '**', '*.service.js'))
|
||||
)
|
||||
servicePaths = getServicePaths('*.service.js')
|
||||
}
|
||||
|
||||
const serviceClasses = []
|
||||
@@ -102,15 +104,16 @@ async function collectDefinitions() {
|
||||
|
||||
async function loadTesters() {
|
||||
return Promise.all(
|
||||
glob
|
||||
.sync(path.join(serviceDir, '**', '*.tester.js'))
|
||||
.map(async path => await import(`file://${path}`))
|
||||
getServicePaths('*.tester.js').map(
|
||||
async path => await import(`file://${path}`)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
InvalidService,
|
||||
loadServiceClasses,
|
||||
getServicePaths,
|
||||
checkNames,
|
||||
collectDefinitions,
|
||||
loadTesters,
|
||||
|
||||
@@ -2,7 +2,11 @@ import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import chai from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import { loadServiceClasses, InvalidService } from './loader.js'
|
||||
import {
|
||||
loadServiceClasses,
|
||||
getServicePaths,
|
||||
InvalidService,
|
||||
} from './loader.js'
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
const { expect } = chai
|
||||
@@ -65,3 +69,15 @@ describe('loadServiceClasses function', function () {
|
||||
).to.eventually.have.length(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getServicePaths', function () {
|
||||
// these tests just make sure we discover a
|
||||
// plausibly large number of .service and .tester files
|
||||
it('finds a non-zero number of services in the project', function () {
|
||||
expect(getServicePaths('*.service.js')).to.have.length.above(400)
|
||||
})
|
||||
|
||||
it('finds a non-zero number of testers in the project', function () {
|
||||
expect(getServicePaths('*.tester.js')).to.have.length.above(400)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
// DANGER_GITHUB_API_TOKEN=your-github-api-token npm run danger -- pr https://github.com/badges/shields/pull/2665
|
||||
|
||||
const { danger, fail, message, warn } = require('danger')
|
||||
const { default: noTestShortcuts } = require('danger-plugin-no-test-shortcuts')
|
||||
const { fileMatch } = danger.git
|
||||
|
||||
const documentation = fileMatch(
|
||||
@@ -173,11 +172,3 @@ affectedServices.forEach(service => {
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Prevent merging exclusive services tests.
|
||||
noTestShortcuts({
|
||||
testFilePredicate: filePath => filePath.endsWith('.tester.js'),
|
||||
patterns: {
|
||||
only: ['only()'],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
1388
package-lock.json
generated
1388
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -24,8 +24,8 @@
|
||||
"@fontsource/lato": "^4.5.10",
|
||||
"@fontsource/lekton": "^4.5.11",
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@renovatebot/ruby-semver": "^1.1.7",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@renovatebot/ruby-semver": "^1.1.8",
|
||||
"@sentry/node": "^7.30.0",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -39,7 +39,7 @@
|
||||
"decamelize": "^3.2.0",
|
||||
"emojic": "^1.1.17",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^4.0.12",
|
||||
"fast-xml-parser": "^4.0.13",
|
||||
"glob": "^8.0.3",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^12.5.3",
|
||||
@@ -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,25 +169,25 @@
|
||||
"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",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-import": "^2.27.4",
|
||||
"eslint-plugin-jsdoc": "^39.6.4",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-react": "^7.32.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sort-class-members": "^1.16.0",
|
||||
"fetch-ponyfill": "^7.1.0",
|
||||
@@ -213,13 +213,13 @@
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.2.0",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.2.9",
|
||||
"nock": "13.3.0",
|
||||
"node-mocks-http": "^1.12.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"open-cli": "^7.1.0",
|
||||
"portfinder": "^1.0.32",
|
||||
"prettier": "2.8.1",
|
||||
"prettier": "2.8.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-overlay": "^6.0.11",
|
||||
@@ -229,7 +229,7 @@
|
||||
"react-select": "^4.3.1",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"redis-server": "^1.2.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"rimraf": "^4.0.4",
|
||||
"sazerac": "^2.0.0",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"sinon": "^15.0.1",
|
||||
|
||||
@@ -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' })
|
||||
|
||||
@@ -66,9 +66,9 @@ class GithubPipenvLockedPythonVersion extends ConditionalGithubAuthV3Service {
|
||||
namedParams: {
|
||||
user: 'metabolize',
|
||||
repo: 'rq-dashboard-on-heroku',
|
||||
branch: 'master',
|
||||
branch: 'main',
|
||||
},
|
||||
staticPreview: this.render({ version: '3.7', branch: 'master' }),
|
||||
staticPreview: this.render({ version: '3.7', branch: 'main' }),
|
||||
documentation,
|
||||
keywords,
|
||||
},
|
||||
@@ -135,7 +135,7 @@ class GithubPipenvLockedDependencyVersion extends ConditionalGithubAuthV3Service
|
||||
repo: 'rq-dashboard-on-heroku',
|
||||
kind: 'dev',
|
||||
packageName: 'black',
|
||||
branch: 'master',
|
||||
branch: 'main',
|
||||
},
|
||||
staticPreview: this.render({ dependency: 'black', version: '19.3b0' }),
|
||||
documentation,
|
||||
|
||||
@@ -47,7 +47,7 @@ t.create('Locked version of default dependency')
|
||||
|
||||
t.create('Locked version of default dependency (branch)')
|
||||
.get(
|
||||
'/locked/dependency-version/metabolize/rq-dashboard-on-heroku/rq-dashboard/master.json'
|
||||
'/locked/dependency-version/metabolize/rq-dashboard-on-heroku/rq-dashboard/main.json'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'rq-dashboard',
|
||||
@@ -65,7 +65,7 @@ t.create('Locked version of dev dependency')
|
||||
|
||||
t.create('Locked version of dev dependency (branch)')
|
||||
.get(
|
||||
'/locked/dependency-version/metabolize/rq-dashboard-on-heroku/dev/black/master.json'
|
||||
'/locked/dependency-version/metabolize/rq-dashboard-on-heroku/dev/black/main.json'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'black',
|
||||
|
||||
@@ -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