Compare commits
30 Commits
daily-test
...
server-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1838979d56 | ||
|
|
7c787b364a | ||
|
|
0e79f75030 | ||
|
|
f25c3f265d | ||
|
|
740a682f05 | ||
|
|
f7b1796807 | ||
|
|
2439cd2fce | ||
|
|
aa10d0b092 | ||
|
|
035e729313 | ||
|
|
5f6a48eabd | ||
|
|
5b813ce908 | ||
|
|
c63244562d | ||
|
|
97ca295662 | ||
|
|
61a6963919 | ||
|
|
5c8129904f | ||
|
|
677e713310 | ||
|
|
92718ff98b | ||
|
|
0c17d9d44d | ||
|
|
02608e1242 | ||
|
|
250686f147 | ||
|
|
35d5e9d964 | ||
|
|
8eff4f51e4 | ||
|
|
1445c3d21a | ||
|
|
95382dd5ce | ||
|
|
5ae8b29923 | ||
|
|
8303f43f2a | ||
|
|
3509d1faf6 | ||
|
|
283ef0bade | ||
|
|
d2344ed220 | ||
|
|
5aae2fdbd5 |
2
.github/workflows/build-docker-image.yml
vendored
2
.github/workflows/build-docker-image.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
||||
4
.github/workflows/create-release.yml
vendored
4
.github/workflows/create-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to DockerHub
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push snapshot release to GHCR
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
4
.github/workflows/publish-docker-next.yml
vendored
4
.github/workflows/publish-docker-next.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
- name: Build and push to DockerHub
|
||||
id: docker_build_push
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to GHCR
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -4,6 +4,17 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2024-07-01
|
||||
|
||||
- Add [AUR] Popularity Badge [#10304](https://github.com/badges/shields/issues/10304)
|
||||
- fix npm badges when `maintainers` not in response [#10286](https://github.com/badges/shields/issues/10286)
|
||||
- Expose `logoBase64` and `links` in badge-maker NPM package [#10283](https://github.com/badges/shields/issues/10283)
|
||||
- Remove `logoPosition` [#10284](https://github.com/badges/shields/issues/10284)
|
||||
- [MBIN] Add subscribers badge [#10270](https://github.com/badges/shields/issues/10270)
|
||||
- Add [Docker] support for loong64 arch [#10241](https://github.com/badges/shields/issues/10241)
|
||||
- Add puppetforge quality score badges [#10201](https://github.com/badges/shields/issues/10201)
|
||||
- Dependency updates
|
||||
|
||||
## server-2024-06-01
|
||||
|
||||
- Remove namedLogo from defaultBadgeData of non-social badges [#10195](https://github.com/badges/shields/issues/10195)
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 4.0.0 [WIP]
|
||||
## 4.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Drop compatibility with Node < 16
|
||||
|
||||
### Features
|
||||
|
||||
- Add `links` and `logoBase64` params
|
||||
|
||||
## 3.3.1
|
||||
|
||||
- Improve font measuring in for-the-badge and social styles
|
||||
|
||||
@@ -67,6 +67,8 @@ The format is the following:
|
||||
message: 'passed', // (Required) Badge message
|
||||
labelColor: '#555', // (Optional) Label color
|
||||
color: '#4c1', // (Optional) Message color
|
||||
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iOCIgZmlsbD0iI2IxY2U1NiIvPjxwYXRoIGQ9Ik04IDBoMjR2NjRIOGMtNC40MzIgMC04LTMuNTY4LTgtOFY4YzAtNC40MzIgMy41NjgtOCA4LTh6IiBmaWxsPSIjNWQ1ZDVkIi8+PC9zdmc+' // (Optional) Any custom logo can be passed in a URL parameter by base64 encoding
|
||||
links: ['https://example.com', 'https://example.com'], // (Optional) Links array of maximum two links
|
||||
|
||||
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
|
||||
// Each offers a different visual design.
|
||||
|
||||
2
badge-maker/index.d.ts
vendored
2
badge-maker/index.d.ts
vendored
@@ -4,6 +4,8 @@ interface Format {
|
||||
labelColor?: string
|
||||
color?: string
|
||||
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social'
|
||||
logoBase64?: string
|
||||
links?: Array<string>
|
||||
}
|
||||
|
||||
export declare class ValidationError extends Error {}
|
||||
|
||||
@@ -16,13 +16,30 @@ function _validate(format) {
|
||||
throw new ValidationError('Field `message` is required')
|
||||
}
|
||||
|
||||
const stringFields = ['labelColor', 'color', 'message', 'label']
|
||||
const stringFields = ['labelColor', 'color', 'message', 'label', 'logoBase64']
|
||||
stringFields.forEach(function (field) {
|
||||
if (field in format && typeof format[field] !== 'string') {
|
||||
throw new ValidationError(`Field \`${field}\` must be of type string`)
|
||||
}
|
||||
})
|
||||
|
||||
if ('links' in format) {
|
||||
if (!Array.isArray(format.links)) {
|
||||
throw new ValidationError('Field `links` must be an array of strings')
|
||||
} else {
|
||||
if (format.links.length > 2) {
|
||||
throw new ValidationError(
|
||||
'Field `links` must not have more than 2 elements',
|
||||
)
|
||||
}
|
||||
format.links.forEach(function (field) {
|
||||
if (typeof field !== 'string') {
|
||||
throw new ValidationError('Field `links` must be an array of strings')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const styleValues = [
|
||||
'plastic',
|
||||
'flat',
|
||||
@@ -38,11 +55,21 @@ function _validate(format) {
|
||||
}
|
||||
|
||||
function _clean(format) {
|
||||
const expectedKeys = ['label', 'message', 'labelColor', 'color', 'style']
|
||||
const expectedKeys = [
|
||||
'label',
|
||||
'message',
|
||||
'labelColor',
|
||||
'color',
|
||||
'style',
|
||||
'logoBase64',
|
||||
'links',
|
||||
]
|
||||
|
||||
const cleaned = {}
|
||||
Object.keys(format).forEach(key => {
|
||||
if (format[key] != null && expectedKeys.includes(key)) {
|
||||
if (format[key] != null && key === 'logoBase64') {
|
||||
cleaned.logo = format[key]
|
||||
} else if (format[key] != null && expectedKeys.includes(key)) {
|
||||
cleaned[key] = format[key]
|
||||
} else {
|
||||
throw new ValidationError(
|
||||
@@ -65,7 +92,9 @@ function _clean(format) {
|
||||
* @param {string} format.message (Required) Badge message (e.g: 'passing')
|
||||
* @param {string} format.labelColor (Optional) Label color
|
||||
* @param {string} format.color (Optional) Message color
|
||||
* @param {string} format.style (Optional) Visual style e.g: 'flat'
|
||||
* @param {string} format.style (Optional) Visual style (e.g: 'flat')
|
||||
* @param {string} format.logoBase64 (Optional) Logo data URL
|
||||
* @param {Array} format.links (Optional) Links array (e.g: ['https://example.com', 'https://example.com'])
|
||||
* @returns {string} Badge in SVG format
|
||||
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,21 @@ describe('makeBadge function', function () {
|
||||
style: 'flat',
|
||||
}),
|
||||
).to.satisfy(isSvg)
|
||||
expect(
|
||||
makeBadge({
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
color: 'green',
|
||||
style: 'flat',
|
||||
labelColor: 'blue',
|
||||
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
links: ['https://example.com', 'https://example.com'],
|
||||
}),
|
||||
)
|
||||
.to.satisfy(isSvg)
|
||||
// explicitly make an assertion about logoBase64
|
||||
// this param is not a straight passthrough
|
||||
.and.to.include('data:image/svg+xml;base64,PHN2ZyB4bWxu')
|
||||
})
|
||||
|
||||
it('should throw a ValidationError with invalid inputs', function () {
|
||||
@@ -46,6 +61,21 @@ describe('makeBadge function', function () {
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', labelColor: 7 }),
|
||||
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', logoBase64: 7 }),
|
||||
).to.throw(ValidationError, 'Field `logoBase64` must be of type string')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', links: 'test' }),
|
||||
).to.throw(ValidationError, 'Field `links` must be an array of strings')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', links: [1] }),
|
||||
).to.throw(ValidationError, 'Field `links` must be an array of strings')
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', links: ['1', '2', '3'] }),
|
||||
).to.throw(
|
||||
ValidationError,
|
||||
'Field `links` must not have more than 2 elements',
|
||||
)
|
||||
expect(() =>
|
||||
makeBadge({ label: 'build', message: 'passed', format: 'png' }),
|
||||
).to.throw(ValidationError, "Unexpected field 'format'")
|
||||
|
||||
@@ -17,7 +17,6 @@ module.exports = function makeBadge({
|
||||
color,
|
||||
labelColor,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoSize,
|
||||
logoWidth,
|
||||
links = ['', ''],
|
||||
@@ -55,7 +54,6 @@ module.exports = function makeBadge({
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoSize,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "3.3.1",
|
||||
"version": "4.0.0",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
|
||||
@@ -66,7 +66,6 @@ const serviceDataSchema = Joi.object({
|
||||
logoSvg: Joi.string(),
|
||||
logoColor: optionalStringWhenNamedLogoPresent,
|
||||
logoWidth: optionalNumberWhenAnyLogoPresent,
|
||||
logoPosition: optionalNumberWhenAnyLogoPresent,
|
||||
cacheSeconds: Joi.number().integer().min(0),
|
||||
style: Joi.string(),
|
||||
})
|
||||
|
||||
@@ -378,7 +378,6 @@ describe('BaseService', function () {
|
||||
logo: undefined,
|
||||
logoWidth: undefined,
|
||||
logoSize: undefined,
|
||||
logoPosition: undefined,
|
||||
links: [],
|
||||
labelColor: undefined,
|
||||
cacheLengthSeconds: undefined,
|
||||
|
||||
@@ -23,9 +23,8 @@ import toArray from './to-array.js'
|
||||
// base64-encoded logos). Otherwise the default color is used. If the color
|
||||
// is specified for a multicolor Shield logo, the named logo will be used and
|
||||
// colored. The appearance of the logo can be customized using `logoWidth`,
|
||||
// and in the case of the popout badge, `logoPosition`. When `?logo=` is
|
||||
// specified, any logo-related parameters specified dynamically by the
|
||||
// service, or by default in the service, are ignored.
|
||||
// When `?logo=` is specified, any logo-related parameters specified
|
||||
// dynamically by the service, or by default in the service, are ignored.
|
||||
// 2. The second precedence is the dynamic logo returned by a service. This is
|
||||
// used only by the Endpoint badge. The `logoColor` can be overridden by the
|
||||
// query string.
|
||||
@@ -56,7 +55,6 @@ export default function coalesceBadge(
|
||||
} = overrides
|
||||
let {
|
||||
logoWidth: overrideLogoWidth,
|
||||
logoPosition: overrideLogoPosition,
|
||||
logoSize: overrideLogoSize,
|
||||
color: overrideColor,
|
||||
labelColor: overrideLabelColor,
|
||||
@@ -78,7 +76,6 @@ export default function coalesceBadge(
|
||||
overrideLabelColor = `${overrideLabelColor}`
|
||||
}
|
||||
overrideLogoWidth = +overrideLogoWidth || undefined
|
||||
overrideLogoPosition = +overrideLogoPosition || undefined
|
||||
|
||||
const {
|
||||
isError,
|
||||
@@ -91,7 +88,6 @@ export default function coalesceBadge(
|
||||
logoColor: serviceLogoColor,
|
||||
logoSize: serviceLogoSize,
|
||||
logoWidth: serviceLogoWidth,
|
||||
logoPosition: serviceLogoPosition,
|
||||
link: serviceLink,
|
||||
cacheSeconds: serviceCacheSeconds,
|
||||
style: serviceStyle,
|
||||
@@ -122,12 +118,7 @@ export default function coalesceBadge(
|
||||
style = 'flat'
|
||||
}
|
||||
|
||||
let namedLogo,
|
||||
namedLogoColor,
|
||||
logoSize,
|
||||
logoWidth,
|
||||
logoPosition,
|
||||
logoSvgBase64
|
||||
let namedLogo, namedLogoColor, logoSize, logoWidth, logoSvgBase64
|
||||
if (overrideLogo) {
|
||||
// `?logo=` could be a named logo or encoded svg.
|
||||
const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrideLogo)
|
||||
@@ -143,7 +134,6 @@ export default function coalesceBadge(
|
||||
// original width or position.
|
||||
logoSize = overrideLogoSize
|
||||
logoWidth = overrideLogoWidth
|
||||
logoPosition = overrideLogoPosition
|
||||
} else {
|
||||
if (serviceLogoSvg) {
|
||||
logoSvgBase64 = svg2base64(serviceLogoSvg)
|
||||
@@ -156,7 +146,6 @@ export default function coalesceBadge(
|
||||
}
|
||||
logoSize = coalesce(overrideLogoSize, serviceLogoSize)
|
||||
logoWidth = coalesce(overrideLogoWidth, serviceLogoWidth)
|
||||
logoPosition = coalesce(overrideLogoPosition, serviceLogoPosition)
|
||||
}
|
||||
if (namedLogo) {
|
||||
const iconSize = getIconSize(String(namedLogo).toLowerCase())
|
||||
@@ -195,7 +184,6 @@ export default function coalesceBadge(
|
||||
namedLogo,
|
||||
logo: logoSvgBase64,
|
||||
logoWidth,
|
||||
logoPosition,
|
||||
logoSize,
|
||||
links: toArray(overrideLink || serviceLink),
|
||||
cacheLengthSeconds: coalesce(serviceCacheSeconds, defaultCacheSeconds),
|
||||
|
||||
@@ -195,14 +195,13 @@ describe('coalesceBadge', function () {
|
||||
).to.equal(getSimpleIcon({ name: 'npm', color: 'blue' })).and.not.be.empty
|
||||
})
|
||||
|
||||
it("when the logo is overridden, it ignores the service's logo color, position, and width", function () {
|
||||
it("when the logo is overridden, it ignores the service's logo color and width", function () {
|
||||
expect(
|
||||
coalesceBadge(
|
||||
{ logo: 'npm' },
|
||||
{
|
||||
namedLogo: 'appveyor',
|
||||
logoColor: 'red',
|
||||
logoPosition: -3,
|
||||
logoWidth: 100,
|
||||
},
|
||||
{},
|
||||
@@ -288,20 +287,6 @@ describe('coalesceBadge', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logo position', function () {
|
||||
it('overrides the logoPosition', function () {
|
||||
expect(coalesceBadge({ logoPosition: -10 }, {}, {})).to.include({
|
||||
logoPosition: -10,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies the logo position', function () {
|
||||
expect(
|
||||
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {}),
|
||||
).to.include({ logoPosition: -10 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Links', function () {
|
||||
it('overrides the links', function () {
|
||||
expect(
|
||||
|
||||
@@ -11,7 +11,6 @@ const globalQueryParams = new Set([
|
||||
'link',
|
||||
'logo',
|
||||
'logoColor',
|
||||
'logoPosition',
|
||||
'logoSize',
|
||||
'logoWidth',
|
||||
'link',
|
||||
|
||||
578
package-lock.json
generated
578
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -23,7 +23,7 @@
|
||||
"dependencies": {
|
||||
"@renovatebot/pep440": "^3.0.20",
|
||||
"@renovatebot/ruby-semver": "^3.0.23",
|
||||
"@sentry/node": "^8.9.2",
|
||||
"@sentry/node": "^8.13.0",
|
||||
"@shields_io/camp": "^18.1.2",
|
||||
"@xmldom/xmldom": "0.8.10",
|
||||
"badge-maker": "file:badge-maker",
|
||||
@@ -32,19 +32,19 @@
|
||||
"chalk": "^5.3.0",
|
||||
"check-node-version": "^4.2.1",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.11",
|
||||
"config": "^3.3.12",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.11",
|
||||
"decamelize": "^3.2.0",
|
||||
"emojic": "^1.1.17",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^4.4.0",
|
||||
"glob": "^10.4.1",
|
||||
"glob": "^10.4.2",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^14.4.1",
|
||||
"graphql": "16.8.2",
|
||||
"graphql": "16.9.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"joi": "17.13.1",
|
||||
"joi": "17.13.3",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonpath": "~1.1.1",
|
||||
@@ -53,18 +53,18 @@
|
||||
"lodash.times": "^4.3.2",
|
||||
"matcher": "^5.0.0",
|
||||
"node-env-flag": "^0.1.0",
|
||||
"node-pg-migrate": "^7.4.0",
|
||||
"node-pg-migrate": "^7.5.2",
|
||||
"parse-link-header": "^2.0.0",
|
||||
"path-to-regexp": "^6.2.2",
|
||||
"pg": "^8.12.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^15.1.2",
|
||||
"prom-client": "^15.1.3",
|
||||
"qs": "^6.12.1",
|
||||
"query-string": "^9.0.0",
|
||||
"semver": "~7.6.2",
|
||||
"simple-icons": "12.3.0",
|
||||
"smol-toml": "1.2.1",
|
||||
"simple-icons": "12.4.0",
|
||||
"smol-toml": "1.2.2",
|
||||
"svg-path-commander": "^2.0.9",
|
||||
"webextension-store-meta": "^1.2.3",
|
||||
"xpath": "~0.0.34"
|
||||
@@ -147,9 +147,9 @@
|
||||
"devDependencies": {
|
||||
"@docusaurus/core": "^3.4.0",
|
||||
"@docusaurus/preset-classic": "^3.4.0",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.44.0",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.44.2",
|
||||
"@mdx-js/react": "^3.0.1",
|
||||
"@typescript-eslint/parser": "^7.13.0",
|
||||
"@typescript-eslint/parser": "^7.14.1",
|
||||
"c8": "^10.1.2",
|
||||
"caller": "^1.1.0",
|
||||
"chai": "^4.4.1",
|
||||
@@ -159,9 +159,9 @@
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clsx": "^2.1.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"cypress": "^13.11.0",
|
||||
"cypress": "^13.12.0",
|
||||
"cypress-wait-for-stable-dom": "^0.1.0",
|
||||
"danger": "^12.3.1",
|
||||
"danger": "^12.3.3",
|
||||
"deepmerge": "^4.3.1",
|
||||
"docusaurus-preset-openapi": "0.7.5",
|
||||
"eslint": "8.57.0",
|
||||
@@ -173,12 +173,12 @@
|
||||
"eslint-plugin-cypress": "^3.3.0",
|
||||
"eslint-plugin-icedfrisby": "^0.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsdoc": "^48.2.12",
|
||||
"eslint-plugin-jsdoc": "^48.5.0",
|
||||
"eslint-plugin-mocha": "^10.4.3",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "6.2.0",
|
||||
"eslint-plugin-react": "^7.34.2",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-sort-class-members": "^1.20.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -189,13 +189,13 @@
|
||||
"lint-staged": "^15.2.7",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.8",
|
||||
"mocha": "^10.4.0",
|
||||
"mocha": "^10.5.2",
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.2.1",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.5.4",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"nodemon": "^3.1.3",
|
||||
"node-mocks-http": "^1.15.0",
|
||||
"nodemon": "^3.1.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"open-cli": "^8.0.0",
|
||||
"portfinder": "^1.0.32",
|
||||
@@ -211,7 +211,7 @@
|
||||
"sinon-chai": "^3.7.0",
|
||||
"snap-shot-it": "^7.9.10",
|
||||
"start-server-and-test": "2.0.4",
|
||||
"tsd": "^0.31.0",
|
||||
"tsd": "^0.31.1",
|
||||
"url": "^0.11.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -20,6 +20,7 @@ const aurSchema = Joi.object({
|
||||
Joi.object({
|
||||
License: Joi.array().items(Joi.string().required()).allow(null),
|
||||
NumVotes: nonNegativeInteger,
|
||||
Popularity: Joi.number().precision(2).min(0).required(),
|
||||
Version: Joi.string().required(),
|
||||
OutOfDate: nonNegativeInteger.allow(null),
|
||||
Maintainer: Joi.string().required().allow(null),
|
||||
@@ -118,6 +119,36 @@ class AurVotes extends BaseAurService {
|
||||
}
|
||||
}
|
||||
|
||||
class AurPopularity extends BaseAurService {
|
||||
static category = 'rating'
|
||||
|
||||
static route = { base: 'aur/popularity', pattern: ':packageName' }
|
||||
|
||||
static openApi = {
|
||||
'/aur/popularity/{packageName}': {
|
||||
get: {
|
||||
summary: 'AUR Popularity',
|
||||
description: 'Arch linux User Repository Popularity',
|
||||
parameters: pathParams({ name: 'packageName', example: 'dropbox' }),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'popularity' }
|
||||
|
||||
static render({ popularity }) {
|
||||
return {
|
||||
message: popularity,
|
||||
color: floorCountColor(popularity, 0.5, 2.5, 5),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ packageName }) {
|
||||
const json = await this.fetch({ packageName })
|
||||
return this.constructor.render({ popularity: json.results[0].Popularity })
|
||||
}
|
||||
}
|
||||
|
||||
class AurVersion extends BaseAurService {
|
||||
static category = 'version'
|
||||
static route = { base: 'aur/version', pattern: ':packageName' }
|
||||
@@ -224,4 +255,11 @@ class AurLastModified extends BaseAurService {
|
||||
}
|
||||
}
|
||||
|
||||
export { AurLicense, AurVersion, AurVotes, AurMaintainer, AurLastModified }
|
||||
export {
|
||||
AurLicense,
|
||||
AurVersion,
|
||||
AurVotes,
|
||||
AurPopularity,
|
||||
AurMaintainer,
|
||||
AurLastModified,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Joi from 'joi'
|
||||
import { ServiceTester } from '../tester.js'
|
||||
import {
|
||||
isVPlusDottedVersionNClausesWithOptionalSuffix,
|
||||
@@ -35,6 +36,19 @@ t.create('votes (not found)')
|
||||
.get('/votes/not-a-package.json')
|
||||
.expectBadge({ label: 'votes', message: 'package not found' })
|
||||
|
||||
// popularity tests
|
||||
|
||||
t.create('popularity (valid)')
|
||||
.get('/popularity/google-chrome.json')
|
||||
.expectBadge({
|
||||
label: 'popularity',
|
||||
message: Joi.number().precision(2).required(),
|
||||
})
|
||||
|
||||
t.create('popularity (not found)')
|
||||
.get('/popularity/not-a-package.json')
|
||||
.expectBadge({ label: 'popularity', message: 'package not found' })
|
||||
|
||||
// license tests
|
||||
|
||||
t.create('license (valid)')
|
||||
@@ -57,6 +71,7 @@ t.create('license (no license)')
|
||||
{
|
||||
License: null,
|
||||
NumVotes: 1,
|
||||
Popularity: 0,
|
||||
Version: '1',
|
||||
OutOfDate: null,
|
||||
Maintainer: null,
|
||||
|
||||
@@ -36,9 +36,14 @@ const endpointSchema = Joi.object({
|
||||
logoSvg: Joi.string(),
|
||||
logoColor: optionalStringWhenNamedLogoPresent,
|
||||
logoWidth: optionalNumberWhenAnyLogoPresent,
|
||||
logoPosition: optionalNumberWhenAnyLogoPresent,
|
||||
style: Joi.string(),
|
||||
cacheSeconds: Joi.number().integer().min(0),
|
||||
/*
|
||||
Retained for legacy compatibility
|
||||
Although this does nothing,
|
||||
passing it should not throw an error
|
||||
*/
|
||||
logoPosition: optionalNumberWhenAnyLogoPresent,
|
||||
})
|
||||
// `namedLogo` or `logoSvg`; not both.
|
||||
.oxor('namedLogo', 'logoSvg')
|
||||
|
||||
@@ -102,13 +102,6 @@ The endpoint badge takes a single required query param: <code>url</code>, which
|
||||
the query string.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>logoPosition</code></td>
|
||||
<td>
|
||||
Default: none. Same meaning as the query string. Can be overridden by
|
||||
the query string.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>style</code></td>
|
||||
<td>
|
||||
@@ -157,7 +150,6 @@ export default class Endpoint extends BaseJsonService {
|
||||
logoSvg,
|
||||
logoColor,
|
||||
logoWidth,
|
||||
logoPosition,
|
||||
style,
|
||||
cacheSeconds,
|
||||
}) {
|
||||
@@ -171,7 +163,6 @@ export default class Endpoint extends BaseJsonService {
|
||||
logoSvg,
|
||||
logoColor,
|
||||
logoWidth,
|
||||
logoPosition,
|
||||
style,
|
||||
// don't allow the user to set cacheSeconds any shorter than this._cacheLength
|
||||
cacheSeconds: Math.max(
|
||||
|
||||
@@ -119,6 +119,24 @@ t.create('logoWidth')
|
||||
logoWidth: 30,
|
||||
})
|
||||
|
||||
// The logoPosition param was removed, but passing it should not
|
||||
// throw a validation error. It should just do nothing.
|
||||
t.create('logoPosition')
|
||||
.get('.json?url=https://example.com/badge')
|
||||
.intercept(nock =>
|
||||
nock('https://example.com/').get('/badge').reply(200, {
|
||||
schemaVersion: 1,
|
||||
label: 'hey',
|
||||
message: 'yo',
|
||||
logoSvg,
|
||||
logoPosition: 30,
|
||||
}),
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'hey',
|
||||
message: 'yo',
|
||||
})
|
||||
|
||||
t.create('Invalid schema')
|
||||
.get('.json?url=https://example.com/badge')
|
||||
.intercept(nock =>
|
||||
|
||||
70
services/mbin/mbin.service.js
Normal file
70
services/mbin/mbin.service.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import Joi from 'joi'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { BaseJsonService, InvalidParameter, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
subscriptionsCount: Joi.number().required(),
|
||||
}).required()
|
||||
|
||||
export default class Mbin extends BaseJsonService {
|
||||
static category = 'social'
|
||||
|
||||
static route = {
|
||||
base: 'mbin',
|
||||
pattern: ':magazine',
|
||||
}
|
||||
|
||||
static openApi = {
|
||||
'/mbin/{magazine}': {
|
||||
get: {
|
||||
summary: 'Mbin',
|
||||
description:
|
||||
'Mbin is a fork of Kbin, a content aggregator for the Fediverse.',
|
||||
parameters: pathParams({
|
||||
name: 'magazine',
|
||||
description: 'The magazine to query. This is CASE SENSITIVE.',
|
||||
example: 'teletext@fedia.io',
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'magazine', namedLogo: 'activitypub' }
|
||||
|
||||
static render({ magazine, members }) {
|
||||
return {
|
||||
label: `subscribe to ${magazine}`,
|
||||
message: metric(members),
|
||||
style: 'social',
|
||||
color: 'brightgreen',
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ magazine }) {
|
||||
const splitAlias = magazine.split('@')
|
||||
// The magazine will be in the format of 'magazine@server'
|
||||
if (splitAlias.length !== 2) {
|
||||
throw new InvalidParameter({
|
||||
prettyMessage: 'invalid magazine',
|
||||
})
|
||||
}
|
||||
|
||||
const mag = splitAlias[0]
|
||||
const host = splitAlias[1]
|
||||
|
||||
const data = await this._requestJson({
|
||||
url: `https://${host}/api/magazine/name/${mag}`,
|
||||
schema,
|
||||
httpErrors: {
|
||||
404: 'magazine not found',
|
||||
},
|
||||
})
|
||||
|
||||
return data.subscriptionsCount
|
||||
}
|
||||
|
||||
async handle({ magazine }) {
|
||||
const members = await this.fetch({ magazine })
|
||||
return this.constructor.render({ magazine, members })
|
||||
}
|
||||
}
|
||||
43
services/mbin/mbin.tester.js
Normal file
43
services/mbin/mbin.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('get magazine subscribers')
|
||||
.get('/magazine@instance.tld.json')
|
||||
.intercept(nock =>
|
||||
nock('https://instance.tld/')
|
||||
.get('/api/magazine/name/magazine')
|
||||
.reply(
|
||||
200,
|
||||
JSON.stringify({
|
||||
subscriptionsCount: 42,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'subscribe to magazine@instance.tld',
|
||||
message: '42',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
|
||||
t.create('unknown community')
|
||||
.get('/01J12N2ETYG3W5B6G8Y11F5EXG@fedia.io.json')
|
||||
.expectBadge({
|
||||
label: 'magazine',
|
||||
message: 'magazine not found',
|
||||
color: 'red',
|
||||
})
|
||||
|
||||
t.create('invalid magazine').get('/magazine.invalid.json').expectBadge({
|
||||
label: 'magazine',
|
||||
message: 'invalid magazine',
|
||||
color: 'red',
|
||||
})
|
||||
|
||||
t.create('test on real mbin magazine for API compliance')
|
||||
.get('/teletext@fedia.io.json')
|
||||
.expectBadge({
|
||||
label: 'subscribe to teletext@fedia.io',
|
||||
message: isMetric,
|
||||
color: 'brightgreen',
|
||||
})
|
||||
@@ -21,7 +21,7 @@ const packageDataSchema = Joi.object({
|
||||
maintainers: Joi.array()
|
||||
// We don't need the keys here, just the length.
|
||||
.items(Joi.object({}))
|
||||
.required(),
|
||||
.default([]),
|
||||
types: Joi.string(),
|
||||
// `typings` is an alias for `types` and often used
|
||||
// https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package
|
||||
|
||||
@@ -16,3 +16,12 @@ t.create('contributor count for unknown package')
|
||||
label: 'npm collaborators',
|
||||
message: 'package not found',
|
||||
})
|
||||
|
||||
t.create('contributor count for package package without a maintainers property')
|
||||
.get('/package-without-maintainers.json')
|
||||
.intercept(nock =>
|
||||
nock('https://registry.npmjs.org')
|
||||
.get('/package-without-maintainers/latest')
|
||||
.reply(200, {}),
|
||||
)
|
||||
.expectBadge({ label: 'npm collaborators', message: '0' })
|
||||
|
||||
Reference in New Issue
Block a user