Compare commits

..

49 Commits

Author SHA1 Message Date
Caleb Cartwright
c6e31d7f32 load and manage persisted tokens with scope support 2021-09-19 11:41:26 -05:00
Caleb Cartwright
3aadb79325 allow github service classes to define scope requirements 2021-09-19 11:40:21 -05:00
Caleb Cartwright
b8412fd80b support scoped and unscoped tokens in API Provider 2021-09-19 11:39:48 -05:00
Caleb Cartwright
345188e34b expose token pools internal counts of held tokens 2021-09-19 11:38:53 -05:00
Caleb Cartwright
a92dc72ff5 support including scopes on oauth authorization 2021-09-19 11:37:18 -05:00
dependabot[bot]
bf469f10df chore(deps-dev): bump typescript from 4.4.2 to 4.4.3 (#7035)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.4.2 to 4.4.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.4.2...v4.4.3)

---
updated-dependencies:
- dependency-name: typescript
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-18 16:36:35 +00:00
dependabot[bot]
994e752fd1 chore(deps): bump simple-icons from 5.13.0 to 5.14.0 (#7032)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 5.13.0 to 5.14.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/5.13.0...5.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-18 16:19:20 +00:00
dependabot[bot]
f6fd8eac4a chore(deps-dev): bump gatsby from 3.13.0 to 3.13.1 (#7031)
Bumps [gatsby](https://github.com/gatsbyjs/gatsby) from 3.13.0 to 3.13.1.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/compare/gatsby@3.13.0...gatsby@3.13.1)

---
updated-dependencies:
- dependency-name: gatsby
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-18 10:57:11 -05:00
dependabot[bot]
c41d45100e chore(deps-dev): bump prettier from 2.4.0 to 2.4.1 (#7029)
Bumps [prettier](https://github.com/prettier/prettier) from 2.4.0 to 2.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/2.4.0...2.4.1)

---
updated-dependencies:
- dependency-name: prettier
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-17 23:54:08 +00:00
dependabot[bot]
e66b266800 chore(deps-dev): bump eslint-plugin-cypress from 2.11.3 to 2.12.1 (#7027)
Bumps [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) from 2.11.3 to 2.12.1.
- [Release notes](https://github.com/cypress-io/eslint-plugin-cypress/releases)
- [Commits](https://github.com/cypress-io/eslint-plugin-cypress/compare/v2.11.3...v2.12.1)

---
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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-17 23:15:44 +00:00
dependabot[bot]
1ac7ccc231 chore(deps-dev): bump cypress from 8.3.1 to 8.4.0 (#7026)
Bumps [cypress](https://github.com/cypress-io/cypress) from 8.3.1 to 8.4.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/.releaserc.base.js)
- [Commits](https://github.com/cypress-io/cypress/compare/v8.3.1...v8.4.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>
2021-09-17 18:01:15 -05:00
Caleb Cartwright
d728749886 fix: support gitlab token via env var (#7023) 2021-09-16 22:28:54 +00:00
Caleb Cartwright
354fb7db99 ensure docker image builds on PRs (#7019)
* ci: ensure docker image builds on PRs

* ci: intentionally make docker build fail to ensure github workflow fails

* ci: unset forced docker failure

* fix docker build step name

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-15 22:38:06 +00:00
Caleb Cartwright
961e13b229 Add API-based support for [GitLab] badges, add new GitLab Tag badge (#6988)
* Added GitLab Tag service

* Added prettyMessage for when repo has no tags

* Added pretty message for repo not found

* core: esm-ify gitlab tag service

* feat: support gitlab auth

* feat: support custom gitlab url on tag badges

* tests: add auth test for gitlab

* docs: fix gitlab config key references

* feat: support gitlab tag sorting options

* docs: add custom gitlab instance example for tags badge

* use v in gitlab route

* fix: gitlab tag examples

Co-authored-by: Ideotec <guille@ideotec.es>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-14 23:06:57 +00:00
Caleb Cartwright
6bb62e4c0b tests: fix dockerversion service test (#7014)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-14 22:46:31 +00:00
Caleb Cartwright
721d0142ff tests: fix spigetdownloadsize service test (#7018) 2021-09-14 22:30:14 +00:00
Seth Falco
13a53f123f [freecodecamp]: allow + symbol in username (#7016) 2021-09-13 18:21:26 -05:00
Pierre-Yves B
ca63f21113 Stop attempting to override Accept header in [GitHub] API provider (#7013) 2021-09-12 20:48:23 +01:00
chris48s
042ae1c45f update branch in [githublernajson] test (#7012)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-12 18:17:58 +00:00
dependabot[bot]
2f52b1617d chore(deps): bump fast-xml-parser from 3.19.0 to 3.20.0 (#7009)
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 3.19.0 to 3.20.0.
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/3.19.0...v3.20.0)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 16:46:07 +00:00
dependabot[bot]
e91da33016 chore(deps): bump graphql from 15.5.2 to 15.5.3 (#7008)
Bumps [graphql](https://github.com/graphql/graphql-js) from 15.5.2 to 15.5.3.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v15.5.2...v15.5.3)

---
updated-dependencies:
- dependency-name: graphql
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 16:33:50 +00:00
dependabot[bot]
a76df09c35 chore(deps-dev): bump eslint-plugin-jsdoc from 36.0.8 to 36.1.0 (#7007)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 36.0.8 to 36.1.0.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v36.0.8...v36.1.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 16:19:30 +00:00
dependabot[bot]
70874e2d5b chore(deps-dev): bump @babel/core from 7.15.4 to 7.15.5 (#7004)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.15.4 to 7.15.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.15.5/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 15:59:59 +00:00
dependabot[bot]
68dbf71d42 chore(deps-dev): bump prettier from 2.3.2 to 2.4.0 (#7002)
Bumps [prettier](https://github.com/prettier/prettier) from 2.3.2 to 2.4.0.
- [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/2.3.2...2.4.0)

---
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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 15:39:24 +00:00
dependabot[bot]
f4bddb9964 chore(deps-dev): bump c8 from 7.8.0 to 7.9.0 (#7001)
Bumps [c8](https://github.com/bcoe/c8) from 7.8.0 to 7.9.0.
- [Release notes](https://github.com/bcoe/c8/releases)
- [Changelog](https://github.com/bcoe/c8/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bcoe/c8/compare/v7.8.0...v7.9.0)

---
updated-dependencies:
- dependency-name: c8
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 04:23:39 +00:00
dependabot[bot]
cb52deec1c chore(deps-dev): bump @types/styled-components from 5.1.13 to 5.1.14 (#7006)
Bumps [@types/styled-components](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/styled-components) from 5.1.13 to 5.1.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/styled-components)

---
updated-dependencies:
- dependency-name: "@types/styled-components"
  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>
2021-09-10 23:09:33 -05:00
dependabot[bot]
047b14b52a chore(deps-dev): bump @typescript-eslint/eslint-plugin (#7000)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.30.0 to 4.31.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.31.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-11 03:48:17 +00:00
dependabot[bot]
dfb68efffb chore(deps): bump simple-icons from 5.12.0 to 5.13.0 (#7010)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 5.12.0 to 5.13.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/5.12.0...5.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>
2021-09-10 17:01:57 -05:00
J. Ryan Stinnett
8284545e22 Rename Riot to Element in Matrix badge help (#6996)
The Riot Matrix client was renamed to Element in July 2020 (https://element.io/blog/welcome-to-element/). This updates the Matrix badge help text to match.
2021-09-08 17:07:53 +00:00
Rohit Sah
8a1c69ead6 Fixed Reddit Negative Karma Issue (#6992) 2021-09-07 19:49:02 +01:00
Caleb Cartwright
1b871a97b4 refactor: update VS Marketplace Ratings badges for unrated extensions (#6986)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 17:33:01 +00:00
dependabot[bot]
0342a3d7c6 chore(deps-dev): bump gatsby-plugin-catch-links from 3.6.0 to 3.13.0 (#6984)
Bumps [gatsby-plugin-catch-links](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-catch-links) from 3.6.0 to 3.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-catch-links/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-catch-links@3.13.0/packages/gatsby-plugin-catch-links)

---
updated-dependencies:
- dependency-name: gatsby-plugin-catch-links
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 17:21:48 +00:00
dependabot[bot]
77871a9f7b chore(deps): bump graphql from 15.5.1 to 15.5.2 (#6983)
Bumps [graphql](https://github.com/graphql/graphql-js) from 15.5.1 to 15.5.2.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v15.5.1...v15.5.2)

---
updated-dependencies:
- dependency-name: graphql
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 17:10:03 +00:00
dependabot[bot]
15be262ba5 chore(deps-dev): bump gatsby-plugin-react-helmet from 4.6.0 to 4.13.0 (#6980)
Bumps [gatsby-plugin-react-helmet](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-react-helmet) from 4.6.0 to 4.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-react-helmet/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-react-helmet@4.13.0/packages/gatsby-plugin-react-helmet)

---
updated-dependencies:
- dependency-name: gatsby-plugin-react-helmet
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 16:58:36 +00:00
dependabot[bot]
05fe731290 chore(deps-dev): bump mocha from 9.1.0 to 9.1.1 (#6981)
Bumps [mocha](https://github.com/mochajs/mocha) from 9.1.0 to 9.1.1.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v9.1.0...v9.1.1)

---
updated-dependencies:
- dependency-name: mocha
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 16:44:56 +00:00
dependabot[bot]
58310f7363 chore(deps-dev): bump babel-preset-gatsby from 1.12.0 to 1.13.0 (#6971)
Bumps [babel-preset-gatsby](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/babel-preset-gatsby) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/babel-preset-gatsby/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/babel-preset-gatsby@1.13.0/packages/babel-preset-gatsby)

---
updated-dependencies:
- dependency-name: babel-preset-gatsby
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 05:58:44 +00:00
dependabot[bot]
570c2750e2 chore(deps-dev): bump @types/node from 16.7.2 to 16.7.10 (#6977)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.7.2 to 16.7.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 05:49:00 +00:00
dependabot[bot]
62af78c488 chore(deps-dev): bump @typescript-eslint/parser from 4.29.0 to 4.30.0 (#6978)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.29.0 to 4.30.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.30.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 05:22:49 +00:00
dependabot[bot]
c48cd071fe chore(deps): bump @sentry/node from 6.11.0 to 6.12.0 (#6976)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 6.11.0 to 6.12.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.11.0...6.12.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 04:58:16 +00:00
dependabot[bot]
a9c9e7d679 chore(deps): bump ioredis from 4.27.8 to 4.27.9 (#6975)
Bumps [ioredis](https://github.com/luin/ioredis) from 4.27.8 to 4.27.9.
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/luin/ioredis/blob/master/Changelog.md)
- [Commits](https://github.com/luin/ioredis/compare/v4.27.8...v4.27.9)

---
updated-dependencies:
- dependency-name: ioredis
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 04:36:45 +00:00
dependabot[bot]
4a47b9a364 chore(deps-dev): bump gatsby-plugin-remove-trailing-slashes (#6970)
Bumps [gatsby-plugin-remove-trailing-slashes](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-remove-trailing-slashes) from 3.6.0 to 3.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-remove-trailing-slashes/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-remove-trailing-slashes@3.13.0/packages/gatsby-plugin-remove-trailing-slashes)

---
updated-dependencies:
- dependency-name: gatsby-plugin-remove-trailing-slashes
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 04:27:14 +00:00
dependabot[bot]
901dd7b9b6 chore(deps-dev): bump @babel/core from 7.15.0 to 7.15.4 (#6967)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.15.0 to 7.15.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.15.4/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-03 23:14:09 -05:00
dependabot[bot]
6dff73065a chore(deps-dev): bump open-cli from 7.0.0 to 7.0.1 (#6966)
Bumps [open-cli](https://github.com/sindresorhus/open-cli) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/sindresorhus/open-cli/releases)
- [Commits](https://github.com/sindresorhus/open-cli/compare/v7.0.0...v7.0.1)

---
updated-dependencies:
- dependency-name: open-cli
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-04 04:03:41 +00:00
dependabot[bot]
a5f803ff2b chore(deps): bump simple-icons from 5.11.0 to 5.12.0 (#6972)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 5.11.0 to 5.12.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/5.11.0...5.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-03 21:59:11 +00:00
dependabot[bot]
9780da024e chore(deps-dev): bump gatsby-plugin-styled-components (#6985)
Bumps [gatsby-plugin-styled-components](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-styled-components) from 4.6.0 to 4.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-styled-components/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-styled-components@4.13.0/packages/gatsby-plugin-styled-components)

---
updated-dependencies:
- dependency-name: gatsby-plugin-styled-components
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-03 21:37:49 +00:00
dependabot[bot]
12b5e8891f chore(deps-dev): bump gatsby-plugin-page-creator from 3.12.0 to 3.13.0 (#6969)
Bumps [gatsby-plugin-page-creator](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-page-creator) from 3.12.0 to 3.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-page-creator/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-page-creator@3.13.0/packages/gatsby-plugin-page-creator)

---
updated-dependencies:
- dependency-name: gatsby-plugin-page-creator
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-03 21:19:05 +00:00
dependabot[bot]
5472c733a6 chore(deps-dev): bump gatsby from 3.12.1 to 3.13.0 (#6965)
Bumps [gatsby](https://github.com/gatsbyjs/gatsby) from 3.12.1 to 3.13.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/compare/gatsby@3.12.1...gatsby@3.13.0)

---
updated-dependencies:
- dependency-name: gatsby
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-03 21:02:05 +00:00
dependabot[bot]
fcab8a52dc chore(deps-dev): bump cypress from 8.3.0 to 8.3.1 (#6973)
Bumps [cypress](https://github.com/cypress-io/cypress) from 8.3.0 to 8.3.1.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/.releaserc.base.js)
- [Commits](https://github.com/cypress-io/cypress/compare/v8.3.0...v8.3.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-09-03 20:43:22 +00:00
dependabot[bot]
a111e9cba8 chore(deps-dev): bump start-server-and-test from 1.13.1 to 1.14.0 (#6968)
Bumps [start-server-and-test](https://github.com/bahmutov/start-server-and-test) from 1.13.1 to 1.14.0.
- [Release notes](https://github.com/bahmutov/start-server-and-test/releases)
- [Commits](https://github.com/bahmutov/start-server-and-test/compare/v1.13.1...v1.14.0)

---
updated-dependencies:
- dependency-name: start-server-and-test
  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>
2021-09-03 20:30:25 +00:00
33 changed files with 1836 additions and 3403 deletions

View File

@@ -0,0 +1,20 @@
name: Build Docker Image
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build
uses: docker/build-push-action@v2
with:
context: .
push: false
tags: shieldsio/shields:pr-validation

View File

@@ -40,6 +40,8 @@ public:
debug:
enabled: 'GITHUB_DEBUG_ENABLED'
intervalSeconds: 'GITHUB_DEBUG_INTERVAL_SECONDS'
gitlab:
authorizedOrigins: 'GITLAB_ORIGINS'
jenkins:
authorizedOrigins: 'JENKINS_ORIGINS'
jira:
@@ -77,6 +79,7 @@ private:
gh_client_id: 'GH_CLIENT_ID'
gh_client_secret: 'GH_CLIENT_SECRET'
gh_token: 'GH_TOKEN'
gitlab_token: 'GITLAB_TOKEN'
jenkins_user: 'JENKINS_USER'
jenkins_pass: 'JENKINS_PASS'
jira_user: 'JIRA_USER'

View File

@@ -3,6 +3,7 @@ private:
discord_bot_token: ...
gh_client_id: ...
gh_client_secret: ...
gitlab_token: ...
redis_url: ...
sentry_dsn: ...
shields_secret: ...

View File

@@ -5,6 +5,7 @@ private:
# you can also set these values through environment variables, which may be
# preferable for self hosting.
gh_token: '...'
gitlab_token: '...'
twitch_client_id: '...'
twitch_client_secret: '...'
weblate_api_key: '...'

View File

@@ -125,6 +125,7 @@ const publicConfigSchema = Joi.object({
intervalSeconds: Joi.number().integer().min(1).required(),
},
},
gitlab: defaultService,
jira: defaultService,
jenkins: Joi.object({
authorizedOrigins: origins,
@@ -161,6 +162,7 @@ const privateConfigSchema = Joi.object({
gh_client_id: Joi.string(),
gh_client_secret: Joi.string(),
gh_token: Joi.string(),
gitlab_token: Joi.string(),
jenkins_user: Joi.string(),
jenkins_pass: Joi.string(),
jira_user: Joi.string(),

View File

@@ -188,6 +188,10 @@ class TokenPool {
this.priorityQueue = new PriorityQueue(this.constructor.compareTokens)
}
count() {
return this.tokenIds.size
}
/**
* compareTokens
*

View File

@@ -32,6 +32,8 @@ Production hosting is managed by the Shields ops team:
| Twitch | OAuth app | @PyvesB |
| Discord | OAuth app | @PyvesB |
| YouTube | Account owner | @PyvesB |
| GitLab | Account owner | @calebcartwright |
| GitLab | Account access | @calebcartwright, @chris48s, @paulmelnikow, @PyvesB |
| OpenStreetMap (for Wheelmap) | Account owner | @paulmelnikow |
| DNS | Account owner | @olivierlacan |
| DNS | Read-only account access | @espadrine, @paulmelnikow, @chris48s |

View File

@@ -147,6 +147,15 @@ These settings are used by shields.io for GitHub OAuth app authorization
but will not be necessary for most self-hosted installations. See
[production-hosting.md](./production-hosting.md).
### GitLab
- `GITLAB_ORIGINS` (yml: `public.services.gitlab.authorizedOrigins`)
- `GITLAB_TOKEN` (yml: `private.gitlab_token`)
A GitLab [Personal Access Token][gitlab-pat] is required for accessing private content. If you need a GitLab token for your self-hosted Shields server then we recommend limiting the scopes to the minimal set necessary for the badges you are using.
[gitlab-pat]: https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
### Jenkins CI
- `JENKINS_ORIGINS` (yml: `public.services.jenkins.authorizedOrigins`)

4200
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@
"dependencies": {
"@fontsource/lato": "^4.5.0",
"@fontsource/lekton": "^4.5.0",
"@sentry/node": "^6.11.0",
"@sentry/node": "^6.12.0",
"@shields_io/camp": "^18.1.1",
"badge-maker": "file:badge-maker",
"bytes": "^3.1.0",
@@ -37,13 +37,13 @@
"decamelize": "^5.0.0",
"emojic": "^1.1.16",
"escape-string-regexp": "^4.0.0",
"fast-xml-parser": "^3.19.0",
"fast-xml-parser": "^3.20.0",
"glob": "^7.1.7",
"global-agent": "^3.0.0",
"got": "11.8.2",
"graphql": "^15.5.1",
"graphql": "^15.5.3",
"graphql-tag": "^2.12.5",
"ioredis": "4.27.8",
"ioredis": "4.27.9",
"joi": "17.4.2",
"joi-extension-semver": "5.0.0",
"js-yaml": "^4.1.0",
@@ -62,7 +62,7 @@
"query-string": "^7.0.1",
"request": "~2.88.2",
"semver": "~7.3.5",
"simple-icons": "5.11.0",
"simple-icons": "5.14.0",
"webextension-store-meta": "^1.0.4",
"xmldom": "~0.6.0",
"xpath": "~0.0.32"
@@ -142,7 +142,7 @@
]
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/core": "^7.15.5",
"@babel/polyfill": "^7.12.1",
"@babel/register": "7.15.3",
"@mapbox/react-click-to-select": "^2.2.1",
@@ -150,17 +150,17 @@
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.groupby": "^4.6.6",
"@types/mocha": "^9.0.0",
"@types/node": "^16.7.2",
"@types/node": "^16.7.10",
"@types/react-helmet": "^6.1.2",
"@types/react-modal": "^3.12.1",
"@types/react-select": "^4.0.17",
"@types/styled-components": "5.1.13",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.0",
"@types/styled-components": "5.1.14",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.30.0",
"babel-plugin-inline-react-svg": "^2.0.1",
"babel-plugin-istanbul": "^6.0.0",
"babel-preset-gatsby": "^1.2.0",
"c8": "^7.8.0",
"babel-preset-gatsby": "^1.13.0",
"c8": "^7.9.0",
"caller": "^1.0.1",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
@@ -169,7 +169,7 @@
"child-process-promise": "^2.2.1",
"clipboard-copy": "^4.0.1",
"concurrently": "^6.2.1",
"cypress": "^8.3.0",
"cypress": "^8.4.0",
"danger": "^10.6.6",
"danger-plugin-no-test-shortcuts": "^2.0.0",
"deepmerge": "^4.2.2",
@@ -179,9 +179,9 @@
"eslint-config-standard-jsx": "^10.0.0",
"eslint-config-standard-react": "^11.0.1",
"eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-cypress": "^2.11.3",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsdoc": "^36.0.8",
"eslint-plugin-jsdoc": "^36.1.0",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-no-extension-in-require": "^0.2.0",
"eslint-plugin-node": "^11.1.0",
@@ -191,12 +191,12 @@
"eslint-plugin-sort-class-members": "^1.11.0",
"fetch-ponyfill": "^7.1.0",
"form-data": "^4.0.0",
"gatsby": "3.12.1",
"gatsby-plugin-catch-links": "^3.1.0",
"gatsby-plugin-page-creator": "^3.12.0",
"gatsby-plugin-react-helmet": "^4.1.0",
"gatsby-plugin-remove-trailing-slashes": "^3.1.0",
"gatsby-plugin-styled-components": "^4.6.0",
"gatsby": "3.13.1",
"gatsby-plugin-catch-links": "^3.13.0",
"gatsby-plugin-page-creator": "^3.13.0",
"gatsby-plugin-react-helmet": "^4.13.0",
"gatsby-plugin-remove-trailing-slashes": "^3.13.0",
"gatsby-plugin-styled-components": "^4.13.0",
"gatsby-plugin-typescript": "^3.2.0",
"humanize-string": "^2.1.0",
"icedfrisby": "4.0.0",
@@ -208,7 +208,7 @@
"lodash.debounce": "^4.0.8",
"lodash.difference": "^4.5.0",
"minimist": "^1.2.5",
"mocha": "^9.1.0",
"mocha": "^9.1.1",
"mocha-env-reporter": "^4.0.0",
"mocha-junit-reporter": "^2.0.0",
"mocha-yaml-loader": "^1.0.3",
@@ -216,9 +216,9 @@
"node-mocks-http": "^1.10.1",
"nodemon": "^2.0.12",
"npm-run-all": "^4.1.5",
"open-cli": "^7.0.0",
"open-cli": "^7.0.1",
"portfinder": "^1.0.28",
"prettier": "2.3.2",
"prettier": "2.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-overlay": "^6.0.9",
@@ -234,11 +234,11 @@
"sinon": "^11.1.2",
"sinon-chai": "^3.7.0",
"snap-shot-it": "^7.9.6",
"start-server-and-test": "1.13.1",
"start-server-and-test": "1.14.0",
"styled-components": "^5.3.1",
"ts-mocha": "^8.0.0",
"tsd": "^0.17.0",
"typescript": "^4.4.2"
"typescript": "^4.4.3"
},
"engines": {
"node": "^14.17.1",

View File

@@ -15,7 +15,7 @@ t.create('docker version (valid, library with tag)')
})
t.create('docker version (valid, user)')
.get('/datadog/agent.json')
.get('/datadog/dogstatsd.json')
.expectBadge({
label: 'version',
message: isSemver,

View File

@@ -2,11 +2,17 @@ import Joi from 'joi'
import { metric } from '../text-formatters.js'
import { BaseJsonService, InvalidResponse, NotFound } from '../index.js'
/**
* Validates that the schema response is what we're expecting.
* The username pattern should match the freeCodeCamp repository.
*
* @see https://github.com/freeCodeCamp/freeCodeCamp/blob/main/utils/validate.js#L14
*/
const schema = Joi.object({
entities: Joi.object({
user: Joi.object()
.required()
.pattern(/^\w+$/, {
.pattern(/^[a-zA-Z0-9\-_+]*$/, {
points: Joi.number().allow(null).required(),
}),
}).optional(),

View File

@@ -3,7 +3,7 @@ import request from 'request'
import { userAgent } from '../../../core/base-service/legacy-request-handler.js'
import log from '../../../core/server/log.js'
function setRoutes({ server, authHelper, onTokenAccepted }) {
function setRoutes({ server, authHelper, onTokenAccepted, tokenScopes }) {
const baseUrl = process.env.GATSBY_BASE_URL || 'https://img.shields.io'
server.route(/^\/github-auth$/, (data, match, end, ask) => {
@@ -15,6 +15,7 @@ function setRoutes({ server, authHelper, onTokenAccepted }) {
// it's not setting a bad example.
client_id: authHelper._user,
redirect_uri: `${baseUrl}/github-auth/done`,
scope: tokenScopes,
})
ask.res.setHeader(
'Location',

View File

@@ -41,6 +41,7 @@ describe('Github token acceptor', function () {
server: camp,
authHelper: oauthHelper,
onTokenAccepted,
tokenScopes: 'read:packages',
})
})
@@ -52,6 +53,7 @@ describe('Github token acceptor', function () {
const qs = queryString.stringify({
client_id: fakeClientId,
redirect_uri: 'https://img.shields.io/github-auth/done',
scope: 'read:packages',
})
const expectedLocationHeader = `https://github.com/login/oauth/authorize?${qs}`
expect(res.headers.location).to.equal(expectedLocationHeader)

View File

@@ -30,11 +30,10 @@ describe('Github API provider', function () {
it('should be able to run 10 requests', async function () {
this.timeout('20s')
for (let i = 0; i < 10; ++i) {
await githubApiProvider.requestAsPromise(
await githubApiProvider.requestAsPromise({
request,
'/repos/rust-lang/rust',
{}
)
url: '/repos/rust-lang/rust',
})
}
})
})
@@ -52,11 +51,10 @@ describe('Github API provider', function () {
const headers = []
async function performOneRequest() {
const { res } = await githubApiProvider.requestAsPromise(
const { res } = await githubApiProvider.requestAsPromise({
request,
'/repos/rust-lang/rust',
{}
)
url: '/repos/rust-lang/rust',
})
expect(res.statusCode).to.equal(200)
headers.push(res.headers)
}

View File

@@ -38,6 +38,7 @@ class GithubApiProvider {
onTokenInvalidated = tokenString => {},
globalToken,
reserveFraction = 0.25,
tokenScopeNames = {},
}) {
Object.assign(this, {
baseUrl,
@@ -45,12 +46,14 @@ class GithubApiProvider {
onTokenInvalidated,
globalToken,
reserveFraction,
tokenScopeNames,
})
if (this.withPooling) {
this.standardTokens = new TokenPool({ batchSize: 25 })
this.searchTokens = new TokenPool({ batchSize: 5 })
this.graphqlTokens = new TokenPool({ batchSize: 25 })
this.packageScopedTokens = new TokenPool({ batchSize: 25 })
}
}
@@ -60,17 +63,41 @@ class GithubApiProvider {
standardTokens: this.standardTokens.serializeDebugInfo({ sanitize }),
searchTokens: this.searchTokens.serializeDebugInfo({ sanitize }),
graphqlTokens: this.graphqlTokens.serializeDebugInfo({ sanitize }),
packageScopedTokens: this.packageScopedTokens.serializeDebugInfo({
sanitize,
}),
}
} else {
return {}
}
}
addToken(tokenString) {
numReservedScopedTokens() {
return this.packageScopedTokens.count()
}
addReservedScopedToken(tokenString, data) {
if (!this.withPooling) {
throw Error('When not using a token pool, do not provide tokens')
}
const { scopes } = data
if (!scopes) {
throw new Error('Cannot add unscoped token to reserved token pools')
}
scopes.split('%20').forEach(scope => {
if (scope === this.tokenScopeNames.readPackages) {
this.packageScopedTokens.add(tokenString, data)
}
})
}
addToken(tokenString, data) {
if (this.withPooling) {
this.standardTokens.add(tokenString)
this.searchTokens.add(tokenString)
this.graphqlTokens.add(tokenString)
this.standardTokens.add(tokenString, data)
this.searchTokens.add(tokenString, data)
this.graphqlTokens.add(tokenString, data)
} else {
throw Error('When not using a token pool, do not provide tokens')
}
@@ -141,7 +168,11 @@ class GithubApiProvider {
this.onTokenInvalidated(token.id)
}
tokenForUrl(url) {
tokenForUrl(url, { needsPackageScope }) {
if (needsPackageScope) {
return this.packageScopedTokens.next()
}
if (url.startsWith('/search')) {
return this.searchTokens.next()
} else if (url.startsWith('/graphql')) {
@@ -154,14 +185,14 @@ class GithubApiProvider {
// Act like request(), but tweak headers and query to avoid hitting a rate
// limit. Inject `request` so we can pass in `cachingRequest` from
// `request-handler.js`.
request(request, url, options = {}, callback) {
request({ request, url, options = {}, neededScopes = {}, callback }) {
const { baseUrl } = this
let token
let tokenString
if (this.withPooling) {
try {
token = this.tokenForUrl(url)
token = this.tokenForUrl(url, neededScopes)
} catch (e) {
callback(e)
return
@@ -178,7 +209,6 @@ class GithubApiProvider {
baseUrl,
headers: {
'User-Agent': userAgent,
Accept: 'application/vnd.github.v3+json',
Authorization: `token ${tokenString}`,
...options.headers,
},
@@ -199,14 +229,20 @@ class GithubApiProvider {
})
}
requestAsPromise(request, url, options) {
requestAsPromise({ request, url, options, neededScopes }) {
return new Promise((resolve, reject) => {
this.request(request, url, options, (err, res, buffer) => {
if (err) {
reject(err)
} else {
resolve({ res, buffer })
}
this.request({
request,
url,
options,
neededScopes,
callback: (err, res, buffer) => {
if (err) {
reject(err)
} else {
resolve({ res, buffer })
}
},
})
})
}

View File

@@ -6,7 +6,11 @@ describe('Github API provider', function () {
const baseUrl = 'https://github-api.example.com'
const reserveFraction = 0.333
let mockStandardToken, mockSearchToken, mockGraphqlToken, provider
let mockStandardToken,
mockSearchToken,
mockGraphqlToken,
mockPackagesScopedToken,
provider
beforeEach(function () {
provider = new GithubApiProvider({ baseUrl, reserveFraction })
@@ -18,6 +22,11 @@ describe('Github API provider', function () {
mockGraphqlToken = { update: sinon.spy(), invalidate: sinon.spy() }
sinon.stub(provider.graphqlTokens, 'next').returns(mockGraphqlToken)
mockPackagesScopedToken = { update: sinon.spy(), invalidate: sinon.spy() }
sinon
.stub(provider.packageScopedTokens, 'next')
.returns(mockPackagesScopedToken)
})
context('a search API request', function () {
@@ -25,12 +34,16 @@ describe('Github API provider', function () {
callback()
}
it('should obtain an appropriate token', function (done) {
provider.request(mockRequest, '/search', {}, (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).to.have.been.calledOnce
expect(provider.standardTokens.next).not.to.have.been.called
expect(provider.graphqlTokens.next).not.to.have.been.called
done()
provider.request({
request: mockRequest,
url: '/search',
callback: (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).to.have.been.calledOnce
expect(provider.standardTokens.next).not.to.have.been.called
expect(provider.graphqlTokens.next).not.to.have.been.called
done()
},
})
})
})
@@ -40,12 +53,37 @@ describe('Github API provider', function () {
callback()
}
it('should obtain an appropriate token', function (done) {
provider.request(mockRequest, '/graphql', {}, (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).not.to.have.been.called
expect(provider.standardTokens.next).not.to.have.been.called
expect(provider.graphqlTokens.next).to.have.been.calledOnce
done()
provider.request({
request: mockRequest,
url: '/graphql',
callback: (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).not.to.have.been.called
expect(provider.standardTokens.next).not.to.have.been.called
expect(provider.graphqlTokens.next).to.have.been.calledOnce
done()
},
})
})
})
context('a request requiring the read:packages scope', function () {
const mockRequest = (options, callback) => {
callback()
}
it('should obtain an appropriate token', function (done) {
provider.request({
request: mockRequest,
url: '/graphql',
neededScopes: { needsPackageScope: true },
callback: (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).not.to.have.been.called
expect(provider.standardTokens.next).not.to.have.been.called
expect(provider.graphqlTokens.next).not.to.have.been.called
expect(provider.packageScopedTokens.next).to.have.been.calledOnce
done()
},
})
})
})
@@ -55,12 +93,16 @@ describe('Github API provider', function () {
callback()
}
it('should obtain an appropriate token', function (done) {
provider.request(mockRequest, '/repo', {}, (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).not.to.have.been.called
expect(provider.standardTokens.next).to.have.been.calledOnce
expect(provider.graphqlTokens.next).not.to.have.been.called
done()
provider.request({
request: mockRequest,
url: '/repo',
callback: (err, res, buffer) => {
expect(err).to.be.undefined
expect(provider.searchTokens.next).not.to.have.been.called
expect(provider.standardTokens.next).to.have.been.calledOnce
expect(provider.graphqlTokens.next).not.to.have.been.called
done()
},
})
})
})
@@ -84,25 +126,33 @@ describe('Github API provider', function () {
}
it('should invoke the callback', function (done) {
provider.request(mockRequest, '/foo', {}, (err, res, buffer) => {
expect(err).to.equal(null)
expect(Object.is(res, mockResponse)).to.be.true
expect(Object.is(buffer, mockBuffer)).to.be.true
done()
provider.request({
request: mockRequest,
url: '/foo',
callback: (err, res, buffer) => {
expect(err).to.equal(null)
expect(Object.is(res, mockResponse)).to.be.true
expect(Object.is(buffer, mockBuffer)).to.be.true
done()
},
})
})
it('should update the token with the expected values', function (done) {
provider.request(mockRequest, '/foo', {}, (err, res, buffer) => {
expect(err).to.equal(null)
const expectedUsesRemaining =
remaining - Math.ceil(reserveFraction * rateLimit)
expect(mockStandardToken.update).to.have.been.calledWith(
expectedUsesRemaining,
nextReset
)
expect(mockStandardToken.invalidate).not.to.have.been.called
done()
provider.request({
request: mockRequest,
url: '/foo',
callback: (err, res, buffer) => {
expect(err).to.equal(null)
const expectedUsesRemaining =
remaining - Math.ceil(reserveFraction * rateLimit)
expect(mockStandardToken.update).to.have.been.calledWith(
expectedUsesRemaining,
nextReset
)
expect(mockStandardToken.invalidate).not.to.have.been.called
done()
},
})
})
})
@@ -132,25 +182,33 @@ describe('Github API provider', function () {
}
it('should invoke the callback', function (done) {
provider.request(mockRequest, '/graphql', {}, (err, res, buffer) => {
expect(err).to.equal(null)
expect(Object.is(res, mockResponse)).to.be.true
expect(Object.is(buffer, mockBuffer)).to.be.true
done()
provider.request({
request: mockRequest,
url: '/graphql',
callback: (err, res, buffer) => {
expect(err).to.equal(null)
expect(Object.is(res, mockResponse)).to.be.true
expect(Object.is(buffer, mockBuffer)).to.be.true
done()
},
})
})
it('should update the token with the expected values', function (done) {
provider.request(mockRequest, '/graphql', {}, (err, res, buffer) => {
expect(err).to.equal(null)
const expectedUsesRemaining =
remaining - Math.ceil(reserveFraction * rateLimit)
expect(mockGraphqlToken.update).to.have.been.calledWith(
expectedUsesRemaining,
nextReset
)
expect(mockGraphqlToken.invalidate).not.to.have.been.called
done()
provider.request({
request: mockRequest,
url: '/graphql',
callback: (err, res, buffer) => {
expect(err).to.equal(null)
const expectedUsesRemaining =
remaining - Math.ceil(reserveFraction * rateLimit)
expect(mockGraphqlToken.update).to.have.been.calledWith(
expectedUsesRemaining,
nextReset
)
expect(mockGraphqlToken.invalidate).not.to.have.been.called
done()
},
})
})
})
@@ -164,11 +222,15 @@ describe('Github API provider', function () {
}
it('should invoke the callback and update the token with the expected values', function (done) {
provider.request(mockRequest, '/foo', {}, (err, res, buffer) => {
expect(err).to.equal(null)
expect(mockStandardToken.invalidate).to.have.been.calledOnce
expect(mockStandardToken.update).not.to.have.been.called
done()
provider.request({
request: mockRequest,
url: '/foo',
callback: (err, res, buffer) => {
expect(err).to.equal(null)
expect(mockStandardToken.invalidate).to.have.been.calledOnce
expect(mockStandardToken.update).not.to.have.been.called
done()
},
})
})
})
@@ -180,10 +242,14 @@ describe('Github API provider', function () {
}
it('should pass the error to the callback', function (done) {
provider.request(mockRequest, '/foo', {}, (err, res, buffer) => {
expect(err).to.be.an.instanceof(Error)
expect(err.message).to.equal('connection timeout')
done()
provider.request({
request: mockRequest,
url: '/foo',
callback: (err, res, buffer) => {
expect(err).to.be.an.instanceof(Error)
expect(err.message).to.equal('connection timeout')
done()
},
})
})
})

View File

@@ -2,21 +2,22 @@ import gql from 'graphql-tag'
import { mergeQueries } from '../../core/base-service/graphql.js'
import { BaseGraphqlService, BaseJsonService } from '../index.js'
function createRequestFetcher(context, config) {
function createRequestFetcher(context, config, neededScopes) {
const { sendAndCacheRequestWithCallbacks, githubApiProvider } = context
return async (url, options) =>
githubApiProvider.requestAsPromise(
sendAndCacheRequestWithCallbacks,
githubApiProvider.requestAsPromise({
request: sendAndCacheRequestWithCallbacks,
url,
options
)
options,
neededScopes,
})
}
class GithubAuthV3Service extends BaseJsonService {
constructor(context, config) {
constructor(context, config, neededScopes) {
super(context, config)
this._requestFetcher = createRequestFetcher(context, config)
this._requestFetcher = createRequestFetcher(context, config, neededScopes)
this.staticAuthConfigured = true
}
}
@@ -27,10 +28,10 @@ class GithubAuthV3Service extends BaseJsonService {
// useful when consuming GitHub endpoints which are not rate-limited: it
// avoids wasting API quota on them in production.
class ConditionalGithubAuthV3Service extends BaseJsonService {
constructor(context, config) {
constructor(context, config, neededScopes) {
super(context, config)
if (context.githubApiProvider.globalToken) {
this._requestFetcher = createRequestFetcher(context, config)
this._requestFetcher = createRequestFetcher(context, config, neededScopes)
this.staticAuthConfigured = true
} else {
this.staticAuthConfigured = false
@@ -39,9 +40,9 @@ class ConditionalGithubAuthV3Service extends BaseJsonService {
}
class GithubAuthV4Service extends BaseGraphqlService {
constructor(context, config) {
constructor(context, config, neededScopes) {
super(context, config)
this._requestFetcher = createRequestFetcher(context, config)
this._requestFetcher = createRequestFetcher(context, config, neededScopes)
this.staticAuthConfigured = true
}

View File

@@ -0,0 +1,90 @@
import Joi from 'joi'
import { expect } from 'chai'
import sinon from 'sinon'
import { GithubAuthV3Service } from './github-auth-service.js'
import GithubApiProvider from './github-api-provider.js'
describe('GithubAuthV3Service', function () {
class DummyGithubAuthV3Service extends GithubAuthV3Service {
static category = 'build'
static route = { base: 'runs' }
async handle() {
const { requiredString } = await this._requestJson({
schema: Joi.object({
requiredString: Joi.string().required(),
}).required(),
url: 'https://github-api.example.com/repos/badges/shields/check-runs',
options: {
headers: {
Accept: 'application/vnd.github.antiope-preview+json',
},
},
})
return { message: requiredString }
}
}
class ScopedDummyGithubAuthV3Service extends DummyGithubAuthV3Service {
constructor(context, config) {
super(context, config, { needsPackageScope: true })
}
}
let sendAndCacheRequestWithCallbacks, mockToken
const githubApiProvider = new GithubApiProvider({
baseUrl: 'https://github-api.example.com',
})
beforeEach(function () {
sendAndCacheRequestWithCallbacks = sinon.stub().returns(
Promise.resolve({
buffer: '{"requiredString": "some-string"}',
res: { statusCode: 200 },
})
)
mockToken = { id: 'abc123', update: sinon.mock(), invalidate: sinon.mock() }
})
afterEach(function () {
sinon.restore()
})
it('forwards custom Accept header', async function () {
sinon.stub(githubApiProvider.standardTokens, 'next').returns(mockToken)
DummyGithubAuthV3Service.invoke({
sendAndCacheRequestWithCallbacks,
githubApiProvider,
})
expect(sendAndCacheRequestWithCallbacks).to.have.been.calledOnceWith({
headers: {
'User-Agent': 'Shields.io/2003a',
Accept: 'application/vnd.github.antiope-preview+json',
Authorization: 'token abc123',
},
url: 'https://github-api.example.com/repos/badges/shields/check-runs',
baseUrl: 'https://github-api.example.com',
})
})
it('uses token with correct read scope', function () {
sinon.stub(githubApiProvider.packageScopedTokens, 'next').returns(mockToken)
ScopedDummyGithubAuthV3Service.invoke({
sendAndCacheRequestWithCallbacks,
githubApiProvider,
})
expect(sendAndCacheRequestWithCallbacks).to.have.been.calledOnceWith({
headers: {
'User-Agent': 'Shields.io/2003a',
Accept: 'application/vnd.github.antiope-preview+json',
Authorization: 'token abc123',
},
url: 'https://github-api.example.com/repos/badges/shields/check-runs',
baseUrl: 'https://github-api.example.com',
})
})
})

View File

@@ -5,6 +5,11 @@ import GithubApiProvider from './github-api-provider.js'
import { setRoutes as setAdminRoutes } from './auth/admin.js'
import { setRoutes as setAcceptorRoutes } from './auth/acceptor.js'
const readPackagesScope = 'read:packages'
// Multiple scopes need to be uri-encoded space delimited
const tokenScopes = `${readPackagesScope}`
const persistenceScopeDelimiter = '.scopes.'
// Convenience class with all the stuff related to the Github API and its
// authorization tokens, to simplify server initialization.
class GithubConstellation {
@@ -24,6 +29,8 @@ class GithubConstellation {
this._debugEnabled = config.service.debug.enabled
this._debugIntervalSeconds = config.service.debug.intervalSeconds
this.shieldsSecret = config.private.shields_secret
this._tokenScopes = {}
this._maxNumReservedScopedTokens = 0
const { redis_url: redisUrl, gh_token: globalToken } = config.private
if (redisUrl) {
@@ -38,6 +45,9 @@ class GithubConstellation {
baseUrl: process.env.GITHUB_URL || 'https://api.github.com',
globalToken,
withPooling: !globalToken,
tokenScopeNames: {
readPackages: readPackagesScope,
},
onTokenInvalidated: tokenString => this.onTokenInvalidated(tokenString),
})
@@ -70,8 +80,21 @@ class GithubConstellation {
log.error(e)
}
// Reserve a subset of scoped tokens from the total set
// to be used for queries which require an explicit scope,
// while leaving a sufficient amount of tokens (scoped or unscoped)
// for the bulk of our requests which don't care about scopes.
this._maxNumReservedScopedTokens = Math.floor(tokens.length * 0.15)
tokens.forEach(tokenString => {
this.apiProvider.addToken(tokenString)
const [token, scopes] = tokenString.split(persistenceScopeDelimiter)
this._tokenScopes[token] = scopes || null
const data = { scopes }
const numReserved = this.apiProvider.numReservedScopedTokens()
if (scopes && numReserved < this._maxNumReservedScopedTokens) {
this.apiProvider.addReservedScopedToken(token, data)
} else {
this.apiProvider.addToken(token, data)
}
})
const { shieldsSecret, apiProvider } = this
@@ -81,19 +104,53 @@ class GithubConstellation {
setAcceptorRoutes({
server,
authHelper: this.oauthHelper,
onTokenAccepted: tokenString => this.onTokenAdded(tokenString),
tokenScopes,
onTokenAccepted: tokenString =>
this.onTokenAdded(tokenString, tokenScopes),
})
}
}
onTokenAdded(tokenString) {
onTokenAdded(tokenString, tokenScopes) {
if (!this.persistence) {
throw Error('Token persistence is not configured')
}
this.apiProvider.addToken(tokenString)
const data = { scopes: tokenScopes }
const numReserved = this.apiProvider.numReservedScopedTokens()
if (numReserved < this._maxNumReservedScopedTokens) {
this.apiProvider.addReservedScopedToken(tokenString, data)
} else {
this.apiProvider.addToken(tokenString, data)
}
process.nextTick(async () => {
try {
await this.persistence.noteTokenAdded(tokenString)
// To avoid having multiple set entries for re-authorized/re-scoped
// tokens we need to first remove the previous entry that had different scopes
if (
Object.prototype.hasOwnProperty.call(this._tokenScopes, tokenString)
) {
const currentScopes = this._tokenScopes[tokenString]
// These scopes shouldn't match in practice, as that would
// indicate the function has somehow been invoked with an existing
// token but without any scope changes. Nevertheless, the conditional
// guard is here in case there are circumstances that assumption fails
// to be upheld.
if (currentScopes !== tokenScopes) {
const token = currentScopes
? `${tokenString}${persistenceScopeDelimiter}${currentScopes}`
: tokenString
await this.persistence.noteTokenRemoved(token)
}
}
// It's unlikely that we'd evert revert back to no longer requesting any scopes
// but handling that scenario regardless so we don't end up
// with junk like `abc123.scopes.undefined` in redis
const token = tokenScopes
? `${tokenString}${persistenceScopeDelimiter}${tokenScopes}`
: tokenString
await this.persistence.noteTokenAdded(token)
this._tokenScopes[tokenString] = tokenScopes || null
} catch (e) {
log.error(e)
}
@@ -104,7 +161,12 @@ class GithubConstellation {
if (this.persistence) {
process.nextTick(async () => {
try {
await this.persistence.noteTokenRemoved(tokenString)
const scopes = this._tokenScopes[tokenString]
const token = scopes
? `${tokenString}${persistenceScopeDelimiter}${scopes}`
: tokenString
await this.persistence.noteTokenRemoved(token)
delete this._tokenScopes[tokenString]
} catch (e) {
log.error(e)
}

View File

@@ -0,0 +1,208 @@
import { expect } from 'chai'
import sinon from 'sinon'
import log from '../../core/server/log.js'
import RedisTokenPersistence from '../../core/token-pooling/redis-token-persistence.js'
import GithubConstellation from './github-constellation.js'
import GithubApiProvider from './github-api-provider.js'
describe('GithubConstellation', function () {
const tokens = [
'abc123',
'def4567.scopes.read:packages%20read:user',
'def789.scopes.read:packages',
'ghi012',
'fff444.scopes.read:packages',
'555eee.scopes.read:packages',
'ddd666',
'777ccc',
'bbb888',
'999aaa',
'000111.scopes.read:packages',
'222333.scopes.read:packages',
'111111',
'888888',
]
const config = {
private: {
redis_url: 'localhost',
},
service: {
debug: {
enabled: false,
},
},
}
const server = { ajax: { on: sinon.stub() } }
beforeEach(function () {
sinon.stub(log, 'log')
sinon
.stub(GithubConstellation, '_createOauthHelper')
.returns({ isConfigured: false })
sinon.stub(GithubConstellation.prototype, 'scheduleDebugLogging')
sinon.stub(RedisTokenPersistence.prototype, 'initialize').returns(tokens)
sinon.stub(RedisTokenPersistence.prototype, 'noteTokenAdded')
sinon.stub(RedisTokenPersistence.prototype, 'noteTokenRemoved')
sinon.spy(GithubApiProvider.prototype, 'addToken')
sinon.spy(GithubApiProvider.prototype, 'addReservedScopedToken')
})
afterEach(function () {
sinon.restore()
})
context('initialize', function () {
it('does not fetch tokens when pooling disabled', async function () {
const constellation = new GithubConstellation({
...config,
...{ private: { gh_token: 'secret' } },
})
await constellation.initialize(server)
expect(RedisTokenPersistence.prototype.initialize).not.to.have.been.called
})
it('loads both scoped and unscoped tokens', async function () {
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
expect(constellation.apiProvider.graphqlTokens.count()).to.equal(12)
expect(constellation.apiProvider.searchTokens.count()).to.equal(12)
expect(constellation.apiProvider.standardTokens.count()).to.equal(12)
expect(constellation.apiProvider.packageScopedTokens.count()).to.equal(2)
expect(
GithubApiProvider.prototype.addReservedScopedToken
).to.be.calledWithExactly('def4567', {
scopes: 'read:packages%20read:user',
})
expect(
GithubApiProvider.prototype.addReservedScopedToken
).to.be.calledWithExactly('def789', {
scopes: 'read:packages',
})
})
})
context('onTokenAdded', function () {
it('adds new scoped token with met reserves', async function () {
const token = 'shh_secret'
sinon
.stub(GithubApiProvider.prototype, 'numReservedScopedTokens')
.returns(2)
const clock = sinon.useFakeTimers()
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
constellation._maxNumReservedScopedTokens = 2
constellation.onTokenAdded(token, 'read:packages')
await clock.tickAsync()
expect(GithubApiProvider.prototype.addToken).to.be.calledWithExactly(
token,
{ scopes: 'read:packages' }
)
expect(
GithubApiProvider.prototype.addReservedScopedToken
).to.not.be.calledWith(token)
expect(RedisTokenPersistence.prototype.noteTokenAdded).to.be.calledWith(
`${token}.scopes.read:packages`
)
expect(RedisTokenPersistence.prototype.noteTokenRemoved).to.not.be.called
expect(Object.keys(constellation._tokenScopes).length).to.equal(15)
expect(constellation._tokenScopes[token]).to.equal('read:packages')
})
it('adds new scoped token with unmet reserves', async function () {
const token = 'shh_secret'
sinon
.stub(GithubApiProvider.prototype, 'numReservedScopedTokens')
.returns(2)
const clock = sinon.useFakeTimers()
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
constellation._maxNumReservedScopedTokens = 3
constellation.onTokenAdded(token, 'read:packages')
await clock.tickAsync()
expect(
GithubApiProvider.prototype.addReservedScopedToken
).to.be.calledWithExactly(token, { scopes: 'read:packages' })
expect(GithubApiProvider.prototype.addToken).to.not.be.calledWith(token)
expect(RedisTokenPersistence.prototype.noteTokenAdded).to.be.calledWith(
`${token}.scopes.read:packages`
)
expect(RedisTokenPersistence.prototype.noteTokenRemoved).to.not.be.called
expect(Object.keys(constellation._tokenScopes).length).to.equal(15)
expect(constellation._tokenScopes[token]).to.equal('read:packages')
})
it('adds new unscoped token', async function () {
const token = '1234567890987654321'
const clock = sinon.useFakeTimers()
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
constellation.onTokenAdded(token)
await clock.tickAsync()
expect(GithubApiProvider.prototype.addToken).to.be.calledWithExactly(
token,
{ scopes: undefined }
)
expect(
GithubApiProvider.prototype.addReservedScopedToken
).to.not.be.calledWith(token)
expect(RedisTokenPersistence.prototype.noteTokenAdded).to.be.calledWith(
token
)
expect(RedisTokenPersistence.prototype.noteTokenRemoved).to.not.be.called
expect(Object.keys(constellation._tokenScopes).length).to.equal(15)
expect(constellation._tokenScopes[token]).to.equal(null)
})
it('updates scopes on existing token', async function () {
const existingToken = 'abc123'
const clock = sinon.useFakeTimers()
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
sinon
.stub(GithubApiProvider.prototype, 'numReservedScopedTokens')
.returns(1)
constellation.onTokenAdded(existingToken, 'read:packages')
await clock.tickAsync()
expect(
GithubApiProvider.prototype.addReservedScopedToken
).to.be.calledWithExactly(existingToken, { scopes: 'read:packages' })
expect(GithubApiProvider.prototype.addToken.callCount).to.equal(12)
expect(RedisTokenPersistence.prototype.noteTokenAdded).to.be.calledWith(
`${existingToken}.scopes.read:packages`
)
expect(RedisTokenPersistence.prototype.noteTokenRemoved).to.be.calledWith(
existingToken
)
expect(Object.keys(constellation._tokenScopes).length).to.equal(14)
expect(constellation._tokenScopes[existingToken]).to.equal(
'read:packages'
)
})
})
context('onTokenInvalidated', function () {
it('removes scoped token', async function () {
const clock = sinon.useFakeTimers()
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
constellation.onTokenInvalidated('def789')
await clock.tickAsync()
expect(RedisTokenPersistence.prototype.noteTokenRemoved).to.be.calledWith(
'def789.scopes.read:packages'
)
expect(Object.keys(constellation._tokenScopes).length).to.equal(13)
})
it('removes unscoped token', async function () {
const clock = sinon.useFakeTimers()
const constellation = new GithubConstellation(config)
await constellation.initialize(server)
constellation.onTokenInvalidated('888888')
await clock.tickAsync()
expect(
RedisTokenPersistence.prototype.noteTokenRemoved
).to.be.calledWithExactly('888888')
expect(Object.keys(constellation._tokenScopes).length).to.equal(13)
})
})
})

View File

@@ -14,12 +14,10 @@ t.create('Lerna version (independent)')
message: 'independent',
})
t.create('Lerna version (branch)')
.get('/facebook/jest/master.json')
.expectBadge({
label: 'lerna@master',
message: isSemver,
})
t.create('Lerna version (branch)').get('/facebook/jest/main.json').expectBadge({
label: 'lerna@main',
message: isSemver,
})
t.create('Lerna version (lerna.json missing)')
.get('/PyvesB/empty-repo.json')

View File

@@ -0,0 +1,19 @@
import { BaseJsonService } from '../index.js'
export default class GitLabBase extends BaseJsonService {
static auth = {
passKey: 'gitlab_token',
serviceKey: 'gitlab',
}
async fetch({ url, options, schema, errorMessages }) {
return this._requestJson(
this.authHelper.withBasicAuth({
schema,
url,
options,
errorMessages,
})
)
}
}

View File

@@ -0,0 +1,131 @@
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 { NotFound } from '../index.js'
import GitLabBase from './gitlab-base.js'
const schema = Joi.array().items(
Joi.object({
name: Joi.string().required(),
})
)
const queryParamSchema = Joi.object({
gitlab_url: optionalUrl,
include_prereleases: Joi.equal(''),
sort: Joi.string().valid('date', 'semver').default('date'),
}).required()
export default class GitlabTag extends GitLabBase {
static category = 'version'
static route = {
base: 'gitlab/v/tag',
pattern: ':user/:repo',
queryParamSchema,
}
static examples = [
{
title: 'GitLab tag (latest by date)',
namedParams: {
user: 'shields-ops-group',
repo: 'tag-test',
},
queryParams: { sort: 'date' },
staticPreview: this.render({ version: 'v2.0.0' }),
},
{
title: 'GitLab tag (latest by SemVer)',
namedParams: {
user: 'shields-ops-group',
repo: 'tag-test',
},
queryParams: { sort: 'semver' },
staticPreview: this.render({ version: 'v4.0.0' }),
},
{
title: 'GitLab tag (latest by SemVer pre-release)',
namedParams: {
user: 'shields-ops-group',
repo: 'tag-test',
},
queryParams: {
sort: 'semver',
include_prereleases: null,
},
staticPreview: this.render({ version: 'v5.0.0-beta.1', sort: 'semver' }),
},
{
title: 'GitLab tag (custom instance)',
namedParams: {
user: 'GNOME',
repo: 'librsvg',
},
queryParams: {
sort: 'semver',
include_prereleases: null,
gitlab_url: 'https://gitlab.gnome.org',
},
staticPreview: this.render({ version: 'v2.51.4' }),
},
]
static defaultBadgeData = { label: 'tag' }
static render({ version, sort }) {
return {
message: addv(version),
color: sort === 'semver' ? versionColor(version) : 'blue',
}
}
async fetch({ user, repo, baseUrl }) {
// https://docs.gitlab.com/ee/api/tags.html
// N.B. the documentation has contradictory information about default sort order.
// As of 2020-10-11 the default is by date, but we add the `order_by` query param
// explicitly in case that changes upstream.
return super.fetch({
schema,
url: `${baseUrl}/api/v4/projects/${user}%2F${repo}/repository/tags`,
options: { qs: { order_by: 'updated' } },
errorMessages: {
404: 'repo not found',
},
})
}
static transform({ tags, sort, includePrereleases }) {
if (tags.length === 0) {
throw new NotFound({ prettyMessage: 'no tags found' })
}
if (sort === 'date') {
return tags[0].name
}
return latest(
tags.map(t => t.name),
{ pre: includePrereleases }
)
}
async handle(
{ user, repo },
{
gitlab_url: baseUrl = 'https://gitlab.com',
include_prereleases: pre,
sort,
}
) {
const tags = await this.fetch({ user, repo, baseUrl })
const version = this.constructor.transform({
tags,
sort,
includePrereleases: pre !== undefined,
})
return this.constructor.render({ version, sort })
}
}

View File

@@ -0,0 +1,47 @@
import { expect } from 'chai'
import nock from 'nock'
import { cleanUpNockAfterEach, defaultContext } from '../test-helpers.js'
import GitLabTag from './gitlab-tag.service.js'
describe('GitLabTag', function () {
describe('auth', function () {
cleanUpNockAfterEach()
const fakeToken = 'abc123'
const config = {
public: {
services: {
gitlab: {
authorizedOrigins: ['https://gitlab.com'],
},
},
},
private: {
gitlab_token: fakeToken,
},
}
it('sends the auth information as configured', async function () {
const scope = nock('https://gitlab.com/')
.get('/api/v4/projects/foo%2Fbar/repository/tags?order_by=updated')
// This ensures that the expected credentials are actually being sent with the HTTP request.
// Without this the request wouldn't match and the test would fail.
.basicAuth({ user: '', pass: fakeToken })
.reply(200, [{ name: '1.9' }])
expect(
await GitLabTag.invoke(
defaultContext,
config,
{ user: 'foo', repo: 'bar' },
{}
)
).to.deep.equal({
message: 'v1.9',
color: 'blue',
})
scope.done()
})
})
})

View File

@@ -0,0 +1,27 @@
import { isSemver } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Tag (latest by date)')
.get('/shields-ops-group/tag-test.json')
.expectBadge({ label: 'tag', message: 'v2.0.0', color: 'blue' })
t.create('Tag (latest by SemVer)')
.get('/shields-ops-group/tag-test.json?sort=semver')
.expectBadge({ label: 'tag', message: 'v4.0.0', color: 'blue' })
t.create('Tag (latest by SemVer pre-release)')
.get('/shields-ops-group/tag-test.json?sort=semver&include_prereleases')
.expectBadge({ label: 'tag', message: 'v5.0.0-beta.1', color: 'orange' })
t.create('Tag (custom instance')
.get('/GNOME/librsvg.json?gitlab_url=https://gitlab.gnome.org')
.expectBadge({ label: 'tag', message: isSemver, color: 'blue' })
t.create('Tag (repo not found)')
.get('/fdroid/nonexistant.json')
.expectBadge({ label: 'tag', message: 'repo not found' })
t.create('Tag (no tags)')
.get('/fdroid/fdroiddata.json')
.expectBadge({ label: 'tag', message: 'no tags found' })

View File

@@ -30,10 +30,10 @@ const documentation = `
<p>
In order for this badge to work, the host of your room must allow guest accounts or dummy accounts to register, and the room must be world readable (chat history visible to anyone).
</br>
The following steps will show you how to setup the badge URL using the Riot.im Matrix client.
The following steps will show you how to setup the badge URL using the Element Matrix client.
</br>
<ul>
<li>Select the desired room inside the Riot.im client</li>
<li>Select the desired room inside the Element client</li>
<li>Click on the room settings button (gear icon) located near the top right of the client</li>
<li>Scroll to the very bottom of the settings page and look under the <code>Addresses</code> section</li>
<li>You should see one or more <code>room addresses (or aliases)</code>, which can be easily identified with their starting hash (<code>#</code>) character (ex: <code>#twim:matrix.org</code>)</li>

View File

@@ -1,12 +1,12 @@
import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { anyInteger } from '../validators.js'
import { metric } from '../text-formatters.js'
import { BaseJsonService } from '../index.js'
const schema = Joi.object({
data: Joi.object({
link_karma: nonNegativeInteger,
comment_karma: nonNegativeInteger,
link_karma: anyInteger,
comment_karma: anyInteger,
}).required(),
}).required()

View File

@@ -2,16 +2,16 @@ import { isFileSize } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('EssentialsX (id 9089)')
.get('/9089.json')
t.create('EssentialsX (hosted resource)')
.get('/771.json')
.expectBadge({ label: 'size', message: isFileSize })
t.create('Pet Master (id 15904)').get('/15904.json').expectBadge({
t.create('Pet Master (external resource)').get('/15904.json').expectBadge({
lavel: 'size',
message: 'resource hosted externally',
})
t.create('Invalid Resource (id 1)').get('/1.json').expectBadge({
t.create('Invalid Resource').get('/1.json').expectBadge({
label: 'size',
message: 'not found',
})

View File

@@ -75,10 +75,10 @@ async function githubLicense(githubApiProvider, user, repo) {
let link = `https://github.com/${repoSlug}`
const { buffer } = await githubApiProvider.requestAsPromise(
const { buffer } = await githubApiProvider.requestAsPromise({
request,
`/repos/${repoSlug}/license`
)
url: `/repos/${repoSlug}/license`,
})
try {
const data = JSON.parse(buffer)
if ('html_url' in data) {

View File

@@ -93,7 +93,9 @@ const isPercentage = Joi.alternatives().try(
isDecimalPercentage
)
const isFileSize = withRegex(/^[0-9]*[.]?[0-9]+\s(B|kB|MB|GB|TB|PB|EB|ZB|YB)$/)
const isFileSize = withRegex(
/^[0-9]*[.]?[0-9]+\s(B|kB|KB|MB|GB|TB|PB|EB|ZB|YB)$/
)
const isFormattedDate = Joi.alternatives().try(
Joi.equal('today', 'yesterday'),

View File

@@ -40,6 +40,13 @@ export default class VisualStudioMarketplaceRating extends VisualStudioMarketpla
}
static render({ format, averageRating, ratingCount }) {
if (ratingCount < 1) {
return {
message: 'no ratings',
color: 'inactive',
}
}
const message =
format === 'r'
? `${averageRating.toFixed(1)}/5 (${ratingCount})`

View File

@@ -83,8 +83,8 @@ t.create('zero rating')
)
.expectBadge({
label: 'rating',
message: '0.0/5 (0)',
color: 'red',
message: 'no ratings',
color: 'lightgrey',
})
t.create('stars')