Compare commits

...

117 Commits

Author SHA1 Message Date
github-actions[bot]
2a20f813df Changelog for Release server-2024-12-01 (#10716)
* Update Changelog

* Update CHANGELOG.md

---------

Co-authored-by: release[bot] <actions@users.noreply.github.com>
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2024-12-01 12:39:41 +00:00
dependabot[bot]
3b465533fd chore(deps): bump simple-icons from 13.17.0 to 13.18.0 (#10712)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.17.0 to 13.18.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.17.0...13.18.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:44:01 +00:00
dependabot[bot]
df719ea2db chore(deps-dev): bump @easyops-cn/docusaurus-search-local (#10711)
Bumps [@easyops-cn/docusaurus-search-local](https://github.com/easyops-cn/docusaurus-search-local/tree/HEAD/packages/docusaurus-search-local) from 0.45.0 to 0.46.1.
- [Release notes](https://github.com/easyops-cn/docusaurus-search-local/releases)
- [Commits](https://github.com/easyops-cn/docusaurus-search-local/commits/v0.46.1/packages/docusaurus-search-local)

---
updated-dependencies:
- dependency-name: "@easyops-cn/docusaurus-search-local"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:43:57 +00:00
dependabot[bot]
9ee8ee8cbf chore(deps): bump got from 14.4.4 to 14.4.5 (#10713)
Bumps [got](https://github.com/sindresorhus/got) from 14.4.4 to 14.4.5.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v14.4.4...v14.4.5)

---
updated-dependencies:
- dependency-name: got
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:32:40 +00:00
dependabot[bot]
def3007602 chore(deps): bump @sentry/node from 8.40.0 to 8.41.0 (#10710)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.40.0 to 8.41.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.40.0...8.41.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:32:19 +00:00
dependabot[bot]
7d7f70b4b5 chore(deps-dev): bump @typescript-eslint/parser from 8.15.0 to 8.16.0 (#10709)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.15.0 to 8.16.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.16.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:30:50 +00:00
dependabot[bot]
2ded4aa7b6 chore(deps-dev): bump prettier from 3.3.3 to 3.4.1 (#10714)
Bumps [prettier](https://github.com/prettier/prettier) from 3.3.3 to 3.4.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.3...3.4.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:30:25 +00:00
dependabot[bot]
da2745d523 chore(deps-dev): bump chai-as-promised from 8.0.0 to 8.0.1 (#10707)
Bumps [chai-as-promised](https://github.com/chaijs/chai-as-promised) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/chaijs/chai-as-promised/releases)
- [Commits](https://github.com/chaijs/chai-as-promised/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: chai-as-promised
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:30:02 +00:00
dependabot[bot]
1338bf3192 chore(deps-dev): bump eslint-plugin-jsdoc from 50.5.0 to 50.6.0 (#10706)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 50.5.0 to 50.6.0.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.5.0...v50.6.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 13:29:37 +00:00
jNullj
e8e253d21e remove obsolete [CratesSize] test for null size (#10688)
* remove obsolete test for version without size

changes upstream removed all null crate_size
see also rust-lang/crates.io#9926

* remove null handling in CratesSize service
2024-11-26 21:01:29 +00:00
jNullj
60aa530966 fix [DockerVersion] service test (#10690)
* Update docker version tester to check Python instead of Memcached

Memcached tags don't follow semver, might fail depending on which  tag was last added.
Python seems to follow semver for all tags.
fixes #10689

* Update docker version tester to check docker-dev instead of python

its a depricated image and is not expected to change tags
tag is semver

* Update docker version tester to check example-voting-app-vote

deprecated yet by docker, i think we can count on that to be stable
2024-11-26 20:33:46 +00:00
dependabot[bot]
9447077c08 chore(deps): bump @sentry/node from 8.38.0 to 8.40.0 (#10691)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.38.0 to 8.40.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.38.0...8.40.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 17:23:11 +00:00
dependabot[bot]
b60d738999 chore(deps-dev): bump cypress from 13.15.2 to 13.16.0 (#10695)
Bumps [cypress](https://github.com/cypress-io/cypress) from 13.15.2 to 13.16.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/cypress/compare/v13.15.2...v13.16.0)

---
updated-dependencies:
- dependency-name: cypress
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 16:59:48 +00:00
dependabot[bot]
85fb206c8b chore(deps-dev): bump @typescript-eslint/parser from 8.14.0 to 8.15.0 (#10696)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.14.0 to 8.15.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.15.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 16:38:42 +00:00
dependabot[bot]
d1210e2311 chore(deps): bump jsonpath-plus from 10.1.0 to 10.2.0 (#10693)
Bumps [jsonpath-plus](https://github.com/s3u/JSONPath) from 10.1.0 to 10.2.0.
- [Release notes](https://github.com/s3u/JSONPath/releases)
- [Changelog](https://github.com/JSONPath-Plus/JSONPath/blob/main/CHANGES.md)
- [Commits](https://github.com/s3u/JSONPath/compare/v10.1.0...v10.2.0)

---
updated-dependencies:
- dependency-name: jsonpath-plus
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 16:13:16 +00:00
dependabot[bot]
85dd5a599f chore(deps): bump qs from 6.13.0 to 6.13.1 (#10698)
Bumps [qs](https://github.com/ljharb/qs) from 6.13.0 to 6.13.1.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.13.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 15:36:50 +00:00
dependabot[bot]
e108e40930 chore(deps): bump simple-icons from 13.16.0 to 13.17.0 (#10697)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.16.0 to 13.17.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.16.0...13.17.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 15:23:43 +00:00
dependabot[bot]
eaa4317039 chore(deps): bump smol-toml from 1.3.0 to 1.3.1 (#10694)
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/commits)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 14:54:08 +00:00
chris48s
5cdef88bcc Add renderDateBadge helper; affects [aur BitbucketLastCommit chrome date eclipse factorio galaxytoolshed GiteaLastCommit GistLastCommit GithubCreatedAt GithubHacktoberfest GithubIssueDetail GithubLastCommit GithubReleaseDate GitlabLastCommit maven npm openvsx snapcraft SourceforgeLastCommit steam vaadin visualstudio wordpress] (#10682)
* add and consistently use parseDate and renderDateBadge helpers

also move

- age
- formatDate
- formatRelativeDate

to date.js

* fix bug in wordpress last update badge

* validate in formatDate() and age()

it is going to be unlikely we'll invoke either of these
directly now, but lets calidate here too

* remove unusued imports

* reverse colours for galaxy toolshed
2024-11-17 13:15:28 +00:00
chris48s
4132ca2e7e Add blog post about token pool, improve 'authorise our app' CTA (#10683)
* Add blog post about token pool, improve 'authorise our app' CTA

* simplify first sentence
2024-11-16 16:50:04 +00:00
dependabot[bot]
e1541acc11 chore(deps): bump @renovatebot/ruby-semver from 3.0.23 to 4.0.0 (#10677)
Bumps [@renovatebot/ruby-semver](https://github.com/renovatebot/ruby-semver) from 3.0.23 to 4.0.0.
- [Release notes](https://github.com/renovatebot/ruby-semver/releases)
- [Changelog](https://github.com/renovatebot/ruby-semver/blob/main/.releaserc.json)
- [Commits](https://github.com/renovatebot/ruby-semver/compare/3.0.23...4.0.0)

---
updated-dependencies:
- dependency-name: "@renovatebot/ruby-semver"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 21:26:36 +00:00
dependabot[bot]
f5dd749ae0 chore(deps-dev): bump eslint-plugin-jsdoc from 50.4.3 to 50.5.0 (#10680)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 50.4.3 to 50.5.0.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.4.3...v50.5.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 21:11:38 +00:00
dependabot[bot]
ef17850f7e chore(deps-dev): bump @typescript-eslint/parser from 8.13.0 to 8.14.0 (#10679)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.13.0 to 8.14.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.14.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 21:11:20 +00:00
dependabot[bot]
5bde4266c8 chore(deps): bump node-pg-migrate from 7.7.1 to 7.8.0 (#10676)
Bumps [node-pg-migrate](https://github.com/salsita/node-pg-migrate) from 7.7.1 to 7.8.0.
- [Release notes](https://github.com/salsita/node-pg-migrate/releases)
- [Changelog](https://github.com/salsita/node-pg-migrate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/salsita/node-pg-migrate/compare/v7.7.1...v7.8.0)

---
updated-dependencies:
- dependency-name: node-pg-migrate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 21:10:57 +00:00
dependabot[bot]
e57edb42bd chore(deps): bump @sentry/node from 8.37.1 to 8.38.0 (#10674)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.37.1 to 8.38.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.37.1...8.38.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 21:10:29 +00:00
dependabot[bot]
69f9251e1c chore(deps-dev): bump nock from 13.5.5 to 13.5.6 (#10675)
Bumps [nock](https://github.com/nock/nock) from 13.5.5 to 13.5.6.
- [Release notes](https://github.com/nock/nock/releases)
- [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nock/nock/compare/v13.5.5...v13.5.6)

---
updated-dependencies:
- dependency-name: nock
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 21:09:36 +00:00
dependabot[bot]
39d3fd332b chore(deps): bump @renovatebot/pep440 from 3.0.20 to 4.0.1 (#10678)
Bumps [@renovatebot/pep440](https://github.com/renovatebot/pep440) from 3.0.20 to 4.0.1.
- [Release notes](https://github.com/renovatebot/pep440/releases)
- [Changelog](https://github.com/renovatebot/pep440/blob/main/.releaserc.json)
- [Commits](https://github.com/renovatebot/pep440/compare/3.0.20...4.0.1)

---
updated-dependencies:
- dependency-name: "@renovatebot/pep440"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 19:36:10 +00:00
chris48s
58f5b99fea remove 'operations' section from README (#10673) 2024-11-14 20:20:34 +00:00
chris48s
20959b15db add jNullj, remove inactive maintainers (#10672) 2024-11-14 18:21:04 +00:00
chris48s
cbb7ab5e8b reduce overhead of NPM Last Update badge; test [npm] (#10666)
* reduce overhead of [NpmLastUpdate] badge

* use buildRoute for version without tag
2024-11-13 19:02:48 +00:00
chris48s
2bd926e65f rename postfix param to suffix (#10667) 2024-11-11 19:34:08 +00:00
jNullj
04638ab0ee Refactor - use renderVersionBadge - part 4 [githubrelease githubtag] (#10656)
* feat: add forcePrerelease option to renderVersionBadge function

Sometimes API would indicate if a version is pre-release while the version number does not have to be semantically a prerelease like in github-release service.
We don't use a isPrerelease that can also force a non-preleases as we trust here developer semantic over API tagging.

* refactor: GithubRelease to use renderVersionBadge

* refactor: GithubTag use renderVersionBadge

* refactor: change forcePrerelease to isPrerelease
2024-11-11 19:16:14 +00:00
jNullj
4d203e1937 fix [githubpipenv] service tests (#10658)
* fix failing test 'Locked version of VCS dependency'

replaced tested package which is missing with another example

* Add regex validator for commit hash format

* fix vcs dependency test to use pipenv's pypiserver
2024-11-11 19:15:35 +00:00
dependabot[bot]
6219c6da82 chore(deps-dev): bump cypress from 13.15.1 to 13.15.2 (#10661)
Bumps [cypress](https://github.com/cypress-io/cypress) from 13.15.1 to 13.15.2.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/cypress/compare/v13.15.1...v13.15.2)

---
updated-dependencies:
- dependency-name: cypress
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 13:42:45 +00:00
dependabot[bot]
a16cf24b52 chore(deps): bump simple-icons from 13.15.0 to 13.16.0 (#10660)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.15.0 to 13.16.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.15.0...13.16.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 13:36:52 +00:00
dependabot[bot]
d3a1ef2ff7 chore(deps): bump @sentry/node from 8.36.0 to 8.37.1 (#10665)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.36.0 to 8.37.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.36.0...8.37.1)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 13:28:43 +00:00
dependabot[bot]
32b1e341d7 chore(deps-dev): bump @typescript-eslint/parser from 8.12.2 to 8.13.0 (#10664)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.12.2 to 8.13.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.13.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 13:28:23 +00:00
dependabot[bot]
2d5b72b207 chore(deps-dev): bump concurrently from 9.0.1 to 9.1.0 (#10663)
Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 9.0.1 to 9.1.0.
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v9.0.1...v9.1.0)

---
updated-dependencies:
- dependency-name: concurrently
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 13:28:08 +00:00
dependabot[bot]
0c4fed4dc6 chore(deps): bump got from 14.4.3 to 14.4.4 (#10662)
Bumps [got](https://github.com/sindresorhus/got) from 14.4.3 to 14.4.4.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v14.4.3...v14.4.4)

---
updated-dependencies:
- dependency-name: got
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 13:27:48 +00:00
jNullj
8af909d118 fix [githubmanifest] service tests (#10657)
* Change badge label from 'version' to 'manifest' in GitHub manifest tests

* revert version label for failed paths
2024-11-10 13:09:27 +00:00
anatawa12
00d72da97e add [WingetVersion] Badge (#10245)
* feat: add winget version badge

* chore: accept dotted path instead of slashed

* test: add test for winget-version

* fix: remove debug code

* chore: use winget-specific version compare algorithm

* fix: support latest and unknown

* fix(winget/version): trailing '.0' handling is incorrect

* fix(winget/version): latest returns last newest version instead of the first newest version

* fix(winget/version): confusing subpackage and version name

* fix(winget/version): example for latest is incorrect

* add a couple of extra test cases for latest()

---------

Co-authored-by: chris48s <git@chris-shaw.dev>
2024-11-04 19:05:32 +00:00
usr3
4ec62fa445 Fix broken URL for pingpong.one (#10655)
Fix broken URL for https://pingpong.one for uptime status monitoring.
2024-11-04 11:40:03 +00:00
Pierre-Yves Bigourdan
57520a974f Remove [Nuget MyGet] color tests (#10654) 2024-11-02 18:34:25 +00:00
Ambati Mohan Kumar
8c7872a666 [npm] - Last update badge added (#10641)
* Added npm last update badge

* extended NpmBase class instead of BaseJsonService.

* added scoped packages to last update.

* introduced additionalQueryParamSchema

this is to add other query params schema, other than the one present in NpmBase.

* removed version query param

* in absence of modified date, it'll fetch created.

* removed version query param.

* added dist-tags.

* Update services/npm/npm-last-update.service.js

Co-authored-by: jNullj <15849761+jNullj@users.noreply.github.com>

* refactored handle method for dist-tags.

* Update services/npm/npm-last-update.service.js

Co-authored-by: chris48s <chris48s@users.noreply.github.com>

* added date validation check.

* added date validation check.

* added date validation check.

---------

Co-authored-by: jNullj <15849761+jNullj@users.noreply.github.com>
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2024-11-02 17:37:07 +00:00
Pierre-Yves Bigourdan
ad82f7647a Add YouTube-specific privacy notes (#10646) 2024-11-02 11:20:57 +00:00
github-actions[bot]
43940aeeae Changelog for Release server-2024-11-02 (#10653)
* Update Changelog

* Update CHANGELOG.md

---------

Co-authored-by: release[bot] <actions@users.noreply.github.com>
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2024-11-02 10:32:32 +00:00
dependabot[bot]
d05172557d chore(deps): bump simple-icons from 13.14.1 to 13.15.0 (#10651)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.14.1 to 13.15.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.14.1...13.15.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 10:00:46 +00:00
dependabot[bot]
4aa9aa339e chore(deps): bump @xmldom/xmldom from 0.9.4 to 0.9.5 (#10652)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.9.4 to 0.9.5.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.9.4...0.9.5)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 09:54:31 +00:00
dependabot[bot]
91e4f4a78a chore(deps-dev): bump @typescript-eslint/parser from 8.11.0 to 8.12.2 (#10650)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.11.0 to 8.12.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.12.2/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 09:51:55 +00:00
dependabot[bot]
383812e160 chore(deps): bump @sentry/node from 8.35.0 to 8.36.0 (#10649)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.35.0 to 8.36.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.35.0...8.36.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 09:51:34 +00:00
dependabot[bot]
20ab5255cd chore(deps-dev): bump mocha from 10.7.3 to 10.8.2 (#10648)
Bumps [mocha](https://github.com/mochajs/mocha) from 10.7.3 to 10.8.2.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.7.3...v10.8.2)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 09:51:09 +00:00
chris48s
20ae47e5ff cleanly handle null or undefined result from jsonpath-plus (#10645) 2024-11-02 09:50:34 +00:00
chris48s
62430392f8 add content security policy header to SVG responses (#10642) 2024-10-27 15:09:26 +00:00
jNullj
cc90c190f2 Refactor - use renderVersionBadge - part 3 [luarocks gitlab nuget feedz] (#10630)
* Refactor luarocks to use renderVersionBadge

Loarocks does not appear to have version conventions and there are no issues in history that require usage of existing color usage.
For better consistency use color scheme as the rest of the badges.

Also add scm and cvs as preview in renderVersionBadge

* add missing test for version color formatter

* refactor nuget to use renderVersionBadge

* Refactor GitlabTag to use renderVersionBadge

* add comment about non-standard render of coljarsVersion

* Refactor FeedzVersionService to use renderVersionBadge from version.js

* Refactor nuget.tester.js to remove unnecessary version tests

* add missing label in gitlab-tag.spec
2024-10-27 13:35:03 +00:00
dependabot[bot]
48e25771ee chore(deps-dev): bump @mdx-js/react from 3.0.1 to 3.1.0 (#10631)
Bumps [@mdx-js/react](https://github.com/mdx-js/mdx/tree/HEAD/packages/react) from 3.0.1 to 3.1.0.
- [Release notes](https://github.com/mdx-js/mdx/releases)
- [Changelog](https://github.com/mdx-js/mdx/blob/main/changelog.md)
- [Commits](https://github.com/mdx-js/mdx/commits/3.1.0/packages/react)

---
updated-dependencies:
- dependency-name: "@mdx-js/react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:34:06 +00:00
dependabot[bot]
fe2d2fbb44 chore(deps): bump simple-icons from 13.14.0 to 13.14.1 (#10639)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.14.0 to 13.14.1.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.14.0...13.14.1)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:33:26 +00:00
dependabot[bot]
d87673f6a8 chore(deps-dev): bump eslint-plugin-react from 7.37.1 to 7.37.2 (#10635)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.1 to 7.37.2.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.1...v7.37.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:28:36 +00:00
dependabot[bot]
ffee2e103c chore(deps-dev): bump jsdoc from 4.0.3 to 4.0.4 (#10632)
Bumps [jsdoc](https://github.com/jsdoc/jsdoc) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/jsdoc/jsdoc/releases)
- [Changelog](https://github.com/jsdoc/jsdoc/blob/4.0.4/CHANGES.md)
- [Commits](https://github.com/jsdoc/jsdoc/compare/4.0.3...4.0.4)

---
updated-dependencies:
- dependency-name: jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:23:12 +00:00
dependabot[bot]
bbc8c2035f chore(deps): bump pg from 8.13.0 to 8.13.1 (#10634)
Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) from 8.13.0 to 8.13.1.
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.13.1/packages/pg)

---
updated-dependencies:
- dependency-name: pg
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:22:37 +00:00
dependabot[bot]
caa3ffef39 chore(deps-dev): bump cypress from 13.15.0 to 13.15.1 (#10633)
Bumps [cypress](https://github.com/cypress-io/cypress) from 13.15.0 to 13.15.1.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/cypress/compare/v13.15.0...v13.15.1)

---
updated-dependencies:
- dependency-name: cypress
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:22:24 +00:00
dependabot[bot]
d37d6c8aab chore(deps-dev): bump @typescript-eslint/parser from 8.10.0 to 8.11.0 (#10636)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.10.0 to 8.11.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.11.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:21:26 +00:00
dependabot[bot]
51de256952 chore(deps): bump jsonpath-plus from 10.0.6 to 10.1.0 (#10638)
Bumps [jsonpath-plus](https://github.com/s3u/JSONPath) from 10.0.6 to 10.1.0.
- [Release notes](https://github.com/s3u/JSONPath/releases)
- [Changelog](https://github.com/JSONPath-Plus/JSONPath/blob/main/CHANGES.md)
- [Commits](https://github.com/s3u/JSONPath/compare/v10.0.6...v10.1.0)

---
updated-dependencies:
- dependency-name: jsonpath-plus
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:20:38 +00:00
dependabot[bot]
3c7f38861a chore(deps-dev): bump eslint-plugin-sort-class-members (#10637)
Bumps [eslint-plugin-sort-class-members](https://github.com/bryanrsmith/eslint-plugin-sort-class-members) from 1.20.0 to 1.21.0.
- [Release notes](https://github.com/bryanrsmith/eslint-plugin-sort-class-members/releases)
- [Commits](https://github.com/bryanrsmith/eslint-plugin-sort-class-members/compare/v1.20.0...v1.21.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-sort-class-members
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:20:12 +00:00
dependabot[bot]
85e4ee6b2b chore(deps): bump @sentry/node from 8.34.0 to 8.35.0 (#10640)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.34.0 to 8.35.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.34.0...8.35.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 13:19:35 +00:00
Ambati Mohan Kumar
5e40080099 [Scoop] Added scoop-license badge. (#10627)
* scoop-license service is added.

* refactored scoop badges - added scoop base

* refactor - description changed to base class.

* refactor - description changed to base class.

* Update services/scoop/scoop-license.tester.js

Co-authored-by: jNullj <15849761+jNullj@users.noreply.github.com>

* refactor - buckets variable is moved to base class, also updated tester with createTestService.

* moved queryParamSchema to base class.

---------

Co-authored-by: jNullj <15849761+jNullj@users.noreply.github.com>
2024-10-25 16:35:16 +00:00
dependabot[bot]
28bee8681a chore(deps): bump jsonpath-plus from 10.0.0 to 10.0.6 (#10620)
Bumps [jsonpath-plus](https://github.com/s3u/JSONPath) from 10.0.0 to 10.0.6.
- [Release notes](https://github.com/s3u/JSONPath/releases)
- [Changelog](https://github.com/JSONPath-Plus/JSONPath/blob/main/CHANGES.md)
- [Commits](https://github.com/s3u/JSONPath/commits)

---
updated-dependencies:
- dependency-name: jsonpath-plus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 15:12:40 +00:00
dependabot[bot]
17a50b81e4 chore(deps): bump simple-icons from 13.13.0 to 13.14.0 (#10623)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.13.0 to 13.14.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.13.0...13.14.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 15:02:22 +00:00
dependabot[bot]
d186f7cfb5 chore(deps): bump @xmldom/xmldom from 0.9.3 to 0.9.4 (#10624)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.9.3 to 0.9.4.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.9.3...0.9.4)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 14:53:45 +00:00
dependabot[bot]
8ba3a74aa2 chore(deps-dev): bump @typescript-eslint/parser from 8.8.1 to 8.10.0 (#10626)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.8.1 to 8.10.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.10.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 14:53:10 +00:00
dependabot[bot]
9d22508df1 chore(deps-dev): bump eslint-plugin-cypress from 3.5.0 to 3.6.0 (#10625)
Bumps [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/cypress-io/eslint-plugin-cypress/releases)
- [Commits](https://github.com/cypress-io/eslint-plugin-cypress/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-cypress
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 14:48:56 +00:00
dependabot[bot]
d79879cd75 chore(deps-dev): bump eslint-plugin-jsdoc from 50.3.1 to 50.4.3 (#10621)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 50.3.1 to 50.4.3.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.3.1...v50.4.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 14:48:20 +00:00
Aiden Gardner
4e4e3f82c6 [Chromewebstore] Extension size & last updated (#10613)
* Bump webextension-store-meta

* Add extension last updated

* Add extension size

* Run linter

* Rename last updated badge
2024-10-21 08:22:43 +00:00
jNullj
04f4fbd156 Deprecate HackageDeps service (#10618)
HackageDeps will allways return a landing page with 200 and no text about outdated packages.
The site is down and looking for someone to take over.
Therefor api information is false and non functional.
2024-10-20 20:05:23 +00:00
jNullj
66631524c5 Add [CratesUserDownloads] service and tester (#10619)
* add jsdoc for crates fetch func

* add BaseCratesUserService for user stats api route

part of solution for #10614

* Add CratesUserDownloads service and tester

This commit adds the CratesUserDownloads service and tester files. The CratesUserDownloads service shows the user total downloads at Crates.io.

as requested by #10614

* render userid in code block

* add non-exsistent user CratesUserDownloads test

userid for API usage is int32, therefor to minimize chance of user taking the id used the max value for int32 is used.

* fixed typo
2024-10-20 20:03:54 +00:00
jNullj
e7d76b117e refactor - usage renderVersionBadge - part 2 [amo archlinux aur bower cdnjs chromewebstore cocoapods conan conda cookbook cpan cran crates ctan curseforge debian docker dub eclipsemarketplace elmpackage f-droid factorio fedora feedz flathub galaxytoolshed gem gitea github gitlab greasyfork hackage hexpm homebrew itunes jenkins jetbrains jitpack jsr mavenmetadata modrinth nexus npm nuget openvsx opm ore packagist piwheels polymart pub puppetforge pypi ros scoop snapcraft spack spiget thunderstore twitch ubuntu vaadindirectory vcpkg visualstudioappcenter visualstudiomarketplace vpm wordpress] (#10615)
* use defaultLabel in renderVersionBadge without tag

As we refactor the codebase to use renderVersionBadge.
some badges need to show default label regardless of tag existance.
This is usefull for cases where the label is dynamic.

This change requires fixing test for npm, not sure how it worked before.

* Refactor AurVersion to use renderVersionBadge

part of #2026

* Refactor CondaVersion to use renderVersionBadge

part of #2026

* Refactor WordpressRequiresVersion to use renderVersionBadge

* add postfix option to renderVersionBadge

* add missing tests for renderVersionBadge

add defaultLabel without tag test
add postfix test
add test for all options together

* Refactor WordpressPluginTestedVersion to use renderVersionBadge

* add prefix override to renderVersionBadge

adds tests for all options with prefix as well

used for #2026 but also usefull for usage letting people override v prefix for versions all over the project once #2026 is done as requested for example in #10574

* Refactor RequiresPHPVersionForType to use renderVersionBadge
2024-10-20 21:55:58 +02:00
chris48s
868643ee6c set VM size to shared-cpu-1x/256Mb on staging apps (#10617) 2024-10-17 16:24:50 +00:00
jNullj
0a57af28bd update failing test to use package iptables (#10616)
use iptables as its a stable long term package that uses more standard version numbers
archlinux uses upstream versions which makes version regex for tests hard to predict
2024-10-16 17:36:29 +00:00
Ambati Mohan Kumar
1d2bf19100 [Snapcraft] - Added snapcraft last update badge (#10610)
* Added badge for Maven-Cenral last update.

* Update services/maven-central/maven-central-last-update.service.js

Co-authored-by: chris48s <chris48s@users.noreply.github.com>

* updated according to the review comments.

* added Snapcraft last updated badges.

* made changes according to the review.

---------

Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2024-10-14 05:48:16 +00:00
Pierre-Yves Bigourdan
d502e7031f [GitHubHacktoberfest] 2024 support (#10612) 2024-10-13 18:15:07 +00:00
jNullj
a8a1e77433 change nexus test for valid version (#10609) 2024-10-13 17:11:40 +00:00
jNullj
d00c4de4a3 Refactor version rendering in [wordpress nexus] and [f-droid] services (#10608)
* Refactor version rendering in f-droid, nexus, and wordpress services

* add missing label in nexus test
2024-10-13 19:10:17 +02:00
valkyrie_pilot
9ab1a906b2 Add option for IDs to have a prefix (#10576)
* Add option for IDs to have a prefix

* Switch to suffixes to protect ID values

* Document idSuffix

* Fix validation error message consistency

* Fix ID suffix regex test

* Add tests

* snapshot tests for idSuffix covering all 5 badge styles

* tweak docs

* update typescript definitions

* badge-maker 4.1.0 release

---------

Co-authored-by: chris48s <git@chris-shaw.dev>
2024-10-13 16:47:50 +00:00
dependabot[bot]
5580b1cd5c chore(deps-dev): bump @easyops-cn/docusaurus-search-local (#10606)
Bumps [@easyops-cn/docusaurus-search-local](https://github.com/easyops-cn/docusaurus-search-local/tree/HEAD/packages/docusaurus-search-local) from 0.44.5 to 0.45.0.
- [Release notes](https://github.com/easyops-cn/docusaurus-search-local/releases)
- [Commits](https://github.com/easyops-cn/docusaurus-search-local/commits/v0.45.0/packages/docusaurus-search-local)

---
updated-dependencies:
- dependency-name: "@easyops-cn/docusaurus-search-local"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:46:04 +00:00
dependabot[bot]
f28578f4e8 chore(deps): bump simple-icons from 13.12.0 to 13.13.0 (#10605)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.12.0 to 13.13.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.12.0...13.13.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:43:11 +00:00
dependabot[bot]
68e5eb6b8b chore(deps): bump query-string from 9.1.0 to 9.1.1 (#10604)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 9.1.0 to 9.1.1.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v9.1.0...v9.1.1)

---
updated-dependencies:
- dependency-name: query-string
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:37:10 +00:00
dependabot[bot]
58ea5aead3 chore(deps): bump jsonpath-plus from 9.0.0 to 10.0.0 (#10601)
Bumps [jsonpath-plus](https://github.com/s3u/JSONPath) from 9.0.0 to 10.0.0.
- [Release notes](https://github.com/s3u/JSONPath/releases)
- [Changelog](https://github.com/JSONPath-Plus/JSONPath/blob/main/CHANGES.md)
- [Commits](https://github.com/s3u/JSONPath/commits)

---
updated-dependencies:
- dependency-name: jsonpath-plus
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:35:22 +00:00
dependabot[bot]
ba6a24e84c chore(deps-dev): bump form-data from 4.0.0 to 4.0.1 (#10600)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: form-data
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:34:13 +00:00
dependabot[bot]
2170a022f0 chore(deps): bump node-pg-migrate from 7.7.0 to 7.7.1 (#10603)
Bumps [node-pg-migrate](https://github.com/salsita/node-pg-migrate) from 7.7.0 to 7.7.1.
- [Release notes](https://github.com/salsita/node-pg-migrate/releases)
- [Changelog](https://github.com/salsita/node-pg-migrate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/salsita/node-pg-migrate/compare/v7.7.0...v7.7.1)

---
updated-dependencies:
- dependency-name: node-pg-migrate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:30:32 +00:00
dependabot[bot]
7d0fd4a07a chore(deps-dev): bump @typescript-eslint/parser from 8.8.0 to 8.8.1 (#10602)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.8.0 to 8.8.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:30:13 +00:00
dependabot[bot]
64da4d07c2 chore(deps): bump got from 14.4.2 to 14.4.3 (#10599)
Bumps [got](https://github.com/sindresorhus/got) from 14.4.2 to 14.4.3.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v14.4.2...v14.4.3)

---
updated-dependencies:
- dependency-name: got
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:29:47 +00:00
dependabot[bot]
4c31202935 chore(deps): bump @sentry/node from 8.33.1 to 8.34.0 (#10598)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.33.1 to 8.34.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.33.1...8.34.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 14:29:34 +00:00
Tapaj Kumar Das
8ed3dc8db4 add [homebrew] cask download badge (#10595)
* add homebrew cask download badge

* add homebrew cask download badge

* fix: updates test cases

* fix: updates test cases

* tidy up homebrew filenames and docs site titles

---------

Co-authored-by: chris48s <git@chris-shaw.dev>
2024-10-12 12:02:23 +00:00
jNullj
f767fabf43 remove prefix v for commit hash version (#10597)
* addv ignore commit hash

some services might return version as a commit hash like aur.
it makes no sense to prefix v before the commit.

Fixes #10591

* add addv commit hash tests

add both full commit hash and short commit hash tests to addv.
expected result - no v prefix.
2024-10-12 09:21:27 +00:00
Ambati Mohan Kumar
e3808c1738 [Maven] Added badge for Maven-Cenral last-update (#10301) (#10585)
* Added badge for Maven-Cenral last update.

* Update services/maven-central/maven-central-last-update.service.js

Co-authored-by: chris48s <chris48s@users.noreply.github.com>

* updated according to the review comments.

---------

Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2024-10-12 08:52:08 +00:00
chris48s
f3e0cc06f4 [DynamicXml] parse doc as html if served with text/html content type (#10607) 2024-10-12 08:07:28 +00:00
chris48s
b85cfc7c1e add created timestamp to tokens table (#10573) 2024-10-12 08:07:10 +00:00
dependabot[bot]
b8abface20 chore(deps-dev): bump eslint-plugin-jsdoc from 50.3.0 to 50.3.1 (#10580)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 50.3.0 to 50.3.1.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.3.0...v50.3.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:58:08 +00:00
dependabot[bot]
c095e5c855 chore(deps): bump @actions/core (#10592)
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.10.1 to 1.11.1.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

---
updated-dependencies:
- dependency-name: "@actions/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:52:12 +00:00
dependabot[bot]
b0fa13ba2d chore(deps-dev): bump eslint-plugin-react from 7.37.0 to 7.37.1 (#10579)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.0 to 7.37.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.0...v7.37.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:52:08 +00:00
dependabot[bot]
814311e488 chore(deps): bump simple-icons from 13.11.0 to 13.12.0 (#10583)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.11.0 to 13.12.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.11.0...13.12.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:50:45 +00:00
dependabot[bot]
ae8414a6f0 chore(deps-dev): bump node-mocks-http from 1.16.0 to 1.16.1 (#10582)
Bumps [node-mocks-http](https://github.com/eugef/node-mocks-http) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/eugef/node-mocks-http/releases)
- [Changelog](https://github.com/eugef/node-mocks-http/blob/master/HISTORY.md)
- [Commits](https://github.com/eugef/node-mocks-http/compare/v1.16.0...v1.16.1)

---
updated-dependencies:
- dependency-name: node-mocks-http
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:41:19 +00:00
dependabot[bot]
ee86905883 chore(deps-dev): bump @typescript-eslint/parser from 8.7.0 to 8.8.0 (#10581)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.7.0 to 8.8.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:40:41 +00:00
dependabot[bot]
f8404935e9 chore(deps): bump @sentry/node from 8.32.0 to 8.33.1 (#10584)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.32.0 to 8.33.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.32.0...8.33.1)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:40:23 +00:00
dependabot[bot]
114ebe0bb1 chore(deps-dev): bump eslint-plugin-import from 2.30.0 to 2.31.0 (#10578)
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.30.0 to 2.31.0.
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.30.0...v2.31.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:37:58 +00:00
chris48s
2515cf9b27 Revert "Use old.stats.jenkins.io for JSON data (#10522)" (#10537)
This reverts commit 394a8271a6.
2024-09-30 10:09:27 +01:00
chris48s
da6002099f catch queries that cause TypeError (#10556) 2024-09-30 09:00:51 +00:00
dependabot[bot]
0f14eed035 chore(deps): bump simple-icons from 13.10.0 to 13.11.0 (#10566)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 13.10.0 to 13.11.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/13.10.0...13.11.0)

---
updated-dependencies:
- dependency-name: simple-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:46:08 +00:00
dependabot[bot]
2e6b1b8909 chore(deps): bump @xmldom/xmldom from 0.9.2 to 0.9.3 (#10561)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.9.2...0.9.3)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:42:37 +00:00
dependabot[bot]
808c6774ca chore(deps-dev): bump eslint-plugin-react from 7.36.1 to 7.37.0 (#10567)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.36.1 to 7.37.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.36.1...v7.37.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:39:44 +00:00
dependabot[bot]
5902dfc1cf chore(deps-dev): bump @typescript-eslint/parser from 8.6.0 to 8.7.0 (#10565)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.6.0 to 8.7.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.7.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:39:25 +00:00
dependabot[bot]
fdce66f470 chore(deps-dev): bump cypress from 13.14.2 to 13.15.0 (#10562)
Bumps [cypress](https://github.com/cypress-io/cypress) from 13.14.2 to 13.15.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/cypress/compare/v13.14.2...v13.15.0)

---
updated-dependencies:
- dependency-name: cypress
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:39:09 +00:00
dependabot[bot]
d6df7f8edb chore(deps-dev): bump nodemon from 3.1.6 to 3.1.7 (#10563)
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.6...v3.1.7)

---
updated-dependencies:
- dependency-name: nodemon
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:38:54 +00:00
dependabot[bot]
6eff6a7110 chore(deps-dev): bump eslint-plugin-jsdoc from 50.2.4 to 50.3.0 (#10560)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 50.2.4 to 50.3.0.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.2.4...v50.3.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:38:30 +00:00
dependabot[bot]
a92c2f1e64 chore(deps): bump @sentry/node from 8.30.0 to 8.32.0 (#10559)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 8.30.0 to 8.32.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/8.30.0...8.32.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:38:13 +00:00
dependabot[bot]
a9ecbb064e chore(deps): bump node-pg-migrate from 7.6.1 to 7.7.0 (#10558)
Bumps [node-pg-migrate](https://github.com/salsita/node-pg-migrate) from 7.6.1 to 7.7.0.
- [Release notes](https://github.com/salsita/node-pg-migrate/releases)
- [Changelog](https://github.com/salsita/node-pg-migrate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/salsita/node-pg-migrate/compare/v7.6.1...v7.7.0)

---
updated-dependencies:
- dependency-name: node-pg-migrate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 18:37:51 +00:00
chris48s
b9955143db publish blog about security vuln (#10555) 2024-09-25 19:17:21 +01:00
chris48s
d07dd65dfe call out security advisory in changelog (#10554) 2024-09-25 18:53:30 +01:00
127 changed files with 4362 additions and 1575 deletions

View File

@@ -9,17 +9,25 @@
"version": "0.0.0",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.0"
}
},
"node_modules/@actions/core": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/github": {
@@ -42,6 +50,11 @@
"undici": "^5.25.4"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"node_modules/@fastify/busboy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz",
@@ -220,14 +233,6 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View File

@@ -10,7 +10,7 @@
"author": "jNullj",
"license": "CC0",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.0"
}
}

View File

@@ -4,8 +4,42 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2024-12-01
- add [WingetVersion] Badge [#10245](https://github.com/badges/shields/issues/10245)
- Fix broken URL for pingpong.one [#10655](https://github.com/badges/shields/issues/10655)
- [npm] - Last update badge added [#10641](https://github.com/badges/shields/issues/10641)
- reduce overhead of NPM Last Update badge; test [npm] [#10666](https://github.com/badges/shields/issues/10666)
- Add YouTube-specific privacy notes [#10646](https://github.com/badges/shields/issues/10646)
- Dependency updates
## server-2024-11-02
- cleanly handle null or undefined result from jsonpath-plus [#10645](https://github.com/badges/shields/issues/10645)
- add content security policy header to SVG responses [#10642](https://github.com/badges/shields/issues/10642)
- [Scoop] Added scoop-license badge. [#10627](https://github.com/badges/shields/issues/10627)
- [Chromewebstore] Extension size & last updated [#10613](https://github.com/badges/shields/issues/10613)
- Deprecate HackageDeps service [#10618](https://github.com/badges/shields/issues/10618)
- Add [CratesUserDownloads] service and tester [#10619](https://github.com/badges/shields/issues/10619)
- [Snapcraft] - Added snapcraft last update badge [#10610](https://github.com/badges/shields/issues/10610)
- [GitHubHacktoberfest] 2024 support [#10612](https://github.com/badges/shields/issues/10612)
- add [homebrew] cask download badge [#10595](https://github.com/badges/shields/issues/10595)
- remove prefix v for commit hash version [#10597](https://github.com/badges/shields/issues/10597)
- [Maven] Added badge for Maven-Cenral last-update (#10301) [#10585](https://github.com/badges/shields/issues/10585)
- [DynamicXml] parse doc as html if served with text/html content type [#10607](https://github.com/badges/shields/issues/10607)
- Revert "Use old.stats.jenkins.io for JSON data (#10522)" [#10537](https://github.com/badges/shields/issues/10537)
- catch queries that cause TypeError [#10556](https://github.com/badges/shields/issues/10556)
- Dependency updates
## server-2024-09-25
This release includes an important security fix. See
- https://github.com/badges/shields/security/advisories/GHSA-rxvx-x284-4445
- https://github.com/badges/shields/issues/10553
for more details
- [dynamicjson dynamicyaml dynamictoml] switch to jsonpath-plus [#10551](https://github.com/badges/shields/issues/10551)
- [Snapcraft] license [#10520](https://github.com/badges/shields/issues/10520)
- deprecate [wheelmap] service [#10538](https://github.com/badges/shields/issues/10538)

View File

@@ -198,25 +198,19 @@ You can read more about [the project's inception][thread],
Maintainers:
- [calebcartwright](https://github.com/calebcartwright) (core team)
- [chris48s](https://github.com/chris48s) (core team)
- [Daniel15](https://github.com/Daniel15) (core team)
- [paulmelnikow](https://github.com/paulmelnikow) (core team)
- [platan](https://github.com/platan) (core team)
- [PyvesB](https://github.com/PyvesB) (core team)
- [RedSparr0w](https://github.com/RedSparr0w) (core team)
Operations:
- [calebcartwright](https://github.com/calebcartwright)
- [chris48s](https://github.com/chris48s)
- [jNullj](https://github.com/jnullj)
- [paulmelnikow](https://github.com/paulmelnikow)
- [PyvesB](https://github.com/PyvesB)
Alumni:
- [Daniel15](https://github.com/Daniel15)
- [espadrine](https://github.com/espadrine)
- [olivierlacan](https://github.com/olivierlacan)
- [platan](https://github.com/platan)
- [RedSparr0w](https://github.com/RedSparr0w)
## License

View File

@@ -2229,3 +2229,325 @@ exports['The badge generator badges with logos should always produce the same ba
</svg>
`
exports['The badge generator "flat" template badge generation should match snapshots: message with custom suffix 1'] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="107"
height="20"
role="img"
aria-label="cactus: grown"
>
<title>cactus: grown</title>
<linearGradient id="s1" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
<stop offset="1" stop-opacity=".1" />
</linearGradient>
<clipPath id="r1">
<rect width="107" height="20" rx="3" fill="#fff" />
</clipPath>
<g clip-path="url(#r1)">
<rect width="62" height="20" fill="#0f0" />
<rect x="62" width="45" height="20" fill="#b3e" />
<rect width="107" height="20" fill="url(#s1)" />
</g>
<g
fill="#fff"
text-anchor="middle"
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
text-rendering="geometricPrecision"
font-size="110"
>
<image
x="5"
y="3"
width="14"
height="14"
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
/>
<text
aria-hidden="true"
x="405"
y="150"
fill="#010101"
fill-opacity=".3"
transform="scale(.1)"
textLength="350"
>
cactus
</text>
<text x="405" y="140" transform="scale(.1)" fill="#fff" textLength="350">
cactus
</text>
<text
aria-hidden="true"
x="835"
y="150"
fill="#010101"
fill-opacity=".3"
transform="scale(.1)"
textLength="350"
>
grown
</text>
<text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="350">
grown
</text>
</g>
</svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message with custom suffix 1'] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="107"
height="20"
role="img"
aria-label="cactus: grown"
>
<title>cactus: grown</title>
<g shape-rendering="crispEdges">
<rect width="62" height="20" fill="#0f0" />
<rect x="62" width="45" height="20" fill="#b3e" />
</g>
<g
fill="#fff"
text-anchor="middle"
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
text-rendering="geometricPrecision"
font-size="110"
>
<image
x="5"
y="3"
width="14"
height="14"
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
/>
<text x="405" y="140" transform="scale(.1)" fill="#fff" textLength="350">
cactus
</text>
<text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="350">
grown
</text>
</g>
</svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message with custom suffix 1'] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="107"
height="18"
role="img"
aria-label="cactus: grown"
>
<title>cactus: grown</title>
<linearGradient id="s1" x2="0" y2="100%">
<stop offset="0" stop-color="#fff" stop-opacity=".7" />
<stop offset=".1" stop-color="#aaa" stop-opacity=".1" />
<stop offset=".9" stop-color="#000" stop-opacity=".3" />
<stop offset="1" stop-color="#000" stop-opacity=".5" />
</linearGradient>
<clipPath id="r1">
<rect width="107" height="18" rx="4" fill="#fff" />
</clipPath>
<g clip-path="url(#r1)">
<rect width="62" height="18" fill="#0f0" />
<rect x="62" width="45" height="18" fill="#b3e" />
<rect width="107" height="18" fill="url(#s1)" />
</g>
<g
fill="#fff"
text-anchor="middle"
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
text-rendering="geometricPrecision"
font-size="110"
>
<image
x="5"
y="2"
width="14"
height="14"
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
/>
<text
aria-hidden="true"
x="405"
y="140"
fill="#010101"
fill-opacity=".3"
transform="scale(.1)"
textLength="350"
>
cactus
</text>
<text x="405" y="130" transform="scale(.1)" fill="#fff" textLength="350">
cactus
</text>
<text
aria-hidden="true"
x="835"
y="140"
fill="#010101"
fill-opacity=".3"
transform="scale(.1)"
textLength="350"
>
grown
</text>
<text x="835" y="130" transform="scale(.1)" fill="#fff" textLength="350">
grown
</text>
</g>
</svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message with custom suffix 1'] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="163.75"
height="28"
role="img"
aria-label="CACTUS: GROWN"
>
<title>CACTUS: GROWN</title>
<g shape-rendering="crispEdges">
<rect width="89.5" height="28" fill="#0f0" />
<rect x="89.5" width="74.25" height="28" fill="#b3e" />
</g>
<g
fill="#fff"
text-anchor="middle"
font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
text-rendering="geometricPrecision"
font-size="100"
>
<image
x="9"
y="7"
width="14"
height="14"
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
/>
<text transform="scale(.1)" x="532.5" y="175" textLength="485" fill="#fff">
CACTUS
</text>
<text
transform="scale(.1)"
x="1266.25"
y="175"
textLength="502.5"
fill="#fff"
font-weight="bold"
>
GROWN
</text>
</g>
</svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message with custom suffix 1'] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="112"
height="20"
role="img"
aria-label="Cactus: grown"
>
<title>Cactus: grown</title>
<style>
a:hover #llink1 {
fill: url(#b1);
stroke: #ccc;
}
a:hover #rlink1 {
fill: #4183c4;
}
</style>
<linearGradient id="a1" x2="0" y2="100%">
<stop offset="0" stop-color="#fcfcfc" stop-opacity="0" />
<stop offset="1" stop-opacity=".1" />
</linearGradient>
<linearGradient id="b1" x2="0" y2="100%">
<stop offset="0" stop-color="#ccc" stop-opacity=".1" />
<stop offset="1" stop-opacity=".1" />
</linearGradient>
<g stroke="#d5d5d5">
<rect
stroke="none"
fill="#fcfcfc"
x="0.5"
y="0.5"
width="64"
height="19"
rx="2"
/>
<rect x="70.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa" />
<rect x="70" y="7.5" width="0.5" height="5" stroke="#fafafa" />
<path d="M70.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa" />
</g>
<image
x="5"
y="3"
width="14"
height="14"
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"
/>
<g
aria-hidden="true"
fill="#333"
text-anchor="middle"
font-family="Helvetica Neue,Helvetica,Arial,sans-serif"
text-rendering="geometricPrecision"
font-weight="700"
font-size="110px"
line-height="14px"
>
<rect
id="llink1"
stroke="#d5d5d5"
fill="url(#a1)"
x=".5"
y=".5"
width="64"
height="19"
rx="2"
/>
<text
aria-hidden="true"
x="405"
y="150"
fill="#fff"
transform="scale(.1)"
textLength="370"
>
Cactus
</text>
<text x="405" y="140" transform="scale(.1)" textLength="370">Cactus</text>
<text
aria-hidden="true"
x="905"
y="150"
fill="#fff"
transform="scale(.1)"
textLength="330"
>
grown
</text>
<text id="rlink1" x="905" y="140" transform="scale(.1)" textLength="330">
grown
</text>
</g>
</svg>
`

View File

@@ -1,5 +1,11 @@
# Changelog
## 4.1.0
### Features
- Add `idSuffix` param. This can be used to ensure every element id within the SVG is unique
## 4.0.0
### Breaking Changes

View File

@@ -73,6 +73,11 @@ The format is the following:
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
// Each offers a different visual design.
style: 'flat',
// (Optional) A string with only letters, numbers, -, and _. This can be used
// to ensure every element id within the SVG is unique and prevent CSS
// cross-contamination when the SVG badge is rendered inline in HTML pages.
idSuffix: 'dd'
}
```

View File

@@ -6,6 +6,7 @@ interface Format {
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social'
logoBase64?: string
links?: Array<string>
idSuffix?: string
}
export declare class ValidationError extends Error {}

View File

@@ -128,6 +128,7 @@ class Badge {
logoPadding,
color = '#4c1',
labelColor,
idSuffix = '',
}) {
const horizPadding = 5
const hasLogo = !!logo
@@ -178,6 +179,7 @@ class Badge {
this.label = label
this.message = message
this.accessibleText = accessibleText
this.idSuffix = idSuffix
this.logoElement = getLogoElement({
logo,
@@ -286,7 +288,7 @@ class Badge {
},
}),
],
attrs: { id: 'r' },
attrs: { id: `r${this.idSuffix}` },
})
}
@@ -313,7 +315,7 @@ class Badge {
attrs: {
width: this.width,
height: this.constructor.height,
fill: 'url(#s)',
fill: `url(#s${this.idSuffix})`,
},
})
const content = withGradient
@@ -379,14 +381,14 @@ class Plastic extends Badge {
attrs: { offset: 1, 'stop-color': '#000', 'stop-opacity': '.5' },
}),
],
attrs: { id: 's', x2: 0, y2: '100%' },
attrs: { id: `s${this.idSuffix}`, x2: 0, y2: '100%' },
})
const clipPath = this.getClipPathElement(4)
const backgroundGroup = this.getBackgroundGroupElement({
withGradient: true,
attrs: { 'clip-path': 'url(#r)' },
attrs: { 'clip-path': `url(#r${this.idSuffix})` },
})
return renderBadge(
@@ -428,14 +430,14 @@ class Flat extends Badge {
attrs: { offset: 1, 'stop-opacity': '.1' },
}),
],
attrs: { id: 's', x2: 0, y2: '100%' },
attrs: { id: `s${this.idSuffix}`, x2: 0, y2: '100%' },
})
const clipPath = this.getClipPathElement(3)
const backgroundGroup = this.getBackgroundGroupElement({
withGradient: true,
attrs: { 'clip-path': 'url(#r)' },
attrs: { 'clip-path': `url(#r${this.idSuffix})` },
})
return renderBadge(
@@ -492,6 +494,7 @@ function social({
logoPadding,
color = '#4c1',
labelColor = '#555',
idSuffix = '',
}) {
// Social label is styled with a leading capital. Convert to caps here so
// width can be measured using the correct characters.
@@ -565,9 +568,9 @@ function social({
const rect = new XmlElement({
name: 'rect',
attrs: {
id: 'llink',
id: `llink${idSuffix}`,
stroke: '#d5d5d5',
fill: 'url(#a)',
fill: `url(#a${idSuffix})`,
x: '.5',
y: '.5',
width: labelRectWidth,
@@ -640,7 +643,7 @@ function social({
name: 'text',
content: [message],
attrs: {
id: 'rlink',
id: `rlink${idSuffix}`,
x: messageTextX,
y: 140,
transform: FONT_SCALE_DOWN_VALUE,
@@ -660,7 +663,7 @@ function social({
const style = new XmlElement({
name: 'style',
content: [
'a:hover #llink{fill:url(#b);stroke:#ccc}a:hover #rlink{fill:#4183c4}',
`a:hover #llink${idSuffix}{fill:url(#b${idSuffix});stroke:#ccc}a:hover #rlink${idSuffix}{fill:#4183c4}`,
],
})
const gradients = new ElementList({
@@ -681,7 +684,7 @@ function social({
attrs: { offset: 1, 'stop-opacity': '.1' },
}),
],
attrs: { id: 'a', x2: 0, y2: '100%' },
attrs: { id: `a${idSuffix}`, x2: 0, y2: '100%' },
}),
new XmlElement({
name: 'linearGradient',
@@ -695,7 +698,7 @@ function social({
attrs: { offset: 1, 'stop-opacity': '.1' },
}),
],
attrs: { id: 'b', x2: 0, y2: '100%' },
attrs: { id: `b${idSuffix}`, x2: 0, y2: '100%' },
}),
],
})

View File

@@ -52,6 +52,11 @@ function _validate(format) {
`Field \`style\` must be one of (${styleValues.toString()})`,
)
}
if ('idSuffix' in format && !/^[a-zA-Z0-9\-_]*$/.test(format.idSuffix)) {
throw new ValidationError(
'Field `idSuffix` must contain only numbers, letters, -, and _',
)
}
}
function _clean(format) {
@@ -63,6 +68,7 @@ function _clean(format) {
'style',
'logoBase64',
'links',
'idSuffix',
]
const cleaned = {}
@@ -95,6 +101,7 @@ function _clean(format) {
* @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'])
* @param {string} format.idSuffix (Optional) Suffix for IDs, e.g. 1, 2, and 3 for three invocations that will be used on the same page.
* @returns {string} Badge in SVG format
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
*/

View File

@@ -101,5 +101,11 @@ describe('makeBadge function', function () {
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)',
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', idSuffix: '\\' }),
).to.throw(
ValidationError,
'Field `idSuffix` must contain only numbers, letters, -, and _',
)
})
})

View File

@@ -20,6 +20,7 @@ module.exports = function makeBadge({
logoSize,
logoWidth,
links = ['', ''],
idSuffix,
}) {
// String coercion and whitespace removal.
label = `${label}`.trim()
@@ -38,6 +39,7 @@ module.exports = function makeBadge({
link: links,
name: label,
value: message,
idSuffix,
})
}
@@ -59,6 +61,7 @@ module.exports = function makeBadge({
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),
idSuffix,
}),
)
}

View File

@@ -167,6 +167,18 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
label: '',
@@ -259,6 +271,19 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
label: '',
@@ -351,6 +376,19 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'plastic',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
label: '',
@@ -470,6 +508,19 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
label: '',
@@ -589,6 +640,19 @@ describe('The badge generator', function () {
})
})
it('should match snapshots: message with custom suffix', async function () {
await expectBadgeToMatchSnapshot({
label: 'cactus',
message: 'grown',
format: 'svg',
style: 'social',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
idSuffix: '1',
})
})
it('should match snapshots: message only, no logo', async function () {
await expectBadgeToMatchSnapshot({
label: '',

View File

@@ -1,6 +1,6 @@
{
"name": "badge-maker",
"version": "4.0.0",
"version": "4.1.0",
"description": "Shields.io badge library",
"keywords": [
"GitHub",

View File

@@ -11,6 +11,7 @@ function streamFromString(str) {
function sendSVG(res, askres, end) {
askres.setHeader('Content-Type', 'image/svg+xml;charset=utf-8')
askres.setHeader('Content-Security-Policy', "script-src 'none';")
askres.setHeader('Content-Length', Buffer.byteLength(res, 'utf8'))
end(null, { template: streamFromString(res) })
}

View File

@@ -139,6 +139,27 @@ describe('The server', function () {
expect(() => JSON.parse(body)).not.to.throw()
})
describe('Content Security Policy', function () {
it('should disable javascript when serving SVG content (no extension)', async function () {
const { headers } = await got(`${baseUrl}:fruit-apple-green`)
expect(headers['content-security-policy']).to.equal(
"script-src 'none';",
)
})
it('should disable javascript when serving SVG content (with extension)', async function () {
const { headers } = await got(`${baseUrl}:fruit-apple-green.svg`)
expect(headers['content-security-policy']).to.equal(
"script-src 'none';",
)
})
it('should not send content security headers when serving JSON content', async function () {
const { headers } = await got(`${baseUrl}:fruit-apple-green.json`)
expect(headers).not.to.have.property('content-security-policy')
})
})
it('should preserve label case', async function () {
const { statusCode, body } = await got(`${baseUrl}:fRuiT-apple-green.svg`)
expect(statusCode).to.equal(200)

View File

@@ -45,3 +45,7 @@ processes = []
interval = "15s"
restart_limit = 0
timeout = "2s"
[[vm]]
size = "shared-cpu-1x"
memory = "256mb"

View File

@@ -0,0 +1,661 @@
---
slug: GHSA-rxvx-x284-4445
title: Our response to RCE Security Advisory
authors:
name: chris48s
title: Shields.io Core Team
url: https://github.com/chris48s
image_url: https://avatars.githubusercontent.com/u/6025893
tags: []
---
We've just published a critical security advisory relating to a Remote Code Execution vulnerability in Dynamic JSON/TOML/YAML badges: https://github.com/badges/shields/security/advisories/GHSA-rxvx-x284-4445 Thanks to [@nickcopi](https://github.com/nickcopi) for his help with this.
If you self-host your own instance of Shields you should upgrade to [server-2024-09-25](https://hub.docker.com/layers/shieldsio/shields/server-2024-09-25/images/sha256-28aaea75049e325c9f1d63c8a8b477fc387d3d3fe35b933d6581487843cd610f?context=explore) or later as soon as possible to protect your instance.
This is primarily a concern for self-hosting users. However this does also have a couple of knock-on implications for some users of shields.io itself.
## 1. Users who have authorized the Shields.io GitHub OAuth app
While we have taken steps to close this vulnerability quickly after becoming aware of it, this attack vector has existed in our application for some time. We aren't aware of it having been actively exploited on shields.io. We also can't prove that it has not been exploited.
We don't log or track our users, so a breach offers a very limited attack surface against end users of shields.io. This is by design. One of the (few) information assets shields.io does hold is our GitHub token pool. This allows users to share a token with us by authorizing our OAuth app. Doing this gives us access to a token with read-only access to public data which we can use to increase our rate limit when making calls to the GitHub API.
The tokens we hold are not of high value to an attacker because they have read-only access to public data, but we can't say for sure they haven't been exfiltrated. If you've donated a token in the past and want to revoke it, you can revoke the Shields.io OAuth app at https://github.com/settings/applications which will de-activate the token you have shared with us.
## 2. Users of Dynamic JSON/TOML/YAML badges
Up until now, we have been using https://github.com/dchester/jsonpath as our library querying documents using JSONPath expressions. [@nickcopi](https://github.com/nickcopi) responsibly reported to us how a prototype pollution vulnerability in this library could be exploited to construct a JSONPath expression allowing an attacker to perform remote code execution. This vulnerability was reported on the package's issue tracker but not flagged by security scanning tools. It seems extremely unlikely that this will be fixed in the upstream package despite being widely used. It also seems unlikely this package will receive any further maintenance in future, even in response to critical security issues. In order to resolve this issue, we needed to switch to a different JSONPath library. We've decided to switch https://github.com/JSONPath-Plus/JSONPath using the `eval: false` option to disable script expressions.
This is an important security improvement and we have to make a change to protect the security of shields.io and users hosting their own instance of the application. However, this does come with some tradeoffs from a backwards-compatibility perspective.
### Using `eval: false`
Using JSONPath-Plus with `eval: false` does disable some query syntax which relies on evaluating javascript expressions.
For example, it would previously have been possible to use a JSONPath query like `$..keywords[(@.length-1)]` against the document https://github.com/badges/shields/raw/master/package.json to select the last element from the keywords array https://github.com/badges/shields/blob/e237e40ab88b8ad4808caad4f3dae653822aa79a/package.json#L6-L12
This is now not a supported query.
In this particular case, you could rewrite that query to `$..keywords[-1:]` and obtain the same result, but that may not be possible in all cases. We do realise that this removes some functionality that previously worked but closing this remote code execution vulnerability is the top priority, especially since there will be workarounds in many cases.
### Implementation Quirks
Historically, every JSONPath implementation has had its own idiosyncrasies. While most simple and common queries will behave the same way across different implementations, switching to another library will mean that some subset of queries may not work or produce different results.
One interesting thing that has happened in the world of JSONPath lately is RFC 9535 https://www.rfc-editor.org/rfc/rfc9535 which is an attempt to standardise JSONPath. As part of this mitigation, we did look at whether it would be possible to migrate to something that is RFC9535-compliant. However it is our assessment that the JavaScript community does not yet have a sufficiently mature/supported RFC9535-compliant JSONPath implementation. This means we are switching from one quirky implementation to another implementation with different quirks.
Again, this represents an unfortunate break in backwards-compatibility. However, it was necessary to prioritise closing off this remote code execution vulnerability over backwards-compatibility.
Although we can not provide a precise migration guide, here is a table of query types where https://github.com/dchester/jsonpath and https://github.com/JSONPath-Plus/JSONPath are known to diverge from the consensus implementation. This is sourced from the excellent https://cburgmer.github.io/json-path-comparison/
While this is a long list, many of these inputs represent edge cases or pathological inputs rather than common usage.
<details>
<summary>Table</summary>
<table>
<thead>
<tr>
<th>Query Type</th>
<th>Example Query</th>
</tr>
</thead>
<tbody>
<tr>
<td>Array slice with large number for end and negative step</td>
<td><code>$[2:-113667776004:-1]</code></td>
</tr>
<tr>
<td>Array slice with large number for start end negative step</td>
<td><code>$[113667776004:2:-1]</code></td>
</tr>
<tr>
<td>Array slice with negative step</td>
<td><code>$[3:0:-2]</code></td>
</tr>
<tr>
<td>Array slice with negative step on partially overlapping array</td>
<td><code>$[7:3:-1]</code></td>
</tr>
<tr>
<td>Array slice with negative step only</td>
<td><code>$[::-2]</code></td>
</tr>
<tr>
<td>Array slice with open end and negative step</td>
<td><code>$[3::-1]</code></td>
</tr>
<tr>
<td>Array slice with open start and negative step</td>
<td><code>$[:2:-1]</code></td>
</tr>
<tr>
<td>Array slice with range of 0</td>
<td><code>$[0:0]</code></td>
</tr>
<tr>
<td>Array slice with step 0</td>
<td><code>$[0:3:0]</code></td>
</tr>
<tr>
<td>Array slice with step and leading zeros</td>
<td><code>$[010:024:010]</code></td>
</tr>
<tr>
<td>Bracket notation with empty path</td>
<td><code>$[]</code></td>
</tr>
<tr>
<td>Bracket notation with number on object</td>
<td><code>$[0]</code></td>
</tr>
<tr>
<td>Bracket notation with number on string</td>
<td><code>$[0]</code></td>
</tr>
<tr>
<td>Bracket notation with number -1</td>
<td><code>$[-1]</code></td>
</tr>
<tr>
<td>Bracket notation with quoted array slice literal</td>
<td><code>$[':']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted closing bracket literal</td>
<td><code>$[']']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted current object literal</td>
<td><code>$['@']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted escaped backslash</td>
<td><code>$['\\']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted escaped single quote</td>
<td><code>$['\'']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted root literal</td>
<td><code>$['$']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted special characters combined</td>
<td><code>$[':@."$,*\'\\']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted string and unescaped single quote</td>
<td><code>$['single'quote']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted union literal</td>
<td><code>$[',']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted wildcard literal ?</td>
<td><code>$['*']</code></td>
</tr>
<tr>
<td>Bracket notation with quoted wildcard literal on object without key</td>
<td><code>$['*']</code></td>
</tr>
<tr>
<td>Bracket notation with spaces</td>
<td><code>$[ 'a' ]</code></td>
</tr>
<tr>
<td>Bracket notation with two literals separated by dot</td>
<td><code>$['two'.'some']</code></td>
</tr>
<tr>
<td>Bracket notation with two literals separated by dot without quotes</td>
<td><code>$[two.some]</code></td>
</tr>
<tr>
<td>Bracket notation without quotes</td>
<td><code>$[key]</code></td>
</tr>
<tr>
<td>Current with dot notation</td>
<td><code>@.a</code></td>
</tr>
<tr>
<td>Dot bracket notation</td>
<td><code>$.['key']</code></td>
</tr>
<tr>
<td>Dot bracket notation with double quotes</td>
<td><code>$.["key"]</code></td>
</tr>
<tr>
<td>Dot bracket notation without quotes</td>
<td><code>$.[key]</code></td>
</tr>
<tr>
<td>Dot notation after recursive descent with extra dot ?</td>
<td><code>$...key</code></td>
</tr>
<tr>
<td>Dot notation after union with keys</td>
<td><code>$['one','three'].key</code></td>
</tr>
<tr>
<td>Dot notation with dash</td>
<td><code>$.key-dash</code></td>
</tr>
<tr>
<td>Dot notation with double quotes</td>
<td><code>$."key"</code></td>
</tr>
<tr>
<td>Dot notation with double quotes after recursive descent ?</td>
<td><code>$.."key"</code></td>
</tr>
<tr>
<td>Dot notation with empty path</td>
<td><code>$.</code></td>
</tr>
<tr>
<td>Dot notation with key named length on array</td>
<td><code>$.length</code></td>
</tr>
<tr>
<td>Dot notation with key root literal</td>
<td><code>$.$</code></td>
</tr>
<tr>
<td>Dot notation with non ASCII key</td>
<td><code>$.??</code></td>
</tr>
<tr>
<td>Dot notation with number</td>
<td><code>$.2</code></td>
</tr>
<tr>
<td>Dot notation with number -1</td>
<td><code>$.-1</code></td>
</tr>
<tr>
<td>Dot notation with single quotes</td>
<td><code>$.'key'</code></td>
</tr>
<tr>
<td>Dot notation with single quotes after recursive descent ?</td>
<td><code>$..'key'</code></td>
</tr>
<tr>
<td>Dot notation with single quotes and dot</td>
<td><code>$.'some.key'</code></td>
</tr>
<tr>
<td>Dot notation with space padded key</td>
<td><code>$. a</code></td>
</tr>
<tr>
<td>Dot notation with wildcard after recursive descent on scalar ?</td>
<td><code>$..*</code></td>
</tr>
<tr>
<td>Dot notation without dot</td>
<td><code>$a</code></td>
</tr>
<tr>
<td>Dot notation without root</td>
<td><code>.key</code></td>
</tr>
<tr>
<td>Dot notation without root and dot</td>
<td><code>key</code></td>
</tr>
<tr>
<td>Empty</td>
<td><code>n/a</code></td>
</tr>
<tr>
<td>Filter expression on object</td>
<td><code>$[?(@.key)]</code></td>
</tr>
<tr>
<td>Filter expression after dot notation with wildcard after recursive descent ?</td>
<td><code>$..*[?(@.id&gt;2)]</code></td>
</tr>
<tr>
<td>Filter expression after recursive descent ?</td>
<td><code>$..[?(@.id==2)]</code></td>
</tr>
<tr>
<td>Filter expression with addition</td>
<td><code>$[?(@.key+50==100)]</code></td>
</tr>
<tr>
<td>Filter expression with boolean and operator and value false</td>
<td><code>$[?(@.key&gt;0 &amp;&amp; false)]</code></td>
</tr>
<tr>
<td>Filter expression with boolean and operator and value true</td>
<td><code>$[?(@.key&gt;0 &amp;&amp; true)]</code></td>
</tr>
<tr>
<td>Filter expression with boolean or operator and value false</td>
<td><code>$[?(@.key&gt;0 &amp;#124;&amp;#124; false)]</code></td>
</tr>
<tr>
<td>Filter expression with boolean or operator and value true</td>
<td><code>$[?(@.key&gt;0 &amp;#124;&amp;#124; true)]</code></td>
</tr>
<tr>
<td>Filter expression with bracket notation with -1</td>
<td><code>$[?(@[-1]==2)]</code></td>
</tr>
<tr>
<td>Filter expression with bracket notation with number on object</td>
<td><code>$[?(@[1]=='b')]</code></td>
</tr>
<tr>
<td>Filter expression with current object</td>
<td><code>$[?(@)]</code></td>
</tr>
<tr>
<td>Filter expression with different ungrouped operators</td>
<td><code>$[?(@.a &amp;&amp; @.b &amp;#124;&amp;#124; @.c)]</code></td>
</tr>
<tr>
<td>Filter expression with division</td>
<td><code>$[?(@.key/10==5)]</code></td>
</tr>
<tr>
<td>Filter expression with dot notation with dash</td>
<td><code>$[?(@.key-dash == 'value')]</code></td>
</tr>
<tr>
<td>Filter expression with dot notation with number</td>
<td><code>$[?(@.2 == 'second')]</code></td>
</tr>
<tr>
<td>Filter expression with dot notation with number on array</td>
<td><code>$[?(@.2 == 'third')]</code></td>
</tr>
<tr>
<td>Filter expression with empty expression</td>
<td><code>$[?()]</code></td>
</tr>
<tr>
<td>Filter expression with equals</td>
<td><code>$[?(@.key==42)]</code></td>
</tr>
<tr>
<td>Filter expression with equals on array of numbers</td>
<td><code>$[?(@==42)]</code></td>
</tr>
<tr>
<td>Filter expression with equals on object</td>
<td><code>$[?(@.key==42)]</code></td>
</tr>
<tr>
<td>Filter expression with equals array</td>
<td><code>$[?(@.d==["v1","v2"])]</code></td>
</tr>
<tr>
<td>Filter expression with equals array for array slice with range 1</td>
<td><code>$[?(@[0:1]==[1])]</code></td>
</tr>
<tr>
<td>Filter expression with equals array for dot notation with star</td>
<td><code>$[?(@.*==[1,2])]</code></td>
</tr>
<tr>
<td>Filter expression with equals array or equals true</td>
<td><code>$[?(@.d==["v1","v2"] &amp;#124;&amp;#124; (@.d == true))]</code></td>
</tr>
<tr>
<td>Filter expression with equals array with single quotes</td>
<td><code>$[?(@.d==['v1','v2'])]</code></td>
</tr>
<tr>
<td>Filter expression with equals boolean expression value</td>
<td><code>$[?((@.key&lt;44)==false)]</code></td>
</tr>
<tr>
<td>Filter expression with equals false</td>
<td><code>$[?(@.key==false)]</code></td>
</tr>
<tr>
<td>Filter expression with equals null</td>
<td><code>$[?(@.key==null)]</code></td>
</tr>
<tr>
<td>Filter expression with equals number for array slice with range 1</td>
<td><code>$[?(@[0:1]==1)]</code></td>
</tr>
<tr>
<td>Filter expression with equals number for bracket notation with star</td>
<td><code>$[?(@[*]==2)]</code></td>
</tr>
<tr>
<td>Filter expression with equals number for dot notation with star</td>
<td><code>$[?(@.*==2)]</code></td>
</tr>
<tr>
<td>Filter expression with equals number with fraction</td>
<td><code>$[?(@.key==-0.123e2)]</code></td>
</tr>
<tr>
<td>Filter expression with equals number with leading zeros</td>
<td><code>$[?(@.key==010)]</code></td>
</tr>
<tr>
<td>Filter expression with equals object</td>
<td><code>$[?(@.d==&lbrace;"k":"v"&rbrace;)]</code></td>
</tr>
<tr>
<td>Filter expression with equals string</td>
<td><code>$[?(@.key=="value")]</code></td>
</tr>
<tr>
<td>Filter expression with equals string with unicode character escape</td>
<td><code>$[?(@.key=="Mot\u00f6rhead")]</code></td>
</tr>
<tr>
<td>Filter expression with equals true</td>
<td><code>$[?(@.key==true)]</code></td>
</tr>
<tr>
<td>Filter expression with equals with path and path</td>
<td><code>$[?(@.key1==@.key2)]</code></td>
</tr>
<tr>
<td>Filter expression with equals with root reference</td>
<td><code>$.items[?(@.key==$.value)]</code></td>
</tr>
<tr>
<td>Filter expression with greater than</td>
<td><code>$[?(@.key&gt;42)]</code></td>
</tr>
<tr>
<td>Filter expression with greater than or equal</td>
<td><code>$[?(@.key&gt;=42)]</code></td>
</tr>
<tr>
<td>Filter expression with in array of values</td>
<td><code>$[?(@.d in [2, 3])]</code></td>
</tr>
<tr>
<td>Filter expression with in current object</td>
<td><code>$[?(2 in @.d)]</code></td>
</tr>
<tr>
<td>Filter expression with length free function</td>
<td><code>$[?(length(@) == 4)]</code></td>
</tr>
<tr>
<td>Filter expression with length function</td>
<td><code>$[?(@.length() == 4)]</code></td>
</tr>
<tr>
<td>Filter expression with length property</td>
<td><code>$[?(@.length == 4)]</code></td>
</tr>
<tr>
<td>Filter expression with less than</td>
<td><code>$[?(@.key&lt;42)]</code></td>
</tr>
<tr>
<td>Filter expression with less than or equal</td>
<td><code>$[?(@.key&lt;=42)]</code></td>
</tr>
<tr>
<td>Filter expression with local dot key and null in data</td>
<td><code>$[?(@.key='value')]</code></td>
</tr>
<tr>
<td>Filter expression with multiplication</td>
<td><code>$[?(@.key*2==100)]</code></td>
</tr>
<tr>
<td>Filter expression with negation and equals</td>
<td><code>$[?(!(@.key==42))]</code></td>
</tr>
<tr>
<td>Filter expression with negation and equals array or equals true</td>
<td><code>$[?(!(@.d==["v1","v2"]) &amp;#124;&amp;#124; (@.d == true))]</code></td>
</tr>
<tr>
<td>Filter expression with negation and less than</td>
<td><code>$[?(!(@.key&lt;42))]</code></td>
</tr>
<tr>
<td>Filter expression with negation and without value</td>
<td><code>$[?(!@.key)]</code></td>
</tr>
<tr>
<td>Filter expression with non singular existence test</td>
<td><code>$[?(@.a.*)]</code></td>
</tr>
<tr>
<td>Filter expression with not equals</td>
<td><code>$[?(@.key!=42)]</code></td>
</tr>
<tr>
<td>Filter expression with not equals array or equals true</td>
<td><code>$[?((@.d!=["v1","v2"]) &amp;#124;&amp;#124; (@.d == true))]</code></td>
</tr>
<tr>
<td>Filter expression with parent axis operator</td>
<td><code>$[*].bookmarks[?(@.page == 45)]^^^</code></td>
</tr>
<tr>
<td>Filter expression with regular expression</td>
<td><code>$[?(@.name=~/hello.*/)]</code></td>
</tr>
<tr>
<td>Filter expression with regular expression from member</td>
<td><code>$[?(@.name=~/@.pattern/)]</code></td>
</tr>
<tr>
<td>Filter expression with set wise comparison to scalar</td>
<td><code>$[?(@[*]&gt;=4)]</code></td>
</tr>
<tr>
<td>Filter expression with set wise comparison to set</td>
<td><code>$.x[?(@[*]&gt;=$.y[*])]</code></td>
</tr>
<tr>
<td>Filter expression with single equal</td>
<td><code>$[?(@.key=42)]</code></td>
</tr>
<tr>
<td>Filter expression with subfilter</td>
<td><code>$[?(@.a[?(@.price&gt;10)])]</code></td>
</tr>
<tr>
<td>Filter expression with subpaths deeply nested</td>
<td><code>$[?(@.a.b.c==3)]</code></td>
</tr>
<tr>
<td>Filter expression with subtraction</td>
<td><code>$[?(@.key-50==-100)]</code></td>
</tr>
<tr>
<td>Filter expression with triple equal</td>
<td><code>$[?(@.key===42)]</code></td>
</tr>
<tr>
<td>Filter expression with value</td>
<td><code>$[?(@.key)]</code></td>
</tr>
<tr>
<td>Filter expression with value after recursive descent ?</td>
<td><code>$..[?(@.id)]</code></td>
</tr>
<tr>
<td>Filter expression with value false</td>
<td><code>$[?(false)]</code></td>
</tr>
<tr>
<td>Filter expression with value from recursive descent</td>
<td><code>$[?(@..child)]</code></td>
</tr>
<tr>
<td>Filter expression with value null</td>
<td><code>$[?(null)]</code></td>
</tr>
<tr>
<td>Filter expression with value true</td>
<td><code>$[?(true)]</code></td>
</tr>
<tr>
<td>Filter expression without parens</td>
<td><code>$[?@.key==42]</code></td>
</tr>
<tr>
<td>Filter expression without value</td>
<td><code>$[?(@.key)]</code></td>
</tr>
<tr>
<td>Function sum</td>
<td><code>$.data.sum()</code></td>
</tr>
<tr>
<td>Parens notation</td>
<td><code>$(key,more)</code></td>
</tr>
<tr>
<td>Recursive descent ?</td>
<td><code>$..</code></td>
</tr>
<tr>
<td>Recursive descent after dot notation ?</td>
<td><code>$.key..</code></td>
</tr>
<tr>
<td>Root on scalar</td>
<td><code>$</code></td>
</tr>
<tr>
<td>Root on scalar false</td>
<td><code>$</code></td>
</tr>
<tr>
<td>Root on scalar true</td>
<td><code>$</code></td>
</tr>
<tr>
<td>Script expression</td>
<td><code>$[(@.length-1)]</code></td>
</tr>
<tr>
<td>Union with duplication from array</td>
<td><code>$[0,0]</code></td>
</tr>
<tr>
<td>Union with duplication from object</td>
<td><code>$['a','a']</code></td>
</tr>
<tr>
<td>Union with filter</td>
<td><code>$[?(@.key&lt;3),?(@.key&gt;6)]</code></td>
</tr>
<tr>
<td>Union with keys</td>
<td><code>$['key','another']</code></td>
</tr>
<tr>
<td>Union with keys on object without key</td>
<td><code>$['missing','key']</code></td>
</tr>
<tr>
<td>Union with keys after array slice</td>
<td><code>$[:]['c','d']</code></td>
</tr>
<tr>
<td>Union with keys after bracket notation</td>
<td><code>$[0]['c','d']</code></td>
</tr>
<tr>
<td>Union with keys after dot notation with wildcard</td>
<td><code>$.*['c','d']</code></td>
</tr>
<tr>
<td>Union with keys after recursive descent ?</td>
<td><code>$..['c','d']</code></td>
</tr>
<tr>
<td>Union with repeated matches after dot notation with wildcard</td>
<td><code>$.*[0,:5]</code></td>
</tr>
<tr>
<td>Union with slice and number</td>
<td><code>$[1:3,4]</code></td>
</tr>
<tr>
<td>Union with spaces</td>
<td><code>$[ 0 , 1 ]</code></td>
</tr>
<tr>
<td>Union with wildcard and number</td>
<td><code>$[*,1]</code></td>
</tr>
</tbody>
</table>
</details>

View File

@@ -0,0 +1,28 @@
---
slug: token-pool
title: How shields.io uses the GitHub API
authors:
name: chris48s
title: Shields.io Core Team
url: https://github.com/chris48s
image_url: https://avatars.githubusercontent.com/u/6025893
tags: []
---
We serve a lot of badges which display information fetched from the GitHub API. When I say a lot, this varies a bit but in a typical hour we make hundreds of thousands of calls to the GitHub API.
But hang on. GitHub's API has rate limits.
Specifically, users can make up to [5,000 requests per hour](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#primary-rate-limit-for-authenticated-users) to GitHub's v3/REST API. The v4/GraphQL also applies rate limits, but it is based on a slightly more complicated [points-based system](https://docs.github.com/en/graphql/overview/rate-limits-and-node-limits-for-the-graphql-api#primary-rate-limit).
In any case, we are clearly making many times more requests to GitHub's API than would be allowed with a single token.
So how are we doing that? Well, we have lots of tokens. To elaborate on that slightly, as a user of shields.io you can choose to share a token with us to help increase our rate limit. Here's how it works:
- Authorize our [OAuth Application](https://img.shields.io/github-auth).
- This shares with us a GitHub token which has read-only access to public data. We only ask for the minimum permissions necessary. Authorizing the OAuth app doesn't allow us access to your private data or allow us to perform any actions on your behalf.
- Your token is added to a pool of tokens shared by other users like you.
- When we need to make a request to the GitHub API, we pick one of the tokens from our pool. We only make a handful of requests with each token before picking another from the pool.
- If you ever decide you would not like to continue sharing a token with us, you can revoke the Shields.io OAuth app at https://github.com/settings/applications. You can do this at any time. This will de-activate the token you have shared with us and we'll remove it from the pool.
This method allows us (with your help) to make hundreds of thousands of request per hour to the GitHub API. Because we have thousands of tokens in the pool and we only make a few requests with each one before picking another token from the pool, most users don't notice any meaningful impact on their available rate limit as a result of authorizing our app.

View File

@@ -0,0 +1,15 @@
exports.shorthands = undefined
exports.up = pgm => {
pgm.addColumn('github_user_tokens', {
created: {
type: 'TIMESTAMP',
notNull: true,
default: pgm.func('CURRENT_TIMESTAMP'),
},
})
}
exports.down = pgm => {
pgm.dropColumn('github_user_tokens', 'created')
}

1187
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,11 +21,11 @@
"url": "https://github.com/badges/shields"
},
"dependencies": {
"@renovatebot/pep440": "^3.0.20",
"@renovatebot/ruby-semver": "^3.0.23",
"@sentry/node": "^8.30.0",
"@renovatebot/pep440": "^4.0.1",
"@renovatebot/ruby-semver": "^4.0.0",
"@sentry/node": "^8.41.0",
"@shields_io/camp": "^18.1.2",
"@xmldom/xmldom": "0.9.2",
"@xmldom/xmldom": "0.9.5",
"badge-maker": "file:badge-maker",
"bytes": "^3.1.2",
"camelcase": "^8.0.0",
@@ -41,33 +41,33 @@
"fast-xml-parser": "^4.5.0",
"glob": "^11.0.0",
"global-agent": "^3.0.0",
"got": "^14.4.2",
"got": "^14.4.5",
"graphql": "16.9.0",
"graphql-tag": "^2.12.6",
"joi": "17.13.3",
"joi-extension-semver": "5.0.0",
"js-yaml": "^4.1.0",
"jsonpath-plus": "^9.0.0",
"jsonpath-plus": "^10.2.0",
"lodash.countby": "^4.6.0",
"lodash.groupby": "^4.6.0",
"lodash.times": "^4.3.2",
"matcher": "^5.0.0",
"node-env-flag": "^0.1.0",
"node-pg-migrate": "^7.6.1",
"node-pg-migrate": "^7.8.0",
"parse-link-header": "^2.0.0",
"path-to-regexp": "^6.3.0",
"pg": "^8.13.0",
"pg": "^8.13.1",
"pretty-bytes": "^6.1.1",
"priorityqueuejs": "^2.0.0",
"prom-client": "^15.1.3",
"qs": "^6.13.0",
"query-string": "^9.1.0",
"qs": "^6.13.1",
"query-string": "^9.1.1",
"semver": "~7.6.3",
"simple-icons": "13.10.0",
"smol-toml": "1.3.0",
"simple-icons": "13.18.0",
"smol-toml": "1.3.1",
"svg-path-bbox": "^2.1.0",
"svgpath": "^2.6.0",
"webextension-store-meta": "^1.2.3",
"webextension-store-meta": "^1.2.4",
"xpath": "~0.0.34"
},
"scripts": {
@@ -148,19 +148,19 @@
"devDependencies": {
"@docusaurus/core": "^3.5.2",
"@docusaurus/preset-classic": "^3.5.2",
"@easyops-cn/docusaurus-search-local": "^0.44.5",
"@mdx-js/react": "^3.0.1",
"@typescript-eslint/parser": "^8.6.0",
"@easyops-cn/docusaurus-search-local": "^0.46.1",
"@mdx-js/react": "^3.1.0",
"@typescript-eslint/parser": "^8.16.0",
"c8": "^10.1.2",
"caller": "^1.1.0",
"chai": "^4.5.0",
"chai-as-promised": "^8.0.0",
"chai-as-promised": "^8.0.1",
"chai-datetime": "^1.8.1",
"chai-string": "^1.4.0",
"child-process-promise": "^2.2.1",
"clsx": "^2.1.1",
"concurrently": "^9.0.1",
"cypress": "^13.14.2",
"concurrently": "^9.1.0",
"cypress": "^13.16.0",
"cypress-wait-for-stable-dom": "^0.1.0",
"danger": "^12.3.3",
"deepmerge": "^4.3.1",
@@ -171,36 +171,36 @@
"eslint-config-standard-jsx": "11.0.0",
"eslint-config-standard-react": "13.0.0",
"eslint-plugin-chai-friendly": "^1.0.1",
"eslint-plugin-cypress": "^3.5.0",
"eslint-plugin-cypress": "^3.6.0",
"eslint-plugin-icedfrisby": "^0.1.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsdoc": "^50.2.4",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsdoc": "^50.6.0",
"eslint-plugin-mocha": "^10.5.0",
"eslint-plugin-no-extension-in-require": "^0.2.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-react": "^7.36.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-sort-class-members": "^1.20.0",
"form-data": "^4.0.0",
"eslint-plugin-sort-class-members": "^1.21.0",
"form-data": "^4.0.1",
"icedfrisby": "4.0.0",
"icedfrisby-nock": "^2.1.0",
"is-svg": "^5.1.0",
"jsdoc": "^4.0.3",
"jsdoc": "^4.0.4",
"lint-staged": "^15.2.10",
"lodash.difference": "^4.5.0",
"minimist": "^1.2.8",
"mocha": "^10.7.3",
"mocha": "^10.8.2",
"mocha-env-reporter": "^4.0.0",
"mocha-junit-reporter": "^2.2.1",
"mocha-yaml-loader": "^1.0.3",
"nock": "13.5.5",
"node-mocks-http": "^1.16.0",
"nodemon": "^3.1.6",
"nock": "13.5.6",
"node-mocks-http": "^1.16.1",
"nodemon": "^3.1.7",
"npm-run-all": "^4.1.5",
"open-cli": "^8.0.0",
"portfinder": "^1.0.32",
"prettier": "3.3.3",
"prettier": "3.4.1",
"prism-react-renderer": "^2.4.0",
"react": "^18.3.0",
"react-dom": "^18.3.1",

View File

@@ -3,7 +3,7 @@ import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Arch Linux package (valid)')
.get('/core/x86_64/pacman.json')
.get('/core/x86_64/iptables.json')
.expectBadge({
label: 'arch linux',
message: isVPlusDottedVersionNClausesWithOptionalSuffixAndEpoch,

View File

@@ -1,10 +1,8 @@
import Joi from 'joi'
import {
floorCount as floorCountColor,
age as ageColor,
} from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { floorCount as floorCountColor } from '../color-formatters.js'
import { renderLicenseBadge } from '../licenses.js'
import { addv, metric, formatDate } from '../text-formatters.js'
import { metric } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import {
BaseJsonService,
@@ -12,6 +10,7 @@ import {
InvalidResponse,
pathParams,
} from '../index.js'
import { renderVersionBadge } from '../version.js'
const aurSchema = Joi.object({
resultcount: nonNegativeInteger,
@@ -170,7 +169,7 @@ class AurVersion extends BaseAurService {
static render({ version, outOfDate }) {
const color = outOfDate === null ? 'blue' : 'orange'
return { message: addv(version), color }
return renderVersionBadge({ version, versionFormatter: () => color })
}
async handle({ packageName }) {
@@ -242,16 +241,10 @@ class AurLastModified extends BaseAurService {
static defaultBadgeData = { label: 'last modified' }
static render({ date }) {
const color = ageColor(date)
const message = formatDate(date)
return { color, message }
}
async handle({ packageName }) {
const json = await this.fetch({ packageName })
const date = 1000 * parseInt(json.results[0].LastModified)
return this.constructor.render({ date })
return renderDateBadge(date)
}
}

View File

@@ -6,11 +6,13 @@ describe('AurVersion', function () {
given({ version: '1:1.1.42.622-1', outOfDate: 1 }).expect({
color: 'orange',
message: 'v1:1.1.42.622-1',
label: undefined,
})
given({ version: '7', outOfDate: null }).expect({
color: 'blue',
message: 'v7',
label: undefined,
})
})
})

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { age as ageColor } from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { BaseJsonService, NotFound, pathParam, queryParam } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { relativeUri } from '../validators.js'
const schema = Joi.object({
@@ -43,13 +42,6 @@ export default class BitbucketLastCommit extends BaseJsonService {
static defaultBadgeData = { label: 'last commit' }
static render({ commitDate }) {
return {
message: formatDate(commitDate),
color: ageColor(Date.parse(commitDate)),
}
}
async fetch({ user, repo, branch, path }) {
// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-commits-get
return this._requestJson({
@@ -76,6 +68,6 @@ export default class BitbucketLastCommit extends BaseJsonService {
if (!commit) throw new NotFound({ prettyMessage: 'no commits found' })
return this.constructor.render({ commitDate: commit.date })
return renderDateBadge(commit.date)
}
}

View File

@@ -0,0 +1,35 @@
import { renderDateBadge } from '../date.js'
import { NotFound, pathParams } from '../index.js'
import BaseChromeWebStoreService from './chrome-web-store-base.js'
export default class ChromeWebStoreLastUpdated extends BaseChromeWebStoreService {
static category = 'activity'
static route = { base: 'chrome-web-store/last-updated', pattern: ':storeId' }
static openApi = {
'/chrome-web-store/last-updated/{storeId}': {
get: {
summary: 'Chrome Web Store Last Updated',
parameters: pathParams({
name: 'storeId',
example: 'nccfelhkfpbnefflolffkclhenplhiab',
}),
},
},
}
static defaultBadgeData = {
label: 'last updated',
}
async handle({ storeId }) {
const chromeWebStore = await this.fetch({ storeId })
const lastUpdated = chromeWebStore.lastUpdated()
if (lastUpdated == null) {
throw new NotFound({ prettyMessage: 'not found' })
}
return renderDateBadge(lastUpdated)
}
}

View File

@@ -0,0 +1,18 @@
import { isFormattedDate } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Last updated')
.get('/nccfelhkfpbnefflolffkclhenplhiab.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('Last updated (not found)')
.get('/invalid-name-of-addon.json')
.expectBadge({
label: 'last updated',
message: 'not found',
})

View File

@@ -0,0 +1,35 @@
import { NotFound, pathParams } from '../index.js'
import BaseChromeWebStoreService from './chrome-web-store-base.js'
export default class ChromeWebStoreSize extends BaseChromeWebStoreService {
static category = 'size'
static route = { base: 'chrome-web-store/size', pattern: ':storeId' }
static openApi = {
'/chrome-web-store/size/{storeId}': {
get: {
summary: 'Chrome Web Store Size',
parameters: pathParams({
name: 'storeId',
example: 'nccfelhkfpbnefflolffkclhenplhiab',
}),
},
},
}
static defaultBadgeData = {
label: 'extension size',
color: 'blue',
}
async handle({ storeId }) {
const chromeWebStore = await this.fetch({ storeId })
const size = chromeWebStore.size()
if (size == null) {
throw new NotFound({ prettyMessage: 'not found' })
}
return { message: size }
}
}

View File

@@ -0,0 +1,13 @@
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
const isFileSize = /^\d+(\.\d+)?(MiB|KiB)$/
t.create('Size').get('/nccfelhkfpbnefflolffkclhenplhiab.json').expectBadge({
label: 'extension size',
message: isFileSize,
})
t.create('Size (not found)')
.get('/invalid-name-of-addon.json')
.expectBadge({ label: 'extension size', message: 'not found' })

View File

@@ -34,6 +34,10 @@ class ClojarsVersionService extends BaseClojarsService {
static defaultBadgeData = { label: 'clojars' }
static render({ clojar, version }) {
// clojars format is non standard to fit community style
// dont use renderVersionBadge
// see also https://github.com/badges/shields/pull/431
// commit d0414c9
return {
message: `[${clojar} "${version}"]`,
color: versionColor(version),

View File

@@ -6,7 +6,6 @@
*/
import pep440 from '@renovatebot/pep440'
import dayjs from 'dayjs'
/**
* Determines the color used for a badge based on version.
@@ -23,7 +22,10 @@ function version(version) {
if (first === 'v') {
first = version[1]
}
if (first === '0' || /alpha|beta|snapshot|dev|pre|rc/i.test(version)) {
if (
first === '0' ||
/alpha|beta|snapshot|dev|pre|rc|scm|cvs/i.test(version)
) {
return 'orange'
} else {
return 'blue'
@@ -172,24 +174,7 @@ function colorScale(steps, colors, reversed) {
}
}
/**
* Determines the color used for a badge according to the age.
* Age is calculated as days elapsed till current date.
* The color varies from bright green to red as the age increases
* or the other way around if `reverse` is given `true`.
*
* @param {string} date Date string
* @param {boolean} reversed Reverse the color scale a.k.a. the older, the better
* @returns {string} Badge color
*/
function age(date, reversed = false) {
const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, !reversed)
const daysElapsed = dayjs().diff(dayjs(date), 'days')
return colorByAge(daysElapsed)
}
export {
age,
colorScale,
coveragePercentage,
downloadCount,

View File

@@ -1,7 +1,6 @@
import { expect } from 'chai'
import { forCases, given, test } from 'sazerac'
import {
age,
colorScale,
coveragePercentage,
letterScore,
@@ -53,46 +52,6 @@ describe('Color formatters', function () {
given('Z').expect('red')
})
const monthsAgo = months => {
const result = new Date()
// This looks wack but it works.
result.setMonth(result.getMonth() - months)
return result
}
test(age, () => {
given(Date.now())
.describe('when given the current timestamp')
.expect('brightgreen')
given(new Date())
.describe('when given the current Date')
.expect('brightgreen')
given(new Date(2001, 1, 1))
.describe('when given a Date many years ago')
.expect('red')
given(monthsAgo(2))
.describe('when given a Date two months ago')
.expect('yellowgreen')
given(monthsAgo(15))
.describe('when given a Date 15 months ago')
.expect('orange')
// --- reversed --- //
given(Date.now(), true)
.describe('when given the current timestamp and reversed')
.expect('red')
given(new Date(), true)
.describe('when given the current Date and reversed')
.expect('red')
given(new Date(2001, 1, 1), true)
.describe('when given a Date many years ago and reversed')
.expect('brightgreen')
given(monthsAgo(2), true)
.describe('when given a Date two months ago and reversed')
.expect('yellow')
given(monthsAgo(15), true)
.describe('when given a Date 15 months ago and reversed')
.expect('green')
})
test(version, () => {
forCases([given('1.0'), given(9), given(1.0)]).expect('blue')
@@ -105,6 +64,8 @@ describe('Color formatters', function () {
given('1.0.1-dev'),
given('2.1.6-prerelease'),
given('2.1.6-RC1'),
given('cvs-1'),
given('scm-2'),
]).expect('orange')
expect(() => version(null)).to.throw(

View File

@@ -1,6 +1,5 @@
import { pathParams } from '../index.js'
import { addv as versionText } from '../text-formatters.js'
import { version as versionColor } from '../color-formatters.js'
import { renderVersionBadge } from '../version.js'
import BaseCondaService from './conda-base.js'
export default class CondaVersion extends BaseCondaService {
@@ -33,20 +32,12 @@ export default class CondaVersion extends BaseCondaService {
},
}
static render({ variant, channel, version }) {
return {
label: variant === 'vn' ? channel : `conda | ${channel}`,
message: versionText(version),
color: versionColor(version),
}
}
async handle({ variant, channel, packageName }) {
const json = await this.fetch({ channel, packageName })
return this.constructor.render({
variant,
channel,
const defaultLabel = variant === 'vn' ? channel : `conda | ${channel}`
return renderVersionBadge({
version: json.latest_version,
defaultLabel,
})
}
}

View File

@@ -4,8 +4,7 @@ import { BaseJsonService, InvalidResponse } from '../index.js'
const versionSchema = Joi.object({
downloads: nonNegativeInteger,
// Crate size is not available for all versions.
crate_size: nonNegativeInteger.allow(null),
crate_size: nonNegativeInteger,
num: Joi.string().required(),
license: Joi.string().required().allow(null),
rust_version: Joi.string().allow(null),
@@ -24,9 +23,21 @@ const versionResponseSchema = Joi.object({
version: versionSchema.required(),
}).required()
const userStatsSchema = Joi.object({
total_downloads: nonNegativeInteger.required(),
}).required()
class BaseCratesService extends BaseJsonService {
static defaultBadgeData = { label: 'crates.io' }
/**
* Fetches data from the crates.io API.
*
* @param {object} options - The options for the request
* @param {string} options.crate - The crate name.
* @param {string} [options.version] - The crate version number (optional).
* @returns {Promise<object>} the JSON response from the API.
*/
async fetch({ crate, version }) {
const url = version
? `https://crates.io/api/v1/crates/${crate}/${version}`
@@ -54,7 +65,23 @@ class BaseCratesService extends BaseJsonService {
}
}
class BaseCratesUserService extends BaseJsonService {
static defaultBadgeData = { label: 'crates.io' }
/**
* Fetches data from the crates.io API.
*
* @param {object} options - The options for the request
* @param {string} options.userId - The user ID.
* @returns {Promise<object>} the JSON response from the API.
*/
async fetch({ userId }) {
const url = `https://crates.io/api/v1/users/${userId}/stats`
return this._requestJson({ schema: userStatsSchema, url })
}
}
const description =
'[Crates.io](https://crates.io/) is a package registry for Rust.'
export { BaseCratesService, description }
export { BaseCratesService, BaseCratesUserService, description }

View File

@@ -1,5 +1,5 @@
import prettyBytes from 'pretty-bytes'
import { InvalidResponse, pathParams } from '../index.js'
import { pathParams } from '../index.js'
import { BaseCratesService, description } from './crates-base.js'
export default class CratesSize extends BaseCratesService {
@@ -49,11 +49,6 @@ export default class CratesSize extends BaseCratesService {
async handle({ crate, version }) {
const json = await this.fetch({ crate, version })
const size = this.constructor.getVersionObj(json).crate_size
if (size == null) {
throw new InvalidResponse({ prettyMessage: 'unknown' })
}
return this.render({ size })
}
}

View File

@@ -10,10 +10,6 @@ t.create('size (with version)')
.get('/tokio/1.32.0.json')
.expectBadge({ label: 'size', message: '725 kB' })
t.create('size (with version where version doesnt have size)')
.get('/tokio/0.1.6.json')
.expectBadge({ label: 'crates.io', message: 'unknown' })
t.create('size (not found)')
.get('/not-a-crate.json')
.expectBadge({ label: 'crates.io', message: 'not found' })

View File

@@ -0,0 +1,32 @@
import { renderDownloadsBadge } from '../downloads.js'
import { pathParams } from '../index.js'
import { BaseCratesUserService, description } from './crates-base.js'
export default class CratesUserDownloads extends BaseCratesUserService {
static category = 'downloads'
static route = {
base: 'crates',
pattern: 'udt/:userId',
}
static openApi = {
'/crates/udt/{userId}': {
get: {
summary: 'Crates.io User Total Downloads',
description,
parameters: pathParams({
name: 'userId',
example: '3027',
description:
'The user ID can be found using `https://crates.io/api/v1/users/{username}`',
}),
},
},
}
async handle({ userId }) {
const json = await this.fetch({ userId })
const { total_downloads: downloads } = json
return renderDownloadsBadge({ downloads, labelOverride: 'downloads' })
}
}

View File

@@ -0,0 +1,16 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('total user downloads')
.get('/udt/3027.json')
.expectBadge({ label: 'downloads', message: isMetric })
// non-existent user returns 0 downloads with 200 OK status code rather than 404.
t.create('total user downloads (user not found)')
.get('/udt/2147483647.json') // 2147483647 is the maximum valid user id as API uses i32
.expectBadge({ label: 'downloads', message: '0' })
t.create('total user downloads (invalid)')
.get('/udt/999999999999999999999999.json')
.expectBadge({ label: 'crates.io', message: 'invalid' })

108
services/date.js Normal file
View File

@@ -0,0 +1,108 @@
/**
* Commonly-used functions for rendering badges containing a date
*
* @module
*/
import dayjs from 'dayjs'
import calendar from 'dayjs/plugin/calendar.js'
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
import { colorScale } from './color-formatters.js'
import { InvalidResponse } from './index.js'
dayjs.extend(calendar)
dayjs.extend(customParseFormat)
/**
* Parse and validate a string date into a dayjs object. Use this helper
* in preference to invoking dayjs directly when parsing a date from string.
*
* @param {...any} args - Variadic: Arguments to pass through to dayjs
* @returns {dayjs} - Parsed object
* @throws {InvalidResponse} - Error if validation fails
* @see https://day.js.org/docs/en/parse/string
* @see https://day.js.org/docs/en/parse/string-format
* @see https://day.js.org/docs/en/parse/is-valid
* @example
* parseDate('2024-01-01')
* parseDate('31/01/2024', 'DD/MM/YYYY')
* parseDate('2018 Enero 15', 'YYYY MMMM DD', 'es')
*/
function parseDate(...args) {
let date
if (args.length >= 2) {
// always use strict mode if format arg is supplied
date = dayjs(...args, true)
} else {
date = dayjs(...args)
}
if (!date.isValid()) {
throw new InvalidResponse({ prettyMessage: 'invalid date' })
}
return date
}
/**
* Returns a formatted date string without the year based on the value of input date param d.
*
* @param {Date | string | number | dayjs } d JS Date object, string, unix timestamp or dayjs object
* @returns {string} Formatted date string
*/
function formatDate(d) {
const date = parseDate(d)
const dateString = date.calendar(null, {
lastDay: '[yesterday]',
sameDay: '[today]',
lastWeek: '[last] dddd',
sameElse: 'MMMM YYYY',
})
// Trim current year from date string
return dateString.replace(` ${dayjs().year()}`, '').toLowerCase()
}
/**
* Determines the color used for a badge according to the age.
* Age is calculated as days elapsed till current date.
* The color varies from bright green to red as the age increases
* or the other way around if `reverse` is given `true`.
*
* @param {Date | string | number | dayjs } date JS Date object, string, unix timestamp or dayjs object
* @param {boolean} reversed Reverse the color scale (the older, the better)
* @returns {string} Badge color
*/
function age(date, reversed = false) {
const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, !reversed)
const daysElapsed = dayjs().diff(parseDate(date), 'days')
return colorByAge(daysElapsed)
}
/**
* Creates a badge object that displays a date
*
* @param {Date | string | number | dayjs } date JS Date object, string, unix timestamp or dayjs object
* @param {boolean} reversed Reverse the color scale (the older, the better)
* @returns {object} A badge object that has two properties: message, and color
*/
function renderDateBadge(date, reversed = false) {
const d = parseDate(date)
const color = age(d, reversed)
const message = formatDate(d)
return { message, color }
}
/**
* Returns a relative date from the input timestamp.
* For example, day after tomorrow's timestamp will return 'in 2 days'.
*
* @param {number | string} timestamp - Unix timestamp
* @returns {string} Relative date from the unix timestamp
*/
function formatRelativeDate(timestamp) {
const parsedDate = dayjs.unix(parseInt(timestamp, 10))
if (!parsedDate.isValid()) {
return 'invalid date'
}
return dayjs().to(parsedDate).toLowerCase()
}
export { parseDate, renderDateBadge, formatDate, formatRelativeDate, age }

132
services/date.spec.js Normal file
View File

@@ -0,0 +1,132 @@
import { expect } from 'chai'
import { test, given } from 'sazerac'
import sinon from 'sinon'
import { parseDate, formatDate, formatRelativeDate, age } from './date.js'
import { InvalidResponse } from './index.js'
describe('parseDate', function () {
it('parses valid inputs', function () {
expect(parseDate('2024-01-01').valueOf()).to.equal(
new Date('2024-01-01').valueOf(),
)
expect(parseDate('Jan 01 01:00:00 2024 GMT').valueOf()).to.equal(
new Date('2024-01-01T01:00:00.000Z').valueOf(),
)
expect(parseDate('31/01/2024', 'DD/MM/YYYY').valueOf()).to.equal(
new Date('2024-01-31T00:00:00.000Z').valueOf(),
)
})
it('throws when given invalid inputs', function () {
// not a date
expect(() => parseDate('foo')).to.throw(InvalidResponse)
expect(() => parseDate([])).to.throw(InvalidResponse)
expect(() => parseDate(null)).to.throw(InvalidResponse)
// invalid dates (only works with format string)
expect(() => parseDate('2024-02-31', 'YYYY-MM-DD')).to.throw(
InvalidResponse,
)
expect(() => parseDate('2024-12-32', 'YYYY-MM-DD')).to.throw(
InvalidResponse,
)
// non-standard format with no format string
expect(() => parseDate('31/01/2024')).to.throw(InvalidResponse)
// parse format doesn't match date
expect(() => parseDate('2024-01-01', 'YYYYMMDDHHmmss')).to.throw(
InvalidResponse,
)
})
test(formatDate, () => {
given(1465513200000)
.describe('when given a timestamp in june 2016')
.expect('june 2016')
})
context('in october', function () {
let clock
beforeEach(function () {
clock = sinon.useFakeTimers(new Date(2017, 9, 15).getTime())
})
afterEach(function () {
clock.restore()
})
test(formatDate, () => {
given(new Date(2017, 0, 1).getTime())
.describe('when given the beginning of this year')
.expect('january')
})
})
context('in october', function () {
let clock
beforeEach(function () {
clock = sinon.useFakeTimers(new Date(2018, 9, 29).getTime())
})
afterEach(function () {
clock.restore()
})
test(formatRelativeDate, () => {
given(new Date(2018, 9, 31).getTime() / 1000)
.describe('when given the end of october')
.expect('in 2 days')
})
test(formatRelativeDate, () => {
given(new Date(2018, 9, 1).getTime() / 1000)
.describe('when given the beginning of october')
.expect('a month ago')
})
test(formatRelativeDate, () => {
given(9999999999999)
.describe('when given invalid date')
.expect('invalid date')
})
})
const monthsAgo = months => {
const result = new Date()
// This looks wack but it works.
result.setMonth(result.getMonth() - months)
return result
}
test(age, () => {
given(Date.now())
.describe('when given the current timestamp')
.expect('brightgreen')
given(new Date())
.describe('when given the current Date')
.expect('brightgreen')
given(new Date(2001, 1, 1))
.describe('when given a Date many years ago')
.expect('red')
given(monthsAgo(2))
.describe('when given a Date two months ago')
.expect('yellowgreen')
given(monthsAgo(15))
.describe('when given a Date 15 months ago')
.expect('orange')
// --- reversed --- //
given(Date.now(), true)
.describe('when given the current timestamp and reversed')
.expect('red')
given(new Date(), true)
.describe('when given the current Date and reversed')
.expect('red')
given(new Date(2001, 1, 1), true)
.describe('when given a Date many years ago and reversed')
.expect('brightgreen')
given(monthsAgo(2), true)
.describe('when given a Date two months ago and reversed')
.expect('yellow')
given(monthsAgo(15), true)
.describe('when given a Date 15 months ago and reversed')
.expect('green')
})
})

View File

@@ -1,4 +1,4 @@
import { formatRelativeDate } from '../text-formatters.js'
import { formatRelativeDate } from '../date.js'
import { BaseService, pathParams } from '../index.js'
const description = `

View File

@@ -3,10 +3,10 @@ import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('docker version (valid, library)')
.get('/_/memcached.json')
.get('/docker/example-voting-app-vote.json')
.expectBadge({
label: 'version',
message: isSemver,
message: 'latest',
})
t.create('docker version (valid, library with tag)')

View File

@@ -160,12 +160,25 @@ t.create('query with parse error')
})
// Example from https://stackoverflow.com/q/11670384/893113
const badQuery =
const invalidTokenQuery =
"$[?(en|**|(@.object.property.one=='other') && (@.object.property.two=='something(abc/def)'))]"
t.create('query with invalid token')
.get(
`.json?url=https://github.com/badges/shields/raw/master/package.json&query=${encodeURIComponent(
badQuery,
invalidTokenQuery,
)}`,
)
.expectBadge({
label: 'custom badge',
message: 'query not supported',
color: 'red',
})
const invalidEscapequery = "$.definitions.common.properties.['@id'].format"
t.create('query with invalid escape')
.get(
`.json?url=https://raw.githubusercontent.com/json-ld/json-ld.org/refs/heads/main/schemas/jsonld-schema.json&query=${encodeURIComponent(
invalidEscapequery,
)}`,
)
.expectBadge({

View File

@@ -70,7 +70,7 @@ export default class DynamicXml extends BaseService {
static defaultBadgeData = { label: 'custom badge' }
transform({ pathExpression, buffer }) {
transform({ pathExpression, buffer, contentType = 'text/xml' }) {
// e.g. //book[2]/@id
const pathIsAttr = (
pathExpression.split('/').slice(-1)[0] || ''
@@ -78,14 +78,20 @@ export default class DynamicXml extends BaseService {
let parsed
try {
parsed = new DOMParser().parseFromString(buffer, 'text/xml')
parsed = new DOMParser().parseFromString(buffer, contentType)
} catch (e) {
throw new InvalidResponse({ prettyMessage: e.message })
}
let values
try {
values = xpath.select(pathExpression, parsed)
if (contentType === 'text/html') {
values = xpath
.parse(pathExpression)
.select({ node: parsed, isHtml: true })
} else {
values = xpath.select(pathExpression, parsed)
}
} catch (e) {
throw new InvalidParameter({ prettyMessage: e.message })
}
@@ -122,16 +128,25 @@ export default class DynamicXml extends BaseService {
}
async handle(_namedParams, { url, query: pathExpression, prefix, suffix }) {
const { buffer } = await this._request({
const { buffer, res } = await this._request({
url,
options: { headers: { Accept: 'application/xml, text/xml' } },
httpErrors,
logErrors: [],
})
let contentType = 'text/xml'
if (
res.headers['content-type'] &&
res.headers['content-type'].includes('text/html')
) {
contentType = 'text/html'
}
const { values: value } = this.transform({
pathExpression,
buffer,
contentType,
})
return renderDynamicBadge({ value, prefix, suffix })

View File

@@ -20,6 +20,29 @@ const exampleXml = `<?xml version="1.0"?>
</catalog>
`
const exampleHtml = `<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Herman Melville - Moby-Dick</h1>
<div>
<p>
Availing himself of the mild, summer-cool weather that now reigned in these
latitudes, and in preparation for the peculiarly active pursuits shortly to
be anticipated, Perth, the begrimed, blistered old blacksmith, had not
removed his portable forge to the hold again, after concluding his
contributory work for Ahab's leg, but still retained it on deck, fast lashed
to ringbolts by the foremast; being now almost incessantly invoked by the
headsmen, and harpooneers, and bowsmen to do some little job for them;
altering, or repairing, or new shaping their various weapons and boat
furniture.
</p>
</div>
</body>
</html>
`
describe('DynamicXml', function () {
describe('transform()', function () {
beforeEach(function () {
@@ -126,5 +149,12 @@ describe('DynamicXml', function () {
}).expect({
values: ["XML Developer's Guide", '44.95'],
})
given({
pathExpression: '//h1[1]',
buffer: exampleHtml,
contentType: 'text/html',
}).expect({
values: ['Herman Melville - Moby-Dick'],
})
})
})

View File

@@ -215,3 +215,16 @@ t.create('query with type conversion to number')
message: '44.95',
color: 'blue',
})
t.create('query HTML document')
.get(
`.json?${queryString.stringify({
url: 'https://httpbin.org/html',
query: '//h1[1]',
})}`,
)
.expectBadge({
label: 'custom badge',
message: 'Herman Melville - Moby-Dick',
color: 'blue',
})

View File

@@ -48,7 +48,10 @@ export default superclass =>
values = jp({ json: data, path: pathExpression, eval: false })
} catch (e) {
const { message } = e
if (message.includes('prevented in JSONPath expression')) {
if (
message.includes('prevented in JSONPath expression') ||
e instanceof TypeError
) {
throw new InvalidParameter({
prettyMessage: 'query not supported',
})
@@ -57,7 +60,7 @@ export default superclass =>
}
}
if (!values.length) {
if (!values || !values.length) {
throw new InvalidResponse({ prettyMessage: 'no result' })
}

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { pathParams } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { age as ageColor } from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { nonNegativeInteger } from '../validators.js'
import EclipseMarketplaceBase from './eclipse-marketplace-base.js'
@@ -34,19 +33,12 @@ export default class EclipseMarketplaceUpdate extends EclipseMarketplaceBase {
static defaultBadgeData = { label: 'updated' }
static render({ date }) {
return {
message: formatDate(date),
color: ageColor(date),
}
}
async handle({ name }) {
const { marketplace } = await this.fetch({
name,
schema: updateResponseSchema,
})
const date = 1000 * parseInt(marketplace.node.changed)
return this.constructor.render({ date })
return renderDateBadge(date)
}
}

View File

@@ -3,8 +3,7 @@ import {
optionalNonNegativeInteger,
nonNegativeInteger,
} from '../validators.js'
import { addv } from '../text-formatters.js'
import { version as versionColor } from '../color-formatters.js'
import { renderVersionBadge } from '../version.js'
import { BaseJsonService, NotFound, pathParam, queryParam } from '../index.js'
const schema = Joi.object({
@@ -46,13 +45,6 @@ export default class FDroid extends BaseJsonService {
static defaultBadgeData = { label: 'f-droid' }
static render({ version }) {
return {
message: addv(version),
color: versionColor(version),
}
}
async fetch({ appId }) {
const url = `https://f-droid.org/api/v1/packages/${appId}`
return this._requestJson({
@@ -83,6 +75,6 @@ export default class FDroid extends BaseJsonService {
const json = await this.fetch({ appId })
const suggested = includePre === undefined
const { version } = this.transform({ json, suggested })
return this.constructor.render({ version })
return renderVersionBadge({ version })
}
}

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { BaseJsonService, pathParams } from '../index.js'
import { age } from '../color-formatters.js'
import { formatDate } from '../text-formatters.js'
import { renderDateBadge } from '../date.js'
import { nonNegativeInteger } from '../validators.js'
import { renderDownloadsBadge } from '../downloads.js'
import { renderVersionBadge } from '../version.js'
@@ -131,18 +130,9 @@ class FactorioModPortalLastUpdated extends BaseFactorioModPortalService {
static defaultBadgeData = { label: 'last updated' }
static render({ lastUpdated }) {
return {
message: formatDate(lastUpdated),
color: age(lastUpdated),
}
}
async handle({ modName }) {
const resp = await this.fetch({ modName })
return this.constructor.render({
lastUpdated: resp.latest_release.released_at,
})
return renderDateBadge(resp.latest_release.released_at)
}
}

View File

@@ -1,11 +1,11 @@
import Joi from 'joi'
import { BaseJsonService, NotFound, pathParams } from '../index.js'
import {
renderVersionBadge,
searchServiceUrl,
stripBuildMetadata,
selectVersion,
} from '../nuget/nuget-helpers.js'
import { renderVersionBadge } from '../version.js'
const singlePageSchema = Joi.object({
'@id': Joi.string().required(),
@@ -64,10 +64,6 @@ class FeedzVersionService extends BaseJsonService {
label: 'feedz',
}
static render(props) {
return renderVersionBadge(props)
}
apiUrl({ organization, repository }) {
return `https://f.feedz.io/${organization}/${repository}/nuget`
}
@@ -122,9 +118,9 @@ class FeedzVersionService extends BaseJsonService {
const json = await this.fetch({ baseUrl, packageName })
const fetchedJson = await this.fetchItems({ json })
const version = this.transform({ json: fetchedJson, includePrereleases })
return this.constructor.render({
return renderVersionBadge({
version,
feed: FeedzVersionService.defaultBadgeData.label,
defaultLabel: FeedzVersionService.defaultBadgeData.label,
})
}
}

View File

@@ -24,14 +24,6 @@ t.create('version (valid)')
color: 'blue',
})
t.create('version (yellow badge)')
.get('/feedz/v/shieldstests/public/Shields.TestPreOnly.json')
.expectBadge({
label: 'feedz',
message: 'v0.1.0-pre',
color: 'yellow',
})
t.create('version (orange badge)')
.get('/feedz/v/shieldstests/public/Shields.NoV1.json')
.expectBadge({
@@ -77,14 +69,6 @@ t.create('version (pre) (valid)')
color: 'blue',
})
t.create('version (pre) (yellow badge)')
.get('/feedz/vpre/shieldstests/public/Shields.TestPreOnly.json')
.expectBadge({
label: 'feedz',
message: 'v0.1.0-pre',
color: 'yellow',
})
t.create('version (pre) (orange badge)')
.get('/feedz/vpre/shieldstests/public/Shields.NoV1.json')
.expectBadge({

View File

@@ -1,5 +1,5 @@
import { pathParams } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { renderDateBadge } from '../date.js'
import BaseGalaxyToolshedService from './galaxytoolshed-base.js'
export default class GalaxyToolshedCreatedDate extends BaseGalaxyToolshedService {
@@ -29,11 +29,6 @@ export default class GalaxyToolshedCreatedDate extends BaseGalaxyToolshedService
static defaultBadgeData = {
label: 'created date',
color: 'blue',
}
static render({ date }) {
return { message: formatDate(date) }
}
async handle({ repository, owner }) {
@@ -42,6 +37,6 @@ export default class GalaxyToolshedCreatedDate extends BaseGalaxyToolshedService
owner,
})
const { create_time: date } = response[0]
return this.constructor.render({ date })
return renderDateBadge(date, true)
}
}

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { age as ageColor } from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { pathParam, queryParam } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { optionalUrl, relativeUri } from '../validators.js'
import GiteaBase from './gitea-base.js'
import { description, httpErrorsFor } from './gitea-helper.js'
@@ -114,13 +113,6 @@ export default class GiteaLastCommit extends GiteaBase {
static defaultBadgeData = { label: 'last commit' }
static render({ commitDate }) {
return {
message: formatDate(commitDate),
color: ageColor(Date.parse(commitDate)),
}
}
async fetch({ user, repo, branch, baseUrl, path }) {
// https://gitea.com/api/swagger#/repository
return super.fetch({
@@ -146,8 +138,6 @@ export default class GiteaLastCommit extends GiteaBase {
baseUrl,
path,
})
return this.constructor.render({
commitDate: body[0].commit[displayTimestamp].date,
})
return renderDateBadge(body[0].commit[displayTimestamp].date)
}
}

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { pathParams } from '../../index.js'
import { formatDate } from '../../text-formatters.js'
import { age as ageColor } from '../../color-formatters.js'
import { renderDateBadge } from '../../date.js'
import { GithubAuthV3Service } from '../github-auth-service.js'
import { documentation, httpErrorsFor } from '../github-helpers.js'
@@ -27,13 +26,6 @@ export default class GistLastCommit extends GithubAuthV3Service {
static defaultBadgeData = { label: 'last commit' }
static render({ commitDate }) {
return {
message: formatDate(commitDate),
color: ageColor(Date.parse(commitDate)),
}
}
async fetch({ gistId }) {
return this._requestJson({
url: `/gists/${gistId}`,
@@ -44,6 +36,6 @@ export default class GistLastCommit extends GithubAuthV3Service {
async handle({ gistId }) {
const { updated_at: commitDate } = await this.fetch({ gistId })
return this.constructor.render({ commitDate })
return renderDateBadge(commitDate)
}
}

View File

@@ -1,8 +1,6 @@
import dayjs from 'dayjs'
import Joi from 'joi'
import { age } from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { pathParams } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, httpErrorsFor } from './github-helpers.js'
@@ -34,14 +32,6 @@ export default class GithubCreatedAt extends GithubAuthV3Service {
static defaultBadgeData = { label: 'created at' }
static render({ createdAt }) {
const date = dayjs(createdAt)
return {
message: formatDate(date),
color: age(date, true),
}
}
async handle({ user, repo }) {
const { created_at: createdAt } = await this._requestJson({
schema,
@@ -49,6 +39,6 @@ export default class GithubCreatedAt extends GithubAuthV3Service {
httpErrors: httpErrorsFor('repo not found'),
})
return this.constructor.render({ createdAt })
return renderDateBadge(createdAt, true)
}
}

View File

@@ -2,6 +2,7 @@ import gql from 'graphql-tag'
import Joi from 'joi'
import dayjs from 'dayjs'
import { pathParam, queryParam } from '../index.js'
import { parseDate } from '../date.js'
import { metric, maybePluralize } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { GithubAuthV4Service } from './github-auth-service.js'
@@ -52,7 +53,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
static category = 'issue-tracking'
static route = {
base: 'github/hacktoberfest',
pattern: ':year(2019|2020|2021|2022|2023)/:user/:repo',
pattern: ':year(2019|2020|2021|2022|2023|2024)/:user/:repo',
queryParamSchema,
}
@@ -64,7 +65,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
parameters: [
pathParam({
name: 'year',
example: '2023',
example: '2024',
schema: { type: 'string', enum: this.getEnum('year') },
}),
pathParam({ name: 'user', example: 'tmrowco' }),
@@ -97,7 +98,7 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
// The global cutoff time is 11/1 noon UTC.
// https://github.com/badges/shields/pull/4109#discussion_r330782093
// We want to show "1 day left" on the last day so we add 1.
daysLeft = dayjs(`${year}-11-01 12:00:00 Z`).diff(dayjs(), 'days') + 1
daysLeft = parseDate(`${year}-11-01 12:00:00 Z`).diff(dayjs(), 'days') + 1
}
if (daysLeft < 0) {
return {
@@ -181,7 +182,10 @@ export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Servi
}
static getCalendarPosition(year) {
const daysToStart = dayjs(`${year}-10-01 00:00:00 Z`).diff(dayjs(), 'days')
const daysToStart = parseDate(`${year}-10-01 00:00:00 Z`).diff(
dayjs(),
'days',
)
const isBefore = daysToStart > 0
return { daysToStart, isBefore }
}

View File

@@ -2,10 +2,9 @@ import { colorScale } from '../color-formatters.js'
import { InvalidResponse, NotFound } from '../index.js'
const documentation = `
If your GitHub badge errors, it might be because you hit GitHub's rate limits.
You can increase Shields.io's rate limit by
[adding the Shields GitHub application](https://img.shields.io/github-auth)
using your GitHub account.
You can help increase Shields.io's rate limit by
[authorizing the Shields.io GitHub application](https://img.shields.io/github-auth).
Read more about [how it works](/blog/token-pool).
`
function issueStateColor(s) {

View File

@@ -1,7 +1,7 @@
import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { formatDate, metric } from '../text-formatters.js'
import { age } from '../color-formatters.js'
import { metric } from '../text-formatters.js'
import { renderDateBadge } from '../date.js'
import { InvalidResponse, pathParams } from '../index.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import {
@@ -133,11 +133,13 @@ const ageUpdateMap = {
}).required(),
transform: ({ json, property }) =>
property === 'age' ? json.created_at : json.updated_at,
render: ({ property, value }) => ({
color: age(value),
label: property === 'age' ? 'created' : 'updated',
message: formatDate(value),
}),
render: ({ property, value }) => {
const label = property === 'age' ? 'created' : 'updated'
return {
...renderDateBadge(value),
label,
}
},
}
const milestoneMap = {

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai'
import { test, given } from 'sazerac'
import { age } from '../color-formatters.js'
import { formatDate, metric } from '../text-formatters.js'
import { age, formatDate } from '../date.js'
import { metric } from '../text-formatters.js'
import { InvalidResponse } from '../index.js'
import GithubIssueDetail from './github-issue-detail.service.js'
import { issueStateColor, commentsColor } from './github-helpers.js'

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { age as ageColor } from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { NotFound, pathParam, queryParam } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { relativeUri } from '../validators.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, httpErrorsFor } from './github-helpers.js'
@@ -88,13 +87,6 @@ export default class GithubLastCommit extends GithubAuthV3Service {
static defaultBadgeData = { label: 'last commit' }
static render({ commitDate }) {
return {
message: formatDate(commitDate),
color: ageColor(Date.parse(commitDate)),
}
}
async fetch({ user, repo, branch, path }) {
return this._requestJson({
url: `/repos/${user}/${repo}/commits`,
@@ -111,8 +103,6 @@ export default class GithubLastCommit extends GithubAuthV3Service {
if (!commit) throw new NotFound({ prettyMessage: 'no commits found' })
return this.constructor.render({
commitDate: commit[displayTimestamp].date,
})
return renderDateBadge(commit[displayTimestamp].date)
}
}

View File

@@ -11,14 +11,14 @@ export const t = new ServiceTester({
t.create('Manifest version')
.get('/v/sindresorhus/show-all-github-issues.json')
.expectBadge({
label: 'version',
label: 'manifest',
message: isVPlusDottedVersionAtLeastOne,
})
t.create('Manifest version (path)')
.get('/v/RedSparr0w/IndieGala-Helper.json?filename=extension/manifest.json')
.expectBadge({
label: 'version',
label: 'manifest',
message: isVPlusDottedVersionAtLeastOne,
})

View File

@@ -1,13 +1,13 @@
import Joi from 'joi'
import { ServiceTester } from '../tester.js'
import {
isCommitHash,
isVPlusDottedVersionAtLeastOne,
isVPlusDottedVersionNClausesWithOptionalSuffix,
} from '../test-validators.js'
// e.g. v19.3b0
const isBlackVersion = Joi.string().regex(/^v\d+(\.\d+)*(.*)?$/)
const isShortSha = Joi.string().regex(/[0-9a-f]{7}/)
export const t = new ServiceTester({
id: 'GithubPipenv',
@@ -82,10 +82,8 @@ t.create('Locked version of unknown dependency')
})
t.create('Locked version of VCS dependency')
.get(
'/locked/dependency-version/thorn-oss/perception/dev/videoalignment.json',
)
.get('/locked/dependency-version/pypa/pipenv/dev/pypiserver.json')
.expectBadge({
label: 'videoalignment',
message: isShortSha,
label: 'pypiserver',
message: isCommitHash,
})

View File

@@ -1,8 +1,6 @@
import dayjs from 'dayjs'
import Joi from 'joi'
import { pathParam, queryParam } from '../index.js'
import { age } from '../color-formatters.js'
import { formatDate } from '../text-formatters.js'
import { renderDateBadge } from '../date.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, httpErrorsFor } from './github-helpers.js'
@@ -63,14 +61,6 @@ export default class GithubReleaseDate extends GithubAuthV3Service {
static defaultBadgeData = { label: 'release date' }
static render({ date }) {
const releaseDate = dayjs(date)
return {
message: formatDate(releaseDate),
color: age(releaseDate),
}
}
async fetch({ variant, user, repo }) {
const url =
variant === 'release-date'
@@ -86,10 +76,8 @@ export default class GithubReleaseDate extends GithubAuthV3Service {
async handle({ variant, user, repo }, queryParams) {
const body = await this.fetch({ variant, user, repo })
if (Array.isArray(body)) {
return this.constructor.render({
date: body[0][queryParams.display_date],
})
return renderDateBadge(body[0][queryParams.display_date])
}
return this.constructor.render({ date: body[queryParams.display_date] })
return renderDateBadge(body[queryParams.display_date])
}
}

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { addv } from '../text-formatters.js'
import { version as versionColor } from '../color-formatters.js'
import { redirector, pathParam, queryParam } from '../index.js'
import { renderVersionBadge } from '../version.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import {
fetchLatestRelease,
@@ -46,13 +45,6 @@ class GithubRelease extends GithubAuthV3Service {
static defaultBadgeData = { label: 'release' }
static render({ version, sort, isPrerelease }) {
let color = 'blue'
color = sort === 'semver' ? versionColor(version) : color
color = isPrerelease ? 'orange' : color
return { message: addv(version), color }
}
static transform(latestRelease, display) {
const { name, tag_name: tagName, prerelease: isPrerelease } = latestRelease
if (display === 'tag') {
@@ -72,9 +64,8 @@ class GithubRelease extends GithubAuthV3Service {
latestRelease,
queryParams.display_name,
)
return this.constructor.render({
return renderVersionBadge({
version,
sort: queryParams.sort,
isPrerelease,
})
}

View File

@@ -1,9 +1,7 @@
import gql from 'graphql-tag'
import Joi from 'joi'
import { matcher } from 'matcher'
import { addv } from '../text-formatters.js'
import { version as versionColor } from '../color-formatters.js'
import { latest } from '../version.js'
import { latest, renderVersionBadge } from '../version.js'
import { NotFound, redirector, pathParam } from '../index.js'
import { GithubAuthV4Service } from './github-auth-service.js'
import {
@@ -55,13 +53,6 @@ class GithubTag extends GithubAuthV4Service {
label: 'tag',
}
static render({ version, sort }) {
return {
message: addv(version),
color: sort === 'semver' ? versionColor(version) : 'blue',
}
}
static getLimit({ sort, filter }) {
if (!filter && sort === 'date') {
return 1
@@ -123,13 +114,12 @@ class GithubTag extends GithubAuthV4Service {
const prettyMessage = filter ? 'no matching tags found' : 'no tags found'
throw new NotFound({ prettyMessage })
}
return this.constructor.render({
return renderVersionBadge({
version: this.constructor.getLatestTag({
tags,
sort,
includePrereleases,
}),
sort,
})
}
}

View File

@@ -43,17 +43,6 @@ describe('GithubTag', function () {
}).expect('1.2.0-beta')
})
test(GithubTag.render, () => {
given({ usingSemver: false, version: '1.2.3' }).expect({
message: 'v1.2.3',
color: 'blue',
})
given({ usingSemver: true, version: '2.0.0' }).expect({
message: 'v2.0.0',
color: 'blue',
})
})
test(GithubTag.getLimit, () => {
given({ sort: 'date', filter: undefined }).expect(1)
given({ sort: 'date', filter: '' }).expect(1)

View File

@@ -1,7 +1,6 @@
import Joi from 'joi'
import { age as ageColor } from '../color-formatters.js'
import { renderDateBadge } from '../date.js'
import { NotFound, pathParam, queryParam } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { optionalUrl, relativeUri } from '../validators.js'
import GitLabBase from './gitlab-base.js'
import { description, httpErrorsFor } from './gitlab-helper.js'
@@ -66,13 +65,6 @@ export default class GitlabLastCommit extends GitLabBase {
static defaultBadgeData = { label: 'last commit' }
static render({ commitDate }) {
return {
message: formatDate(commitDate),
color: ageColor(Date.parse(commitDate)),
}
}
async fetch({ project, baseUrl, ref, path }) {
// https://docs.gitlab.com/ee/api/commits.html#list-repository-commits
return super.fetch({
@@ -94,6 +86,6 @@ export default class GitlabLastCommit extends GitLabBase {
if (!commit) throw new NotFound({ prettyMessage: 'no commits found' })
return this.constructor.render({ commitDate: commit.committed_date })
return renderDateBadge(commit.committed_date)
}
}

View File

@@ -1,8 +1,6 @@
import Joi from 'joi'
import { version as versionColor } from '../color-formatters.js'
import { optionalUrl } from '../validators.js'
import { latest } from '../version.js'
import { addv } from '../text-formatters.js'
import { latest, renderVersionBadge } from '../version.js'
import { NotFound, pathParam, queryParam } from '../index.js'
import { description, httpErrorsFor } from './gitlab-helper.js'
import GitLabBase from './gitlab-base.js'
@@ -63,13 +61,6 @@ export default class GitlabTag extends GitLabBase {
static defaultBadgeData = { label: 'tag' }
static render({ version, sort }) {
return {
message: addv(version),
color: sort === 'semver' ? versionColor(version) : 'blue',
}
}
async fetch({ project, baseUrl }) {
// https://docs.gitlab.com/ee/api/tags.html
// N.B. the documentation has contradictory information about default sort order.
@@ -114,6 +105,6 @@ export default class GitlabTag extends GitLabBase {
sort,
includePrereleases: pre !== undefined,
})
return this.constructor.render({ version, sort })
return renderVersionBadge({ version })
}
}

View File

@@ -39,6 +39,7 @@ describe('GitLabTag', function () {
).to.deep.equal({
message: 'v1.9',
color: 'blue',
label: undefined,
})
scope.done()

View File

@@ -1,48 +1,11 @@
import { BaseService, pathParams } from '../index.js'
import { deprecatedService } from '../index.js'
export default class HackageDeps extends BaseService {
static category = 'dependencies'
static route = {
export const HackageDeps = deprecatedService({
category: 'dependencies',
route: {
base: 'hackage-deps/v',
pattern: ':packageName',
}
static openApi = {
'/hackage-deps/v/{packageName}': {
get: {
summary: 'Hackage Dependencies',
parameters: pathParams({
name: 'packageName',
example: 'lens',
}),
},
},
}
static defaultBadgeData = { label: 'dependencies' }
static render({ isOutdated }) {
if (isOutdated) {
return { message: 'outdated', color: 'orange' }
} else {
return { message: 'up to date', color: 'brightgreen' }
}
}
async handle({ packageName }) {
const reverseUrl = `http://packdeps.haskellers.com/licenses/${packageName}`
const feedUrl = `http://packdeps.haskellers.com/feed/${packageName}`
// first call /reverse to check if the package exists
// this will throw a 404 if it doesn't
await this._request({ url: reverseUrl })
// if the package exists, then query /feed to check the dependencies
const { buffer } = await this._request({ url: feedUrl })
const outdatedStr = `Outdated dependencies for ${packageName} `
const isOutdated = buffer.includes(outdatedStr)
return this.constructor.render({ isOutdated })
}
}
},
label: 'hackagedeps',
dateAdded: new Date('2024-10-18'),
})

View File

@@ -1,14 +1,10 @@
import Joi from 'joi'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
import { ServiceTester } from '../tester.js'
export const t = new ServiceTester({
id: 'hackagedeps',
title: 'Hackage Dependencies',
pathPrefix: '/hackage-deps/v',
})
t.create('hackage deps (valid)')
.get('/lens.json')
.expectBadge({
label: 'dependencies',
message: Joi.string().regex(/^(up to date|outdated)$/),
})
t.create('hackage deps (not found)')
.get('/not-a-package.json')
.expectBadge({ label: 'dependencies', message: 'not found' })
t.create('hackage deps (deprecated)')
.get('/package.json')
.expectBadge({ label: 'hackagedeps', message: 'no longer available' })

View File

@@ -0,0 +1,82 @@
import Joi from 'joi'
import { renderDownloadsBadge } from '../downloads.js'
import { BaseJsonService, pathParams } from '../index.js'
import { nonNegativeInteger } from '../validators.js'
function getSchema({ cask }) {
return Joi.object({
analytics: Joi.object({
install: Joi.object({
'30d': Joi.object({ [cask]: nonNegativeInteger }).required(),
'90d': Joi.object({ [cask]: nonNegativeInteger }).required(),
'365d': Joi.object({ [cask]: nonNegativeInteger }).required(),
}).required(),
}).required(),
}).required()
}
const periodMap = {
dm: {
api_field: '30d',
interval: 'month',
},
dq: {
api_field: '90d',
interval: 'quarter',
},
dy: {
api_field: '365d',
interval: 'year',
},
}
export default class HomebrewCaskDownloads extends BaseJsonService {
static category = 'downloads'
static route = {
base: 'homebrew/cask/installs',
pattern: ':interval(dm|dq|dy)/:cask',
}
static openApi = {
'/homebrew/cask/installs/{interval}/{cask}': {
get: {
summary: 'Homebrew Cask Downloads',
parameters: pathParams(
{
name: 'interval',
example: 'dm',
schema: { type: 'string', enum: this.getEnum('interval') },
description: 'Monthly, Quarterly or Yearly downloads',
},
{
name: 'cask',
example: 'freetube',
},
),
},
},
}
static defaultBadgeData = { label: 'downloads' }
async fetch({ cask }) {
const schema = getSchema({ cask })
return this._requestJson({
schema,
url: `https://formulae.brew.sh/api/cask/${cask}.json`,
httpErrors: { 404: 'cask not found' },
})
}
async handle({ interval, cask }) {
const {
analytics: { install },
} = await this.fetch({ cask })
return renderDownloadsBadge({
downloads: install[periodMap[interval].api_field][cask],
interval: periodMap[interval].interval,
})
}
}

View File

@@ -0,0 +1,28 @@
import { createServiceTester } from '../tester.js'
import { isMetricOverTimePeriod } from '../test-validators.js'
export const t = await createServiceTester()
t.create('daily downloads (valid)')
.get('/dm/freetube.json')
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
t.create('yearly downloads (valid)')
.get('/dq/freetube.json')
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
t.create('yearly downloads (valid)')
.get('/dy/freetube.json')
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
t.create('daily downloads (not found)')
.get('/dm/not-a-package.json')
.expectBadge({ label: 'downloads', message: 'cask not found' })
t.create('yearly downloads (not found)')
.get('/dq/not-a-package.json')
.expectBadge({ label: 'downloads', message: 'cask not found' })
t.create('yearly downloads (not found)')
.get('/dy/not-a-package.json')
.expectBadge({ label: 'downloads', message: 'cask not found' })

View File

@@ -13,7 +13,7 @@ export default class HomebrewCask extends BaseJsonService {
static openApi = {
'/homebrew/cask/v/{cask}': {
get: {
summary: 'homebrew cask',
summary: 'Homebrew Cask Version',
parameters: pathParams({
name: 'cask',
example: 'iterm2',

View File

@@ -41,7 +41,7 @@ export default class HomebrewDownloads extends BaseJsonService {
static openApi = {
'/homebrew/installs/{interval}/{formula}': {
get: {
summary: 'homebrew downloads',
summary: 'Homebrew Formula Downloads',
parameters: pathParams(
{
name: 'interval',

View File

@@ -16,7 +16,7 @@ export default class HomebrewVersion extends BaseJsonService {
static openApi = {
'/homebrew/v/{formula}': {
get: {
summary: 'homebrew version',
summary: 'Homebrew Formula Version',
parameters: pathParams({
name: 'formula',
example: 'cake',

View File

@@ -68,7 +68,7 @@ export default class JenkinsPluginInstalls extends BaseJsonService {
async fetch({ plugin, version }) {
return this._requestJson({
url: `https://old.stats.jenkins.io/plugin-installation-trend/${plugin}.stats.json`,
url: `https://stats.jenkins.io/plugin-installation-trend/${plugin}.stats.json`,
schema: version ? schemaInstallationsPerVersion : schemaInstallations,
httpErrors: {
404: 'plugin not found',

View File

@@ -1,6 +1,6 @@
import Joi from 'joi'
import { addv } from '../text-formatters.js'
import { BaseJsonService, NotFound, pathParams } from '../index.js'
import { renderVersionBadge } from '../version.js'
import { latestVersion } from './luarocks-version-helpers.js'
const schema = Joi.object({
@@ -42,25 +42,6 @@ export default class Luarocks extends BaseJsonService {
label: 'luarocks',
}
static render({ version }) {
// The badge colors are following the heuristic rule where `scm < dev <
// stable` (e.g., `scm-1` < `dev-1` < `0.1.0-1`).
let color
switch (version.slice(0, 3).toLowerCase()) {
case 'dev':
color = 'yellow'
break
case 'scm':
case 'cvs':
color = 'orange'
break
default:
color = 'brightgreen'
}
return { message: addv(version), color }
}
async fetch({ user, moduleName }) {
const { repository } = await this._requestJson({
url: `https://luarocks.org/manifests/${encodeURIComponent(
@@ -91,6 +72,6 @@ export default class Luarocks extends BaseJsonService {
const versions = Object.keys(moduleInfo)
version = latestVersion(versions)
}
return this.constructor.render({ version })
return renderVersionBadge({ version })
}
}

View File

@@ -1,12 +0,0 @@
import { test, given } from 'sazerac'
import Luarocks from './luarocks.service.js'
test(Luarocks.render, () => {
given({ version: 'dev-1' }).expect({ message: 'dev-1', color: 'yellow' })
given({ version: 'scm-1' }).expect({ message: 'scm-1', color: 'orange' })
given({ version: 'cvs-1' }).expect({ message: 'cvs-1', color: 'orange' })
given({ version: '0.1-1' }).expect({
message: 'v0.1-1',
color: 'brightgreen',
})
})

View File

@@ -0,0 +1,13 @@
import { BaseXmlService } from '../index.js'
export default class MavenCentralBase extends BaseXmlService {
async fetch({ groupId, artifactId, schema }) {
const group = encodeURIComponent(groupId).replace(/\./g, '/')
const artifact = encodeURIComponent(artifactId)
return this._requestXml({
schema,
url: `https://repo1.maven.org/maven2/${group}/${artifact}/maven-metadata.xml`,
httpErrors: { 404: 'artifact not found' },
})
}
}

View File

@@ -0,0 +1,51 @@
import Joi from 'joi'
import { pathParams } from '../index.js'
import { parseDate, renderDateBadge } from '../date.js'
import { nonNegativeInteger } from '../validators.js'
import MavenCentralBase from './maven-central-base.js'
const updateResponseSchema = Joi.object({
metadata: Joi.object({
versioning: Joi.object({
lastUpdated: nonNegativeInteger,
}).required(),
}).required(),
}).required()
export default class MavenCentralLastUpdate extends MavenCentralBase {
static category = 'activity'
static route = {
base: 'maven-central/last-update',
pattern: ':groupId/:artifactId',
}
static openApi = {
'/maven-central/last-update/{groupId}/{artifactId}': {
get: {
summary: 'Maven Central Last Update',
parameters: pathParams(
{ name: 'groupId', example: 'com.google.guava' },
{ name: 'artifactId', example: 'guava' },
),
},
},
}
static defaultBadgeData = { label: 'last updated' }
async handle({ groupId, artifactId }) {
const { metadata } = await this.fetch({
groupId,
artifactId,
schema: updateResponseSchema,
})
const date = parseDate(
String(metadata.versioning.lastUpdated),
'YYYYMMDDHHmmss',
)
return renderDateBadge(date)
}
}

View File

@@ -0,0 +1,15 @@
import { isFormattedDate } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('last update date').get('/com.google.guava/guava.json').expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last update when artifact not found')
.get('/com.fail.test/this-does-not-exist.json')
.expectBadge({
label: 'last updated',
message: 'artifact not found',
})

View File

@@ -3,12 +3,6 @@ import {
isMetric,
isVPlusDottedVersionNClausesWithOptionalSuffix,
} from '../test-validators.js'
import {
queryIndex,
nuGetV3VersionJsonWithDash,
nuGetV3VersionJsonFirstCharZero,
nuGetV3VersionJsonFirstCharNotZero,
} from '../nuget-fixtures.js'
import { invalidJSON } from '../response-fixtures.js'
export const t = new ServiceTester({
@@ -75,66 +69,6 @@ t.create('version (tenant)')
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
})
t.create('version (yellow badge)')
.get('/myget/mongodb/v/MongoDB.Driver.Core.json')
.intercept(nock =>
nock('https://www.myget.org')
.get('/F/mongodb/api/v3/index.json')
.reply(200, queryIndex),
)
.intercept(nock =>
nock('https://api-v2v3search-0.nuget.org')
.get(
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2',
)
.reply(200, nuGetV3VersionJsonWithDash),
)
.expectBadge({
label: 'mongodb',
message: 'v1.2-beta',
color: 'yellow',
})
t.create('version (orange badge)')
.get('/myget/mongodb/v/MongoDB.Driver.Core.json')
.intercept(nock =>
nock('https://www.myget.org')
.get('/F/mongodb/api/v3/index.json')
.reply(200, queryIndex),
)
.intercept(nock =>
nock('https://api-v2v3search-0.nuget.org')
.get(
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2',
)
.reply(200, nuGetV3VersionJsonFirstCharZero),
)
.expectBadge({
label: 'mongodb',
message: 'v0.35',
color: 'orange',
})
t.create('version (blue badge)')
.get('/myget/mongodb/v/MongoDB.Driver.Core.json')
.intercept(nock =>
nock('https://www.myget.org')
.get('/F/mongodb/api/v3/index.json')
.reply(200, queryIndex),
)
.intercept(nock =>
nock('https://api-v2v3search-0.nuget.org')
.get(
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2',
)
.reply(200, nuGetV3VersionJsonFirstCharNotZero),
)
.expectBadge({
label: 'mongodb',
message: 'v1.2.7',
color: 'blue',
})
t.create('version (not found)')
.get('/myget/foo/v/not-a-real-package.json')
.expectBadge({ label: 'myget', message: 'package not found' })
@@ -148,66 +82,6 @@ t.create('version (pre) (valid)')
message: isVPlusDottedVersionNClausesWithOptionalSuffix,
})
t.create('version (pre) (yellow badge)')
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json')
.intercept(nock =>
nock('https://www.myget.org')
.get('/F/mongodb/api/v3/index.json')
.reply(200, queryIndex),
)
.intercept(nock =>
nock('https://api-v2v3search-0.nuget.org')
.get(
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2',
)
.reply(200, nuGetV3VersionJsonWithDash),
)
.expectBadge({
label: 'mongodb',
message: 'v1.2-beta',
color: 'yellow',
})
t.create('version (pre) (orange badge)')
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json')
.intercept(nock =>
nock('https://www.myget.org')
.get('/F/mongodb/api/v3/index.json')
.reply(200, queryIndex),
)
.intercept(nock =>
nock('https://api-v2v3search-0.nuget.org')
.get(
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2',
)
.reply(200, nuGetV3VersionJsonFirstCharZero),
)
.expectBadge({
label: 'mongodb',
message: 'v0.35',
color: 'orange',
})
t.create('version (pre) (blue badge)')
.get('/myget/mongodb/vpre/MongoDB.Driver.Core.json')
.intercept(nock =>
nock('https://www.myget.org')
.get('/F/mongodb/api/v3/index.json')
.reply(200, queryIndex),
)
.intercept(nock =>
nock('https://api-v2v3search-0.nuget.org')
.get(
'/query?q=packageid%3Amongodb.driver.core&prerelease=true&semVerLevel=2',
)
.reply(200, nuGetV3VersionJsonFirstCharNotZero),
)
.expectBadge({
label: 'mongodb',
message: 'v1.2.7',
color: 'blue',
})
t.create('version (pre) (not found)')
.get('/myget/foo/vpre/not-a-real-package.json')
.expectBadge({ label: 'myget', message: 'package not found' })

View File

@@ -1,6 +1,5 @@
import Joi from 'joi'
import { version as versionColor } from '../color-formatters.js'
import { addv } from '../text-formatters.js'
import { renderVersionBadge } from '../version.js'
import {
optionalUrl,
optionalDottedVersionNClausesWithOptionalSuffix,
@@ -143,13 +142,6 @@ export default class Nexus extends BaseJsonService {
label: 'nexus',
}
static render({ version }) {
return {
message: addv(version),
color: versionColor(version),
}
}
addQueryParamsToQueryString({ searchParams, queryOpt }) {
// Users specify query options with 'key=value' pairs, using a
// colon delimiter between pairs ([:k1=v1[:k2=v2[...]]]).
@@ -321,6 +313,6 @@ export default class Nexus extends BaseJsonService {
})
const { version } = this.transform({ repo, json, actualNexusVersion })
return this.constructor.render({ version })
return renderVersionBadge({ version })
}
}

View File

@@ -154,6 +154,7 @@ describe('Nexus', function () {
},
),
).to.deep.equal({
label: undefined,
message: 'v2.3.4',
color: 'blue',
})

View File

@@ -258,7 +258,7 @@ t.create('Nexus 3 - search release version of an nonexistent artifact')
t.create('Nexus 3 - search snapshot version valid snapshot artifact')
.get(
'/s/com.tomkeuper.bedwars/bedwars-api.json?server=https://repo.tomkeuper.com&nexusVersion=3',
'/s/net.voxelpi.event/event.json?server=https://repo.voxelpi.net&nexusVersion=3',
)
.expectBadge({
label: 'nexus',

View File

@@ -81,8 +81,11 @@ export default class NpmBase extends BaseJsonService {
}
async _requestJson(data) {
return super._requestJson(
this.authHelper.withBearerAuthHeader({
let payload
if (data?.options?.headers?.Accept) {
payload = data
} else {
payload = {
...data,
options: {
headers: {
@@ -91,8 +94,9 @@ export default class NpmBase extends BaseJsonService {
Accept: '*/*',
},
},
}),
)
}
}
return super._requestJson(this.authHelper.withBearerAuthHeader(payload))
}
async fetchPackageData({ registryUrl, scope, packageName, tag }) {
@@ -143,4 +147,37 @@ export default class NpmBase extends BaseJsonService {
return this.constructor._validate(packageData, packageDataSchema)
}
async fetch({
registryUrl,
scope,
packageName,
schema,
abbreviated = false,
}) {
registryUrl = registryUrl || this.constructor.defaultRegistryUrl
let url
if (scope === undefined) {
url = `${registryUrl}/${packageName}`
} else {
const scoped = this.constructor.encodeScopedPackage({
scope,
packageName,
})
url = `${registryUrl}/${scoped}`
}
// https://github.com/npm/registry/blob/main/docs/responses/package-metadata.md
const options = abbreviated
? { headers: { Accept: 'application/vnd.npm.install-v1+json' } }
: {}
return this._requestJson({
url,
schema,
options,
httpErrors: { 404: 'package not found' },
})
}
}

View File

@@ -34,7 +34,7 @@ describe('npm', function () {
await NpmVersion.invoke(defaultContext, config, { packageName: 'npm' }),
).to.deep.equal({
color: 'orange',
label: undefined,
label: 'npm',
message: 'v0.1.0',
})

View File

@@ -0,0 +1,119 @@
import Joi from 'joi'
import { NotFound, pathParam, queryParam } from '../index.js'
import { renderDateBadge } from '../date.js'
import NpmBase, {
packageNameDescription,
queryParamSchema,
} from './npm-base.js'
const fullSchema = Joi.object({
time: Joi.object()
.pattern(Joi.string().required(), Joi.string().required())
.required(),
'dist-tags': Joi.object()
.pattern(Joi.string().required(), Joi.string().required())
.required(),
}).required()
const abbreviatedSchema = Joi.object({
modified: Joi.string().required(),
}).required()
export class NpmLastUpdateWithTag extends NpmBase {
static category = 'activity'
static route = {
base: 'npm/last-update',
pattern: ':scope(@[^/]+)?/:packageName/:tag',
queryParamSchema,
}
static defaultBadgeData = { label: 'last updated' }
static openApi = {
'/npm/last-update/{packageName}/{tag}': {
get: {
summary: 'NPM Last Update (with dist tag)',
parameters: [
pathParam({
name: 'packageName',
example: 'verdaccio',
packageNameDescription,
}),
pathParam({
name: 'tag',
example: 'next-8',
}),
queryParam({
name: 'registry_uri',
example: 'https://registry.npmjs.com',
}),
],
},
},
}
async handle(namedParams, queryParams) {
const { scope, packageName, tag, registryUrl } =
this.constructor.unpackParams(namedParams, queryParams)
const packageData = await this.fetch({
registryUrl,
scope,
packageName,
schema: fullSchema,
})
const tagVersion = packageData['dist-tags'][tag]
if (!tagVersion) {
throw new NotFound({ prettyMessage: 'tag not found' })
}
return renderDateBadge(packageData.time[tagVersion])
}
}
export class NpmLastUpdate extends NpmBase {
static category = 'activity'
static route = this.buildRoute('npm/last-update', { withTag: false })
static defaultBadgeData = { label: 'last updated' }
static openApi = {
'/npm/last-update/{packageName}': {
get: {
summary: 'NPM Last Update',
parameters: [
pathParam({
name: 'packageName',
example: 'verdaccio',
packageNameDescription,
}),
queryParam({
name: 'registry_uri',
example: 'https://registry.npmjs.com',
}),
],
},
},
}
async handle(namedParams, queryParams) {
const { scope, packageName, registryUrl } = this.constructor.unpackParams(
namedParams,
queryParams,
)
const packageData = await this.fetch({
registryUrl,
scope,
packageName,
schema: abbreviatedSchema,
abbreviated: true,
})
return renderDateBadge(packageData.modified)
}
}

View File

@@ -0,0 +1,81 @@
import { isFormattedDate } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('last updated date, no tag, valid package')
.get('/verdaccio.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last updated date, no tag, invalid package')
.get('/not-a-package.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})
t.create('last updated date, no tag, custom repository, valid package')
.get('/verdaccio.json?registry_uri=https://registry.npmjs.com')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last updated date, no tag, valid package with scope')
.get('/@npm/types.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last updated date, no tag, invalid package with scope')
.get('/@not-a-scoped-package/not-a-valid-package.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})
t.create('last updated date, with tag, valid package')
.get('/verdaccio/latest.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last updated date, with tag, invalid package')
.get('/not-a-package/doesnt-matter.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})
t.create('last updated date, with tag, invalid tag')
.get('/verdaccio/not-a-valid-tag.json')
.expectBadge({
label: 'last updated',
message: 'tag not found',
})
t.create('last updated date, with tag, custom repository, valid package')
.get('/verdaccio/latest.json?registry_uri=https://registry.npmjs.com')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last updated date, with tag, valid package with scope')
.get('/@npm/types/latest.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})
t.create('last updated date, with tag, invalid package with scope')
.get('/@not-a-scoped-package/not-a-valid-package/doesnt-matter.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})

View File

@@ -1,57 +0,0 @@
const queryIndex = JSON.stringify({
resources: [
{
'@id': 'https://api-v2v3search-0.nuget.org/query',
'@type': 'SearchQueryService',
},
],
})
const nuGetV3VersionJsonWithDash = JSON.stringify({
data: [
{
totalDownloads: 0,
versions: [{ version: '1.2-beta' }],
},
],
})
const nuGetV3VersionJsonFirstCharZero = JSON.stringify({
data: [
{
totalDownloads: 0,
versions: [{ version: '0.35' }],
},
],
})
const nuGetV3VersionJsonFirstCharNotZero = JSON.stringify({
data: [
{
totalDownloads: 0,
versions: [{ version: '1.2.7' }],
},
],
})
const nuGetV3VersionJsonBuildMetadataWithDash = JSON.stringify({
data: [
{
totalDownloads: 0,
versions: [
{
version: '1.16.0+388',
},
{
version: '1.17.0+1b81349-429',
},
],
},
],
})
export {
queryIndex,
nuGetV3VersionJsonWithDash,
nuGetV3VersionJsonFirstCharZero,
nuGetV3VersionJsonFirstCharNotZero,
nuGetV3VersionJsonBuildMetadataWithDash,
}

View File

@@ -1,25 +1,8 @@
import semver from 'semver'
import { metric, addv } from '../text-formatters.js'
import { metric } from '../text-formatters.js'
import { downloadCount as downloadCountColor } from '../color-formatters.js'
import { getCachedResource } from '../../core/base-service/resource-cache.js'
function renderVersionBadge({ version, feed }) {
let color
if (version.includes('-')) {
color = 'yellow'
} else if (version.startsWith('0')) {
color = 'orange'
} else {
color = 'blue'
}
return {
message: addv(version),
color,
label: feed,
}
}
function renderDownloadBadge({ downloads }) {
return {
message: metric(downloads),
@@ -100,7 +83,6 @@ function selectVersion(versions, includePrereleases) {
}
export {
renderVersionBadge,
renderDownloadBadge,
odataToObject,
searchServiceUrl,

View File

@@ -1,30 +1,11 @@
import { test, given } from 'sazerac'
import {
renderVersionBadge,
odataToObject,
stripBuildMetadata,
selectVersion,
} from './nuget-helpers.js'
describe('NuGet helpers', function () {
test(renderVersionBadge, () => {
given({ version: '1.2-beta' }).expect({
label: undefined,
message: 'v1.2-beta',
color: 'yellow',
})
given({ version: '0.35' }).expect({
label: undefined,
message: 'v0.35',
color: 'orange',
})
given({ version: '1.2.7' }).expect({
label: undefined,
message: 'v1.2.7',
color: 'blue',
})
})
test(odataToObject, () => {
given({ 'm:properties': { 'd:Version': '1.2.3' } }).expect({
Version: '1.2.3',

View File

@@ -9,11 +9,8 @@ import {
pathParam,
queryParam,
} from '../index.js'
import {
renderVersionBadge,
renderDownloadBadge,
odataToObject,
} from './nuget-helpers.js'
import { renderVersionBadge } from '../version.js'
import { renderDownloadBadge, odataToObject } from './nuget-helpers.js'
function createFilter({ packageName, includePrereleases }) {
const releaseTypeFilter = includePrereleases
@@ -127,10 +124,6 @@ function createServiceFamily({
label: defaultLabel,
}
static render(props) {
return renderVersionBadge(props)
}
async handle({ packageName }, queryParams) {
const packageData = await fetch(this, {
baseUrl: apiBaseUrl,
@@ -138,7 +131,7 @@ function createServiceFamily({
includePrereleases: queryParams.include_prereleases !== undefined,
})
const version = packageData.NormalizedVersion || `${packageData.Version}`
return this.constructor.render({ version })
return renderVersionBadge({ version })
}
}

View File

@@ -1,8 +1,8 @@
import Joi from 'joi'
import RouteBuilder from '../route-builder.js'
import { BaseJsonService, NotFound } from '../index.js'
import { renderVersionBadge } from '../version.js'
import {
renderVersionBadge,
renderDownloadBadge,
searchServiceUrl,
stripBuildMetadata,
@@ -127,10 +127,6 @@ function createServiceFamily({
label: defaultLabel,
}
static render(props) {
return renderVersionBadge(props)
}
/*
* Extract version information from the raw package info.
*/
@@ -158,7 +154,7 @@ function createServiceFamily({
})
const json = await fetch(this, { baseUrl, packageName })
const version = this.transform({ json, includePrereleases })
return this.constructor.render({ version, feed })
return renderVersionBadge({ version, defaultLabel: feed })
}
}

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