Compare commits

...

78 Commits

Author SHA1 Message Date
Caleb Cartwright
d60bca325f fix: add some missing spaces in docs 2023-06-17 14:53:52 -05:00
chris48s
50ea7068a8 migrate frontend to docusaurus (#9014)
* delete loads of really important stuff that we definitely need

* v basic MVP smoosh docusaurus PoC into repo

* TODO

* delete more really important stuff

* TODO

* tidyup: use run-s

* don't redirect images used in frontend to raster proxy

* fix routing

* preserve the /endpoint link

* delete the blog (for now)

I would quite like to re-add this at some point
but its not really the top priority thing right now

* content edits

* appease the lint gods

* update danger rules

* remove placeholder

* cypress tests

* dockerhub --> ghcr

* Revert "dockerhub --> ghcr"

This reverts commit ef74cbb26b.

* downgrade lockfile format

* implement defs/BASE_URL

* fix e2e build

* actually fix cypress tests

* always run cypress tests on build

* this never worked

* add command for docusaurus:clear

* delete more code we don't need any more

* update ESLint/prettier config

* delete unsused exports

* documentation updates

* delete a fairly large chunk of our dependency tree

* allow base_url as build arg to Dockerfile

* fixup dockerfile

* work out base url at runtime if not set

doing this at image build time is not the right approach

* remove gatsby monorepo from closebot

* rename HomepageFeatures to homepage-features
2023-06-17 10:59:07 +01:00
jNullj
67d935492d feat: Add author filter option for [GithubCommitActivity] (#9251)
* feat: Add author filter option for CommitActivity

Add a new filter option to [GithubCommitActivity], allowing users to filter the commit activity by a specific author.

To make the filter more explicit, The label display "commits by [author]" for the total amount of commits and "commit activity by [author]" for other intervals when an author filter is selected.

To maintain a clear and organized code structure, The filtered author is added as an argument and not to the shield path.

The request to find the number of commits by the author is made using the REST api rather then the GraphQL api to make it in 1 request rather then 2.

Resolves #9215

* fix: solve eslint errors

* Add tests for [GithubCommitActivity] filter by author

Add tests for the new filter by author feature.

* update [GithubCommitActivity] spec file for new author feat

Add test for new transformAuthorFilter function of GithubCommitActivity added for the author filter feature.

* Fix null string for label of GithubCommitActivity

* Update GithubCommitActivity example

* improve error handeling for GithubCommitActivity

The author filter error handling removed was redundent as it would never execute, there is no way to seperate branch not found from repo not found.

* update depricated functions

PR #9233 replaced errorsMessages with httpErrors.
This commit updates the new changes to stay up to date with that PR

* remove test for nonexisting error

this exception was removed in commit 9e358c8 and is not needed anymore

* Fixed test for commit activity unexisting repo

* Update example for GithubCommitActivity

Picked a user with commits in the repo as an example that would work

* Add test for invalid commit activity branch

Add test for REST API calls in commit activity branch

---------

Co-authored-by: jNullj <jNullj@users.noreply.github.com>
2023-06-15 19:20:16 +01:00
jNullj
35dfd75ef5 Fix: [GithubCommitActivity] invalid branch error handling (#9258)
* Fix error handling of bad branch in [GithubCommitActivity]

When GraphQL response with a bad repo it returns an error indicating the bad repo.
When GraphQL has the currect repo with a bad branch it will null inside the repo object without an error object.
The privouse commits seems to try and use that but due not allowing null in schema it did not work and an error due to bad schema would show insted.

This commit fixes this issue

* Add test for invalid branch in [GithubCommitActivity]

---------

Co-authored-by: jNullj <jNullj@users.noreply.github.com>
2023-06-14 20:53:48 +01:00
chris48s
14892e3943 Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] (#9233)
* allow serviceData to override cacheSeconds with a longer value

* prevent [endpoint] json cacheSeconds property exceeding service default

* allow ShieldsRuntimeError to specify a cacheSeconds property

By default error responses use the cacheLength of
the service class throwing the error.

This allows error to tell the handling layer the maxAge
that should be set on the error badge response.

* add customExceptions param

This

1. allows us to specify custom properties to pass to the exception
   constructor if we throw any of the standard got errors
   e.g: `ETIMEDOUT`, `ECONNRESET`, etc
2. uses a custom `cacheSeconds` property (if set on the exception)
   to set the response maxAge

* customExceptions --> systemErrors

* errorMessages --> httpErrors
2023-06-13 21:08:43 +01:00
Pierre-Yves Bigourdan
2651e5fe87 Delete old deprecated services (#9254)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-12 07:49:20 +00:00
Pierre-Yves Bigourdan
554c01097d Fix [CodeClimate] tests and examples (#9253)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-11 23:41:39 +00:00
dependabot[bot]
42603e139f chore(deps-dev): bump @babel/register from 7.21.0 to 7.22.5 (#9244)
Bumps [@babel/register](https://github.com/babel/babel/tree/HEAD/packages/babel-register) from 7.21.0 to 7.22.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.22.5/packages/babel-register)

---
updated-dependencies:
- dependency-name: "@babel/register"
  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>
2023-06-10 15:50:07 +00:00
dependabot[bot]
72bdc64f8a chore(deps-dev): bump @babel/core from 7.22.1 to 7.22.5 (#9243)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.22.1 to 7.22.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.22.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>
2023-06-10 15:44:08 +00:00
dependabot[bot]
6337206a36 chore(deps-dev): bump cypress from 12.13.0 to 12.14.0 (#9248)
Bumps [cypress](https://github.com/cypress-io/cypress) from 12.13.0 to 12.14.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/v12.13.0...v12.14.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-10 15:37:20 +00:00
dependabot[bot]
ec307324a1 chore(deps-dev): bump eslint-plugin-jsdoc from 46.2.0 to 46.2.6 (#9247)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 46.2.0 to 46.2.6.
- [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/v46.2.0...v46.2.6)

---
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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-10 15:32:29 +00:00
dependabot[bot]
7fbe7bb286 chore(deps-dev): bump concurrently from 8.1.0 to 8.2.0 (#9245)
Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v8.1.0...v8.2.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-10 15:06:23 +00:00
dependabot[bot]
7e7758c0ff chore(deps): bump glob from 10.2.6 to 10.2.7 (#9242)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.2.6 to 10.2.7.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.2.6...v10.2.7)

---
updated-dependencies:
- dependency-name: glob
  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>
2023-06-10 15:00:43 +00:00
chris48s
6593927f23 embiggen youtube cache, again (#9250) 2023-06-09 23:17:27 +01:00
dependabot[bot]
8cb9adc23d chore(deps): bump fast-xml-parser from 4.2.2 to 4.2.4 (#9241)
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 4.2.2 to 4.2.4.
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/commits)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-type: direct:production
...

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>
2023-06-09 10:20:50 +00:00
uncenter
00aef7e63a feat: add 'canceled' status to netlify deploy badge (#9240)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-07 09:41:12 +00:00
Christian Clauss
3b7495a985 php-version.js: Fix typos (#9239) 2023-06-07 09:35:04 +00:00
chris48s
bf1bea8b4a increase default cache on youtube badges (#9238) 2023-06-04 19:04:40 +01:00
dependabot[bot]
150334b141 chore(deps-dev): bump eslint-plugin-jsdoc from 46.1.0 to 46.2.0 (#9235)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 46.1.0 to 46.2.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/v46.1.0...v46.2.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>
2023-06-03 14:39:31 +00:00
chris48s
07b657497a disable dependabot auto rebase (#9234) 2023-06-03 15:26:13 +01:00
dependabot[bot]
e3b1d3c8d9 chore(deps): bump dayjs from 1.11.7 to 1.11.8 (#9232)
Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.11.7 to 1.11.8.
- [Release notes](https://github.com/iamkun/dayjs/releases)
- [Changelog](https://github.com/iamkun/dayjs/blob/v1.11.8/CHANGELOG.md)
- [Commits](https://github.com/iamkun/dayjs/compare/v1.11.7...v1.11.8)

---
updated-dependencies:
- dependency-name: dayjs
  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>
2023-06-03 15:09:08 +01:00
dependabot[bot]
164b5c31a0 chore(deps-dev): bump @babel/core from 7.21.8 to 7.22.1 (#9227)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.21.8 to 7.22.1.
- [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.22.1/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  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>
2023-06-03 11:09:58 +00:00
dependabot[bot]
a7902eaeb3 chore(deps-dev): bump c8 from 7.13.0 to 7.14.0 (#9224)
Bumps [c8](https://github.com/bcoe/c8) from 7.13.0 to 7.14.0.
- [Release notes](https://github.com/bcoe/c8/releases)
- [Changelog](https://github.com/bcoe/c8/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bcoe/c8/compare/v7.13.0...v7.14.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>
2023-06-03 11:06:28 +00:00
dependabot[bot]
c422d01085 chore(deps): bump @sentry/node from 7.53.1 to 7.54.0 (#9223)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.53.1 to 7.54.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/7.53.1...7.54.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>
2023-06-03 11:02:58 +00:00
dependabot[bot]
909b45e80e chore(deps-dev): bump concurrently from 8.0.1 to 8.1.0 (#9221)
Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 8.0.1 to 8.1.0.
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v8.0.1...v8.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>
2023-06-03 10:58:18 +00:00
dependabot[bot]
96520ab33f chore(deps): bump @fontsource/lekton from 5.0.1 to 5.0.2 (#9222)
Bumps [@fontsource/lekton](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/lekton) from 5.0.1 to 5.0.2.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/lekton/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/lekton)

---
updated-dependencies:
- dependency-name: "@fontsource/lekton"
  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>
2023-06-03 11:52:41 +01:00
dependabot[bot]
d7531353aa chore(deps): bump @xmldom/xmldom from 0.8.7 to 0.8.8 (#9231)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.7 to 0.8.8.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/0.8.8/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.7...0.8.8)

---
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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-06-03 08:58:24 +00:00
dependabot[bot]
e8233fbfaf chore(deps): bump @fontsource/lato from 5.0.1 to 5.0.2 (#9225)
Bumps [@fontsource/lato](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/lato) from 5.0.1 to 5.0.2.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/lato/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/lato)

---
updated-dependencies:
- dependency-name: "@fontsource/lato"
  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>
2023-06-03 08:53:55 +00:00
dependabot[bot]
1a108ac03a chore(deps): bump got from 12.6.0 to 13.0.0 (#9226)
Bumps [got](https://github.com/sindresorhus/got) from 12.6.0 to 13.0.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v12.6.0...v13.0.0)

---
updated-dependencies:
- dependency-name: got
  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>
2023-06-03 09:49:20 +01:00
dependabot[bot]
cd036ab50f chore(deps-dev): bump typescript from 5.0.4 to 5.1.3 (#9228)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.0.4 to 5.1.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.0.4...v5.1.3)

---
updated-dependencies:
- dependency-name: typescript
  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>
2023-06-03 09:32:23 +01:00
dependabot[bot]
c1a9ddde2b chore(deps-dev): bump eslint-plugin-jsdoc from 44.2.7 to 46.1.0 (#9229)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 44.2.7 to 46.1.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/v44.2.7...v46.1.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  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>
2023-06-02 19:57:53 +00:00
github-actions[bot]
6117638868 Changelog for Release server-2023-06-01 (#9219)
* Update Changelog

* Update CHANGELOG.md

---------

Co-authored-by: release[bot] <actions@users.noreply.github.com>
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2023-06-01 19:07:24 +00:00
chris48s
64d3380e1f fix typo in test name (#9216) 2023-05-30 15:03:19 +01:00
Prashant Rawat
a65a86c6bf Add docstrings for validators service (#9197)
* add docstrings for validators service

* Update services/validators.js

---------

Co-authored-by: chris48s <chris48s@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-29 20:00:03 +00:00
jNullj
8a11db8265 feat: Add total commits to [GitHubCommitActivity] (#9196)
* feat: Add total commits to GithubCommitActivity

As part of a new feature proposed at issue #6070 added the requested feature.
I also used the conversation at pull request #6081 as a basis for those changes.

This change adds a new interval to the github/commit-activity shield 'total' (t for short).
The interval shows the total commits of the repo since its creation.

* Fix format with prettier

* Label for 'total' interval is now commits

Label change for the 'total' interval from 'commit activity' to 'commits'

---------

Co-authored-by: jNullj <jNullj@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-29 19:46:41 +00:00
dependabot[bot]
759514337c chore(deps): bump simple-icons from 8.14.0 to 9.0.0 (#9212)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 8.14.0 to 9.0.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/8.14.0...9.0.0)

---
updated-dependencies:
- dependency-name: simple-icons
  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>
2023-05-29 19:13:13 +01:00
dependabot[bot]
ed296ecb57 chore(deps): bump @fontsource/lekton from 4.5.11 to 5.0.1 (#9201)
Bumps [@fontsource/lekton](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/lekton) from 4.5.11 to 5.0.1.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/lekton/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/lekton)

---
updated-dependencies:
- dependency-name: "@fontsource/lekton"
  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>
2023-05-29 18:08:05 +00:00
dependabot[bot]
8dd5801a2c chore(deps-dev): bump styled-components from 5.3.10 to 5.3.11 (#9203)
Bumps [styled-components](https://github.com/styled-components/styled-components) from 5.3.10 to 5.3.11.
- [Release notes](https://github.com/styled-components/styled-components/releases)
- [Commits](https://github.com/styled-components/styled-components/compare/v5.3.10...v5.3.11)

---
updated-dependencies:
- dependency-name: 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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-29 18:59:06 +01:00
dependabot[bot]
c0e406251c chore(deps): bump @fontsource/lato from 4.5.10 to 5.0.1 (#9202)
Bumps [@fontsource/lato](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/lato) from 4.5.10 to 5.0.1.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/lato/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/lato)

---
updated-dependencies:
- dependency-name: "@fontsource/lato"
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-29 17:33:32 +00:00
dependabot[bot]
0e85ae392b chore(deps): bump glob from 10.2.5 to 10.2.6 (#9199)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.2.5 to 10.2.6.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.2.5...v10.2.6)

---
updated-dependencies:
- dependency-name: glob
  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>
2023-05-29 17:29:50 +00:00
dependabot[bot]
378cf43d9b chore(deps-dev): bump cypress from 12.12.0 to 12.13.0 (#9205)
Bumps [cypress](https://github.com/cypress-io/cypress) from 12.12.0 to 12.13.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/v12.12.0...v12.13.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-29 17:07:12 +00:00
dependabot[bot]
30eb531890 chore(deps): bump @sentry/node from 7.52.1 to 7.53.1 (#9206)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.52.1 to 7.53.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/7.52.1...7.53.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-29 17:00:19 +00:00
dependabot[bot]
f42b66171f chore(deps-dev): bump eslint-plugin-jsdoc from 44.2.4 to 44.2.7 (#9198)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 44.2.4 to 44.2.7.
- [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/v44.2.4...v44.2.7)

---
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>
2023-05-29 16:55:24 +00:00
Prashant Rawat
da615eeef7 add docstrings for text-formatters service (#9188)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-20 19:18:06 +00:00
dependabot[bot]
abb3ab4086 chore(deps): bump pg from 8.10.0 to 8.11.0 (#9184)
Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) from 8.10.0 to 8.11.0.
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.11.0/packages/pg)

---
updated-dependencies:
- dependency-name: pg
  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>
2023-05-20 19:10:14 +00:00
dependabot[bot]
6195d19979 chore(deps): bump qs from 6.11.1 to 6.11.2 (#9175)
Bumps [qs](https://github.com/ljharb/qs) from 6.11.1 to 6.11.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.11.1...v6.11.2)

---
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>
2023-05-20 19:05:48 +00:00
dependabot[bot]
08d4e198d3 chore(deps): bump simple-icons from 8.13.0 to 8.14.0 (#9187)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 8.13.0 to 8.14.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/8.13.0...8.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>
2023-05-19 19:56:06 +00:00
dependabot[bot]
9f93f7a5a7 chore(deps-dev): bump sinon from 15.0.4 to 15.1.0 (#9186)
Bumps [sinon](https://github.com/sinonjs/sinon) from 15.0.4 to 15.1.0.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v15.0.4...v15.1.0)

---
updated-dependencies:
- dependency-name: sinon
  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>
2023-05-19 19:19:13 +00:00
dependabot[bot]
1969227b65 chore(deps-dev): bump eslint-plugin-jsdoc from 44.2.3 to 44.2.4 (#9179)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 44.2.3 to 44.2.4.
- [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/v44.2.3...v44.2.4)

---
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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-19 19:13:49 +00:00
dependabot[bot]
8de69e8d18 chore(deps-dev): bump rimraf from 5.0.0 to 5.0.1 (#9178)
Bumps [rimraf](https://github.com/isaacs/rimraf) from 5.0.0 to 5.0.1.
- [Changelog](https://github.com/isaacs/rimraf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/rimraf/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: rimraf
  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>
2023-05-19 19:06:20 +00:00
dependabot[bot]
dcaea2cb38 chore(deps): bump @sentry/node from 7.51.2 to 7.52.1 (#9176)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.51.2 to 7.52.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/7.51.2...7.52.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>
2023-05-19 19:01:56 +00:00
chris48s
e3154f67be set a custom error on 429 (#9159)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-18 23:44:05 +00:00
chris48s
165159eec3 deprecate [travis].org badges (#9171)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-14 19:24:07 +00:00
chris48s
0c44117b5b count private sponsors on [GithubSponsors] badge (#9170) 2023-05-13 17:10:40 -05:00
dependabot[bot]
c80faa8088 chore(deps): bump simple-icons from 8.12.1 to 8.13.0 (#9167)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 8.12.1 to 8.13.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/8.12.1...8.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>
2023-05-13 09:42:00 +00:00
dependabot[bot]
10d1d1bac4 chore(deps): bump glob from 10.2.2 to 10.2.3 (#9164)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.2.2 to 10.2.3.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.2.2...v10.2.3)

---
updated-dependencies:
- dependency-name: glob
  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>
2023-05-13 09:37:14 +00:00
dependabot[bot]
507db8f57a chore(deps-dev): bump eslint-plugin-sort-class-members (#9168)
Bumps [eslint-plugin-sort-class-members](https://github.com/bryanrsmith/eslint-plugin-sort-class-members) from 1.17.1 to 1.18.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.17.1...v1.18.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-12 20:14:33 +00:00
dependabot[bot]
983691f914 chore(deps-dev): bump eslint-plugin-jsdoc from 43.1.1 to 44.2.3 (#9165)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 43.1.1 to 44.2.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/v43.1.1...v44.2.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-12 20:09:45 +00:00
dependabot[bot]
1bcba59243 chore(deps-dev): bump cypress from 12.11.0 to 12.12.0 (#9163)
Bumps [cypress](https://github.com/cypress-io/cypress) from 12.11.0 to 12.12.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/v12.11.0...v12.12.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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-12 20:05:20 +00:00
dependabot[bot]
a3320c9e9f chore(deps): bump @sentry/node from 7.51.0 to 7.51.2 (#9162)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.51.0 to 7.51.2.
- [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/7.51.0...7.51.2)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  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>
2023-05-12 20:00:37 +00:00
Laurent Demailly
c773563e60 Correct issue template in footer (new badge requests url is incorrect) (#9153)
* Correct issue template in footer

https://github.com/badges/shields/issues/9152#issuecomment-1537534258

* Same in TUTORIAL.md
2023-05-07 16:18:33 -05:00
dependabot[bot]
58637f244c chore(deps): bump simple-icons from 8.11.0 to 8.12.1 (#9148)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 8.11.0 to 8.12.1.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/8.11.0...8.12.1)

---
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>
2023-05-05 20:33:34 +00:00
dependabot[bot]
ff6f2c410a chore(deps-dev): bump @babel/core from 7.21.4 to 7.21.8 (#9147)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.21.4 to 7.21.8.
- [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.21.8/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>
2023-05-05 20:29:07 +00:00
dependabot[bot]
1196041b82 chore(deps-dev): bump @types/chai from 4.3.4 to 4.3.5 (#9146)
Bumps [@types/chai](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chai) from 4.3.4 to 4.3.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chai)

---
updated-dependencies:
- dependency-name: "@types/chai"
  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>
2023-05-05 20:21:10 +00:00
dependabot[bot]
c87820f382 chore(deps): bump @sentry/node from 7.50.0 to 7.51.0 (#9144)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.50.0 to 7.51.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/7.50.0...7.51.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>
2023-05-05 20:16:10 +00:00
dependabot[bot]
0aa103bdd3 chore(deps-dev): bump eslint-plugin-cypress from 2.13.2 to 2.13.3 (#9149)
Bumps [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) from 2.13.2 to 2.13.3.
- [Release notes](https://github.com/cypress-io/eslint-plugin-cypress/releases)
- [Commits](https://github.com/cypress-io/eslint-plugin-cypress/compare/v2.13.2...v2.13.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-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>
2023-05-05 20:10:31 +00:00
github-actions[bot]
0feb444202 Changelog for Release server-2023-05-01 (#9139)
* Update Changelog

* Update CHANGELOG.md

---------

Co-authored-by: release[bot] <actions@users.noreply.github.com>
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2023-05-01 21:03:17 +00:00
chris48s
4f4d49d424 switch from xmldom to @xmldom/xmldom (#9100) 2023-05-01 19:17:40 +01:00
dependabot[bot]
3228efad1f chore(deps): bump joi from 17.9.1 to 17.9.2 (#9133)
Bumps [joi](https://github.com/hapijs/joi) from 17.9.1 to 17.9.2.
- [Release notes](https://github.com/hapijs/joi/releases)
- [Commits](https://github.com/hapijs/joi/compare/v17.9.1...v17.9.2)

---
updated-dependencies:
- dependency-name: joi
  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>
2023-05-01 18:14:31 +00:00
chris48s
388459a8ca fail to start server if there are duplicate service names (#9099)
* fail to start server if there are duplicate service names

* update class names in loader test fixtures

---------

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-01 18:07:13 +00:00
dependabot[bot]
69f0651902 chore(deps-dev): bump nock from 13.3.0 to 13.3.1 (#9134)
Bumps [nock](https://github.com/nock/nock) from 13.3.0 to 13.3.1.
- [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.3.0...v13.3.1)

---
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>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2023-05-01 18:03:45 +00:00
dependabot[bot]
4fdb1bdd25 chore(deps-dev): bump @typescript-eslint/eslint-plugin (#9137)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.59.0 to 5.59.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.59.2/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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>
2023-05-01 18:00:15 +00:00
Prashant Rawat
96a06d48b4 Add docstrings for route builder service (#9118)
* add docstrings for route builder service
2023-05-01 18:54:50 +01:00
dependabot[bot]
ac3effd87f chore(deps-dev): bump styled-components from 5.3.9 to 5.3.10 (#9130)
Bumps [styled-components](https://github.com/styled-components/styled-components) from 5.3.9 to 5.3.10.
- [Release notes](https://github.com/styled-components/styled-components/releases)
- [Commits](https://github.com/styled-components/styled-components/compare/v5.3.9...v5.3.10)

---
updated-dependencies:
- dependency-name: 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>
2023-05-01 17:21:18 +00:00
dependabot[bot]
c06b774b32 chore(deps): bump @sentry/node from 7.49.0 to 7.50.0 (#9127)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.49.0 to 7.50.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/7.49.0...7.50.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>
2023-05-01 17:16:38 +00:00
dependabot[bot]
abd77b5223 chore(deps): bump glob from 10.2.1 to 10.2.2 (#9132)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.2.1 to 10.2.2.
- [Release notes](https://github.com/isaacs/node-glob/releases)
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.2.1...v10.2.2)

---
updated-dependencies:
- dependency-name: glob
  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>
2023-05-01 18:09:44 +01:00
dependabot[bot]
eae375a3bb chore(deps-dev): bump eslint-plugin-jsdoc from 43.0.7 to 43.1.1 (#9124)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 43.0.7 to 43.1.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/v43.0.7...v43.1.1)

---
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>
2023-05-01 14:54:03 +00:00
dependabot[bot]
5b89ccc789 chore(deps-dev): bump cypress from 12.10.0 to 12.11.0 (#9131)
Bumps [cypress](https://github.com/cypress-io/cypress) from 12.10.0 to 12.11.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/v12.10.0...v12.11.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>
2023-05-01 14:49:14 +00:00
299 changed files with 24310 additions and 32742 deletions

View File

@@ -2,7 +2,6 @@ extends:
- standard
- standard-jsx
- standard-react
- plugin:@typescript-eslint/recommended
- prettier
- eslint:recommended
@@ -18,7 +17,7 @@ settings:
react:
version: '16.8'
jsdoc:
mode: jsdoc
mode: typescript
plugins:
- chai-friendly
@@ -37,39 +36,33 @@ overrides:
# rules listed here are only ones which conflict.
- files:
- '**/*.js'
- '!frontend/**/*.js'
- 'badge-maker/**/*.js'
- '**/*.cjs'
env:
node: true
es6: true
- files:
- '**/*.js'
- '!frontend/**/*.js'
- '!badge-maker/**/*.js'
env:
node: true
es6: true
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
rules:
no-console: 'off'
'@typescript-eslint/explicit-module-boundary-types': 'off'
- files:
- '**/*.@(ts|tsx)'
- '**/*.ts'
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
rules:
# Argh.
'@typescript-eslint/explicit-function-return-type':
['error', { 'allowExpressions': true }]
'@typescript-eslint/no-empty-function': 'error'
'@typescript-eslint/no-var-requires': 'error'
'@typescript-eslint/no-object-literal-type-assertion': 'off'
'@typescript-eslint/no-explicit-any': 'error'
'@typescript-eslint/ban-ts-ignore': 'off'
'@typescript-eslint/explicit-module-boundary-types': 'off'
- files:
- core/**/*.ts
parserOptions:
sourceType: 'module'
parser: '@typescript-eslint/parser'
- files:
- gatsby-browser.js
- 'frontend/**/*.@(js|ts|tsx)'
- 'frontend/**/*.js'
parserOptions:
sourceType: 'module'
env:
@@ -128,14 +121,6 @@ rules:
# Disable some rules from eslint:recommended.
no-empty: ['error', { 'allowEmptyCatch': true }]
# Allow unused parameters. In callbacks, removing them seems to obscure
# what the functions are doing.
'@typescript-eslint/no-unused-vars': ['error', { 'args': 'none' }]
no-unused-vars: 'off'
'@typescript-eslint/no-var-requires': 'off'
'@typescript-eslint/no-use-before-define': 'error'
no-use-before-define: 'off'
# These should be disabled by eslint-config-prettier, but are not.
@@ -197,11 +182,7 @@ rules:
jsdoc/require-returns-type: 'error'
jsdoc/valid-types: 'error'
# Disable some from TypeScript.
'@typescript-eslint/camelcase': off
'@typescript-eslint/explicit-function-return-type': 'off'
'@typescript-eslint/no-empty-function': 'off'
react/prop-types: 'off'
react/jsx-sort-props: 'error'
react-hooks/rules-of-hooks: 'error'
react-hooks/exhaustive-deps: 'error'

View File

@@ -35,7 +35,6 @@ function allChangelogLinesAreVersionBump(changelogLines) {
function isPointlessVersionBump(body) {
const pointlessBumpLinks = [
'https://github.com/gatsbyjs/gatsby',
'https://github.com/typescript-eslint/typescript-eslint',
]

View File

@@ -1,31 +0,0 @@
name: 'Frontend tests'
description: 'Run frontend tests and check types'
runs:
using: 'composite'
steps:
- name: Prepare frontend tests
if: always()
run: npm run defs && npm run features
shell: bash
- name: Tests
if: always()
run: npm run test:frontend -- --reporter json --reporter-option 'output=reports/frontend-tests.json'
shell: bash
- name: Type Checks
if: always()
run: |
set -o pipefail
npm run check-types:frontend 2>&1 | tee reports/frontend-types.txt
shell: bash
- name: Write Markdown Summary
if: always()
run: |
node scripts/mocha2md.js 'Frontend Tests' reports/frontend-tests.json >> $GITHUB_STEP_SUMMARY
echo '# Frontend Types' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat reports/frontend-types.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -8,6 +8,7 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
ignore:
# https://github.com/badges/shields/issues/7324
# https://github.com/badges/shields/issues/7447
@@ -27,6 +28,7 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
# close-bot package dependencies
- package-ecosystem: npm
@@ -36,8 +38,12 @@ updates:
day: friday
time: '12:00'
open-pull-requests-limit: 99
rebase-strategy: disabled
# GH actions
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99
rebase-strategy: disabled

View File

@@ -29,13 +29,10 @@ jobs:
node-version: 16
cypress: true
- name: Frontend build
run: GATSBY_BASE_URL=http://localhost:8080 npm run build
- name: Run tests
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: npm run e2e-on-build
run: npm run e2e
- name: Archive videos
if: always()

View File

@@ -1,26 +0,0 @@
name: Frontend
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches-ignore:
- 'gh-pages'
- 'dependabot/**'
jobs:
test-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 16
- name: Frontend tests
uses: ./.github/actions/frontend-tests
- name: Frontend build
run: npm run build

14
.gitignore vendored
View File

@@ -92,10 +92,6 @@ typings/
# Temporary build artifacts.
/build
.next
badge-examples.json
supported-features.json
service-definitions.yml
frontend/categories/*.yaml
# Local runtime configuration.
@@ -104,11 +100,6 @@ frontend/categories/*.yaml
# Template for the local runtime configuration.
!/config/local*.template.yml
# Gatsby
/frontend/.cache
/frontend/public
/public
# Cypress
/cypress/videos/
/cypress/screenshots/
@@ -121,3 +112,8 @@ flamegraph.html
# config file for node-pg-migrate
migrations-config.json
# Frontend/Docusaurus
frontend/.docusaurus
frontend/.cache-loader
/public

View File

@@ -1,5 +0,0 @@
reporter: mocha-env-reporter
require:
- '@babel/polyfill'
- '@babel/register'
- mocha-yaml-loader

View File

@@ -1,10 +0,0 @@
{
"reporter": ["lcov"],
"all": false,
"silent": true,
"clean": false,
"sourceMap": false,
"instrument": false,
"include": ["frontend/**/*.js"],
"exclude": ["**/*.spec.js", "**/mocha-*.js"]
}

View File

@@ -10,5 +10,5 @@ public
private/*.json
/.nyc_output
analytics.json
supported-features.json
service-definitions.yml
frontend/.docusaurus
frontend/categories

View File

@@ -4,6 +4,23 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
---
## server-2023-06-01
- feat: Add total commits to [GitHubCommitActivity] [#9196](https://github.com/badges/shields/issues/9196)
- set a custom error on 429 [#9159](https://github.com/badges/shields/issues/9159)
- deprecate [travis].org badges [#9171](https://github.com/badges/shields/issues/9171)
- count private sponsors on [GithubSponsors] badge [#9170](https://github.com/badges/shields/issues/9170)
- Dependency updates
## server-2023-05-01
** Removal:** For users who need to maintain a Github Token pool, storage has been provided via the `RedisTokenPersistence` and `REDIS_URL` settings. This feature was deprecated in `server-2023-03-01`. As of this release, the `RedisTokenPersistence` backend is now removed. If you are using this feature, you will need to migrate to using the `SQLTokenPersistence` backend for storage and provide a postgres connection string via the `POSTGRES_URL` setting. [#8922](https://github.com/badges/shields/issues/8922)
- fail to start server if there are duplicate service names [#9099](https://github.com/badges/shields/issues/9099)
- [SourceForge] Added badges for SourceForge [#9078](https://github.com/badges/shields/issues/9078) [#9102](https://github.com/badges/shields/issues/9102)
- crates: Use `?include=` to reduce crates.io backend load [#9081](https://github.com/badges/shields/issues/9081)
- Dependency updates
## server-2023-04-02
- [JenkinsCoverage] Update Jenkins Code Coverage API for new plugin version [#9010](https://github.com/badges/shields/issues/9010)

View File

@@ -107,7 +107,7 @@ You can read a [tutorial on how to add a badge][tutorial].
When server source files change, the badge server should automatically restart
itself (using [nodemon][]). When the frontend files change, the frontend dev
server (`gatsby dev`) should also automatically reload. However the badge
server (`docusaurus start`) should also automatically reload. However the badge
definitions are built only before the server first starts. To regenerate those,
either run `npm run defs` or manually restart the server.

View File

@@ -1,94 +0,0 @@
export function badgeUrlFromPath({
baseUrl,
path,
queryParams,
style,
format,
longCache,
}: {
baseUrl?: string
path: string
queryParams: { [k: string]: string | number | boolean }
style?: string
format?: string
longCache?: boolean
}): string
export function encodeField(s: string): string
export function staticBadgeUrl({
baseUrl,
label,
message,
labelColor,
color,
style,
namedLogo,
format,
links,
}: {
baseUrl?: string
label: string
message: string
labelColor?: string
color?: string
style?: string
namedLogo?: string
format?: string
links?: string[]
}): string
export function queryStringStaticBadgeUrl({
baseUrl,
label,
message,
color,
labelColor,
style,
namedLogo,
logoColor,
logoWidth,
logoPosition,
format,
}: {
baseUrl?: string
label: string
message: string
color?: string
labelColor?: string
style?: string
namedLogo?: string
logoColor?: string
logoWidth?: number
logoPosition?: number
format?: string
}): string
export function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format,
}: {
baseUrl?: string
datatype: string
label: string
dataUrl: string
query: string
prefix: string
suffix: string
color?: string
style?: string
format?: string
}): string
export function rasterRedirectUrl(
{ rasterUrl }: { rasterUrl: string },
badgeUrl: string
): string

View File

@@ -1,119 +1,5 @@
// Avoid "Attempted import error: 'URL' is not exported from 'url'" in frontend.
import url from 'url'
import queryString from 'query-string'
function badgeUrlFromPath({
baseUrl = '',
path,
queryParams,
style,
format = '',
longCache = false,
}) {
const outExt = format.length ? `.${format}` : ''
const outQueryString = queryString.stringify({
cacheSeconds: longCache ? '2592000' : undefined,
style,
...queryParams,
})
const suffix = outQueryString ? `?${outQueryString}` : ''
return `${baseUrl}${path}${outExt}${suffix}`
}
function encodeField(s) {
return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__'))
}
function staticBadgeUrl({
baseUrl = '',
label,
message,
labelColor,
color = 'lightgray',
style,
namedLogo,
format = '',
links = [],
}) {
const path = [label, message, color].map(encodeField).join('-')
const outQueryString = queryString.stringify({
labelColor,
style,
logo: namedLogo,
link: links,
})
const outExt = format.length ? `.${format}` : ''
const suffix = outQueryString ? `?${outQueryString}` : ''
return `${baseUrl}/badge/${path}${outExt}${suffix}`
}
function queryStringStaticBadgeUrl({
baseUrl = '',
label,
message,
color,
labelColor,
style,
namedLogo,
logoColor,
logoWidth,
logoPosition,
format = '',
}) {
// schemaVersion could be a parameter if we iterate on it,
// for now it's hardcoded to the only supported version.
const schemaVersion = '1'
const suffix = `?${queryString.stringify({
label,
message,
color,
labelColor,
style,
logo: namedLogo,
logoColor,
logoWidth,
logoPosition,
})}`
const outExt = format.length ? `.${format}` : ''
return `${baseUrl}/static/v${schemaVersion}${outExt}${suffix}`
}
function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format = '',
}) {
const outExt = format.length ? `.${format}` : ''
const queryParams = {
label,
url: dataUrl,
query,
style,
}
if (color) {
queryParams.color = color
}
if (prefix) {
queryParams.prefix = prefix
}
if (suffix) {
queryParams.suffix = suffix
}
const outQueryString = queryString.stringify(queryParams)
return `${baseUrl}/badge/dynamic/${datatype}${outExt}?${outQueryString}`
}
function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
// Ensure we're always using the `rasterUrl` by using just the path from
@@ -124,11 +10,4 @@ function rasterRedirectUrl({ rasterUrl }, badgeUrl) {
return result
}
export {
badgeUrlFromPath,
encodeField,
staticBadgeUrl,
queryStringStaticBadgeUrl,
dynamicBadgeUrl,
rasterRedirectUrl,
}
export { rasterRedirectUrl }

View File

@@ -1,142 +0,0 @@
import { test, given } from 'sazerac'
import {
badgeUrlFromPath,
encodeField,
staticBadgeUrl,
queryStringStaticBadgeUrl,
dynamicBadgeUrl,
} from './make-badge-url.js'
describe('Badge URL generation functions', function () {
test(badgeUrlFromPath, () => {
given({
baseUrl: 'http://example.com',
path: '/npm/v/gh-badges',
style: 'flat-square',
longCache: true,
}).expect(
'http://example.com/npm/v/gh-badges?cacheSeconds=2592000&style=flat-square'
)
})
test(encodeField, () => {
given('foo').expect('foo')
given('').expect('')
given('happy go lucky').expect('happy%20go%20lucky')
given('do-right').expect('do--right')
given('it_is_a_snake').expect('it__is__a__snake')
})
test(staticBadgeUrl, () => {
given({
label: 'foo',
message: 'bar',
color: 'blue',
style: 'flat-square',
}).expect('/badge/foo-bar-blue?style=flat-square')
given({
label: 'foo',
message: 'bar',
color: 'blue',
style: 'flat-square',
format: 'png',
namedLogo: 'github',
}).expect('/badge/foo-bar-blue.png?logo=github&style=flat-square')
given({
label: 'Hello World',
message: 'Привет Мир',
color: '#aabbcc',
}).expect(
'/badge/Hello%20World-%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80-%23aabbcc'
)
given({
label: '123-123',
message: 'abc-abc',
color: 'blue',
}).expect('/badge/123--123-abc--abc-blue')
given({
label: '123-123',
message: '',
color: 'blue',
style: 'social',
}).expect('/badge/123--123--blue?style=social')
given({
label: '',
message: 'blue',
color: 'blue',
}).expect('/badge/-blue-blue')
})
test(queryStringStaticBadgeUrl, () => {
// the query-string library sorts parameters by name
given({
label: 'foo',
message: 'bar',
color: 'blue',
style: 'flat-square',
}).expect('/static/v1?color=blue&label=foo&message=bar&style=flat-square')
given({
label: 'foo Bar',
message: 'bar Baz',
color: 'blue',
style: 'flat-square',
format: 'png',
namedLogo: 'github',
}).expect(
'/static/v1.png?color=blue&label=foo%20Bar&logo=github&message=bar%20Baz&style=flat-square'
)
given({
label: 'Hello World',
message: 'Привет Мир',
color: '#aabbcc',
}).expect(
'/static/v1?color=%23aabbcc&label=Hello%20World&message=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80'
)
})
test(dynamicBadgeUrl, () => {
const dataUrl = 'http://example.com/foo.json'
const query = '$.bar'
const prefix = 'value: '
given({
baseUrl: 'http://img.example.com',
datatype: 'json',
label: 'foo',
dataUrl,
query,
prefix,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json',
'?label=foo',
`&prefix=${encodeURIComponent(prefix)}`,
`&query=${encodeURIComponent(query)}`,
'&style=plastic',
`&url=${encodeURIComponent(dataUrl)}`,
].join('')
)
const suffix = '<- value'
const color = 'blue'
given({
baseUrl: 'http://img.example.com',
datatype: 'json',
label: 'foo',
dataUrl,
query,
suffix,
color,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json',
'?color=blue',
'&label=foo',
`&query=${encodeURIComponent(query)}`,
'&style=plastic',
`&suffix=${encodeURIComponent(suffix)}`,
`&url=${encodeURIComponent(dataUrl)}`,
].join('')
)
})
})

View File

@@ -44,6 +44,12 @@ class BaseGraphqlService extends BaseService {
* and custom error messages e.g: `{ 404: 'package not found' }`.
* This can be used to extend or override the
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @param {Function} [attrs.transformJson=data => data] Function which takes the raw json and transforms it before
* further procesing. In case of multiple query in a single graphql call and few of them
* throw error, partial data might be used ignoring the error.
@@ -62,6 +68,7 @@ class BaseGraphqlService extends BaseService {
variables = {},
options = {},
httpErrorMessages = {},
systemErrors = {},
transformJson = data => data,
transformErrors = defaultTransformErrors,
}) {
@@ -74,7 +81,8 @@ class BaseGraphqlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
errorMessages: httpErrorMessages,
httpErrors: httpErrorMessages,
systemErrors,
})
const json = transformJson(this._parseJson(buffer))
if (json.errors) {

View File

@@ -30,14 +30,26 @@ class BaseJsonService extends BaseService {
* @param {string} attrs.url URL to request
* @param {object} [attrs.options={}] Options to pass to got. See
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
* @param {object} [attrs.httpErrors={}] Key-value map of status codes
* and custom error messages e.g: `{ 404: 'package not found' }`.
* This can be used to extend or override the
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @returns {object} Parsed response
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
*/
async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
async _requestJson({
schema,
url,
options = {},
httpErrors = {},
systemErrors = {},
}) {
const mergedOptions = {
...{ headers: { Accept: 'application/json' } },
...options,
@@ -45,7 +57,8 @@ class BaseJsonService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
errorMessages,
httpErrors,
systemErrors,
})
const json = this._parseJson(buffer)
return this.constructor._validate(json, schema)

View File

@@ -53,10 +53,16 @@ class BaseSvgScrapingService extends BaseService {
* @param {string} attrs.url URL to request
* @param {object} [attrs.options={}] Options to pass to got. See
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
* @param {object} [attrs.httpErrors={}] Key-value map of status codes
* and custom error messages e.g: `{ 404: 'package not found' }`.
* This can be used to extend or override the
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @returns {object} Parsed response
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
*/
@@ -65,7 +71,8 @@ class BaseSvgScrapingService extends BaseService {
valueMatcher,
url,
options = {},
errorMessages = {},
httpErrors = {},
systemErrors = {},
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
const mergedOptions = {
@@ -75,7 +82,8 @@ class BaseSvgScrapingService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
errorMessages,
httpErrors,
systemErrors,
})
logTrace(emojic.dart, 'Response SVG', buffer)
const data = {

View File

@@ -24,10 +24,16 @@ class BaseXmlService extends BaseService {
* @param {string} attrs.url URL to request
* @param {object} [attrs.options={}] Options to pass to got. See
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
* @param {object} [attrs.httpErrors={}] Key-value map of status codes
* and custom error messages e.g: `{ 404: 'package not found' }`.
* This can be used to extend or override the
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
* [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
* @returns {object} Parsed response
@@ -38,7 +44,8 @@ class BaseXmlService extends BaseService {
schema,
url,
options = {},
errorMessages = {},
httpErrors = {},
systemErrors = {},
parserOptions = {},
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -49,7 +56,8 @@ class BaseXmlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
errorMessages,
httpErrors,
systemErrors,
})
const validateResult = XMLValidator.validate(buffer)
if (validateResult !== true) {

View File

@@ -23,10 +23,16 @@ class BaseYamlService extends BaseService {
* @param {string} attrs.url URL to request
* @param {object} [attrs.options={}] Options to pass to got. See
* [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
* @param {object} [attrs.errorMessages={}] Key-value map of status codes
* @param {object} [attrs.httpErrors={}] Key-value map of status codes
* and custom error messages e.g: `{ 404: 'package not found' }`.
* This can be used to extend or override the
* [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
* @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
* and an object of params to pass when we construct an Inaccessible exception object
* e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
* for allowed keys
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
* @param {object} [attrs.encoding='utf8'] Character encoding
* @returns {object} Parsed response
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
@@ -35,7 +41,8 @@ class BaseYamlService extends BaseService {
schema,
url,
options = {},
errorMessages = {},
httpErrors = {},
systemErrors = {},
encoding = 'utf8',
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -51,7 +58,8 @@ class BaseYamlService extends BaseService {
const { buffer } = await this._request({
url,
options: mergedOptions,
errorMessages,
httpErrors,
systemErrors,
})
let parsed
try {

View File

@@ -226,7 +226,7 @@ class BaseService {
this._metricHelper = metricHelper
}
async _request({ url, options = {}, errorMessages = {} }) {
async _request({ url, options = {}, httpErrors = {}, systemErrors = {} }) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
let logUrl = url
const logOptions = Object.assign({}, options)
@@ -246,10 +246,14 @@ class BaseService {
'Request',
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`
)
const { res, buffer } = await this._requestFetcher(url, options)
const { res, buffer } = await this._requestFetcher(
url,
options,
systemErrors
)
await this._meterResponse(res, buffer)
logTrace(emojic.dart, 'Response status code', res.statusCode)
return checkErrorResponse(errorMessages)({ buffer, res })
return checkErrorResponse(httpErrors)({ buffer, res })
}
static enabledMetrics = []
@@ -328,11 +332,15 @@ class BaseService {
error instanceof Deprecated
) {
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
const serviceData = {
isError: true,
message: error.prettyMessage,
color: 'lightgray',
}
if (error.cacheSeconds !== undefined) {
serviceData.cacheSeconds = error.cacheSeconds
}
return serviceData
} else if (this._handleInternalErrors) {
if (
!trace.logTrace(

View File

@@ -39,6 +39,7 @@ function coalesceCacheLength({
assert(defaultCacheLengthSeconds !== undefined)
const cacheLength = coalesce(
serviceOverrideCacheLengthSeconds,
serviceDefaultCacheLengthSeconds,
defaultCacheLengthSeconds
)
@@ -46,7 +47,6 @@ function coalesceCacheLength({
// Overrides can apply _more_ caching, but not less. Query param overriding
// can request more overriding than service override, but not less.
const candidateOverrides = [
serviceOverrideCacheLengthSeconds,
overrideCacheLengthFromQueryParams(queryParams),
].filter(x => x !== undefined)

View File

@@ -74,12 +74,12 @@ describe('Cache header functions', function () {
serviceDefaultCacheLengthSeconds: 900,
serviceOverrideCacheLengthSeconds: 400,
queryParams: {},
}).expect(900)
}).expect(400)
given({
cacheHeaderConfig,
serviceOverrideCacheLengthSeconds: 400,
queryParams: {},
}).expect(777)
}).expect(400)
given({
cacheHeaderConfig,
serviceOverrideCacheLengthSeconds: 900,

View File

@@ -2,21 +2,22 @@ import { NotFound, InvalidResponse, Inaccessible } from './errors.js'
const defaultErrorMessages = {
404: 'not found',
429: 'rate limited by upstream service',
}
export default function checkErrorResponse(errorMessages = {}) {
export default function checkErrorResponse(httpErrors = {}) {
return async function ({ buffer, res }) {
let error
errorMessages = { ...defaultErrorMessages, ...errorMessages }
httpErrors = { ...defaultErrorMessages, ...httpErrors }
if (res.statusCode === 404) {
error = new NotFound({ prettyMessage: errorMessages[404] })
error = new NotFound({ prettyMessage: httpErrors[404] })
} else if (res.statusCode !== 200) {
const underlying = Error(
`Got status code ${res.statusCode} (expected 200)`
)
const props = { underlyingError: underlying }
if (errorMessages[res.statusCode] !== undefined) {
props.prettyMessage = errorMessages[res.statusCode]
if (httpErrors[res.statusCode] !== undefined) {
props.prettyMessage = httpErrors[res.statusCode]
}
if (res.statusCode >= 500) {
error = new Inaccessible(props)

View File

@@ -45,6 +45,42 @@ describe('async error handler', function () {
})
})
context('when status is 429', function () {
const buffer = Buffer.from('some stuff')
const res = { statusCode: 429 }
it('throws InvalidResponse', async function () {
try {
await checkErrorResponse()({ res, buffer })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)'
)
expect(e.prettyMessage).to.equal('rate limited by upstream service')
expect(e.response).to.equal(res)
expect(e.buffer).to.equal(buffer)
}
})
it('displays the custom too many requests', async function () {
const notFoundMessage = "terribly sorry but that's one too many requests"
try {
await checkErrorResponse({ 429: notFoundMessage })({ res, buffer })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.message).to.equal(
'Invalid Response: Got status code 429 (expected 200)'
)
expect(e.prettyMessage).to.equal(
"terribly sorry but that's one too many requests"
)
}
})
})
context('when status is 4xx', function () {
it('throws InvalidResponse', async function () {
const res = { statusCode: 499 }

View File

@@ -42,6 +42,7 @@ class ShieldsRuntimeError extends Error {
if (props.underlyingError) {
this.stack = props.underlyingError.stack
}
this.cacheSeconds = props.cacheSeconds
}
}
@@ -206,6 +207,9 @@ class Deprecated extends ShieldsRuntimeError {
* @property {string} prettyMessage User-facing error message to override the
* value of `defaultPrettyMessage()`. This is the text that will appear on the
* badge when we catch and render the exception (Optional)
* @property {number} cacheSeconds Length of time to cache this error response
* for. Defaults to the cacheLength of the service class throwing the error
* (Optional)
*/
export {

View File

@@ -7,7 +7,7 @@ import {
const userAgent = getUserAgent()
async function sendRequest(gotWrapper, url, options) {
async function sendRequest(gotWrapper, url, options = {}, systemErrors = {}) {
const gotOptions = Object.assign({}, options)
gotOptions.throwHttpErrors = false
gotOptions.retry = { limit: 0 }
@@ -22,6 +22,12 @@ async function sendRequest(gotWrapper, url, options) {
underlyingError: new Error('Maximum response size exceeded'),
})
}
if (err.code in systemErrors) {
throw new Inaccessible({
...systemErrors[err.code],
underlyingError: err,
})
}
throw new Inaccessible({ underlyingError: err })
}
}

View File

@@ -45,6 +45,36 @@ describe('got wrapper', function () {
)
})
it('should throw a custom error if provided', async function () {
const sendRequest = _fetchFactory(1024)
return (
expect(
sendRequest(
'https://www.google.com/foo/bar',
{ timeout: { request: 1 } },
{
ETIMEDOUT: {
prettyMessage: 'Oh no! A terrible thing has happened',
cacheSeconds: 10,
},
}
)
)
.to.be.rejectedWith(
Inaccessible,
"Inaccessible: Timeout awaiting 'request' for 1ms"
)
// eslint-disable-next-line promise/prefer-await-to-then
.then(error => {
expect(error).to.have.property(
'prettyMessage',
'Oh no! A terrible thing has happened'
)
expect(error).to.have.property('cacheSeconds', 10)
})
)
})
it('should pass a custom user agent header', async function () {
nock('https://www.google.com', {
reqheaders: {

View File

@@ -65,7 +65,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
*/
if (match[0] === '/endpoint' && Object.keys(queryParams).length === 0) {
ask.res.statusCode = 301
ask.res.setHeader('Location', '/endpoint/')
ask.res.setHeader('Location', '/badges/endpoint-badge')
ask.res.end()
return
}
@@ -73,11 +73,9 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
// `defaultCacheLengthSeconds` can be overridden by
// `serviceDefaultCacheLengthSeconds` (either by category or on a badge-
// by-badge basis). Then in turn that can be overridden by
// `serviceOverrideCacheLengthSeconds` (which we expect to be used only in
// the dynamic badge) but only if `serviceOverrideCacheLengthSeconds` is
// longer than `serviceDefaultCacheLengthSeconds` and then the `cacheSeconds`
// query param can also override both of those but again only if `cacheSeconds`
// is longer.
// `serviceOverrideCacheLengthSeconds`.
// Then the `cacheSeconds` query param can also override both of those
// but only if `cacheSeconds` is longer.
//
// When the legacy services have been rewritten, all the code in here
// will go away, which should achieve this goal in a simpler way.

View File

@@ -148,7 +148,7 @@ describe('The request handler', function () {
expect(headers['cache-control']).to.equal('max-age=900, s-maxage=900')
})
it('should let live service data override the default cache headers with longer value', async function () {
it('should allow serviceData to override the default cache headers with longer value', async function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
@@ -168,7 +168,7 @@ describe('The request handler', function () {
expect(headers['cache-control']).to.equal('max-age=400, s-maxage=400')
})
it('should not let live service data override the default cache headers with shorter value', async function () {
it('should allow serviceData to override the default cache headers with shorter value', async function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
@@ -185,7 +185,7 @@ describe('The request handler', function () {
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
expect(headers['cache-control']).to.equal('max-age=300, s-maxage=300')
expect(headers['cache-control']).to.equal('max-age=200, s-maxage=200')
})
it('should set the expires header to current time + cacheSeconds', async function () {

View File

@@ -1,10 +1,10 @@
import BaseJsonService from '../base-json.js'
class BadBaseService {}
class GoodService extends BaseJsonService {
class GoodMixedService extends BaseJsonService {
static category = 'build'
static route = { base: 'it/is', pattern: 'good' }
}
class BadService extends BadBaseService {}
class BadMixedService extends BadBaseService {}
export default [GoodService, BadService]
export default [GoodMixedService, BadMixedService]

View File

@@ -1,3 +1,3 @@
class BadService {}
class BadNoBaseService {}
export default BadService
export default BadNoBaseService

View File

@@ -1,4 +1,4 @@
class BadBaseService {}
class BadService extends BadBaseService {}
class BadChildService extends BadBaseService {}
export default BadService
export default BadChildService

View File

@@ -1,12 +1,12 @@
import BaseJsonService from '../base-json.js'
class GoodServiceOne extends BaseJsonService {
class GoodServiceArrayOne extends BaseJsonService {
static category = 'build'
static route = { base: 'good', pattern: 'one' }
}
class GoodServiceTwo extends BaseJsonService {
class GoodServiceArrayTwo extends BaseJsonService {
static category = 'build'
static route = { base: 'good', pattern: 'two' }
}
export default [GoodServiceOne, GoodServiceTwo]
export default [GoodServiceArrayOne, GoodServiceArrayTwo]

View File

@@ -1,12 +1,12 @@
import BaseJsonService from '../base-json.js'
class GoodServiceOne extends BaseJsonService {
class GoodServiceObjectOne extends BaseJsonService {
static category = 'build'
static route = { base: 'good', pattern: 'one' }
}
class GoodServiceTwo extends BaseJsonService {
class GoodServiceObjectTwo extends BaseJsonService {
static category = 'build'
static route = { base: 'good', pattern: 'two' }
}
export { GoodServiceOne, GoodServiceTwo }
export { GoodServiceObjectOne, GoodServiceObjectTwo }

View File

@@ -31,6 +31,18 @@ function getServicePaths(pattern) {
return globSync(toUnixPath(path.join(serviceDir, '**', pattern))).sort()
}
function assertNamesUnique(names, { message }) {
const duplicates = {}
Object.entries(countBy(names))
.filter(([name, count]) => count > 1)
.forEach(([name, count]) => {
duplicates[name] = count
})
if (Object.keys(duplicates).length) {
throw new Error(`${message}: ${JSON.stringify(duplicates, undefined, 2)}`)
}
}
async function loadServiceClasses(servicePaths) {
if (!servicePaths) {
servicePaths = getServicePaths('*.service.js')
@@ -64,29 +76,14 @@ async function loadServiceClasses(servicePaths) {
})
}
return serviceClasses
}
function assertNamesUnique(names, { message }) {
const duplicates = {}
Object.entries(countBy(names))
.filter(([name, count]) => count > 1)
.forEach(([name, count]) => {
duplicates[name] = count
})
if (Object.keys(duplicates).length) {
throw new Error(`${message}: ${JSON.stringify(duplicates, undefined, 2)}`)
}
}
async function checkNames() {
const services = await loadServiceClasses()
assertNamesUnique(
services.map(({ name }) => name),
serviceClasses.map(({ name }) => name),
{
message: 'Duplicate service names found',
}
)
return serviceClasses
}
async function collectDefinitions() {
@@ -114,7 +111,6 @@ export {
InvalidService,
loadServiceClasses,
getServicePaths,
checkNames,
collectDefinitions,
loadTesters,
}

View File

@@ -1,4 +1,4 @@
const baseUrl = process.env.BASE_URL || 'https://img.shields.io'
const baseUrl = process.env.BASE_URL
const globalParamRefs = [
{ $ref: '#/components/parameters/style' },
{ $ref: '#/components/parameters/logo' },
@@ -228,7 +228,7 @@ function category2openapi(category, services) {
name: 'CC0',
},
},
servers: [{ url: baseUrl }],
servers: baseUrl ? [{ url: baseUrl }] : undefined,
components: {
parameters: {
style: {

View File

@@ -76,7 +76,6 @@ class LegacyService extends BaseJsonService {
const expected = {
openapi: '3.0.0',
info: { version: '1.0.0', title: 'build', license: { name: 'CC0' } },
servers: [{ url: 'https://img.shields.io' }],
components: {
parameters: {
style: {

View File

@@ -1,8 +1,5 @@
import Joi from 'joi'
// This should be kept in sync with the schema in
// `frontend/lib/service-definitions/index.ts`.
const arrayOfStrings = Joi.array().items(Joi.string()).min(0).required()
const objectOfKeyValues = Joi.object()
@@ -92,9 +89,4 @@ function assertValidServiceDefinitionExport(examples, message = undefined) {
Joi.assert(examples, serviceDefinitionExport, message)
}
export {
serviceDefinition,
assertValidServiceDefinition,
serviceDefinitionExport,
assertValidServiceDefinitionExport,
}
export { assertValidServiceDefinition, assertValidServiceDefinitionExport }

View File

@@ -362,7 +362,7 @@ class Server {
})
if (!rasterUrl) {
camp.route(/\.png$/, (query, match, end, request) => {
camp.route(/^\/((?!img\/)).*\.png$/, (query, match, end, request) => {
makeSend(
'svg',
request.res,
@@ -412,7 +412,7 @@ class Server {
if (rasterUrl) {
// Redirect to the raster server for raster versions of modern badges.
camp.route(/\.png$/, (queryParams, match, end, ask) => {
camp.route(/^\/((?!img\/)).*\.png$/, (queryParams, match, end, ask) => {
ask.res.statusCode = 301
ask.res.setHeader(
'Location',

View File

@@ -98,6 +98,11 @@ describe('The server', function () {
)
})
it('should not redirect for PNG requests in /img', async function () {
const { statusCode } = await got(`${baseUrl}img/frontend-image.png`)
expect(statusCode).to.equal(200)
})
it('should produce SVG badges with expected headers', async function () {
const { statusCode, headers } = await got(
`${baseUrl}:fruit-apple-green.svg`

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -2,16 +2,9 @@ import { registerCommand } from 'cypress-wait-for-stable-dom'
registerCommand()
describe('Main page', function () {
describe('Frontend', function () {
const backendUrl = Cypress.env('backend_url')
const SEARCH_INPUT = 'input[placeholder="search"]'
function expectBadgeExample(title, previewUrl, pattern) {
cy.contains('tr', `${title}:`).find('code').should('have.text', pattern)
cy.contains('tr', `${title}:`)
.find('img')
.should('have.attr', 'src', previewUrl)
}
const SEARCH_INPUT = 'input[placeholder="Search"]'
function visitAndWait(page) {
cy.visit(page)
@@ -26,36 +19,37 @@ describe('Main page', function () {
cy.contains('PyPI - License')
})
it('Shows badge from category', function () {
visitAndWait('/category/chat')
it('Shows badges from category', function () {
visitAndWait('/badges')
expectBadgeExample(
'Discourse status',
'http://localhost:8080/badge/discourse-online-brightgreen',
'/discourse/status?server=https%3A%2F%2Fmeta.discourse.org'
)
cy.contains('Build')
cy.contains('Chat').click()
cy.contains('Discourse status')
cy.contains('Stack Exchange questions')
})
it('Customizate badges', function () {
visitAndWait('/')
it('Shows expected code examples', function () {
visitAndWait('/badges/static-badge')
cy.get(SEARCH_INPUT).type('issues')
cy.contains('/github/issues/:user/:repo').click()
cy.get('input[name="user"]').type('badges')
cy.get('input[name="repo"]').type('shields')
cy.get('table input[name="color"]').type('orange')
cy.get(`img[src='${backendUrl}/github/issues/badges/shields?color=orange']`)
cy.contains('button', 'URL').should('have.class', 'api-code-tab')
cy.contains('button', 'Markdown').should('have.class', 'api-code-tab')
cy.contains('button', 'rSt').should('have.class', 'api-code-tab')
cy.contains('button', 'AsciiDoc').should('have.class', 'api-code-tab')
cy.contains('button', 'HTML').should('have.class', 'api-code-tab')
})
it('Do not duplicate example parameters', function () {
visitAndWait('/category/funding')
it('Build a badge', function () {
visitAndWait('/badges/git-hub-issues')
cy.contains('GitHub Sponsors').click()
cy.get('[name="style"]').should($style => {
expect($style).to.have.length(1)
})
cy.contains('/github/issues/:user/:repo')
cy.get('input[placeholder="user"]').type('badges')
cy.get('input[placeholder="repo"]').type('shields')
cy.intercept('GET', `${backendUrl}/github/issues/badges/shields`).as('get')
cy.contains('Execute').click()
cy.wait('@get').its('response.statusCode').should('eq', 200)
cy.get('img[id="badge-preview"]')
})
})

View File

@@ -15,8 +15,8 @@ const { fileMatch } = danger.git
const documentation = fileMatch(
'**/*.md',
'frontend/components/usage.tsx',
'frontend/pages/endpoint.tsx'
'frontend/docs/**',
'frontend/src/**'
)
const server = fileMatch('core/server/**.js', '!*.spec.js')
const serverTests = fileMatch('core/server/**.spec.js')

View File

@@ -44,7 +44,7 @@ In case you get the _"getaddrinfo ENOTFOUND localhost"_ error, visit [http://127
## (3) Open an Issue
Before you want to implement your service, you may want to [open an issue](https://github.com/badges/shields/issues/new?template=3_Badge_request.md) and describe what you have in mind:
Before you want to implement your service, you may want to [open an issue](https://github.com/badges/shields/issues/new?template=3_Badge_request.yml) and describe what you have in mind:
- What is the badge for?
- Which API do you want to use?
@@ -229,14 +229,14 @@ Description of the code:
- `_requestJson()` automatically adds an Accept header, checks the status code, parses the response as JSON, and returns the parsed response.
- `_requestJson()` uses [got](https://github.com/sindresorhus/got) to perform the HTTP request. Options can be passed to got, including method, query string, and headers. If headers are provided they will override the ones automatically set by `_requestJson()`. There is no need to specify json, as the JSON parsing is handled by `_requestJson()`. See the `got` docs for [supported options](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md).
- Error messages corresponding to each status code can be returned by passing a dictionary of status codes -> messages in `errorMessages`.
- Error messages corresponding to each status code can be returned by passing a dictionary of status codes -> messages in `httpErrors`.
- A more complex call to `_requestJson()` might look like this:
```js
return this._requestJson({
schema: mySchema,
url,
options: { searchParams: { branch: 'master' } },
errorMessages: {
httpErrors: {
401: 'private application not supported',
404: 'application not found',
},

View File

@@ -4,7 +4,7 @@
The Shields codebase is divided into several parts:
1. The frontend (about 7% of the code)
1. The frontend
1. [`frontend`][frontend]
2. The badge renderer (which is available as an npm package)
1. [`badge-maker`][badge-maker]
@@ -30,16 +30,16 @@ The Shields codebase is divided into several parts:
The tests are also divided into several parts:
1. Unit and functional tests of the frontend
1. `frontend/**/*.spec.js`
2. Unit and functional tests of the badge renderer
1. Unit and functional tests of the badge renderer
1. `badge-maker/**/*.spec.js`
3. Unit and functional tests of the core code
2. Unit and functional tests of the core code
1. `core/**/*.spec.js`
4. Unit and functional tests of the service helper functions
3. Unit and functional tests of the service helper functions
1. `services/*.spec.js`
5. Unit and functional tests of the service code (we have only a few of these)
4. Unit and functional tests of the service code (we have only a few of these)
1. `services/*/**/*.spec.js`
5. End-to-end tests for the frontend
1. `cypress/e2e/*.cy.js`
6. The service tester and service test runner
1. [`core/service-test-runner`][service-test-runner]
7. [The service tests themselves][service tests] live integration tests of the

View File

@@ -90,6 +90,8 @@ Past that point, all related code will be deleted, and a not found error will be
Here is a listing of all deleted badges that were once part of the Shields.io service:
- Beerpay
- Bintray
- bitHound
- Cauditor
- CocoaPods Apps
@@ -97,6 +99,8 @@ Here is a listing of all deleted badges that were once part of the Shields.io se
- Codetally
- continuousphp
- Coverity
- David
- dependabot
- Dockbit
- Dotnet Status
- Gemnasium
@@ -107,8 +111,11 @@ Here is a listing of all deleted badges that were once part of the Shields.io se
- Leanpub
- Libscore
- Magnum CI
- MicroBadger
- NSP
- PHP Eye
- requires.io
- Shippable
- Snap CI
- VersionEye
- Waffle

View File

@@ -155,13 +155,13 @@ These are documented in [server-secrets.md](./server-secrets.md)
If you want to host the frontend on a separate server, such as cloud storage
or a CDN, you can do that.
First, build the frontend, pointing `GATSBY_BASE_URL` to your server.
First, build the frontend, pointing `BASE_URL` to your server.
```sh
GATSBY_BASE_URL=https://your-server.example.com npm run build
BASE_URL=https://your-server.example.com npm run build
```
Then copy the contents of the `build/` folder to your static hosting / CDN.
Then copy the contents of the `public/` folder to your static hosting / CDN.
There are also a couple settings you should configure on the server.

View File

@@ -152,7 +152,7 @@ npm run test:services -- --only="wercker" --fgrep="Build status (with branch)"
Having covered the typical and custom cases, we'll move on to errors. We should include a test for the 'not found' response and also tests for any other custom error handling. The Wercker integration defines a custom error condition for 401 as well as a custom 404 message:
```js
errorMessages: {
httpErrors: {
401: 'private application not supported',
404: 'application not found',
}

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
}

View File

@@ -1,108 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import {
badgeUrlFromPath,
staticBadgeUrl,
} from '../../core/badge-urls/make-badge-url'
import { removeRegexpFromPattern } from '../lib/pattern-helpers'
import {
Example as ExampleData,
RenderableExample,
} from '../lib/service-definitions'
import { Badge } from './common'
import { StyledCode } from './snippet'
const ExampleTable = styled.table`
min-width: 50%;
margin: auto;
th,
td {
text-align: left;
}
`
const ClickableTh = styled.th`
cursor: pointer;
`
const ClickableCode = styled(StyledCode)`
cursor: pointer;
`
function Example({
baseUrl,
onClick,
exampleData,
}: {
baseUrl?: string
onClick: (example: RenderableExample) => void
exampleData: RenderableExample
}): JSX.Element {
const handleClick = React.useCallback(
function (): void {
onClick(exampleData)
},
[exampleData, onClick]
)
const {
example: { pattern, queryParams },
preview: { label, message, color, style, namedLogo },
} = exampleData as ExampleData
const previewUrl = staticBadgeUrl({
baseUrl,
label: label || '',
message,
color,
style,
namedLogo,
})
const exampleUrl = badgeUrlFromPath({
path: removeRegexpFromPattern(pattern),
queryParams,
})
const { title } = exampleData
return (
<tr>
<ClickableTh onClick={handleClick}>{title}:</ClickableTh>
<td>
<Badge
alt={`${title} badge`}
clickable
onClick={handleClick}
src={previewUrl}
/>
</td>
<td>
<ClickableCode onClick={handleClick}>{exampleUrl}</ClickableCode>
</td>
</tr>
)
}
export function BadgeExamples({
examples,
baseUrl,
onClick,
}: {
examples: RenderableExample[]
baseUrl?: string
onClick: (exampleData: RenderableExample) => void
}): JSX.Element {
return (
<ExampleTable>
<tbody>
{examples.map(exampleData => (
<Example
baseUrl={baseUrl}
exampleData={exampleData}
key={`${exampleData.title} ${exampleData.example.pattern}`}
onClick={onClick}
/>
))}
</tbody>
</ExampleTable>
)
}

View File

@@ -1,84 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'
import { H3 } from './common'
export interface Category {
id: string
name: string
}
export function CategoryHeading({
category: { id, name },
}: {
category: Category
}): JSX.Element {
return (
<Link to={`/category/${id}`}>
<H3 id={id}>{name}</H3>
</Link>
)
}
export function CategoryHeadings({
categories,
}: {
categories: Category[]
}): JSX.Element {
return (
<div>
{categories.map(category => (
<CategoryHeading category={category} key={category.id} />
))}
</div>
)
}
const StyledNav = styled.nav`
ul {
display: flex;
min-width: 50%;
max-width: 500px;
margin: 0 auto 20px;
padding-inline-start: 0;
flex-wrap: wrap;
justify-content: center;
list-style-type: none;
}
@media screen and (max-width: 768px) {
ul {
display: none;
}
}
li {
margin: 4px 10px;
}
.active {
font-weight: 900;
}
`
export function CategoryNav({
categories,
}: {
categories: Category[]
}): JSX.Element {
return (
<StyledNav>
<ul>
{categories.map(({ id, name }) => (
<li key={id}>
<Link to={`/category/${id}`}>{name}</Link>
</li>
))}
</ul>
</StyledNav>
)
}

View File

@@ -1,125 +0,0 @@
import React from 'react'
import styled, { css, createGlobalStyle } from 'styled-components'
export const noAutocorrect = Object.freeze({
autoComplete: 'off',
autoCorrect: 'off',
autoCapitalize: 'off',
spellcheck: 'false',
})
export const nonBreakingSpace = '\u00a0'
export const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
}
`
export const BaseFont = styled.div`
font-family: Lekton, sans-serif;
color: #534;
`
export const H2 = styled.h2`
font-style: italic;
margin-top: 12mm;
font-variant: small-caps;
::before {
content: '☙ ';
}
::after {
content: ' ❧';
}
`
export const H3 = styled.h3`
font-style: italic;
`
interface BadgeWrapperProps {
height: string
display: string
clickable: boolean
}
const BadgeWrapper = styled.span<BadgeWrapperProps>`
padding: 2px;
height: ${({ height }) => height};
vertical-align: middle;
display: ${({ display }) => display};
${({ clickable }) =>
clickable &&
css`
cursor: pointer;
`};
`
interface BadgeProps extends React.HTMLAttributes<HTMLImageElement> {
src: string
alt?: string
display?: 'inline' | 'block' | 'inline-block'
height?: string
clickable?: boolean
object?: boolean
}
export function Badge({
src,
alt = '',
display = 'inline',
height = '20px',
clickable = false,
object = false,
...rest
}: BadgeProps): JSX.Element {
return (
<BadgeWrapper clickable={clickable} display={display} height={height}>
{src ? (
object ? (
<object data={src}>alt</object>
) : (
<img alt={alt} src={src} {...rest} />
)
) : (
nonBreakingSpace
)}
</BadgeWrapper>
)
}
export const StyledInput = styled.input`
height: 15px;
border: solid #b9a;
border-width: 0 0 1px 0;
padding: 0;
text-align: center;
color: #534;
:focus {
outline: 0;
}
`
export const InlineInput = styled(StyledInput)`
width: 70px;
margin-left: 5px;
margin-right: 5px;
`
export const BlockInput = styled(StyledInput)`
width: 40%;
background-color: transparent;
`
export const VerticalSpace = styled.hr`
border: 0;
display: block;
height: 3mm;
`

View File

@@ -1,46 +0,0 @@
import React from 'react'
import styled from 'styled-components'
const BuilderOuterContainer = styled.div`
margin-top: 10px;
margin-bottom: 10px;
`
// The inner container is inline-block so that its width matches its columns.
const BuilderInnerContainer = styled.div`
display: inline-block;
padding: 1px 14px 10px;
border-radius: 4px;
background: #eef;
`
export function BuilderContainer({
children,
}: {
children: JSX.Element[] | JSX.Element
}): JSX.Element {
return (
<BuilderOuterContainer>
<BuilderInnerContainer>{children}</BuilderInnerContainer>
</BuilderOuterContainer>
)
}
const labelFont = `
font-family: system-ui;
font-size: 11px;
`
export const BuilderLabel = styled.label`
${labelFont}
text-transform: lowercase;
`
export const BuilderCaption = styled.span`
${labelFont}
color: #999;
`

View File

@@ -1,73 +0,0 @@
import React, { useState, useImperativeHandle, forwardRef } from 'react'
import posed from 'react-pose'
import styled from 'styled-components'
const ContentAnchor = styled.span`
position: relative;
display: inline-block;
`
// 100vw allows providing styled content which is wider than its container.
const ContentContainer = styled.span`
width: 100vw;
position: absolute;
left: 50%;
transform: translateX(-50%);
will-change: opacity, top;
pointer-events: none;
`
const PosedContentContainer = posed(ContentContainer)({
hidden: { opacity: 0, transition: { duration: 100 } },
effectStart: { top: '-10px', opacity: 1.0, transition: { duration: 0 } },
effectEnd: { top: '-75px', opacity: 0.5 },
})
export interface CopiedContentIndicatorHandle {
trigger: () => void
}
// When `trigger()` is called, render copied content that floats up, then
// disappears.
function _CopiedContentIndicator(
{
copiedContent,
children,
}: {
copiedContent: JSX.Element | string
children: JSX.Element | JSX.Element[]
},
ref: React.Ref<CopiedContentIndicatorHandle>
): JSX.Element {
const [pose, setPose] = useState('hidden')
useImperativeHandle(ref, () => ({
trigger() {
setPose('effectStart')
},
}))
const handlePoseComplete = React.useCallback(
function (): void {
if (pose === 'effectStart') {
setPose('effectEnd')
} else {
setPose('hidden')
}
},
[pose, setPose]
)
return (
<ContentAnchor>
<PosedContentContainer onPoseComplete={handlePoseComplete} pose={pose}>
{copiedContent}
</PosedContentContainer>
{children}
</ContentAnchor>
)
}
export const CopiedContentIndicator = forwardRef(_CopiedContentIndicator)

View File

@@ -1,156 +0,0 @@
import React, { useRef, useState } from 'react'
import clipboardCopy from 'clipboard-copy'
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { generateMarkup, MarkupFormat } from '../../lib/generate-image-markup'
import { Badge } from '../common'
import PathBuilder from './path-builder'
import QueryStringBuilder from './query-string-builder'
import RequestMarkupButtom from './request-markup-button'
import {
CopiedContentIndicator,
CopiedContentIndicatorHandle,
} from './copied-content-indicator'
export default function Customizer({
baseUrl,
title,
pattern,
exampleNamedParams,
exampleQueryParams,
initialStyle,
}: {
baseUrl: string
title: string
pattern: string
exampleNamedParams: { [k: string]: string }
exampleQueryParams: { [k: string]: string }
initialStyle?: string
}): JSX.Element {
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/35572
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884#issuecomment-471341041
const indicatorRef =
useRef<CopiedContentIndicatorHandle>() as React.MutableRefObject<CopiedContentIndicatorHandle>
const [path, setPath] = useState('')
const [queryString, setQueryString] = useState<string>()
const [pathIsComplete, setPathIsComplete] = useState<boolean>()
const [markup, setMarkup] = useState<string>()
const [message, setMessage] = useState<string>()
const generateBuiltBadgeUrl = React.useCallback(
function (): string {
const suffix = queryString ? `?${queryString}` : ''
return `${baseUrl}${path}${suffix}`
},
[baseUrl, path, queryString]
)
function renderLivePreview(): JSX.Element {
// There are some usability issues here. It would be better if the message
// changed from a validation error to a loading message once the
// parameters were filled in, and also switched back to loading when the
// parameters changed.
let src
if (pathIsComplete) {
src = generateBuiltBadgeUrl()
} else {
src = staticBadgeUrl({
baseUrl,
label: 'preview',
message: 'some parameters missing',
})
}
return (
<p>
<Badge alt="preview badge" display="block" src={src} />
</p>
)
}
const copyMarkup = React.useCallback(
async function (markupFormat: MarkupFormat): Promise<void> {
const builtBadgeUrl = generateBuiltBadgeUrl()
const markup = generateMarkup({
badgeUrl: builtBadgeUrl,
title,
markupFormat,
})
try {
await clipboardCopy(markup)
} catch (e) {
setMessage('Copy failed')
setMarkup(markup)
return
}
setMarkup(markup)
if (indicatorRef.current) {
indicatorRef.current.trigger()
}
},
[generateBuiltBadgeUrl, title, setMessage, setMarkup]
)
function renderMarkupAndLivePreview(): JSX.Element {
return (
<div>
{renderLivePreview()}
<CopiedContentIndicator copiedContent="Copied" ref={indicatorRef}>
<RequestMarkupButtom
isDisabled={!pathIsComplete}
onMarkupRequested={copyMarkup}
/>
</CopiedContentIndicator>
{message && (
<div>
<p>{message}</p>
<p>Markup: {markup}</p>
</div>
)}
</div>
)
}
const handlePathChange = React.useCallback(
function ({
path,
isComplete,
}: {
path: string
isComplete: boolean
}): void {
setPath(path)
setPathIsComplete(isComplete)
},
[setPath, setPathIsComplete]
)
const handleQueryStringChange = React.useCallback(
function ({
queryString,
isComplete,
}: {
queryString: string
isComplete: boolean
}): void {
setQueryString(queryString)
},
[setQueryString]
)
return (
<form action="">
<PathBuilder
exampleParams={exampleNamedParams}
onChange={handlePathChange}
pattern={pattern}
/>
<QueryStringBuilder
exampleParams={exampleQueryParams}
initialStyle={initialStyle}
onChange={handleQueryStringChange}
/>
<div>{renderMarkupAndLivePreview()}</div>
</form>
)
}

View File

@@ -1,258 +0,0 @@
import React, { useState, useEffect, ChangeEvent } from 'react'
import styled, { css } from 'styled-components'
import { Token, Key, parse } from 'path-to-regexp'
import humanizeString from 'humanize-string'
import { patternToOptions } from '../../lib/pattern-helpers'
import { noAutocorrect, StyledInput } from '../common'
import {
BuilderContainer,
BuilderLabel,
BuilderCaption,
} from './builder-common'
interface PathBuilderColumnProps {
pathContainsOnlyLiterals: boolean
withHorizPadding?: boolean
}
const PathBuilderColumn = styled.span<PathBuilderColumnProps>`
height: ${({ pathContainsOnlyLiterals }) =>
pathContainsOnlyLiterals ? '18px' : '78px'};
float: left;
display: flex;
flex-direction: column;
margin: 0;
${({ withHorizPadding }) =>
withHorizPadding &&
css`
padding: 0 8px;
`};
`
interface PathLiteralProps {
isFirstToken: boolean
pathContainsOnlyLiterals: boolean
}
const PathLiteral = styled.div<PathLiteralProps>`
margin-top: ${({ pathContainsOnlyLiterals }) =>
pathContainsOnlyLiterals ? '0px' : '39px'};
${({ isFirstToken }) =>
isFirstToken &&
css`
margin-left: 3px;
`};
`
const NamedParamLabelContainer = styled.span`
display: flex;
flex-direction: column;
height: 37px;
width: 100%;
justify-content: center;
`
const inputStyling = `
width: 100%;
text-align: center;
`
// 2px to align with input boxes alongside.
const NamedParamInput = styled(StyledInput)`
${inputStyling}
margin-top: 2px;
margin-bottom: 10px;
`
const NamedParamSelect = styled.select`
${inputStyling}
margin-bottom: 9px;
font-size: 10px;
`
const NamedParamCaption = styled(BuilderCaption)`
width: 100%;
text-align: center;
`
export function constructPath({
tokens,
namedParams,
}: {
tokens: Token[]
namedParams: { [k: string]: string }
}): { path: string; isComplete: boolean } {
let isComplete = true
let path = tokens
.map(token => {
if (typeof token === 'string') {
return token.trim()
} else {
const { prefix, name, modifier } = token
const value = namedParams[name]
if (value) {
return `${prefix}${value.trim()}`
} else if (modifier === '?' || modifier === '*') {
return ''
} else {
isComplete = false
return `${prefix}:${name}`
}
}
})
.join('')
path = encodeURI(path)
return { path, isComplete }
}
export default function PathBuilder({
pattern,
exampleParams,
onChange,
}: {
pattern: string
exampleParams: { [k: string]: string }
onChange: ({
path,
isComplete,
}: {
path: string
isComplete: boolean
}) => void
}): JSX.Element {
const [tokens] = useState(() => parse(pattern))
const [namedParams, setNamedParams] = useState(() =>
// `pathToRegexp.parse()` returns a mixed array of strings for literals
// and objects for parameters. Filter out the literals and work with the
// objects.
tokens
.filter(t => typeof t !== 'string')
.map(t => t as Key)
.reduce((accum, { name }) => {
accum[name] = ''
return accum
}, {} as { [k: string]: string })
)
useEffect(() => {
// Ensure the default style is applied right away.
if (onChange) {
const { path, isComplete } = constructPath({ tokens, namedParams })
onChange({ path, isComplete })
}
}, [tokens, namedParams, onChange])
const handleTokenChange = React.useCallback(
function ({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setNamedParams({
...namedParams,
[name]: value,
})
},
[setNamedParams, namedParams]
)
function renderLiteral(
literal: string,
tokenIndex: number,
pathContainsOnlyLiterals: boolean
): JSX.Element {
return (
<PathBuilderColumn
key={`${tokenIndex}-${literal}`}
pathContainsOnlyLiterals={pathContainsOnlyLiterals}
>
<PathLiteral
isFirstToken={tokenIndex === 0}
pathContainsOnlyLiterals={pathContainsOnlyLiterals}
>
{literal}
</PathLiteral>
</PathBuilderColumn>
)
}
function renderNamedParamInput(token: Key): JSX.Element {
const { pattern } = token
const name = `${token.name}`
const options = patternToOptions(pattern)
const value = namedParams[name]
if (options) {
return (
<NamedParamSelect
name={name}
onChange={handleTokenChange}
value={value}
>
<option key="empty" value="">
{' '}
</option>
{options.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</NamedParamSelect>
)
} else {
return (
<NamedParamInput
name={name}
onChange={handleTokenChange}
type="text"
value={value}
{...noAutocorrect}
/>
)
}
}
function renderNamedParam(
token: Key,
tokenIndex: number,
namedParamIndex: number
): JSX.Element {
const { prefix, modifier } = token
const optional = modifier === '?' || modifier === '*'
const name = `${token.name}`
const exampleValue = exampleParams[name] || '(not set)'
return (
<React.Fragment key={token.name}>
{renderLiteral(prefix, tokenIndex, false)}
<PathBuilderColumn pathContainsOnlyLiterals={false} withHorizPadding>
<NamedParamLabelContainer>
<BuilderLabel htmlFor={name}>{humanizeString(name)}</BuilderLabel>
{optional ? <BuilderLabel>(optional)</BuilderLabel> : null}
</NamedParamLabelContainer>
{renderNamedParamInput(token)}
<NamedParamCaption>
{namedParamIndex === 0 ? `e.g. ${exampleValue}` : exampleValue}
</NamedParamCaption>
</PathBuilderColumn>
</React.Fragment>
)
}
let namedParamIndex = 0
const pathContainsOnlyLiterals = tokens.every(
token => typeof token === 'string'
)
return (
<BuilderContainer>
{tokens.map((token, tokenIndex) =>
typeof token === 'string'
? renderLiteral(token, tokenIndex, pathContainsOnlyLiterals)
: renderNamedParam(token, tokenIndex, namedParamIndex++)
)}
</BuilderContainer>
)
}

View File

@@ -1,348 +0,0 @@
import React, {
useState,
useEffect,
ChangeEvent,
ChangeEventHandler,
} from 'react'
import styled from 'styled-components'
import humanizeString from 'humanize-string'
import qs from 'query-string'
import { advertisedStyles } from '../../lib/supported-features'
import { noAutocorrect, StyledInput } from '../common'
import {
BuilderContainer,
BuilderLabel,
BuilderCaption,
} from './builder-common'
const QueryParamLabel = styled(BuilderLabel)`
margin: 5px;
`
const QueryParamInput = styled(StyledInput)`
margin: 5px 10px;
`
const QueryParamCaption = styled(BuilderCaption)`
margin: 5px;
`
type BadgeOptionName = 'style' | 'label' | 'color' | 'logo' | 'logoColor'
interface BadgeOptionInfo {
name: BadgeOptionName
label?: string
shieldsDefaultValue?: string
}
const supportedBadgeOptions = [
{ name: 'style', shieldsDefaultValue: 'flat' },
{ name: 'label', label: 'override label' },
{ name: 'color', label: 'override color' },
{ name: 'logo', label: 'named logo' },
{ name: 'logoColor', label: 'override logo color' },
] as BadgeOptionInfo[]
function getBadgeOption(name: BadgeOptionName): BadgeOptionInfo {
const result = supportedBadgeOptions.find(opt => opt.name === name)
if (!result) {
throw Error(`Unknown badge option: ${name}`)
}
return result
}
function getQueryString({
queryParams,
badgeOptions,
}: {
queryParams: Record<string, string | boolean>
badgeOptions: Record<BadgeOptionName, string | undefined>
}): {
queryString: string
isComplete: boolean
} {
// Use `string | null`, because `query-string` renders e.g.
// `{ compact_message: null }` as `?compact_message`. This is
// what we want for boolean params that are true (see below).
const outQuery = {} as Record<string, string | null>
let isComplete = true
Object.entries(queryParams).forEach(([name, value]) => {
// As above, there are two types of supported params: strings and
// booleans.
if (typeof value === 'string') {
if (value) {
outQuery[name] = value.trim()
} else {
// Skip empty params.
isComplete = false
}
} else {
// Generate empty query params for boolean parameters by translating
// `{ compact_message: true }` to `?compact_message`. When values are
// false, skip the param.
if (value) {
outQuery[name] = null
}
}
})
Object.entries(badgeOptions).forEach(([name, value]) => {
const { shieldsDefaultValue } = getBadgeOption(name as BadgeOptionName)
if (value && value !== shieldsDefaultValue) {
outQuery[name] = value
}
})
const queryString = qs.stringify(outQuery)
return { queryString, isComplete }
}
function ServiceQueryParam({
name,
value,
exampleValue,
isStringParam,
stringParamCount,
handleServiceQueryParamChange,
}: {
name: string
value: string | boolean
exampleValue: string
isStringParam: boolean
stringParamCount?: number
handleServiceQueryParamChange: ChangeEventHandler<HTMLInputElement>
}): JSX.Element {
return (
<tr>
<td>
<QueryParamLabel htmlFor={name}>
{humanizeString(name).toLowerCase()}
</QueryParamLabel>
</td>
<td>
{isStringParam && (
<QueryParamCaption>
{stringParamCount === 0 ? `e.g. ${exampleValue}` : exampleValue}
</QueryParamCaption>
)}
</td>
<td>
{isStringParam ? (
<QueryParamInput
name={name}
onChange={handleServiceQueryParamChange}
type="text"
value={value as string}
{...noAutocorrect}
/>
) : (
<input
checked={value as boolean}
name={name}
onChange={handleServiceQueryParamChange}
type="checkbox"
/>
)}
</td>
</tr>
)
}
function BadgeOptionInput({
name,
value,
handleBadgeOptionChange,
}: {
name: BadgeOptionName
value: string
handleBadgeOptionChange: ChangeEventHandler<
HTMLSelectElement | HTMLInputElement
>
}): JSX.Element {
if (name === 'style') {
return (
<select name="style" onChange={handleBadgeOptionChange} value={value}>
{advertisedStyles.map(style => (
<option key={style} value={style}>
{style}
</option>
))}
</select>
)
} else {
return (
<QueryParamInput
name={name}
onChange={handleBadgeOptionChange}
type="text"
value={value}
{...noAutocorrect}
/>
)
}
}
function BadgeOption({
name,
value,
handleBadgeOptionChange,
}: {
name: BadgeOptionName
value: string
handleBadgeOptionChange: ChangeEventHandler<HTMLInputElement>
}): JSX.Element {
const {
label = humanizeString(name),
shieldsDefaultValue: hasShieldsDefaultValue,
} = getBadgeOption(name)
return (
<tr>
<td>
<QueryParamLabel htmlFor={name}>{label}</QueryParamLabel>
</td>
<td>
{!hasShieldsDefaultValue && (
<QueryParamCaption>optional</QueryParamCaption>
)}
</td>
<td>
<BadgeOptionInput
handleBadgeOptionChange={handleBadgeOptionChange}
name={name}
value={value}
/>
</td>
</tr>
)
}
// The UI for building the query string, which includes two kinds of settings:
// 1. Custom query params defined by the service, stored in
// `this.state.queryParams`
// 2. The standard badge options which apply to all badges, stored in
// `this.state.badgeOptions`
export default function QueryStringBuilder({
exampleParams,
initialStyle = 'flat',
onChange,
}: {
exampleParams: { [k: string]: string }
initialStyle?: string
onChange: ({
queryString,
isComplete,
}: {
queryString: string
isComplete: boolean
}) => void
}): JSX.Element {
const [queryParams, setQueryParams] = useState(() =>
// For each of the custom query params defined in `exampleParams`,
// create empty values in `queryParams`.
Object.entries(exampleParams)
.filter(
// If the example defines a value for one of the standard supported
// options, do not duplicate the corresponding parameter.
([name]) => !supportedBadgeOptions.some(option => name === option.name)
)
.reduce((accum, [name, value]) => {
// Custom query params are either string or boolean. Inspect the example
// value to infer which one, and set empty values accordingly.
// Throughout the component, these two types are supported in the same
// manner: by inspecting this value type.
const isStringParam = typeof value === 'string'
accum[name] = isStringParam ? '' : true
return accum
}, {} as { [k: string]: string | boolean })
)
// For each of the standard badge options, create empty values in
// `badgeOptions`. When `initialStyle` has been provided, use it.
const [badgeOptions, setBadgeOptions] = useState(() =>
supportedBadgeOptions.reduce((accum, { name }) => {
if (name === 'style') {
accum[name] = initialStyle
} else {
accum[name] = ''
}
return accum
}, {} as Record<BadgeOptionName, string>)
)
const handleServiceQueryParamChange = React.useCallback(
function ({
target: { name, type: targetType, checked, value },
}: ChangeEvent<HTMLInputElement>): void {
const outValue = targetType === 'checkbox' ? checked : value
setQueryParams({ ...queryParams, [name]: outValue })
},
[setQueryParams, queryParams]
)
const handleBadgeOptionChange = React.useCallback(
function ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>): void {
setBadgeOptions({ ...badgeOptions, [name]: value })
},
[setBadgeOptions, badgeOptions]
)
useEffect(() => {
if (onChange) {
const { queryString, isComplete } = getQueryString({
queryParams,
badgeOptions,
})
onChange({ queryString, isComplete })
}
}, [onChange, queryParams, badgeOptions])
const hasQueryParams = Boolean(Object.keys(queryParams).length)
let stringParamCount = 0
return (
<>
{hasQueryParams && (
<BuilderContainer>
<table>
<tbody>
{Object.entries(queryParams).map(([name, value]) => {
const isStringParam = typeof value === 'string'
return (
<ServiceQueryParam
exampleValue={exampleParams[name]}
handleServiceQueryParamChange={
handleServiceQueryParamChange
}
isStringParam={isStringParam}
key={name}
name={name}
stringParamCount={
isStringParam ? stringParamCount++ : undefined
}
value={value}
/>
)
})}
</tbody>
</table>
</BuilderContainer>
)}
<BuilderContainer>
<table>
<tbody>
{Object.entries(badgeOptions).map(([name, value]) => (
<BadgeOption
handleBadgeOptionChange={handleBadgeOptionChange}
key={name}
name={name as BadgeOptionName}
value={value}
/>
))}
</tbody>
</table>
</BuilderContainer>
</>
)
}

View File

@@ -1,134 +0,0 @@
import React, { useRef } from 'react'
import styled from 'styled-components'
import Select, { components } from 'react-select'
import { MarkupFormat } from '../../lib/generate-image-markup'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function ClickableControl(props: any): JSX.Element {
return (
<components.Control
{...props}
innerProps={{
onMouseDown: props.selectProps.onControlMouseDown,
}}
/>
)
}
interface Option {
value: MarkupFormat
label: string
}
const MarkupFormatSelect = styled(Select)`
width: 200px;
margin-left: auto;
margin-right: auto;
font-family: 'Lato', sans-serif;
font-size: 12px;
.markup-format__control {
background-image: linear-gradient(-180deg, #00aeff 0%, #0076ff 100%);
border: 1px solid rgba(238, 239, 241, 0.8);
border-width: 0;
box-shadow: unset;
cursor: copy;
}
.markup-format__control--is-disabled {
background: rgba(0, 118, 255, 0.3);
cursor: none;
}
.markup-format__placeholder {
color: #eeeff1;
}
.markup-format__indicator {
color: rgba(238, 239, 241, 0.81);
cursor: pointer;
}
.markup-format__indicator:hover {
color: #eeeff1;
}
.markup-format__control--is-focused .markup-format__indicator,
.markup-format__control--is-focused .markup-format__indicator:hover {
color: #ffffff;
}
.markup-format__option {
text-align: left;
cursor: copy;
}
`
const markupOptions: Option[] = [
{ value: 'markdown', label: 'Copy Markdown' },
{ value: 'rst', label: 'Copy reStructuredText' },
{ value: 'asciidoc', label: 'Copy AsciiDoc' },
{ value: 'html', label: 'Copy HTML' },
]
export default function GetMarkupButton({
onMarkupRequested,
isDisabled,
}: {
onMarkupRequested: (markupFormat: MarkupFormat) => Promise<void>
isDisabled: boolean
}): JSX.Element {
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/35572
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884#issuecomment-471341041
const selectRef = useRef<Select<Option>>() as React.MutableRefObject<
Select<Option>
>
const onControlMouseDown = React.useCallback(
async function (event: MouseEvent): Promise<void> {
if (onMarkupRequested) {
await onMarkupRequested('link')
}
if (selectRef.current) {
selectRef.current.blur()
}
},
[onMarkupRequested, selectRef]
)
const onOptionClick = React.useCallback(
async function onOptionClick(
// Eeesh.
value: Option | readonly Option[] | null | undefined
): Promise<void> {
const { value: markupFormat } = value as Option
if (onMarkupRequested) {
await onMarkupRequested(markupFormat)
}
},
[onMarkupRequested]
)
return (
// TODO It doesn't seem to be possible to check the types and wrap with
// styled-components at the same time. To check the types, replace
// `MarkupFormatSelect` with `Select<Option>`.
<MarkupFormatSelect
blurInputOnSelect
classNamePrefix="markup-format"
closeMenuOnScroll
components={{ Control: ClickableControl }}
isDisabled={isDisabled}
isSearchable={false}
menuPlacement="auto"
onChange={onOptionClick}
onControlMouseDown={onControlMouseDown}
options={markupOptions}
placeholder="Copy Badge URL"
ref={selectRef}
value={null}
/>
)
}

View File

@@ -1,77 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { getBaseUrl } from '../../constants'
import { shieldsLogos, simpleIcons } from '../../lib/supported-features'
import Meta from '../meta'
import Header from '../header'
import { H3, Badge } from '../common'
const StyledTable = styled.table`
border: 1px solid #ccc;
border-collapse: collapse;
td {
border: 1px solid #ccc;
padding: 3px;
text-align: left;
}
`
function NamedLogoTable({ logoNames }: { logoNames: string[] }): JSX.Element {
const baseUrl = getBaseUrl()
return (
<StyledTable>
<thead>
<tr>
<td>Flat</td>
<td>Social</td>
</tr>
</thead>
<tbody>
{logoNames.map(name => (
<tr key={name}>
<td>
<Badge
alt={`logo: ${name}`}
src={staticBadgeUrl({
baseUrl,
label: 'named logo',
message: name,
color: 'blue',
namedLogo: name,
})}
/>
</td>
<td>
<Badge
alt={`logo: ${name}`}
src={staticBadgeUrl({
baseUrl,
label: 'Named Logo',
message: name,
color: 'blue',
namedLogo: name,
style: 'social',
})}
/>
</td>
</tr>
))}
</tbody>
</StyledTable>
)
}
export default function LogoPage(): JSX.Element {
return (
<div>
<Meta />
<Header />
<H3>Named logos</H3>
<NamedLogoTable logoNames={shieldsLogos} />
<H3>Simple-icons</H3>
<NamedLogoTable logoNames={simpleIcons} />
</div>
)
}

View File

@@ -1,168 +0,0 @@
import React, { Fragment } from 'react'
import styled from 'styled-components'
// FIXME: is this needed?
// @ts-ingnore
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { getBaseUrl } from '../../constants'
import Meta from '../meta'
// ts-expect-error: because reasons?
import Header from '../header'
import { H3, Badge } from '../common'
const StyledTable = styled.table`
border: 1px solid #ccc;
border-collapse: collapse;
td {
border: 1px solid #ccc;
padding: 3px;
text-align: left;
}
`
interface BadgeData {
label: string
message: string
labelColor?: string
color: string
namedLogo?: string
links?: string[]
}
function Badges({
baseUrl,
style,
badges,
}: {
baseUrl: string
style: string
badges: BadgeData[]
}): JSX.Element {
return (
<>
{badges.map(({ label, message, labelColor, color, namedLogo, links }) => (
<Fragment key={`${label}-${message}-${color}-${namedLogo}`}>
<Badge
alt="build"
object={Boolean(links)}
src={staticBadgeUrl({
baseUrl,
label,
message,
labelColor,
color,
namedLogo,
style,
links,
})}
/>
<br />
</Fragment>
))}
</>
)
}
const examples = [
{
title: 'Basic examples',
badges: [
{ label: 'build', message: 'passing', color: 'brightgreen' },
{ label: 'tests', message: '5 passing, 1 failed', color: 'red' },
{ label: 'python', message: '3.5 | 3.6 | 3.7', color: 'blue' },
],
},
{
title: 'Logo',
badges: [
{
label: 'build',
message: 'passing',
color: 'brightgreen',
namedLogo: 'appveyor',
},
],
},
{
title: 'No left text',
badges: [
{ label: '', message: 'blueviolet', color: 'blueviolet' },
{
label: '',
message: 'passing',
color: 'brightgreen',
namedLogo: 'appveyor',
},
{
label: '',
message: 'passing',
color: 'brightgreen',
labelColor: 'grey',
namedLogo: 'appveyor',
},
],
},
{
title: 'Links',
badges: [
{
label: 'badges',
message: 'shields',
color: 'blue',
links: [
'https://github.com/badges/',
'https://github.com/badges/shields/',
],
},
],
},
]
function StyleTable({ style }: { style: string }): JSX.Element {
const baseUrl = getBaseUrl()
return (
<StyledTable>
<thead>
<tr>
<td>Description</td>
<td>Badges (new)</td>
<td>Badges (img.shields.io)</td>
</tr>
</thead>
<tbody>
{examples.map(({ title, badges }) => (
<tr key={title}>
<td>{title}</td>
<td>
<Badges badges={badges} baseUrl={baseUrl} style={style} />
</td>
<td>
<Badges
badges={badges}
baseUrl="https://img.shields.io"
style={style}
/>
</td>
</tr>
))}
</tbody>
</StyledTable>
)
}
const styles = ['flat', 'flat-square', 'for-the-badge', 'social', 'plastic']
export default function StylePage(): JSX.Element {
return (
<div>
<Meta />
<Header />
{styles.map(style => (
<Fragment key={style}>
<H3>{style}</H3>
<StyleTable style={style} />
</Fragment>
))}
</div>
)
}

View File

@@ -1,16 +0,0 @@
import React from 'react'
import styled from 'styled-components'
const Donate = styled.div`
padding: 25px 50px;
`
export default function DonateBox(): JSX.Element {
return (
<Donate>
Love Shields? Please consider{' '}
<a href="https://opencollective.com/shields">donating</a> to sustain our
activities
</Donate>
)
}

View File

@@ -1,103 +0,0 @@
import React, { useState, ChangeEvent } from 'react'
import { dynamicBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { InlineInput } from './common'
type StateKey =
| 'datatype'
| 'label'
| 'dataUrl'
| 'query'
| 'color'
| 'prefix'
| 'suffix'
type State = Record<StateKey, string>
interface InputDef {
name: StateKey
placeholder?: string
}
const inputs = [
{ name: 'label' },
{ name: 'dataUrl', placeholder: 'data url' },
{ name: 'query' },
{ name: 'color' },
{ name: 'prefix' },
{ name: 'suffix' },
] as InputDef[]
export default function DynamicBadgeMaker({
baseUrl = document.location.href,
}: {
baseUrl: string
}): JSX.Element {
const [values, setValues] = useState<State>({
datatype: '',
label: '',
dataUrl: '',
query: '',
color: '',
prefix: '',
suffix: '',
})
const isValid =
values.datatype && values.label && values.dataUrl && values.query
const onChange = React.useCallback(
function ({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setValues({
...values,
[name]: value,
})
},
[values]
)
const onSubmit = React.useCallback(
function onSubmit(e: React.FormEvent): void {
e.preventDefault()
const { datatype, label, dataUrl, query, color, prefix, suffix } = values
window.open(
dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
color,
prefix,
suffix,
}),
'_blank'
)
},
[baseUrl, values]
)
return (
<form onSubmit={onSubmit}>
<select name="datatype" onChange={onChange} value={values.datatype}>
<option disabled value="">
data type
</option>
<option value="json">json</option>
<option value="xml">xml</option>
<option value="yaml">yaml</option>
</select>{' '}
{inputs.map(({ name, placeholder = name }) => (
<InlineInput
key={name}
name={name}
onChange={onChange}
placeholder={placeholder}
value={values[name]}
/>
))}
<button disabled={!isValid}>Make Badge</button>
</form>
)
}

View File

@@ -1,83 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { badgeUrlFromPath } from '../../core/badge-urls/make-badge-url'
import { H2 } from './common'
const SpacedA = styled.a`
margin-left: 10px;
margin-right: 10px;
`
export default function Footer({ baseUrl }: { baseUrl: string }): JSX.Element {
return (
<section>
<H2 id="like-this">Like This?</H2>
<p>
<object
data={badgeUrlFromPath({
baseUrl,
path: '/twitter/follow/shields_io',
queryParams: { label: 'Follow' },
style: 'social',
})}
/>{' '}
{}
<object
data={badgeUrlFromPath({
baseUrl,
path: '/opencollective/backers/shields',
queryParams: { link: 'https://opencollective.com/shields' },
style: 'social',
})}
/>{' '}
{}
<object
data={badgeUrlFromPath({
baseUrl,
path: '/opencollective/sponsors/shields',
queryParams: { link: 'https://opencollective.com/shields' },
style: 'social',
})}
/>{' '}
{}
<object
data={badgeUrlFromPath({
baseUrl,
path: '/github/forks/badges/shields',
queryParams: { label: 'Fork' },
style: 'social',
})}
/>{' '}
{}
<object
data={badgeUrlFromPath({
baseUrl,
path: '/discord/308323056592486420',
queryParams: {
label: 'Chat',
link: 'https://discord.gg/HjJCwm5',
},
style: 'social',
})}
/>
</p>
<p>
Have an idea for an awesome new badge?
<br />
<a href="https://github.com/badges/shields/issues/new?labels=service-badge&template=3_Badge_request.md">
Tell us about it
</a>{' '}
and we might bring it to you!
</p>
<p>
<SpacedA href="/community">Community</SpacedA>
<SpacedA href="https://stats.uptimerobot.com/PjXogHB5p">Status</SpacedA>
<SpacedA href="https://metrics.shields.io">Metrics</SpacedA>
<SpacedA href="https://github.com/badges/shields">GitHub</SpacedA>
</p>
</section>
)
}

View File

@@ -1,26 +0,0 @@
import { Link } from 'gatsby'
import React from 'react'
import styled from 'styled-components'
import Logo from '../images/logo.svg'
import { VerticalSpace } from './common'
const Highlights = styled.p`
font-style: italic;
`
export default function Header(): JSX.Element {
return (
<section>
<Link to="/">
<Logo />
</Link>
<VerticalSpace />
<Highlights>
Pixel-perfect &nbsp; Retina-ready &nbsp; Fast &nbsp; Consistent &nbsp;
Hackable &nbsp; No tracking
</Highlights>
</section>
)
}

View File

@@ -1,185 +0,0 @@
import React, { useRef, useState } from 'react'
import styled from 'styled-components'
import groupBy from 'lodash.groupby'
import {
ServiceDefinition,
Example,
categories,
findCategory,
services,
getDefinitionsForCategory,
RenderableExample,
} from '../lib/service-definitions'
import ServiceDefinitionSetHelper from '../lib/service-definitions/service-definition-set-helper'
import { getBaseUrl } from '../constants'
import Meta from './meta'
import Header from './header'
import Search from './search'
import DonateBox from './donate'
import { MarkupModal } from './markup-modal'
import Usage from './usage'
import Footer from './footer'
import {
Category,
CategoryHeading,
CategoryHeadings,
CategoryNav,
} from './category-headings'
import { BadgeExamples } from './badge-examples'
import { BaseFont, GlobalStyle } from './common'
const AppContainer = styled(BaseFont)`
text-align: center;
`
// `pageContext` is the `context` passed to `createPage()` in
// `gatsby-node.js`. In the case of the index page, `pageContext` is empty.
interface PageContext {
category?: Category
}
export default function Main({
pageContext,
}: {
pageContext: PageContext
}): JSX.Element {
const [searchIsInProgress, setSearchIsInProgress] = useState(false)
const [queryIsTooShort, setQueryIsTooShort] = useState(false)
const [searchResults, setSearchResults] = useState<{
[k: string]: ServiceDefinition[]
}>()
const [selectedExample, setSelectedExample] = useState<RenderableExample>()
const searchTimeout = useRef(0)
const baseUrl = getBaseUrl()
const performSearch = React.useCallback(
function (query: string): void {
setSearchIsInProgress(false)
setQueryIsTooShort(query.length === 1)
if (query.length >= 2) {
const flat = ServiceDefinitionSetHelper.create(services)
.notDeprecated()
.search(query)
.toArray()
setSearchResults(groupBy(flat, 'category'))
} else {
setSearchResults(undefined)
}
},
[setSearchIsInProgress, setQueryIsTooShort, setSearchResults]
)
const searchQueryChanged = React.useCallback(
function (query: string): void {
/*
Add a small delay before showing search results
so that we wait until the user has stopped typing
before we start loading stuff.
This
a) reduces the amount of badges we will load and
b) stops the page from 'flashing' as the user types, like this:
https://user-images.githubusercontent.com/7288322/42600206-9b278470-85b5-11e8-9f63-eb4a0c31cb4a.gif
*/
setSearchIsInProgress(true)
window.clearTimeout(searchTimeout.current)
searchTimeout.current = window.setTimeout(() => performSearch(query), 500)
},
[setSearchIsInProgress, performSearch]
)
const dismissMarkupModal = React.useCallback(
function (): void {
setSelectedExample(undefined)
},
[setSelectedExample]
)
function Category({
category,
definitions,
}: {
category: Category
definitions: ServiceDefinition[]
}): JSX.Element {
const flattened = definitions.reduce((accum, current) => {
const { examples } = current
return accum.concat(examples)
}, [] as Example[])
return (
<div>
<CategoryHeading category={category} />
<BadgeExamples
baseUrl={baseUrl}
examples={flattened}
onClick={setSelectedExample}
/>
</div>
)
}
function renderMain(): JSX.Element | JSX.Element[] {
const { category } = pageContext
if (searchIsInProgress) {
return <div>searching...</div>
} else if (queryIsTooShort) {
return <div>Search term must have 2 or more characters</div>
} else if (searchResults) {
return Object.entries(searchResults).map(([categoryId, definitions]) => {
const category = findCategory(categoryId)
if (category === undefined) {
throw Error(`Couldn't find category: ${categoryId}`)
}
return (
<Category
category={category}
definitions={definitions}
key={categoryId}
/>
)
})
} else if (category) {
const definitions = ServiceDefinitionSetHelper.create(
getDefinitionsForCategory(category.id)
)
.notDeprecated()
.toArray()
return (
<div>
<CategoryNav categories={categories} />
<Category
category={category}
definitions={definitions}
key={category.id}
/>
</div>
)
} else {
return <CategoryHeadings categories={categories} />
}
}
return (
<AppContainer id="app">
<GlobalStyle />
<Meta />
<Header />
<MarkupModal
baseUrl={baseUrl}
example={selectedExample}
onRequestClose={dismissMarkupModal}
/>
<section>
<Search queryChanged={searchQueryChanged} />
<DonateBox />
</section>
{renderMain()}
<Usage baseUrl={baseUrl} />
<Footer baseUrl={baseUrl} />
</AppContainer>
)
}

View File

@@ -1,35 +0,0 @@
import React from 'react'
import Modal from 'react-modal'
import styled from 'styled-components'
import { BaseFont } from '../common'
import { RenderableExample } from '../../lib/service-definitions'
import { MarkupModalContent } from './markup-modal-content'
const ContentContainer = styled(BaseFont)`
text-align: center;
`
export function MarkupModal({
example,
baseUrl,
onRequestClose,
}: {
example: RenderableExample | undefined
baseUrl: string
onRequestClose: () => void
}): JSX.Element {
return (
<Modal
ariaHideApp={false}
contentLabel="Example Modal"
isOpen={example !== undefined}
onRequestClose={onRequestClose}
>
{example !== undefined && (
<ContentContainer>
<MarkupModalContent baseUrl={baseUrl} example={example} />
</ContentContainer>
)}
</Modal>
)
}

View File

@@ -1,44 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { Example, RenderableExample } from '../../lib/service-definitions'
import { H3 } from '../common'
import Customizer from '../customizer/customizer'
const Documentation = styled.div`
max-width: 800px;
margin: 35px auto 20px;
text-align: left;
`
export function MarkupModalContent({
example,
baseUrl,
}: {
example: RenderableExample
baseUrl: string
}): JSX.Element {
const { documentation } = example as Example
const {
title,
example: { pattern, namedParams, queryParams },
preview: { style: initialStyle },
} = example
return (
<>
<H3>{title}</H3>
{documentation ? (
<Documentation dangerouslySetInnerHTML={documentation} />
) : null}
<Customizer
baseUrl={baseUrl}
exampleNamedParams={namedParams}
exampleQueryParams={queryParams}
initialStyle={initialStyle}
pattern={pattern}
title={title}
/>
</>
)
}

View File

@@ -1,25 +0,0 @@
import React from 'react'
import { Helmet } from 'react-helmet'
// eslint-disable-next-line
// @ts-ignore
import favicon from '../images/favicon.png'
import '@fontsource/lato'
import '@fontsource/lekton'
const description = `We serve fast and scalable informational images as badges
for GitHub, Travis CI, Jenkins, WordPress and many more services. Use them to
track the state of your projects, or for promotional purposes.`
export default function Meta(): JSX.Element {
return (
<Helmet>
<title>
Shields.io: Quality metadata badges for open source projects
</title>
<meta charSet="utf-8" />
<meta content="width=device-width,initial-scale=1" name="viewport" />
<meta content={description} name="description" />
<link href={favicon} rel="icon" type="image/png" />
</Helmet>
)
}

View File

@@ -1,36 +0,0 @@
import React, { useRef, ChangeEvent } from 'react'
import debounce from 'lodash.debounce'
import { BlockInput } from './common'
export default function Search({
queryChanged,
}: {
queryChanged: (query: string) => void
}): JSX.Element {
const queryChangedDebounced = useRef(
debounce(queryChanged, 50, { leading: true })
)
const onQueryChanged = React.useCallback(
function ({
target: { value: query },
}: ChangeEvent<HTMLInputElement>): void {
queryChangedDebounced.current(query)
},
[queryChangedDebounced]
)
// TODO: Warning: A future version of React will block javascript: URLs as a security precaution
// how else to do this?
return (
<section>
<form action="javascript:void 0" autoComplete="off">
<BlockInput
autoComplete="off"
onChange={onQueryChanged}
placeholder="search"
/>
</form>
</section>
)
}

View File

@@ -1,61 +0,0 @@
import React from 'react'
import ClickToSelect from '@mapbox/react-click-to-select'
import styled, { css } from 'styled-components'
interface CodeContainerProps {
truncate?: boolean
}
const CodeContainer = styled.span<CodeContainerProps>`
position: relative;
vertical-align: middle;
display: inline-block;
${({ truncate }) =>
truncate &&
css`
max-width: 40%;
overflow: hidden;
text-overflow: ellipsis;
`}
`
export interface StyledCodeProps {
fontSize?: string
}
export const StyledCode = styled.code<StyledCodeProps>`
line-height: 1.2em;
padding: 0.1em 0.3em;
border-radius: 4px;
background: #eef;
font-family: Lekton;
${({ fontSize }) =>
fontSize &&
css`
font-size: ${fontSize};
`}
white-space: nowrap;
`
export function Snippet({
snippet,
truncate = false,
fontSize,
}: {
snippet: string
truncate?: boolean
fontSize?: string
}): JSX.Element {
return (
<CodeContainer truncate={truncate}>
<ClickToSelect>
<StyledCode fontSize={fontSize}>{snippet}</StyledCode>
</ClickToSelect>
</CodeContainer>
)
}

View File

@@ -1,77 +0,0 @@
import React, { useState, ChangeEvent } from 'react'
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { InlineInput } from './common'
type StateKey = 'label' | 'message' | 'color'
type State = Record<StateKey, string>
export default function StaticBadgeMaker({
baseUrl = document.location.href,
}: {
baseUrl: string
}): JSX.Element {
const [values, setValues] = useState<State>({
label: '',
message: '',
color: '',
})
const isValid = values.message && values.color
const onChange = React.useCallback(
function onChange({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setValues({
...values,
[name]: value,
})
},
[setValues, values]
)
const onSubmit = React.useCallback(
function (e: React.FormEvent): void {
e.preventDefault()
const { label, message, color } = values
window.open(staticBadgeUrl({ baseUrl, label, message, color }), '_blank')
},
[baseUrl, values]
)
return (
<form onSubmit={onSubmit}>
<InlineInput
name="label"
onChange={onChange}
placeholder="label"
value={values.label}
/>
<InlineInput
name="message"
onChange={onChange}
placeholder="message"
value={values.message}
/>
<InlineInput
list="default-colors"
name="color"
onChange={onChange}
placeholder="color"
value={values.color}
/>
<datalist id="default-colors">
<option value="brightgreen" />
<option value="green" />
<option value="yellowgreen" />
<option value="yellow" />
<option value="orange" />
<option value="red" />
<option value="lightgrey" />
<option value="blue" />
</datalist>
<button disabled={!isValid}>Make Badge</button>
</form>
)
}

View File

@@ -1,448 +0,0 @@
import React from 'react'
import { Link } from 'gatsby'
import styled from 'styled-components'
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { advertisedStyles, shieldsLogos } from '../lib/supported-features'
// ts-expect-error: because reasons?
import StaticBadgeMaker from './static-badge-maker'
import DynamicBadgeMaker from './dynamic-badge-maker'
import { H2, H3, Badge, VerticalSpace } from './common'
import { Snippet, StyledCode } from './snippet'
const LogoName = styled.span`
white-space: nowrap;
`
const Lhs = styled.td`
text-align: right;
`
const EscapingRuleTable = styled.table`
margin: auto;
`
const QueryParamTable = styled.table`
min-width: 50%;
margin: auto;
table-layout: fixed;
border-spacing: 20px 10px;
`
const QueryParamSyntax = styled.td`
max-width: 300px;
text-align: left;
`
const QueryParamDocumentation = styled.td`
max-width: 600px;
text-align: left;
`
function QueryParam({
snippet,
documentation,
}: {
snippet: string
documentation: JSX.Element | JSX.Element[]
}): JSX.Element {
return (
<tr>
<QueryParamSyntax>
<Snippet snippet={snippet} />
</QueryParamSyntax>
<QueryParamDocumentation>{documentation}</QueryParamDocumentation>
</tr>
)
}
function EscapingConversion({
lhs,
rhs,
}: {
lhs: JSX.Element
rhs: JSX.Element
}): JSX.Element {
return (
<tr>
<Lhs>{lhs}</Lhs>
<td></td>
<td>{rhs}</td>
</tr>
)
}
function ColorExamples({
baseUrl,
colors,
}: {
baseUrl: string
colors: string[]
}): JSX.Element {
return (
<span>
{colors.map((color, i) => (
<Badge
alt={color}
key={color}
src={staticBadgeUrl({ baseUrl, label: '', message: color, color })}
/>
))}
</span>
)
}
function StyleExamples({ baseUrl }: { baseUrl: string }): JSX.Element {
return (
<QueryParamTable>
<tbody>
{advertisedStyles.map(style => {
const snippet = `?style=${style}&logo=appveyor`
const badgeUrl = staticBadgeUrl({
baseUrl,
label: 'style',
message: style,
color: 'green',
namedLogo: 'appveyor',
style,
})
return (
<QueryParam
documentation={<Badge alt={style} src={badgeUrl} />}
key={style}
snippet={snippet}
/>
)
})}
</tbody>
</QueryParamTable>
)
}
function NamedLogos(): JSX.Element {
const renderLogo = (logo: string): JSX.Element => (
<LogoName key={logo}>{logo}</LogoName>
)
const [first, ...rest] = shieldsLogos
const result = ([renderLogo(first)] as (JSX.Element | string)[]).concat(
rest.reduce(
(result, logo) => result.concat([', ', renderLogo(logo)]),
[] as (JSX.Element | string)[]
)
)
return <>{result}</>
}
function StaticBadgeEscapingRules(): JSX.Element {
return (
<EscapingRuleTable>
<tbody>
<EscapingConversion
key="dashes"
lhs={
<span>
Dashes <code>--</code>
</span>
}
rhs={
<span>
<code>-</code> Dash
</span>
}
/>
<EscapingConversion
key="underscores"
lhs={
<span>
Underscores <code>__</code>
</span>
}
rhs={
<span>
<code>_</code> Underscore
</span>
}
/>
<EscapingConversion
key="spaces"
lhs={
<span>
<code>_</code> or Space <code>&nbsp;</code>
</span>
}
rhs={
<span>
<code>&nbsp;</code> Space
</span>
}
/>
</tbody>
</EscapingRuleTable>
)
}
export default function Usage({ baseUrl }: { baseUrl: string }): JSX.Element {
return (
<section>
<H2 id="your-badge">Your Badge</H2>
<H3>Static</H3>
<StaticBadgeMaker baseUrl={baseUrl} />
<VerticalSpace />
<p>Using dash "-" separator</p>
<p>
<Snippet snippet={`${baseUrl}/badge/<LABEL>-<MESSAGE>-<COLOR>`} />
</p>
<StaticBadgeEscapingRules />
<p>Using query string parameters</p>
<p>
<Snippet
snippet={`${baseUrl}/static/v1?label=<LABEL>&message=<MESSAGE>&color=<COLOR>`}
/>
</p>
<H3 id="colors">Colors</H3>
<p>
<ColorExamples
baseUrl={baseUrl}
colors={[
'brightgreen',
'green',
'yellowgreen',
'yellow',
'orange',
'red',
'blue',
'lightgrey',
]}
/>
<br />
<ColorExamples
baseUrl={baseUrl}
colors={[
'success',
'important',
'critical',
'informational',
'inactive',
]}
/>
<br />
<ColorExamples
baseUrl={baseUrl}
colors={['blueviolet', 'ff69b4', '9cf']}
/>
</p>
<H3>Endpoint</H3>
<p>
<Snippet snippet={`${baseUrl}/endpoint?url=<URL>&style<STYLE>`} />
</p>
<p>
Create badges from <Link to="/endpoint">your own JSON endpoint</Link>.
</p>
<H3 id="dynamic-badge">Dynamic</H3>
<DynamicBadgeMaker baseUrl={baseUrl} />
<p>
<StyledCode>
{baseUrl}
/badge/dynamic/json?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
href="https://jsonpath.com"
rel="noopener noreferrer"
target="_blank"
title="JSONPath syntax"
>
$.DATA.SUBDATA
</a>
&gt;&amp;color=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;
</StyledCode>
</p>
<p>
<StyledCode>
{baseUrl}
/badge/dynamic/xml?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
href="http://xpather.com"
rel="noopener noreferrer"
target="_blank"
title="XPath syntax"
>
&#x2F;&#x2F;data/subdata
</a>
&gt;&amp;color=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;
</StyledCode>
</p>
<p>
<StyledCode>
{baseUrl}
/badge/dynamic/yaml?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
href="https://jsonpath.com"
rel="noopener noreferrer"
target="_blank"
title="YAML (JSONPath) syntax"
>
$.DATA.SUBDATA
</a>
&gt;&amp;color=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;
</StyledCode>
</p>
<VerticalSpace />
<H2 id="styles">Styles</H2>
<p>
The following styles are available. Flat is the default. Examples are
shown with an optional logo:
</p>
<StyleExamples baseUrl={baseUrl} />
<p>
Here are a few other parameters you can use: (connecting several with
"&" is possible)
</p>
<QueryParamTable>
<tbody>
<QueryParam
documentation={
<span>
Override the default left-hand-side text (
<a href="https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding">
URL-Encoding
</a>
{} needed for spaces or special characters!)
</span>
}
key="label"
snippet="?label=healthinesses"
/>
<QueryParam
documentation={
<span>
Insert one of the named logos from (<NamedLogos />) or
simple-icons. All simple-icons are referenced using icon slugs.
You can click the icon title on{' '}
<a
href="https://simpleicons.org/"
rel="noopener noreferrer"
target="_blank"
>
simple-icons
</a>{' '}
to copy the slug or they can be found in the{' '}
<a href="https://github.com/simple-icons/simple-icons/blob/master/slugs.md">
slugs.md file
</a>{' '}
in the simple-icons repository.
</span>
}
key="logo"
snippet="?logo=appveyor"
/>
<QueryParam
documentation={
<span>
Insert custom logo image ( 14px high). There is a limit on the
total size of request headers we can accept (8192 bytes). From a
practical perspective, this means the base64-encoded image text
is limited to somewhere slightly under 8192 bytes depending on
the rest of the request header.
</span>
}
key="logoSvg"
snippet="?logo=data:image/png;base64,…"
/>
<QueryParam
documentation={
<span>
Set the color of the logo (hex, rgb, rgba, hsl, hsla and css
named colors supported). Supported for named logos and Shields
logos but not for custom logos. For multicolor Shields logos,
the corresponding named logo will be used and colored.
</span>
}
key="logoColor"
snippet="?logoColor=violet"
/>
<QueryParam
documentation={
<span>Set the horizontal space to give to the logo</span>
}
key="logoWidth"
snippet="?logoWidth=40"
/>
<QueryParam
documentation={
<span>
Specify what clicking on the left/right of a badge should do.
Note that this only works when integrating your badge in an
<StyledCode>&lt;object&gt;</StyledCode> HTML tag, but not an
<StyledCode>&lt;img&gt;</StyledCode> tag or a markup language.
</span>
}
key="link"
snippet="?link=http://left&amp;link=http://right"
/>
<QueryParam
documentation={
<span>
Set background of the left part (hex, rgb, rgba, hsl, hsla and
css named colors supported). The legacy name "colorA" is also
supported.
</span>
}
key="labelColor"
snippet="?labelColor=abcdef"
/>
<QueryParam
documentation={
<span>
Set background of the right part (hex, rgb, rgba, hsl, hsla and
css named colors supported). The legacy name "colorB" is also
supported.
</span>
}
key="color"
snippet="?color=fedcba"
/>
<QueryParam
documentation={
<span>
Set the HTTP cache lifetime (rules are applied to infer a
default value on a per-badge basis, any values specified below
the default will be ignored). The legacy name "maxAge" is also
supported.
</span>
}
key="cacheSeconds"
snippet="?cacheSeconds=3600"
/>
</tbody>
</QueryParamTable>
<p>
We support <code>.svg</code> and <code>.json</code>. The default is{' '}
<code>.svg</code>, which can be omitted from the URL.
</p>
<p>
While we highly recommend using SVG, we also support <code>.png</code>{' '}
for use cases where SVG will not work. These requests should be made to
our raster server <code>https://raster.shields.io</code>. For example,
the raster equivalent of{' '}
<code>https://img.shields.io/npm/v/express</code> is{' '}
<code>https://raster.shields.io/npm/v/express</code>. For backward
compatibility, the badge server will redirect <code>.png</code> badges
to the raster server.
</p>
</section>
)
}

View File

@@ -1,36 +0,0 @@
const baseUrl = process.env.GATSBY_BASE_URL
export function getBaseUrl(): string {
if (baseUrl) {
return baseUrl
}
/*
This is a special case for production.
We want to be able to build the front end with no value set for
`GATSBY_BASE_URL` so that we can deploy a build to staging
and then promote the exact same build to production.
When deployed to staging, we want the frontend on
https://staging.shields.io/ to generate badges with the base
https://staging.shields.io/
When we promote to production we want https://shields.io/ and
https://www.shields.io/ to both generate badges with the base
https://img.shields.io/
*/
try {
const { protocol, hostname, port } = window.location
if (['shields.io', 'www.shields.io'].includes(hostname)) {
return 'https://img.shields.io'
}
if (!port) {
return `${protocol}//${hostname}`
}
return `${protocol}//${hostname}:${port}`
} catch (e) {
// server-side rendering
return ''
}
}

5
frontend/docs/intro.md Normal file
View File

@@ -0,0 +1,5 @@
---
sidebar_position: 1
---
# TODO

View File

@@ -0,0 +1,122 @@
const lightCodeTheme = require('prism-react-renderer/themes/github')
const darkCodeTheme = require('prism-react-renderer/themes/dracula')
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Shields.io',
tagline: 'Concise, consistent, and legible badges',
url: 'https://shields.io',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
organizationName: 'badges',
projectName: 'shields',
themes: [
[
require.resolve('@easyops-cn/docusaurus-search-local'),
/** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */
({
hashed: true,
indexPages: true,
}),
],
],
presets: [
[
'docusaurus-preset-openapi',
/** @type {import('docusaurus-preset-openapi').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.cjs'),
editUrl: 'https://github.com/badges/shields/',
},
blog: {
showReadingTime: true,
editUrl: 'https://github.com/badges/shields/',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
api: {
path: 'categories',
routeBasePath: 'badges',
},
}),
],
],
themeConfig:
/** @type {import('docusaurus-preset-openapi').ThemeConfig} */
({
languageTabs: [],
navbar: {
title: 'Shields.io',
logo: {
alt: 'Shields Logo',
src: 'img/logo.png',
},
items: [
{ to: '/badges', label: 'Badges', position: 'left' },
{ to: '/community', label: 'Community', position: 'left' },
{
href: 'https://github.com/badges/shields',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Community',
items: [
{
label: 'GitHub',
href: 'https://github.com/badges/shields',
},
{
label: 'Open Collective',
href: 'https://opencollective.com/shields',
},
{
label: 'Discord',
href: 'https://discord.gg/HjJCwm5',
},
{
label: 'Twitter',
href: 'https://twitter.com/shields_io',
},
{
label: 'Awesome Badges',
href: 'https://github.com/badges/awesome-badges',
},
],
},
{
title: 'Stats',
items: [
{
label: 'Service Status',
href: 'https://stats.uptimerobot.com/PjXogHB5p',
},
{
label: 'Metrics dashboard',
href: 'https://metrics.shields.io/',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Shields.io. Built with Docusaurus.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
}
module.exports = config

View File

@@ -1,18 +0,0 @@
import redirectLegacyRoutes from './lib/redirect-legacy-routes'
// Adapted from https://github.com/gatsbyjs/gatsby/issues/8413
function scrollToElementId(id) {
const el = document.querySelector(id)
if (el) {
return window.scrollTo(0, el.offsetTop - 20)
} else {
return false
}
}
export function onRouteUpdate({ location: { hash } }) {
if (hash) {
redirectLegacyRoutes()
window.setTimeout(() => scrollToElementId(hash), 10)
}
}

View File

@@ -1,33 +0,0 @@
'use strict'
const path = require('path')
module.exports = {
siteMetadata: {
title: 'Shields.io: Quality metadata badges for open source projects',
description:
'We serve fast and scalable informational images as badges for GitHub, Travis CI, Jenkins, WordPress and many more services. Use them to track the state of your projects, or for promotional purposes.',
author: '@shields_io',
},
plugins: [
{
resolve: 'gatsby-plugin-page-creator',
options: {
path: path.join(__dirname, 'pages'),
},
},
'gatsby-plugin-react-helmet',
'gatsby-plugin-catch-links',
'gatsby-plugin-styled-components',
'gatsby-plugin-remove-trailing-slashes',
'gatsby-plugin-typescript',
// This currently is not being used.
// {
// resolve: 'gatsby-source-filesystem',
// options: {
// name: 'static',
// path: `${__dirname}/frontend/static`,
// },
// },
],
}

View File

@@ -1,45 +0,0 @@
'use strict'
/*
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
const fs = require('fs')
const yaml = require('js-yaml')
const envFlag = require('node-env-flag')
const includeDevPages = envFlag(process.env.INCLUDE_DEV_PAGES, true)
const { categories } = yaml.load(
fs.readFileSync('./service-definitions.yml', 'utf8')
)
// Often in Gatsby context gets piped through GraphQL, but GraphQL adds
// unnecessary complexity here, so this uses the programmatic API.
// https://www.gatsbyjs.org/docs/using-gatsby-without-graphql/#the-approach-fetch-data-and-use-gatsbys-createpages-api
async function createPages({ actions: { createPage } }) {
if (includeDevPages) {
createPage({
path: '/dev/styles',
component: require.resolve('./components/development/style-page.tsx'),
})
createPage({
path: '/dev/logos',
component: require.resolve('./components/development/logo-page.tsx'),
})
}
categories.forEach(category => {
const { id } = category
createPage({
path: `/category/${id}`,
component: require.resolve('./components/main.tsx'),
// `context` provided here becomes `props.pageContext` on the page.
context: { category },
})
})
}
module.exports = { createPages }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="198" height="58"><rect rx="8" x="140" width="55" height="58"/><g stroke="#000" stroke-width="8"><path d="M135.5 54a8 8 0 0 0 8.5 -8.5"/><rect x="4" y="4" rx="8" width="190" height="50" fill="none"/></g><path d="m23.906 33.641c.953-.083 1.906-.167 2.859-.25.108 2.099 1.511 4.139 3.578 4.722 2.438.895 5.357.799 7.559-.658 1.49-1.129 1.861-3.674.324-4.925-1.557-1.322-3.685-1.504-5.576-2.057-2.343-.565-4.912-1.133-6.611-2.979-1.805-2.088-1.627-5.485.292-7.443 2.041-2.113 5.222-2.55 8.02-2.274 2.46.244 5.058 1.343 6.252 3.635.426.908 1.095 2.241.656 3.108-.888.173-1.81.148-2.715.245-.077-2.084-1.727-4.073-3.863-4.234-1.902-.317-4.02-.252-5.691.802-1.398.989-1.849 3.363-.381 4.494 1.281 1.01 2.962 1.199 4.482 1.642 2.66.627 5.602 1.118 7.596 3.158 2 2.188 1.893 5.84-.088 8.01-2.01 2.32-5.304 2.972-8.237 2.713-2.585-.147-5.319-1.024-6.916-3.184-.987-1.288-1.517-2.905-1.542-4.523"/><path d="m45.953 41c0-7.635 0-15.271 0-22.906.938 0 1.875 0 2.813 0 0 2.74 0 5.479 0 8.219 1.391-1.721 3.69-2.523 5.86-2.236 1.975.154 4.03 1.371 4.513 3.402.504 1.973.278 4.02.33 6.04 0 2.495 0 4.989 0 7.484-.938 0-1.875 0-2.813 0-.009-3.675.018-7.351-.014-11.03-.026-1.342-.627-2.835-2-3.282-2.187-.802-5.077.393-5.609 2.773-.417 1.764-.216 3.586-.264 5.381 0 2.051 0 4.102 0 6.153-.938 0-1.875 0-2.813 0"/><path d="m63.781 21.328v-3.234h2.813v3.234zm0 19.672v-16.594h2.813v16.594z"/><path d="m82.25 35.656c.969.12 1.938.24 2.906.359-.702 3.464-4.348 5.767-7.781 5.386-3.235-.066-6.43-2.328-7.06-5.598-.843-3.307-.404-7.285 2.101-9.784 3.082-3 8.699-2.618 11.235.892 1.374 1.85 1.676 4.267 1.578 6.51-4.125 0-8.25 0-12.375 0-.142 2.889 2.267 6 5.346 5.658 1.881-.162 3.613-1.566 4.045-3.423m-9.234-4.547c3.089 0 6.177 0 9.266 0 .129-2.774-2.616-5.422-5.419-4.713-2.174.427-3.912 2.474-3.846 4.713"/><path d="m88.64 41v-22.906h2.813v22.906z"/><path d="m106.59 41c0-.698 0-1.396 0-2.094-1.412 2.442-4.776 3.067-7.233 1.949-2.378-1.02-3.971-3.403-4.345-5.924-.507-2.761-.123-5.768 1.389-8.167 1.863-2.705 5.968-3.642 8.711-1.741.422.228 1.028 1.144 1.294 1.018-.006-2.649-.0001-5.298-.003-7.948.932 0 1.865 0 2.797 0 0 7.635 0 15.271 0 22.906-.87 0-1.74 0-2.61 0m-8.89-8.281c-.075 2.246.637 4.861 2.79 5.952 2 1.023 4.682-.047 5.488-2.134.897-1.996.746-4.278.388-6.382-.425-1.95-2.046-3.804-4.158-3.805-1.903-.065-3.633 1.363-4.099 3.181-.327 1.028-.394 2.116-.408 3.188"/><path d="m112.52 36.05c.927-.146 1.854-.292 2.781-.438.126 1.69 1.513 3.244 3.239 3.365 1.398.212 3.01.12 4.12-.851.807-.749 1.1-2.243.159-3.01-.908-.723-2.115-.812-3.182-1.172-1.797-.485-3.713-.848-5.243-1.97-1.83-1.551-1.868-4.679-.099-6.293 1.577-1.507 3.918-1.784 6-1.594 1.685.176 3.54.749 4.535 2.217.464.715.708 1.549.844 2.384-.917.125-1.833.25-2.75.375-.121-1.569-1.653-2.762-3.19-2.695-1.246-.082-2.702.012-3.608.982-.624.724-.543 1.971.314 2.481.998.706 2.269.757 3.389 1.173 1.754.512 3.647.848 5.141 1.965 1.686 1.476 1.728 4.244.396 5.966-1.298 1.788-3.597 2.417-5.709 2.448-1.466-.007-2.984-.214-4.299-.893-1.599-.909-2.585-2.655-2.84-4.444"/><g fill="#fff"><path d="m151.11 41v-22.906h3.03v22.906z"/><path d="m158.55 29.844c-.277-4.765 2.335-9.977 7.05-11.551 4.902-1.757 11.226.197 13.477 5.098 2.266 4.706 1.89 10.92-1.767 14.833-4.554 4.948-13.81 3.976-17.08-1.954-1.111-1.946-1.679-4.188-1.68-6.426m3.125.047c-.377 4.273 2.892 8.844 7.375 8.951 3.791.221 7.557-2.653 7.997-6.497.794-3.731.139-8.292-3.107-10.696-3.788-2.814-10.05-1.104-11.591 3.444-.54 1.539-.642 3.181-.675 4.798"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,72 +0,0 @@
import { test, given } from 'sazerac'
import {
bareLink,
html,
markdown,
reStructuredText,
renderAsciiDocAttributes,
asciiDoc,
generateMarkup,
} from './generate-image-markup'
test(bareLink, () => {
given(
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect('https://img.shields.io/badge')
})
test(html, () => {
given('https://img.shields.io/badge', 'Example').expect(
'<img alt="Example" src="https://img.shields.io/badge">'
)
given('https://img.shields.io/badge', undefined).expect(
'<img src="https://img.shields.io/badge">'
)
})
test(markdown, () => {
given('https://img.shields.io/badge', 'Example').expect(
'![Example](https://img.shields.io/badge)'
)
given('https://img.shields.io/badge', undefined).expect(
'![](https://img.shields.io/badge)'
)
})
test(reStructuredText, () => {
given('https://img.shields.io/badge', undefined).expect(
'.. image:: https://img.shields.io/badge'
)
given('https://img.shields.io/badge', 'Example').expect(
'.. image:: https://img.shields.io/badge\n :alt: Example'
)
})
test(renderAsciiDocAttributes, () => {
given(['abc', '123'], {}).expect('[abc,123]')
given(['abc', '123', null], { foo: 'def', bar: 'hello, world!' }).expect(
'["abc","123",None,foo="def",bar="hello, world!"]'
)
})
test(asciiDoc, () => {
given('https://img.shields.io/badge', undefined).expect(
'image:https://img.shields.io/badge[]'
)
given('https://img.shields.io/badge', 'Example').expect(
'image:https://img.shields.io/badge[Example]'
)
given('https://img.shields.io/badge', 'Example, with comma').expect(
'image:https://img.shields.io/badge["Example, with comma"]'
)
})
test(generateMarkup, () => {
given({
badgeUrl: 'https://img.shields.io/badge',
title: 'Example',
markupFormat: 'markdown',
}).expect('![Example](https://img.shields.io/badge)')
})

View File

@@ -1,99 +0,0 @@
export function bareLink(badgeUrl: string, link?: string, title = ''): string {
return badgeUrl
}
export function html(badgeUrl: string, title?: string): string {
// To be more robust, this should escape the title.
const alt = title ? ` alt="${title}"` : ''
return `<img${alt} src="${badgeUrl}">`
}
export function markdown(badgeUrl: string, title?: string): string {
return `![${title || ''}](${badgeUrl})`
}
export function reStructuredText(badgeUrl: string, title?: string): string {
let result = `.. image:: ${badgeUrl}`
if (title) {
result += `\n :alt: ${title}`
}
return result
}
function quoteAsciiDocAttribute(attr: string | null): string {
if (attr == null) {
return 'None'
} else {
// String values are prepared and returned to users who want to include their badge
// in an AsciiDoc document. We're not using the value in any actual processing, so
// no need to perform proper sanitization. We simply escape quotes, as mandated by
// http://asciidoc.org/userguide.html#X21
const withQuotesEscaped = attr.replace(/"/g, '\\"') // lgtm [js/incomplete-sanitization]
return `"${withQuotesEscaped}"`
}
}
// lodash.mapvalues is huge!
function mapValues(
obj: { [k: string]: string | null },
iteratee: (value: string | null) => string
): { [k: string]: string } {
const result = {} as { [k: string]: string }
for (const k in obj) {
result[k] = iteratee(obj[k])
}
return result
}
export function renderAsciiDocAttributes(
positional: string[],
named: { [k: string]: string | null }
): string {
// http://asciidoc.org/userguide.html#X21
const needsQuoting =
positional.some(attr => attr && attr.includes(',')) ||
Object.keys(named).length > 0
if (needsQuoting) {
positional = positional.map(attr => quoteAsciiDocAttribute(attr))
named = mapValues(named, attr => quoteAsciiDocAttribute(attr))
}
const items = positional.concat(
Object.entries(named).map(([k, v]) => `${k}=${v}`)
)
if (items.length) {
return `[${items.join(',')}]`
} else {
return '[]'
}
}
export function asciiDoc(badgeUrl: string, title?: string): string {
const positional = title ? [title] : []
const named = {} as { [k: string]: string }
const attrs = renderAsciiDocAttributes(positional, named)
return `image:${badgeUrl}${attrs}`
}
export type MarkupFormat = 'markdown' | 'rst' | 'asciidoc' | 'link' | 'html'
export function generateMarkup({
badgeUrl,
title,
markupFormat,
}: {
badgeUrl: string
title?: string
markupFormat: MarkupFormat
}): string {
const generatorFn = {
markdown,
rst: reStructuredText,
asciidoc: asciiDoc,
link: bareLink,
html,
}[markupFormat]
return generatorFn(badgeUrl, title)
}

View File

@@ -1,28 +0,0 @@
import { test, given } from 'sazerac'
import { patternToOptions, removeRegexpFromPattern } from './pattern-helpers'
describe('Badge URL functions', function () {
test(patternToOptions, () => {
given('[^\\/]+?').expect(undefined)
given('abc|[^\\/]+').expect(undefined)
given('abc|def|ghi').expect(['abc', 'def', 'ghi'])
})
test(removeRegexpFromPattern, () => {
given('/appveyor/ci/:user/:repo').expect('/appveyor/ci/:user/:repo')
given('/discourse/:scheme(http|https)/:host/topics').expect(
'/discourse/:scheme/:host/topics'
)
given('/github/size/:user/:repo/:path*').expect(
'/github/size/:user/:repo/:path*'
)
given('/microbadger/image-size/image-size/:imageId+').expect(
'/microbadger/image-size/image-size/:imageId+'
)
given('/node/v/@:scope/:packageName').expect('/node/v/@:scope/:packageName')
given('/ubuntu/v/:packageName/:series?').expect(
'/ubuntu/v/:packageName/:series?'
)
given('/:foo/(.*)').expect('/:foo/(.*)')
})
})

View File

@@ -1,34 +0,0 @@
import { parse } from 'path-to-regexp'
// Given a patternToRegex `pattern` with multiple-choice options like
// `foo|bar|baz`, return an array with the options. If it can't be described
// as multiple-choice options, return `undefined`.
const basicChars = /^[A-za-z0-9-]+$/
export function patternToOptions(pattern: string): string[] | undefined {
const split = pattern.split('|')
if (split.some(part => !part.match(basicChars))) {
return undefined
} else {
return split
}
}
// Removes regexp for named parameters.
export function removeRegexpFromPattern(pattern: string): string {
const tokens = parse(pattern)
const simplePattern = tokens
.map(token => {
if (typeof token === 'string') {
return token
} else {
const { prefix, modifier, name, pattern } = token
if (typeof name === 'number') {
return `${prefix}(${pattern})`
} else {
return `${prefix}:${name}${modifier}`
}
}
})
.join('')
return simplePattern
}

View File

@@ -1,15 +0,0 @@
import { navigate } from 'gatsby'
export default function redirectLegacyRoutes(): void {
const { hash } = window.location
if (hash && hash.startsWith('#/examples/')) {
const category = hash.replace('#/examples/', '')
navigate(`category/${category}`, {
replace: true,
})
} else if (hash === '#/endpoint') {
navigate('endpoint', {
replace: true,
})
}
}

View File

@@ -1,16 +0,0 @@
import { expect } from 'chai'
import { test, given } from 'sazerac'
import { findCategory, getDefinitionsForCategory } from '.'
describe('Service definition helpers', function () {
test(findCategory, () => {
given('build').expect({ id: 'build', name: 'Build', keywords: ['build'] })
given('foo').expect(undefined)
})
it('getDefinitionsForCategory', function () {
expect(getDefinitionsForCategory('build'))
.to.have.length.greaterThan(10)
.and.lessThan(75)
})
})

View File

@@ -1,67 +0,0 @@
import groupBy from 'lodash.groupby'
// load using js-yaml-loader
import definitions from '../../service-definitions.yml'
export interface Category {
id: string
name: string
keywords: string[]
}
export interface ExampleSignature {
pattern: string
namedParams: { [k: string]: string }
queryParams: { [k: string]: string }
}
export interface Preview {
label?: string
message: string
color: string
style?: string
namedLogo?: string
}
export interface Example {
title: string
example: ExampleSignature
preview: Preview
keywords: string[]
documentation?: {
__html: string
}
}
export interface Route {
pattern: string
queryParams: string[]
}
export interface LegacyRoute {
format: string
queryParams: string[]
}
export interface ServiceDefinition {
category: string
name: string
isDeprecated: boolean
route: Route | LegacyRoute
examples: Example[]
}
export const services = definitions.services as ServiceDefinition[]
export const categories = definitions.categories as Category[]
export function findCategory(category: string): Category | undefined {
return categories.find(({ id }) => id === category)
}
const byCategory = groupBy(services, 'category')
export function getDefinitionsForCategory(
category: string
): ServiceDefinition[] {
return byCategory[category] || []
}
export type RenderableExample = Example

View File

@@ -1,31 +0,0 @@
import { test, given, forCases } from 'sazerac'
import { predicateFromQuery } from './service-definition-set-helper'
import { Example } from '.'
describe('Badge example functions', function () {
function exampleMatchesQuery(
{ examples }: { examples: Example[] },
query: string
): boolean {
return predicateFromQuery(query)({ examples })
}
test(exampleMatchesQuery, () => {
forCases([given({ examples: [{ title: 'node version' }] }, 'npm')]).expect(
false
)
forCases([
given(
{ examples: [{ title: 'node version', keywords: ['npm'] }] },
'node'
),
given(
{ examples: [{ title: 'node version', keywords: ['npm'] }] },
'npm'
),
// https://github.com/badges/shields/issues/1578
given({ examples: [{ title: 'c++ is the best language' }] }, 'c++'),
]).expect(true)
})
})

View File

@@ -1,54 +0,0 @@
import escapeStringRegexp from 'escape-string-regexp'
import { Example, ServiceDefinition } from '.'
export function exampleMatchesRegex(example: Example, regex: RegExp): boolean {
const { title, keywords } = example
const haystack = [title].concat(keywords).join(' ')
return regex.test(haystack)
}
export function predicateFromQuery(
query: string
): ({ examples }: { examples: Example[] }) => boolean {
const escaped = escapeStringRegexp(query)
const regex = new RegExp(escaped, 'i') // Case-insensitive.
return ({ examples }: { examples: Example[] }) =>
examples.some(example => exampleMatchesRegex(example, regex))
}
export default class ServiceDefinitionSetHelper {
private readonly definitionData: ServiceDefinition[]
public constructor(definitionData: ServiceDefinition[]) {
this.definitionData = definitionData
}
public static create(
definitionData: ServiceDefinition[]
): ServiceDefinitionSetHelper {
return new ServiceDefinitionSetHelper(definitionData)
}
public getCategory(wantedCategory: string): ServiceDefinitionSetHelper {
return ServiceDefinitionSetHelper.create(
this.definitionData.filter(({ category }) => category === wantedCategory)
)
}
public search(query: string): ServiceDefinitionSetHelper {
const predicate = predicateFromQuery(query)
return ServiceDefinitionSetHelper.create(
this.definitionData.filter(predicate)
)
}
public notDeprecated(): ServiceDefinitionSetHelper {
return ServiceDefinitionSetHelper.create(
this.definitionData.filter(({ isDeprecated }) => !isDeprecated)
)
}
public toArray(): ServiceDefinition[] {
return this.definitionData
}
}

View File

@@ -1,5 +0,0 @@
import supportedFeatures from '../supported-features.json'
export const shieldsLogos = supportedFeatures.shieldsLogos as string[]
export const simpleIcons = supportedFeatures.simpleIcons as string[]
export const advertisedStyles = supportedFeatures.advertisedStyles as string[]

View File

@@ -12,21 +12,5 @@
},
"scripts": {
"test": "echo 'Run tests from parent dir'; false"
},
"devDependencies": {
"gatsby": "*"
},
"babel": {
"plugins": [
[
"inline-react-svg",
{
"svgo": false
}
]
],
"presets": [
"babel-preset-gatsby"
]
}
}

View File

@@ -1,118 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { getBaseUrl } from '../constants'
import Meta from '../components/meta'
import Header from '../components/header'
import Footer from '../components/footer'
import { BaseFont, GlobalStyle, H3 } from '../components/common'
import NodePing from '../../static/images/nodeping.svg'
import Sentry from '../../static/images/sentry-logo-black.svg'
const MainContainer = styled(BaseFont)`
text-align: center;
`
const SponsorContainer = styled.div`
display: block;
max-width: 600px;
margin: 0 auto;
text-align: left;
padding-top: 20px;
`
const SponsorItems = styled.div`
text-align: left;
`
export default function SponsorsPage(): JSX.Element {
const baseUrl = getBaseUrl()
return (
<MainContainer>
<GlobalStyle />
<Meta />
<Header />
<H3>Community</H3>
<SponsorContainer>
Shields.io is possible thanks to the people and companies who donate
money, services or time to keep the project running.
</SponsorContainer>
<SponsorContainer>
<h4>Sponsors</h4>
These companies help us by donating their services to shields:
<ul style={{ listStyleType: 'none' }}>
<SponsorItems>
<li>
<a href="https://nodeping.com/">
<NodePing alt="nodeping_logo" height={60} />
</a>
</li>
<li>
<a href="https://sentry.io/">
<Sentry alt="sentry_logo" height={100} />
</a>
</li>
</SponsorItems>
</ul>
💵 These organisations help keep shields running by donating on
OpenCollective. Your organisation can support this project by{' '}
<a href="https://opencollective.com/shields#sponsor">
becoming a sponsor
</a>
. Your logo will show up here with a link to your website.
<p>
<object data="https://opencollective.com/shields/sponsors.svg?avatarHeight=80&width=600" />
</p>
</SponsorContainer>
<SponsorContainer>
<h4>Backers</h4>
💵 Thank you to all our backers who help keep shields running by
donating on OpenCollective. You can support this project by{' '}
<a href="https://opencollective.com/shields#backer">
becoming a backer
</a>
.
<p>
<object data="https://opencollective.com/shields/backers.svg?width=600" />
</p>
</SponsorContainer>
<SponsorContainer>
<h4>Contributors</h4>
🙏 This project exists thanks to all the nice people who contribute
their time to work on the project.
<p>
<object data="https://opencollective.com/shields/contributors.svg?width=600" />
</p>
</SponsorContainer>
<SponsorContainer>
Shields is helped by these companies which provide a free plan for
their product or service:
<ul>
<li>
<a href="https://coveralls.io/">Coveralls</a>
</li>
<li>
<a href="https://circleci.com/">CircleCI</a>
</li>
<li>
<a href="https://www.cloudflare.com/">Cloudflare</a>
</li>
<li>
<a href="https://discord.com/">Discord</a>
</li>
<li>
<a href="https://github.com/">GitHub</a>
</li>
<li>
<a href="https://uptimerobot.com/">Uptime Robot</a>
</li>
</ul>
</SponsorContainer>
<Footer baseUrl={baseUrl} />
</MainContainer>
)
}

View File

@@ -1,255 +0,0 @@
import React from 'react'
import styled, { css } from 'styled-components'
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { getBaseUrl } from '../constants'
import Meta from '../components/meta'
import Header from '../components/header'
import Footer from '../components/footer'
import { BaseFont, GlobalStyle, H3, Badge } from '../components/common'
import { Snippet } from '../components/snippet'
import Customizer from '../components/customizer/customizer'
const MainContainer = styled(BaseFont)`
text-align: center;
`
const Explanation = styled.div`
display: block;
max-width: 800px;
margin: 0 auto;
text-align: left;
`
interface JsonExampleBlockProps {
fontSize?: string
}
const JsonExampleBlock = styled.code<JsonExampleBlockProps>`
display: inline-block;
text-align: left;
line-height: 1.2em;
padding: 16px 18px;
border-radius: 4px;
background: #eef;
font-family: Lekton;
${({ fontSize }) =>
css`
font-size: ${fontSize};
`};
white-space: pre;
`
// eslint-disable-next-line @typescript-eslint/no-explicit-any, react/prop-types
function JsonExample({ data }: { [k: string]: any }): JSX.Element {
return (
<JsonExampleBlock>{JSON.stringify(data, undefined, 2)}</JsonExampleBlock>
)
}
const Schema = styled.dl`
display: inline-block;
max-width: 800px;
margin: 0;
padding: 10px;
text-align: left;
background: #efefef;
clear: both;
overflow: hidden;
dt,
dd {
padding: 0 1%;
margin-top: 8px;
margin-bottom: 8px;
float: left;
}
dt {
width: 100px;
clear: both;
}
dd {
margin-left: 20px;
width: 75%;
}
@media (max-width: 600px) {
.data_table {
text-align: center;
}
}
`
export default function EndpointPage(): JSX.Element {
const baseUrl = getBaseUrl()
return (
<MainContainer>
<GlobalStyle />
<Meta />
<Header />
<H3>Endpoint</H3>
<Snippet snippet={`${baseUrl}/endpoint?url=...&style=...`} />
<p>Endpoint response:</p>
<JsonExample
data={{
schemaVersion: 1,
label: 'hello',
message: 'sweet world',
color: 'orange',
}}
/>
<p>Shields response:</p>
<Badge
alt="hello | sweet world"
src={staticBadgeUrl({
baseUrl,
label: 'hello',
message: 'sweet world',
color: 'orange',
})}
/>
<Explanation>
<p>
Developers rely on Shields for visual consistency and powerful
customization options. As a service provider or data provider, you can
use the endpoint badge to provide content while giving users the full
power of Shields' badge customization.
</p>
<p>
Using the endpoint badge, you can provide content for a badge through
a JSON endpoint. The content can be prerendered, or generated on the
fly. To strike a balance between responsiveness and bandwidth
utilization on one hand, and freshness on the other, cache behavior is
configurable, subject to the Shields minimum. The endpoint URL is
provided to Shields through the query string. Shields fetches it and
formats the badge.
</p>
<p>
The endpoint badge is a better alternative than redirecting to the
static badge endpoint or generating SVG on your server:
</p>
<ol>
<li>
<a href="https://en.wikipedia.org/wiki/Separation_of_content_and_presentation">
Content and presentation are separate.
</a>{' '}
The service provider authors the badge, and Shields takes input from
the user to format it. As a service provider, you author the badge
but don't have to concern yourself with styling. You don't even have
to pass the formatting options through to Shields.
</li>
<li>
Badge formatting is always 100% up to date. There's no need to track
updates to the npm package, badge templates, or options.
</li>
<li>
A JSON response is easy to implement; easier than an HTTP redirect.
It is trivial in almost any framework and is more compatible with
hosting environments such as{' '}
<a href="https://runkit.com/docs/endpoint">RunKit endpoints</a>.
</li>
<li>
As a service provider, you can rely on the Shields CDN. There's no
need to study the HTTP headers. Adjusting cache behavior is as
simple as setting a property in the JSON response.
</li>
</ol>
</Explanation>
<h4>Schema</h4>
<Explanation>
<p>
Breaking changes to the schema will trigger an increment to the
`schemaVersion`.
</p>
</Explanation>
<Schema>
<dt>schemaVersion</dt>
<dd>
Required. Always the number <code>1</code>.
</dd>
<dt>label</dt>
<dd>
Required. The left text, or the empty string to omit the left side of
the badge. This can be overridden by the query string.
</dd>
<dt>message</dt>
<dd>Required. Can't be empty. The right text.</dd>
<dt>color</dt>
<dd>
Default: <code>lightgrey</code>. The right color. Supports the eight
named colors above, as well as hex, rgb, rgba, hsl, hsla and css named
colors. This can be overridden by the query string.
</dd>
<dt>labelColor</dt>
<dd>
Default: <code>grey</code>. The left color. This can be overridden by
the query string.
</dd>
<dt>isError</dt>
<dd>
Default: <code>false</code>. <code>true</code> to treat this as an
error badge. This prevents the user from overriding the color. In the
future, it may affect cache behavior.
</dd>
<dt>namedLogo</dt>
<dd>
Default: none. One of the named logos supported by Shields or {}
<a href="https://simpleicons.org/">simple-icons</a>. Can be overridden
by the query string.
</dd>
<dt>logoSvg</dt>
<dd>Default: none. An SVG string containing a custom logo.</dd>
<dt>logoColor</dt>
<dd>
Default: none. Same meaning as the query string. Can be overridden by
the query string. Only works for named logos and Shields logos. If you
override the color of a multicolor Shield logo, the corresponding
named logo will be used and colored.
</dd>
<dt>logoWidth</dt>
<dd>
Default: none. Same meaning as the query string. Can be overridden by
the query string.
</dd>
<dt>logoPosition</dt>
<dd>
Default: none. Same meaning as the query string. Can be overridden by
the query string.
</dd>
<dt>style</dt>
<dd>
Default: <code>flat</code>. The default template to use. Can be
overridden by the query string.
</dd>
<dt>cacheSeconds</dt>
<dd>
Default: <code>300</code>, min <code>300</code>. Set the HTTP cache
lifetime in seconds, which should be respected by the Shields' CDN and
downstream users. Values below 300 will be ignored. This lets you tune
performance and traffic vs. responsiveness. The value you specify can
be overridden by the user via the query string, but only to a longer
value.
</dd>
</Schema>
<h4>Customize and test</h4>
<Customizer
baseUrl={baseUrl}
exampleNamedParams={{}}
exampleQueryParams={{
url: 'https://shields.redsparr0w.com/2473/monday',
}}
pattern="/endpoint"
title="Custom badge"
/>
<Footer baseUrl={baseUrl} />
</MainContainer>
)
}

View File

@@ -1,2 +0,0 @@
import Main from '../components/main'
export default Main

31
frontend/sidebars.cjs Normal file
View File

@@ -0,0 +1,31 @@
/**
* Creating a sidebar enables you to:
* - create an ordered group of docs
* - render a sidebar for each doc of that group
* - provide next/previous navigation
*
* The sidebars can be generated from the filesystem, or explicitly defined here.
*
* Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
}
module.exports = sidebars

View File

@@ -0,0 +1,92 @@
import React from 'react'
import clsx from 'clsx'
import styles from './homepage-features.module.css'
const FeatureList = [
{
title: 'Dynamic badges',
description: (
<>
<img
alt="build:passing"
src="https://img.shields.io/badge/build-passing-brightgreen"
/>
<br />
Show metrics for your project. We've got badges for hundreds of
services.
</>
),
},
{
title: 'Static Badges',
description: (
<>
Create a badge with
<br />
<img
alt="any text you like"
src="https://img.shields.io/badge/any%20text-you%20like-blue"
/>
</>
),
},
{
title: 'Badge-Maker NPM library',
description: (
<>
Render badges in your own application using our{' '}
<a href="https://www.npmjs.com/package/badge-maker">NPM library</a>
<br />
<code>npm install badge-maker</code>
</>
),
},
{
title: 'Host your own instance',
description: (
<>
Host a shields instance behind your firewall with our{' '}
<a href="https://registry.hub.docker.com/r/shieldsio/shields/">
docker image
</a>
<br />
<code>docker pull shieldsio/shields</code>
</>
),
},
{
title: 'Love Shields?',
description: (
<>
Please consider{' '}
<a href="https://opencollective.com/shields">donating</a> to sustain our
activities
</>
),
},
]
function Feature({ title, description }) {
return (
<div className={clsx('col col--6')}>
<div className="text--center padding-horiz--md padding-vert--lg">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
)
}
export default function HomepageFeatures() {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@@ -0,0 +1,28 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: rgb(33, 175, 144);
--ifm-color-primary-darker: rgb(31, 165, 136);
--ifm-color-primary-darkest: rgb(26, 136, 112);
--ifm-color-primary-light: rgb(70, 203, 174);
--ifm-color-primary-lighter: rgb(102, 212, 189);
--ifm-color-primary-lightest: rgb(146, 224, 208);
--ifm-code-font-size: 95%;
}
.docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.1);
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
}
html[data-theme="dark"] .docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.3);
}

View File

@@ -0,0 +1,62 @@
# Community
Shields.io is possible thanks to the people and companies who donate money, services or time to keep the project running.
## Sponsors
❤️ These companies help us by donating their services to shields:
<ul>
<li>
<a href="https://nodeping.com/">
NodePing
</a>
</li>
<li>
<a href="https://sentry.io/">
Sentry
</a>
</li>
</ul>
💵 These organisations help keep shields running by donating on OpenCollective. Your organisation can support this project by <a href="https://opencollective.com/shields#sponsor">becoming a sponsor </a>. Your logo will show up here with a link to your website.
<p>
<object data="https://opencollective.com/shields/sponsors.svg?avatarHeight=80&width=600" />
</p>
## Backers
💵 Thank you to all our backers who help keep shields running by donating on OpenCollective. You can support this project by <a href="https://opencollective.com/shields#backer">becoming a backer</a>.
<p>
<object data="https://opencollective.com/shields/backers.svg?width=600" />
</p>
## Contributors
🙏 This project exists thanks to all the nice people who contribute their time to work on the project.
<p>
<object data="https://opencollective.com/shields/contributors.svg?width=600" />
</p>
✨ Shields is helped by these companies which provide a free plan for their product or service:
<ul>
<li>
<a href="https://coveralls.io/">Coveralls</a>
</li>
<li>
<a href="https://www.cloudflare.com/">Cloudflare</a>
</li>
<li>
<a href="https://discord.com/">Discord</a>
</li>
<li>
<a href="https://github.com/">GitHub</a>
</li>
<li>
<a href="https://uptimerobot.com/">Uptime Robot</a>
</li>
</ul>

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