Compare commits

...

108 Commits

Author SHA1 Message Date
chris48s
c03a73b573 v3 stable (#5037)
* fix outdated docblock
* 3.0.0
2020-05-06 19:51:21 +01:00
dependabot-preview[bot]
9ac2b41206 Build(deps-dev): bump react-error-overlay from 3.0.0 to 6.0.7 (#5033)
Bumps [react-error-overlay](https://github.com/facebook/create-react-app/tree/HEAD/packages/react-error-overlay) from 3.0.0 to 6.0.7.
- [Release notes](https://github.com/facebook/create-react-app/releases)
- [Changelog](https://github.com/facebook/create-react-app/blob/master/CHANGELOG-2.x.md)
- [Commits](https://github.com/facebook/create-react-app/commits/react-error-overlay@6.0.7/packages/react-error-overlay)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
2020-05-06 16:19:59 +00:00
Paul Melnikow
46250dd105 Update depcheck script for minimum supported Node version (#5053)
Ref #4977
2020-05-06 12:12:23 -04:00
dependabot-preview[bot]
c4314094dc Build(deps-dev): bump gatsby from 2.19.41 to 2.21.11 (#5052)
* Build(deps-dev): bump gatsby from 2.19.41 to 2.21.11

Bumps [gatsby](https://github.com/gatsbyjs/gatsby) from 2.19.41 to 2.21.11.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/compare/gatsby@2.19.41...gatsby@2.21.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Bump cypress base image to Node 12

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <email@paulmelnikow.com>
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
2020-05-06 11:46:24 -04:00
chris48s
13d75e0607 upgrade to prettier 2 (#5051)
* arrowParens: avoid
* remove trailingComma setting
2020-05-05 21:07:43 +01:00
chris48s
1c736f2159 send s-maxage cache header (#5046)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-05 18:55:22 +01:00
dependabot-preview[bot]
0059599662 Build(deps): bump cross-env from 6.0.3 to 7.0.2 (#5035)
Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 6.0.3 to 7.0.2.
- [Release notes](https://github.com/kentcdodds/cross-env/releases)
- [Changelog](https://github.com/kentcdodds/cross-env/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kentcdodds/cross-env/compare/v6.0.3...v7.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Pierre-Yves B <PyvesDev@gmail.com>
2020-05-05 17:47:32 +00:00
dependabot-preview[bot]
4da0c7b8dd Build(deps-dev): bump gatsby-plugin-react-helmet from 3.1.24 to 3.3.0 (#5032)
Bumps [gatsby-plugin-react-helmet](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-react-helmet) from 3.1.24 to 3.3.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-react-helmet/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-react-helmet@3.3.0/packages/gatsby-plugin-react-helmet)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-05 05:33:02 +00:00
dependabot-preview[bot]
6163de5a80 Build(deps-dev): bump start-server-and-test from 1.10.7 to 1.11.0 (#5031)
Bumps [start-server-and-test](https://github.com/bahmutov/start-server-and-test) from 1.10.7 to 1.11.0.
- [Release notes](https://github.com/bahmutov/start-server-and-test/releases)
- [Commits](https://github.com/bahmutov/start-server-and-test/compare/v1.10.7...v1.11.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-05 00:13:30 -05:00
dependabot-preview[bot]
d967c62497 Build(deps-dev): bump gatsby-plugin-page-creator from 2.1.46 to 2.3.0 (#5030)
Bumps [gatsby-plugin-page-creator](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-page-creator) from 2.1.46 to 2.3.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-page-creator/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-page-creator@2.3.0/packages/gatsby-plugin-page-creator)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-05 03:32:00 +00:00
dependabot-preview[bot]
562dac6711 Use latest beta release of Nock (#5027) 2020-05-04 20:18:29 -04:00
Makarenko Anton
6ca2fa0c89 Fix rendering in Firefox on Windows (#5038)
Close #4813
2020-05-04 20:01:15 -04:00
Munif Tanjim
594e14c1b6 Support [Codecov] coverage badge with flag (#4968)
* Support Codecov coverage badge with flag

* Tweak Codecov service and tests

* Maintain Codecov backward-compatibility

* Tweak Codecov docs

Co-authored-by: Caleb Cartwright <calebcartwright@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-04 17:15:01 +00:00
Caleb Cartwright
6e9e25451f tests: increase timeout for svc name check (#5050)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-04 17:08:27 +00:00
Caleb Cartwright
34d4271509 tests: fix Gitlab service tests (#5049)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-04 14:10:34 +00:00
dependabot-preview[bot]
784b45160b Build(deps-dev): bump sinon from 8.1.1 to 9.0.2 (#5029)
Bumps [sinon](https://github.com/sinonjs/sinon) from 8.1.1 to 9.0.2.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v8.1.1...v9.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-03 22:38:10 +00:00
dependabot-preview[bot]
d2cdbc7a01 Build(deps-dev): bump babel-preset-gatsby from 0.2.36 to 0.4.0 (#5028)
Bumps [babel-preset-gatsby](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/babel-preset-gatsby) from 0.2.36 to 0.4.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/babel-preset-gatsby/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/babel-preset-gatsby@0.4.0/packages/babel-preset-gatsby)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-03 22:29:20 +00:00
dependabot-preview[bot]
ba149f2c75 Build(deps-dev): bump gatsby-plugin-styled-components (#5024)
Bumps [gatsby-plugin-styled-components](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-styled-components) from 3.1.21 to 3.3.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-styled-components/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-styled-components@3.3.0/packages/gatsby-plugin-styled-components)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-03 20:58:43 +00:00
dependabot-preview[bot]
1cd92e2e71 Build(deps-dev): bump eslint-plugin-jsdoc from 20.4.0 to 24.0.0 (#5023)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 20.4.0 to 24.0.0.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v20.4.0...v24.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-03 20:49:29 +00:00
Caleb Cartwright
86c0b41219 tests: increase timeout on servicenames (#5047)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-03 15:34:50 -05:00
dependabot-preview[bot]
622f39635a Build(deps-dev): bump lint-staged from 9.5.0 to 10.2.2 (#5022)
* Build(deps-dev): bump lint-staged from 9.5.0 to 10.2.2

Bumps [lint-staged](https://github.com/okonet/lint-staged) from 9.5.0 to 10.2.2.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v9.5.0...v10.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* deps: remove unneeded lint-stage git add

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Caleb Cartwright <caleb.cartwright@outlook.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-03 20:28:58 +00:00
Dan Untenzu
2b363096bb Streamline Packagist Badge Titles (#5041)
Streamline Packagist Badge titles in frontend.
Always add the general topic/category to each badge title.

Closes #5039

Co-authored-by: Caleb Cartwright <calebcartwright@users.noreply.github.com>
2020-05-03 20:10:29 +00:00
Dan Untenzu
d9c483abbe Add badge for Packagist stars (favers) (#5040)
Add a badge to show stars given to a PHP package on Packagist.

Closes #4912

Co-authored-by: Pierre-Yves B <PyvesDev@gmail.com>
2020-05-03 21:49:04 +02:00
chris48s
171aac3c42 update production hosting/deploy docs (#5013) 2020-05-03 16:00:37 +01:00
chris48s
0129eba673 show link previews on /dev/styles page (#5045) 2020-05-03 15:47:56 +01:00
chris48s
9c3ddab7bf run typescript checks in npm test (#5044) 2020-05-03 15:39:39 +01:00
dependabot-preview[bot]
072c6fa689 Build(deps-dev): bump got from 11.0.3 to 11.1.0 (#5020)
Bumps [got](https://github.com/sindresorhus/got) from 11.0.3 to 11.1.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v11.0.3...v11.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-03 00:09:42 -05:00
dependabot-preview[bot]
cbb3cebfa0 Build(deps-dev): bump gatsby-plugin-catch-links from 2.1.28 to 2.3.0 (#5019)
Bumps [gatsby-plugin-catch-links](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-catch-links) from 2.1.28 to 2.3.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-catch-links/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-catch-links@2.3.0/packages/gatsby-plugin-catch-links)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-02 23:36:37 -05:00
dependabot-preview[bot]
ed9672bfcf Build(deps-dev): bump sazerac from 1.1.0 to 2.0.0 (#5018)
Bumps [sazerac](https://github.com/sazeracjs/sazerac) from 1.1.0 to 2.0.0.
- [Release notes](https://github.com/sazeracjs/sazerac/releases)
- [Changelog](https://github.com/sazeracjs/sazerac/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sazeracjs/sazerac/compare/v1.1.0...v2.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-02 23:16:14 -05:00
dependabot-preview[bot]
0afdeca5c3 Build(deps-dev): bump husky from 3.1.0 to 4.2.5 (#5017)
Bumps [husky](https://github.com/typicode/husky) from 3.1.0 to 4.2.5.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v3.1.0...v4.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-02 22:52:38 -05:00
dependabot-preview[bot]
07eb48fce8 Build(deps-dev): bump gatsby-plugin-typescript from 2.2.5 to 2.4.0 (#5016)
Bumps [gatsby-plugin-typescript](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-typescript) from 2.2.5 to 2.4.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-typescript/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-typescript@2.4.0/packages/gatsby-plugin-typescript)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-02 22:41:07 -05:00
dependabot-preview[bot]
14f91ab16d Build(deps): bump moment from 2.24.0 to 2.25.1 (#5026)
Bumps [moment](https://github.com/moment/moment) from 2.24.0 to 2.25.1.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.24.0...2.25.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-02 21:56:31 +00:00
dependabot-preview[bot]
26d753019e Build(deps-dev): bump gatsby-plugin-remove-trailing-slashes (#5015)
Bumps [gatsby-plugin-remove-trailing-slashes](https://github.com/gatsbyjs/gatsby/tree/HEAD/packages/gatsby-plugin-remove-trailing-slashes) from 2.1.24 to 2.3.0.
- [Release notes](https://github.com/gatsbyjs/gatsby/releases)
- [Changelog](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-remove-trailing-slashes/CHANGELOG.md)
- [Commits](https://github.com/gatsbyjs/gatsby/commits/gatsby-plugin-remove-trailing-slashes@2.3.0/packages/gatsby-plugin-remove-trailing-slashes)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-02 20:47:38 +00:00
dependabot-preview[bot]
ed551b3a68 Build(deps-dev): bump @babel/core from 7.9.0 to 7.9.6 (#4998)
Bumps [@babel/core](https://github.com/babel/babel) from 7.9.0 to 7.9.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.9.0...v7.9.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-02 17:52:37 +01:00
chris48s
6e58db0379 require node 10+ in badge-maker package.json (#5010) 2020-05-02 17:46:22 +01:00
chris48s
4d275e0642 upgrade to got 11 (#5008) 2020-05-02 17:39:23 +01:00
chris48s
af4b9e67cd re-enable dependabot node10+ upgrades (#5009) 2020-05-02 17:32:14 +01:00
dependabot-preview[bot]
8d0b8987ce Build(deps-dev): bump tmp from 0.1.0 to 0.2.1 (#4997)
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.1.0 to 0.2.1.
- [Release notes](https://github.com/raszi/node-tmp/releases)
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-02 16:19:10 +00:00
dependabot-preview[bot]
fb63713f7e Build(deps-dev): bump portfinder from 1.0.25 to 1.0.26 (#4995)
Bumps [portfinder](https://github.com/http-party/node-portfinder) from 1.0.25 to 1.0.26.
- [Release notes](https://github.com/http-party/node-portfinder/releases)
- [Commits](https://github.com/http-party/node-portfinder/compare/v1.0.25...v1.0.26)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-02 04:14:01 +00:00
dependabot-preview[bot]
3424e07d21 Build(deps-dev): bump @typescript-eslint/eslint-plugin (#4999)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.29.0 to 2.30.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.30.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
2020-05-01 21:54:24 +00:00
dependabot-preview[bot]
a29565a02d Build(deps-dev): bump concurrently from 5.1.0 to 5.2.0 (#5005)
Bumps [concurrently](https://github.com/kimmobrunfeldt/concurrently) from 5.1.0 to 5.2.0.
- [Release notes](https://github.com/kimmobrunfeldt/concurrently/releases)
- [Commits](https://github.com/kimmobrunfeldt/concurrently/compare/v5.1.0...v5.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-01 21:23:57 +00:00
dependabot-preview[bot]
271b84e1cb Build(deps): bump simple-icons from 2.9.0 to 2.10.0 (#5004)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/2.9.0...2.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-01 21:14:21 +00:00
chris48s
17374873ad fix link width params (#4991)
closes #4989

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-01 20:55:57 +00:00
Paul Melnikow
6bc0d40c37 Fix production crash in [GithubStars] with trailing spaces (#4983)
* Fix production crash in [GithubStars] with trailing spaces

Closes #4982

* Remove .only()

Co-authored-by: chris48s <chris48s@users.noreply.github.com>
2020-05-01 20:46:42 +00:00
dependabot-preview[bot]
7385739a43 Build(deps-dev): bump @typescript-eslint/parser from 2.29.0 to 2.30.0 (#5003)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.29.0 to 2.30.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.30.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
2020-05-01 20:17:28 +00:00
dependabot-preview[bot]
d95aff75a9 Build(deps-dev): bump @types/node from 13.13.2 to 13.13.4 (#5006)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 13.13.2 to 13.13.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-01 13:21:57 -04:00
dependabot-preview[bot]
07d1466cd3 Build(deps-dev): bump @types/react-select from 3.0.11 to 3.0.12 (#5001)
Bumps [@types/react-select](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-select) from 3.0.11 to 3.0.12.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-select)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-01 16:42:06 +00:00
dependabot-preview[bot]
283ded9e8e Build(deps-dev): bump cypress from 4.4.1 to 4.5.0 (#5002)
Bumps [cypress](https://github.com/cypress-io/cypress) from 4.4.1 to 4.5.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Commits](https://github.com/cypress-io/cypress/compare/v4.4.1...v4.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-05-01 16:26:56 +00:00
dependabot-preview[bot]
7198c5af50 Build(deps-dev): bump chai-datetime from 1.5.0 to 1.6.0 (#4996)
Bumps [chai-datetime](https://github.com/mguterl/chai-datetime) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/mguterl/chai-datetime/releases)
- [Commits](https://github.com/mguterl/chai-datetime/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-01 16:08:45 +00:00
Paul Melnikow
f0030a4025 Fill GitHub token pool on startup (#4987)
We're still seeing a few "Token pool is exhausted" errors at the moment the dynos are coming up. I'm wondering if this might help.

Ref #3771
2020-04-30 23:39:01 -04:00
Paul Melnikow
b6c78a1110 Allow a numeric version for NuGet v2 [chocolatey powershellgallery resharper] (#4985)
Closes #2929
2020-04-30 23:28:40 -04:00
Paul Melnikow
5b42c8310b [dynamicjson] Fix unexpected token in JSONPath query (#4984)
Includes a test.

Fixes #4988
2020-04-30 23:19:39 -04:00
chris48s
612a43aaa9 Update test matrix and docs (#4992)
* update test matrix

* update docs
2020-04-30 21:33:59 +01:00
Paul Melnikow
7af965113b Fix missing [Cocoapods] license (#4986)
Closes #4688
2020-04-29 23:46:41 -04:00
Paul Melnikow
36f9a8083a Lengthen timeout on a sometimes slow test (#4978) 2020-04-29 22:04:34 +00:00
Paul Melnikow
6e76fabe26 Upgrade to Mocha 7 (#4976)
For some reason the `--delay` version is not working in Mocha 7. I'm not sure why that is. Although, invoking Mocha the normal synchronous way seems to be working fine, so maybe `--delay` just isn't necessary anymore for what we're doing in Shields.

I don't see anything in the changelog about this 🤷‍♂️

Closes #4842
2020-04-29 17:20:17 -04:00
chris48s
762306d7aa document request header size limit (#4966)
closes #4960
2020-04-29 11:45:04 -04:00
Paul Melnikow
f9d5f57f49 Delay start until the server is ready (#4959)
Fixes #4958

Fixes, but only in Heroku, #3771, so let's keep that open.
2020-04-27 22:45:19 -04:00
Mohamed Feddad
98ebc3ad5e Add [AUR] maintainer and last modified badges (#4952)
* Add [AUR] maintainer badge

* Add [AUR] last-modified badge

* Fix missing whitelist to Maintainer schema

* Apply suggestions from code review

Fail-safe maintainer and remove additional label color

Co-Authored-By: Caleb Cartwright <calebcartwright@users.noreply.github.com>

* Add Arch wiki link. Replace category social with other.

Co-authored-by: Caleb Cartwright <calebcartwright@users.noreply.github.com>
2020-04-27 21:01:23 -05:00
Caleb Cartwright
4fe80bb150 Get data for [Discord] badges via OVH server proxies (#4956) 2020-04-27 09:39:57 -04:00
Paul Melnikow
5f80d931ac Set Influx config for Heroku production app (#4953) 2020-04-26 20:48:12 -04:00
chris48s
d8840b83e7 fix logo placement with custom logoWidth value (#4946) 2020-04-25 16:34:34 +01:00
dependabot-preview[bot]
6bd4b182b6 Build(deps): bump ioredis from 4.16.2 to 4.16.3 (#4942)
Bumps [ioredis](https://github.com/luin/ioredis) from 4.16.2 to 4.16.3.
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/luin/ioredis/blob/master/Changelog.md)
- [Commits](https://github.com/luin/ioredis/compare/v4.16.2...v4.16.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-25 03:13:51 +00:00
dependabot-preview[bot]
1602443e51 Build(deps): bump @sentry/node from 5.15.4 to 5.15.5 (#4936)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 5.15.4 to 5.15.5.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/5.15.4...5.15.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-25 01:36:54 +00:00
Paul Melnikow
90eb4fc73f Update a few remaining references to gh-badges and remove nowignore (#4945)
I was hoping I'd find something in here that would fix the install issue on Heroku but I don't think any of this is it.
2020-04-24 18:14:09 +00:00
dependabot-preview[bot]
56456bcbed Build(deps-dev): bump @typescript-eslint/eslint-plugin (#4939)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.28.0 to 2.29.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.29.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-24 18:07:42 +00:00
dependabot-preview[bot]
e987358c5e Build(deps-dev): bump eslint-plugin-chai-friendly from 0.5.0 to 0.6.0 (#4940)
Bumps [eslint-plugin-chai-friendly](https://github.com/ihordiachenko/eslint-plugin-chai-friendly) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/ihordiachenko/eslint-plugin-chai-friendly/releases)
- [Commits](https://github.com/ihordiachenko/eslint-plugin-chai-friendly/compare/v0.5.0...v0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-24 17:11:39 +00:00
dependabot-preview[bot]
e60b588feb Build(deps-dev): bump @types/node from 13.11.1 to 13.13.2 (#4944)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 13.11.1 to 13.13.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-24 16:40:18 +00:00
dependabot-preview[bot]
78ad619399 Build(deps-dev): bump @typescript-eslint/parser from 2.28.0 to 2.29.0 (#4943)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.28.0 to 2.29.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.29.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-24 16:23:28 +00:00
dependabot-preview[bot]
446c1031f9 Build(deps-dev): bump eslint-config-prettier from 6.10.1 to 6.11.0 (#4941)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 6.10.1 to 6.11.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v6.10.1...v6.11.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-24 15:38:16 +00:00
dependabot-preview[bot]
6984918cab Build(deps-dev): bump cypress from 4.4.0 to 4.4.1 (#4937)
Bumps [cypress](https://github.com/cypress-io/cypress) from 4.4.0 to 4.4.1.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Commits](https://github.com/cypress-io/cypress/compare/v4.4.0...v4.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-24 15:20:03 +00:00
chris48s
3ba05cb184 📦 version 3 (#4756)
* Validate input to BadgeFactory.create() (#3875)

* validate input to create()

* remove deprecated properties (#3881)

* remove BadgeFactory class (#3884)

* Template literal templates (#4459)

- Remove use of the doT template library and move to generating SVG output using javascript template literals.
- Drop SVGO and mostly manually implement the optimisations.
- Add a bunch more tests

Co-authored-by: Paul Melnikow <github@paulmelnikow.com>

* drop raster support in package CLI (#4523)

* drop raster support in package CLI
* update docs

* rename gh-badges package to badge-maker

* rename gh-badges dir to badge-maker

* update relative imports and other refs to in parent dir

'gh-badges' --> 'badge-maker'

* update snyk service tests

This change is only tangentially related

We've used the shields repo as an example for these tests so
moving files around in our repo has a knock-on effect on them

* add missing type hints to dev style page

* write the changelog/migration guide for v3

* use extension in README CLI example

* update CLI help

whoops - missed this in #4523

* bump version

* update for self-hosting users

* README updates

* drop .format param from CLI, always output SVG

* Change text[] to label and message, Remove JSON output

- Change text[] to label and message
- Fix message only badge
- Remove JSON output format
- Update the docs

* update package-lock

* rename 'template' to 'style'

* handle invalid styles in coalesceBadge

* ensure makeBadge is passed a string for template in coalesceBadge()

issue #4925

* fix (logo/no label text/label color specified) case

issue #4926

* add example of (logo/no label text/label color specified) to style debug page

* update type defs

* padding fix for FTB style

Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
2020-04-23 20:05:48 +01:00
Marcin Mielnicki
b64987d2dd Do not log errors in influx tests (#4931) 2020-04-22 16:58:11 -05:00
Marcin Mielnicki
e66cfa3c21 📈 PaaS-friendly metrics (#4874)
* prom-client JSON to InfluxDB line protocol converter

* Converts a metric with separate names

* prom-client JSON to InfluxDB line protocol (version 2) converter

* Server has instance id

* Read the instance id from an environment variable

* More unit tests for instance-metadata

* Log instance id

* Push influx metrics

* INSTANCE_ID with dyno metadata

* Prepare influx metrics in one place

* Influx metrics endpoint should return metrics

* More readable tests

* Env added to instance metadata

* hostname as an instance label value

* HEROKU_DYNO_ID as an instance id for heroku

* Instance env can be set by env variable

* HEROKU_APP_NAME as an instance env

* Log instance metadata as a JSON

* Typo fix

* Code refactoring in tests

* wait-for-expect dev dependency added

* Test for pushing metrics

* Test for pushing metrics

* Use basic authentication for pushing metrics

* intervalSeconds=2 for development env

* Using existing methods

* TODOs removed

* Schema for influx credentials

* Influx config removed from config files

* Require username and password when influx metrics are enabled

* Unused args removed

* pushing component should log errors

* Speed up tests

* should log error responses

* InstanceMetadata class replaces by simple object

* Influx metrics can be configuredd by env variables

* Use application label name instead of service

* Unused code removed

* Integration test for prom-client and converter

* metrics.influx.enabled configuration option added

* Improved influx configuration schema

* instanceMetadata validation

* Typo fix

* Default value for env

* metrics.infux.hostnameAsAInstanceId added

* should add hostname as an instance label when hostnameAsAInstanceId is enabled

* Default values for influx configuration

* flatMap is not available in Node.js 9.4

* Env vars removed from Procfile

* Better instance metadata values in tests

* Typo fix

* lodash.groupby added to prod dependencies

* Allow other keys in private config

* Missing test - should allow other private keys when influx metrics are enabled

* Missing test - should require private metrics config when influx configuration is enabled

* log.error instead of console.log

* metrics.influx.uri -> metrics.influx.url

* Unused arguments removed

* async removed

* promisify sendMetrics

* Allow to disable prometheus metrics

* Create test server with custom config

* 'metrics-influx' resource removed

* 'metrics-influx' resource removed

* Private config schema flattened out

* Extra code removed in Prometheus tests

* promisify moved outside of the class

* Do not throw errors from got in a specific test

* hostnameAliases added

* instanceIdFrom added

* instanceIdEnvVarName added

* envLabel added to schema

* instanceMetadata is not used by InfluxMetrics

* Instance metadata removed

* hostnameAsAnInstanceId removed

* A comment added

* waitForExpect removed

* Unused code removed
2020-04-19 20:03:00 +02:00
dependabot-preview[bot]
1c48c2207f Build(deps-dev): bump @typescript-eslint/parser from 2.27.0 to 2.28.0 (#4918)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.27.0 to 2.28.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.28.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-19 16:02:29 +00:00
Pierre-Yves B
a2e0e11ead Fix [CodeclimateCoverage] tests (#4927) 2020-04-19 17:06:35 +02:00
chris48s
2c89a8c59f improve latest() fallback result if no stable versions available (#4901)
* ignore case if we fall back to string sorting versions

* if no latest MaybeSemVer, try latest pre-MaybeSemVer

* filter versions before we pass it to latestMaybeSemVer()
2020-04-18 18:40:39 +01:00
dependabot-preview[bot]
d11fa30f06 Build(deps-dev): bump @typescript-eslint/eslint-plugin (#4916)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.27.0 to 2.28.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.28.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-17 23:38:29 +00:00
dependabot-preview[bot]
05b324093a Build(deps-dev): bump danger from 10.1.0 to 10.1.1 (#4914)
Bumps [danger](https://github.com/danger/danger-js) from 10.1.0 to 10.1.1.
- [Release notes](https://github.com/danger/danger-js/releases)
- [Changelog](https://github.com/danger/danger-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/danger/danger-js/compare/10.1.0...10.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-17 23:29:42 +00:00
dependabot-preview[bot]
42ed874112 Build(deps): bump ioredis from 4.16.1 to 4.16.2 (#4913)
Bumps [ioredis](https://github.com/luin/ioredis) from 4.16.1 to 4.16.2.
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/luin/ioredis/blob/master/Changelog.md)
- [Commits](https://github.com/luin/ioredis/compare/v4.16.1...v4.16.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-17 23:15:24 +00:00
dependabot-preview[bot]
e2d8c94dab Build(deps): bump query-string from 6.12.0 to 6.12.1 (#4922)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 6.12.0 to 6.12.1.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v6.12.0...v6.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-17 20:30:50 +00:00
dependabot-preview[bot]
91577cb6e9 Build(deps): bump semver from 7.2.2 to 7.3.2 (#4915)
Bumps [semver](https://github.com/npm/node-semver) from 7.2.2 to 7.3.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.2.2...v7.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-17 20:22:01 +00:00
dependabot-preview[bot]
d578faab50 Build(deps): bump simple-icons from 2.8.0 to 2.9.0 (#4919)
Bumps [simple-icons](https://github.com/simple-icons/simple-icons) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/2.8.0...2.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-17 18:48:14 +00:00
dependabot-preview[bot]
b0f367cdfb Build(deps-dev): bump eslint-plugin-sort-class-members (#4921)
Bumps [eslint-plugin-sort-class-members](https://github.com/bryanrsmith/eslint-plugin-sort-class-members) from 1.6.0 to 1.7.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.6.0...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-17 14:17:57 +00:00
dependabot-preview[bot]
430be7e7d1 Build(deps-dev): bump cypress from 4.3.0 to 4.4.0 (#4917)
Bumps [cypress](https://github.com/cypress-io/cypress) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Commits](https://github.com/cypress-io/cypress/compare/v4.3.0...v4.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-17 14:00:16 +00:00
Pierre-Yves B
2df8289ec8 Fix failing Nexus tests (#4905)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-16 23:46:51 +00:00
dependabot-preview[bot]
478d14300c Build(deps-dev): bump eslint-plugin-import from 2.20.1 to 2.20.2 (#4859)
* Build(deps-dev): bump eslint-plugin-import from 2.20.1 to 2.20.2

Bumps [eslint-plugin-import](https://github.com/benmosher/eslint-plugin-import) from 2.20.1 to 2.20.2.
- [Release notes](https://github.com/benmosher/eslint-plugin-import/releases)
- [Changelog](https://github.com/benmosher/eslint-plugin-import/blob/master/CHANGELOG.md)
- [Commits](https://github.com/benmosher/eslint-plugin-import/compare/v2.20.1...v2.20.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Fixes

* refactor: combine imports

* refactor: combine imports

* refactor: combine imports

* refactor: update import ordering

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <email@paulmelnikow.com>
Co-authored-by: Caleb Cartwright <calebcartwright@users.noreply.github.com>
Co-authored-by: Caleb Cartwright <caleb.cartwright@outlook.com>
2020-04-16 18:39:13 -05:00
Caleb Cartwright
23ceea1d72 fix: TypeError in DockerVersion badge (#4907) 2020-04-15 13:35:05 -05:00
ChrisCarini
2bf6dfdeea Adding badges for JetBrains Plugin Ratings, run [JetBrainsRating]. #4897 (#4898)
* Adding badges for JetBrains Plugin Ratings. Addresses feature request #4897

* Removing unnecessary undefined label and improving variable name in tests.
2020-04-12 17:05:49 +00:00
dependabot-preview[bot]
e4b1fd23b1 Build(deps-dev): bump @typescript-eslint/eslint-plugin (#4894)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.26.0 to 2.27.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.27.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-11 00:07:53 -05:00
dependabot-preview[bot]
8df5eed088 Build(deps-dev): bump @typescript-eslint/parser from 2.26.0 to 2.27.0 (#4893)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.26.0 to 2.27.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.27.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-11 03:32:59 +00:00
dependabot-preview[bot]
4c1eee9218 Build(deps-dev): bump styled-components from 5.0.1 to 5.1.0 (#4891)
Bumps [styled-components](https://github.com/styled-components/styled-components) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/styled-components/styled-components/releases)
- [Changelog](https://github.com/styled-components/styled-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/styled-components/styled-components/compare/v5.0.1...v5.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-11 03:24:06 +00:00
dependabot-preview[bot]
1c2920ac31 Build(deps-dev): bump nyc from 15.0.0 to 15.0.1 (#4882)
Bumps [nyc](https://github.com/istanbuljs/nyc) from 15.0.0 to 15.0.1.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/istanbuljs/nyc/compare/v15.0.0...v15.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-10 23:53:44 +00:00
dependabot-preview[bot]
9df6aade11 Build(deps-dev): bump @types/node from 13.11.0 to 13.11.1 (#4883)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 13.11.0 to 13.11.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-10 20:30:38 +00:00
mondeja
01950a7852 [Conda] license badge added. (#4875)
* Conda license badge added.

* Added schema for Conda license badge.

* Remove comment in conda license service

Co-authored-by: Pierre-Yves B <PyvesDev@gmail.com>
2020-04-10 22:23:21 +02:00
chris48s
935dd25264 ignore camelcase 6, escape-string-regexp 3 updates (#4896)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-10 19:55:13 +00:00
dependabot-preview[bot]
c320a58a24 Build(deps): bump query-string from 6.11.1 to 6.12.0 (#4885)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 6.11.1 to 6.12.0.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v6.11.1...v6.12.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-10 19:48:51 +00:00
dependabot-preview[bot]
10f06ff175 Build(deps): bump semver from 7.1.3 to 7.2.2 (#4895)
Bumps [semver](https://github.com/npm/node-semver) from 7.1.3 to 7.2.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.1.3...v7.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-10 19:38:35 +00:00
dependabot-preview[bot]
68b1a0cfe5 Build(deps-dev): bump nodemon from 2.0.2 to 2.0.3 (#4886)
Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v2.0.2...v2.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-10 19:18:05 +00:00
dependabot-preview[bot]
39b8ff0aa8 Build(deps): bump check-node-version from 4.0.2 to 4.0.3 (#4890)
Bumps [check-node-version](https://github.com/parshap/check-node-version) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/parshap/check-node-version/releases)
- [Changelog](https://github.com/parshap/check-node-version/blob/master/CHANGELOG.md)
- [Commits](https://github.com/parshap/check-node-version/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-10 20:08:57 +01:00
dependabot-preview[bot]
6dc672a03d Build(deps-dev): bump jsdoc from 3.6.3 to 3.6.4 (#4892)
Bumps [jsdoc](https://github.com/jsdoc/jsdoc) from 3.6.3 to 3.6.4.
- [Release notes](https://github.com/jsdoc/jsdoc/releases)
- [Changelog](https://github.com/jsdoc/jsdoc/blob/3.6.4/CHANGES.md)
- [Commits](https://github.com/jsdoc/jsdoc/compare/3.6.3...3.6.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-10 18:57:57 +00:00
Caleb Cartwright
642aac6408 fix: SymfonyInsight transform (#4877)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-08 19:59:42 +00:00
Joschua Becker
b2bb50234f updating node js version (#4879)
Node.js 8.x LTS Carbon is no longer actively supported!

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-08 19:30:26 +00:00
Owen Voke
d16c8404d6 Add [OffsetEarth] badges (#4815)
* Add [OffsetEarth] trees service

* Add mocked tests for [OffsetEarth] tree service

* Update [OffsetEarth] trees name to be more unique

* Add [OffsetEarth] carbon offset service

* Update [OffsetEarth] services to use floorCount()

* Clean up [OffsetEarth] loops and unnecessary code

* Update [OffsetEarth] services to use proper APIs

* Fix order of imports in [OffsetEarth] services

* Update [OffsetEarth] tests to be more robust

* Update [OffsetEarth] tests to check colour

* Update to use unmocked [OffsetEarth] tests

* Update to use nonNegativeInteger in [OffsetEarth]

* Apply additional [OffsetEarth] review comments

* Update [OffsetEarth] references to username

Co-authored-by: Pierre-Yves B <PyvesDev@gmail.com>
2020-04-08 19:18:38 +00:00
dependabot-preview[bot]
b36de3dbf3 Build(deps-dev): bump danger from 10.0.0 to 10.1.0 (#4856)
Bumps [danger](https://github.com/danger/danger-js) from 10.0.0 to 10.1.0.
- [Release notes](https://github.com/danger/danger-js/releases)
- [Changelog](https://github.com/danger/danger-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/danger/danger-js/compare/10.0.0...10.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-08 01:28:16 +00:00
dependabot-preview[bot]
20d4143dfb Build(deps): bump ioredis from 4.16.0 to 4.16.1 (#4861)
Bumps [ioredis](https://github.com/luin/ioredis) from 4.16.0 to 4.16.1.
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/luin/ioredis/blob/master/Changelog.md)
- [Commits](https://github.com/luin/ioredis/compare/v4.16.0...v4.16.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-07 01:54:03 +00:00
Caleb Cartwright
fbe865e149 feat: deprecate JitPackDownloads (#4873)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2020-04-06 18:33:52 +00:00
dependabot-preview[bot]
da29c92910 Build(deps-dev): bump @typescript-eslint/eslint-plugin (#4857)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.25.0 to 2.26.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.26.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
2020-04-06 17:19:30 +00:00
474 changed files with 12118 additions and 7465 deletions

View File

@@ -109,6 +109,7 @@ package_steps: &package_steps
export NVM_DIR="/opt/circleci/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install v12
nvm install v14
nvm use v12
npm install -g npm
npm ci
@@ -116,21 +117,13 @@ package_steps: &package_steps
CYPRESS_INSTALL_BINARY: 0
# Run the package tests on each currently supported node version. See:
# https://github.com/badges/shields/blob/master/gh-badges/README.md#node-version-support
# https://github.com/badges/shields/blob/master/badge-maker/README.md#node-version-support
- run:
<<: *run_package_tests
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/gh-badges/v8/results.xml
NODE_VERSION: v8
name: Run package tests on Node 8
- run:
<<: *run_package_tests
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/gh-badges/v10/results.xml
MOCHA_FILE: junit/badge-maker/v10/results.xml
NODE_VERSION: v10
name: Run package tests on Node 10
@@ -138,17 +131,25 @@ package_steps: &package_steps
<<: *run_package_tests
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/gh-badges/v12/results.xml
MOCHA_FILE: junit/badge-maker/v12/results.xml
NODE_VERSION: v12
name: Run package tests on Node 12
- run:
<<: *run_package_tests
environment:
mocha_reporter: mocha-junit-reporter
MOCHA_FILE: junit/badge-maker/v14/results.xml
NODE_VERSION: v14
name: Run package tests on Node 14
- store_test_results:
path: junit
jobs:
npm-install:
docker:
- image: circleci/node:8
- image: circleci/node:10
steps:
- checkout
@@ -160,33 +161,33 @@ jobs:
main:
docker:
- image: circleci/node:8
- image: circleci/node:10
<<: *main_steps
main@node-latest:
main@node-12:
docker:
- image: circleci/node:latest
- image: circleci/node:12
<<: *main_steps
integration:
docker:
- image: circleci/node:8
- image: circleci/node:10
- image: redis
<<: *integration_steps
integration@node-latest:
integration@node-12:
docker:
- image: circleci/node:latest
- image: circleci/node:12
- image: redis
<<: *integration_steps
danger:
docker:
- image: circleci/node:8
- image: circleci/node:10
steps:
- checkout
@@ -206,7 +207,7 @@ jobs:
frontend:
docker:
- image: circleci/node:8
- image: circleci/node:10
steps:
- checkout
@@ -247,19 +248,19 @@ jobs:
services:
docker:
- image: circleci/node:8
- image: circleci/node:10
<<: *services_steps
services@node-latest:
services@node-12:
docker:
- image: circleci/node:latest
- image: circleci/node:12
<<: *services_steps
e2e:
docker:
- image: cypress/base:8
- image: cypress/base:12
steps:
- checkout
@@ -308,11 +309,11 @@ workflows:
filters:
branches:
ignore: gh-pages
- main@node-latest:
- main@node-12:
filters:
branches:
ignore: gh-pages
- integration@node-latest:
- integration@node-12:
filters:
branches:
ignore: gh-pages
@@ -330,7 +331,7 @@ workflows:
ignore:
- master
- gh-pages
- services@node-latest:
- services@node-12:
filters:
branches:
ignore:

View File

@@ -29,75 +29,8 @@ update_configs:
- match:
dependency_name: 'snap-shot-it'
update_type: 'semver:minor'
ignored_updates:
- match:
dependency_name: babel-preset-gatsby
version_requirement: '>=0.3.0'
- match:
dependency_name: chalk
version_requirement: '>=4.0.0'
- match:
dependency_name: cross-env
version_requirement: '>=7.0.0'
- match:
dependency_name: decamelize
version_requirement: '>=4.0.0'
- match:
dependency_name: eslint-plugin-jsdoc
version_requirement: '>=21.0.0'
- match:
dependency_name: gatsby
version_requirement: '>=2.19.50'
- match:
dependency_name: gatsby-plugin-catch-links
version_requirement: '>=2.2.0'
- match:
dependency_name: gatsby-plugin-page-creator
version_requirement: '>=2.2.0'
- match:
dependency_name: gatsby-plugin-react-helmet
version_requirement: '>=3.2.0'
- match:
dependency_name: gatsby-plugin-remove-trailing-slashes
version_requirement: '>=2.2.0'
- match:
dependency_name: gatsby-plugin-styled-components
version_requirement: '>=3.2.0'
- match:
dependency_name: gatsby-plugin-typescript
version_requirement: '>=2.3.0'
- match:
dependency_name: got
version_requirement: '>=10.0.0'
- match:
dependency_name: '@hapi/joi'
version_requirement: '>=17.0.0'
- match:
dependency_name: husky
version_requirement: '>=4.0.0'
- match:
dependency_name: lint-staged
version_requirement: '>=10.0.0'
- match:
dependency_name: nock
version_requirement: '>=12.0.0'
- match:
dependency_name: prom-client
version_requirement: '>=12.0.0'
- match:
dependency_name: react-error-overlay
version_requirement: '>=3.0.0'
- match:
dependency_name: sinon
version_requirement: '>=9.0.0'
- match:
dependency_name: start-server-and-test
version_requirement: '>=1.10.8'
- match:
dependency_name: xmldom
version_requirement: '>=0.3.0'
# gh-badges package dependencies
# badge-maker package dependencies
- package_manager: 'javascript'
directory: '/gh-badges'
directory: '/badge-maker'
update_schedule: 'weekly'

View File

@@ -3,4 +3,4 @@
/coverage
/__snapshots__
/public
gh-badges/node_modules/
badge-maker/node_modules/

View File

@@ -8,7 +8,7 @@ Are you experiencing an issue with...
- [ ] [shields.io](https://shields.io/#/)
- [ ] My own instance
- [ ] [gh-badges NPM package](https://www.npmjs.com/package/gh-badges)
- [ ] [badge-maker NPM package](https://www.npmjs.com/package/badge-maker)
:beetle: **Description**

2
.gitignore vendored
View File

@@ -7,7 +7,7 @@
/private
/index.html
/shields.env
gh-badges/package-lock.json
badge-maker/package-lock.json
# Folder view configuration files
.DS_Store

View File

@@ -1,30 +0,0 @@
*
!frontend/
!gh-badges/
!lib/
!core/
!logo/
!pages/
!public/
!templates/
!services/
!package-lock.json
!/*.js
!scripts/export-*.js
!config/
config/local*.yml
*.spec.js
*~
.env
.circleci
.github
.vscode
__snapshots__
.buildpacks
.eslint*
.editorconfig
.nycrc*
.gitpod*
.prettier*
CONTRIBUTING.md
Dockerfile

View File

@@ -10,6 +10,5 @@ package-lock.json
private/*.json
/.nyc_output
analytics.json
gh-badges/templates/default-template.json
supported-features.json
service-definitions.yml

View File

@@ -1,5 +1,5 @@
semi: false
singleQuote: true
trailingComma: es5
bracketSpacing: true
endOfLine: lf
arrowParens: avoid

View File

@@ -5,8 +5,8 @@ RUN mkdir /usr/src/app/private
WORKDIR /usr/src/app
COPY package.json package-lock.json /usr/src/app/
# Without the gh-badges package.json and CLI script in place, `npm ci` will fail.
COPY gh-badges /usr/src/app/gh-badges/
# Without the badge-maker package.json and CLI script in place, `npm ci` will fail.
COPY badge-maker /usr/src/app/badge-maker/
# We need dev deps to build the front end. We don't need Cypress, though.
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci

View File

@@ -43,16 +43,16 @@ Every month it serves over 470 million images.
This repo hosts:
- The [Shields.io][shields.io] frontend and server code
- An [NPM library for generating badges][gh-badges]
- [documentation][gh-badges-docs]
- [changelog][gh-badges-changelog]
- An [NPM library for generating badges][badge-maker]
- [documentation][badge-maker-docs]
- [changelog][badge-maker-changelog]
- The [badge design specification][badge-spec]
[shields.io]: https://shields.io/
[gh-badges]: https://www.npmjs.com/package/gh-badges
[badge-maker]: https://www.npmjs.com/package/badge-maker
[badge-spec]: https://github.com/badges/shields/tree/master/spec
[gh-badges-docs]: https://github.com/badges/shields/tree/master/gh-badges/README.md
[gh-badges-changelog]: https://github.com/badges/shields/tree/master/gh-badges/CHANGELOG.md
[badge-maker-docs]: https://github.com/badges/shields/tree/master/badge-maker/README.md
[badge-maker-changelog]: https://github.com/badges/shields/tree/master/badge-maker/CHANGELOG.md
## Examples

View File

@@ -1,19 +1,127 @@
exports['The badge generator SVG should always produce the same SVG (unless we have changed something!) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h45v20H0z"/><path fill="#4c1" d="M45 0h45v20H45z"/><path fill="url(#b)" d="M0 0h90v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g> </svg>
exports['The badge generator SVG should match snapshot 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="20" fill="#555"/><rect x="45" width="45" height="20" fill="#4c1"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge shields GitHub logo custom color (whitesmoke) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="github"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge shields GitHub logo default color (#333333) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="github"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="107" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="107" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="62" height="20" fill="#0f0"/><rect x="62" width="45" height="20" fill="#b3e"/><rect width="107" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="405" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="835" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge simple-icons javascript logo custom color (rgba(46,204,113,0.8)) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="javascript"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="45" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#b3e"/><rect x="0" width="45" height="20" fill="#b3e"/><rect width="45" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="225" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge simple-icons javascript logo default color (#F7DF1E) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="javascript"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="63" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#555"/><rect x="0" width="63" height="20" fill="#b3e"/><rect width="63" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="405" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="69" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="69" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="24" height="20" fill="#0f0"/><rect x="24" width="45" height="20" fill="#b3e"/><rect width="69" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="455" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="455" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="90" height="20" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="45" height="20" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><g shape-rendering="crispEdges"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="107" height="20"><g shape-rendering="crispEdges"><rect width="62" height="20" fill="#0f0"/><rect x="62" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="835" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="20"><g shape-rendering="crispEdges"><rect width="0" height="20" fill="#b3e"/><rect x="0" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="225" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="20"><g shape-rendering="crispEdges"><rect width="0" height="20" fill="#555"/><rect x="0" width="63" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="69" height="20"><g shape-rendering="crispEdges"><rect width="24" height="20" fill="#0f0"/><rect x="24" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="455" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><g shape-rendering="crispEdges"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="90" height="20" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="45" height="20" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="90" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="18" fill="#0f0"/><rect x="45" width="45" height="18" fill="#b3e"/><rect width="90" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="130" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="107" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="107" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="62" height="18" fill="#0f0"/><rect x="62" width="45" height="18" fill="#b3e"/><rect width="107" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="2" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="405" y="130" transform="scale(.1)" textLength="350">cactus</text><text x="835" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="835" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="45" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="18" fill="#b3e"/><rect x="0" width="45" height="18" fill="#b3e"/><rect width="45" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="225" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="225" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="63" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="18" fill="#555"/><rect x="0" width="63" height="18" fill="#b3e"/><rect width="63" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="2" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="405" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="69" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="69" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="24" height="18" fill="#0f0"/><rect x="24" width="45" height="18" fill="#b3e"/><rect width="69" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="2" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="455" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="455" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="90" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="18" fill="#0f0"/><rect x="45" width="45" height="18" fill="#b3e"/><rect width="90" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="235" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="130" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="130" transform="scale(.1)" textLength="350">grown</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="90" height="18" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="45" height="18" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="147" height="28"><g shape-rendering="crispEdges"><rect width="74" height="28" fill="#0f0"/><rect x="74" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><text x="370" y="175" transform="scale(.1)" textLength="500">CACTUS</text><text x="1105" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="164" height="28"><g shape-rendering="crispEdges"><rect width="91" height="28" fill="#0f0"/><rect x="91" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="540" y="175" transform="scale(.1)" textLength="500">CACTUS</text><text x="1275" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="73" height="28"><g shape-rendering="crispEdges"><rect width="0" height="28" fill="#b3e"/><rect x="0" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><text x="365" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="91" height="28"><g shape-rendering="crispEdges"><rect width="0" height="28" fill="#555"/><rect x="0" width="91" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="545" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="105" height="28"><g shape-rendering="crispEdges"><rect width="32" height="28" fill="#0f0"/><rect x="32" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="230" y="175" transform="scale(.1)" textLength="-60"></text><text x="685" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="147" height="28"><g shape-rendering="crispEdges"><rect width="74" height="28" fill="#0f0"/><rect x="74" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><text x="370" y="175" transform="scale(.1)" textLength="500">CACTUS</text><text x="1105" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="147" height="28" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="74" height="28" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="95" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="47" height="19" rx="2"/><rect x="53.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="53" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M53.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px"><text x="235" y="150" fill="#fff" transform="scale(.1)" textLength="370">Cactus</text><text x="235" y="140" transform="scale(.1)" textLength="370">Cactus</text><text x="735" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="735" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="47" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="112" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="64" height="19" rx="2"/><rect x="70.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="70" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M70.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px"><text x="405" y="150" fill="#fff" transform="scale(.1)" textLength="370">Cactus</text><text x="405" y="140" transform="scale(.1)" textLength="370">Cactus</text><text x="905" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="905" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="64" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="59" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="11" height="19" rx="2"/><rect x="17.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="17" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M17.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px"><text x="55" y="150" fill="#fff" transform="scale(.1)" textLength="10"></text><text x="55" y="140" transform="scale(.1)" textLength="10"></text><text x="375" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="375" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="11" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="73" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="25" height="19" rx="2"/><rect x="31.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="31" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M31.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px"><text x="195" y="150" fill="#fff" transform="scale(.1)" textLength="10"></text><text x="195" y="140" transform="scale(.1)" textLength="10"></text><text x="515" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="515" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="25" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="73" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="25" height="19" rx="2"/><rect x="31.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="31" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M31.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px"><text x="195" y="150" fill="#fff" transform="scale(.1)" textLength="10"></text><text x="195" y="140" transform="scale(.1)" textLength="10"></text><text x="515" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="515" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="25" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="95" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="47" height="19" rx="2"/><rect x="53.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="53" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M53.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px"><text x="235" y="150" fill="#fff" transform="scale(.1)" textLength="370">Cactus</text><text x="235" y="140" transform="scale(.1)" textLength="370">Cactus</text><text x="735" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><a target="_blank" xlink:href="https://www.google.co.uk/"><text id="rlink" x="735" y="140" transform="scale(.1)" textLength="330">grown</text></a></g><a target="_blank" xlink:href="https://shields.io/"><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="47" height="19" rx="2" /></a></svg>
`
exports['The badge generator badges with logos should always produce the same badge badge with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="54" height="20" fill="#555"/><rect x="54" width="59" height="20" fill="#4c1"/><rect width="113" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g></svg>
`

View File

@@ -1,6 +1,68 @@
# Changelog
## 2.2.1
## 3.0.0
### Breaking Changes
- Dropped support for node < 10
- Package name has changed to `badge-maker` and moved to https://www.npmjs.com/package/badge-maker
- `BadgeFactory` class is removed and replaced by `makeBadge()` function.
- Deprecated parameters have been removed. In version 2.2.0 the `colorA`, `colorB` and `colorscheme` params were deprecated. In version 3.0.0 these have been removed.
- Only SVG output format is now provided. JSON format has been dropped and the `format` key has been removed.
- The `text` array has been replaced by `label` and `message` keys.
- The `template` key has been renamed `style`.
To upgrade from v2.1.1, change your code from:
```js
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const svg = bf.create({
text: ['build', 'passed'],
format: 'svg',
template: 'flat-square',
})
```
to:
```js
const { makeBadge } = require('badge-maker')
const svg = makeBadge({
label: 'build',
message: 'passed',
style: 'flat-square',
})
```
- `ValidationError` had been added and inputs are now validated. In previous releases, invalid inputs would be discarded and replaced with defaults. For example, in 2.2.1
```js
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const svg = bf.create({
text: ['build', 'passed'],
template: 'some invalid value',
})
```
would generate an SVG badge. In version >=3
```js
const { makeBadge } = require('badge-maker')
const svg = makeBadge({
label: 'build',
message: 'passed',
style: 'some invalid value',
})
```
will throw a `ValidationError`.
- Raster support has been removed from the CLI. It will now only output SVG. On the console, the output of `badge` can be piped to a utility like [imagemagick](https://imagemagick.org/script/command-line-processing.php). If you were previously using
```sh
badge build passed :green .gif
```
this could be replaced by
```sh
badge build passed :green | magick svg:- gif:-
```
### Security
- Removed dependency on doT library which has known vulnerabilities.
## 2.2.1 - 2019-05-30
### Fixes

View File

@@ -1,12 +1,12 @@
# gh-badges
# badge-maker
[![npm version](https://img.shields.io/npm/v/gh-badges.svg)](https://npmjs.org/package/gh-badges)
[![npm license](https://img.shields.io/npm/l/gh-badges.svg)](https://npmjs.org/package/gh-badges)
[![npm version](https://img.shields.io/npm/v/badge-maker.svg)](https://npmjs.org/package/badge-maker)
[![npm license](https://img.shields.io/npm/l/badge-maker.svg)](https://npmjs.org/package/badge-maker)
## Installation
```sh
npm install gh-badges
npm install badge-maker
```
## Usage
@@ -14,29 +14,34 @@ npm install gh-badges
### On the console
```sh
npm install -g gh-badges
badge build passed :green .png > mybadge.png
npm install -g badge-maker
badge build passed :green > mybadge.svg
```
### As a library
```js
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const { makeBadge, ValidationError } = require('badge-maker')
const format = {
text: ['build', 'passed'],
label: 'build',
message: 'passed',
color: 'green',
template: 'flat',
}
const svg = bf.create(format)
const svg = makeBadge(format)
console.log(svg) // <svg...
try {
makeBadge({})
} catch (e) {
console.log(e) // ValidationError: Field `message` is required
}
```
### Node version support
The latest version of gh-badges supports all currently maintained Node
The latest version of badge-maker supports all currently maintained Node
versions. See the [Node Release Schedule][].
[node release schedule]: https://github.com/nodejs/Release#release-schedule
@@ -47,28 +52,17 @@ The format is the following:
```js
{
text: [ 'build', 'passed' ], // Textual information shown, in order
label: 'build', // (Optional) Badge label
message: 'passed', // (Required) Badge message
labelColor: '#555', // (Optional) Label color
color: '#4c1', // (Optional) Message color
format: 'svg', // Also supports json
color: '#4c1',
labelColor: '#555',
// See templates/ for a list of available templates.
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
// Each offers a different visual design.
template: 'flat',
// Deprecated attributes:
colorscheme: 'green', // Now an alias for `color`.
colorB: '#4c1', // Now an alias for `color`.
colorA: '#555', // Now an alias for `labelColor`.
style: 'flat',
}
```
### See also
- [templates/](./templates) for the `template` option
## Colors
There are three ways to specify `color` and `labelColor`:
@@ -126,3 +120,12 @@ There are three ways to specify `color` and `labelColor`:
[lightslategray]: https://img.shields.io/badge/lightslategray-lightslategray.svg
[css color]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
[css/svg color]: http://www.w3.org/TR/SVG/types.html#DataTypeColor
## Raster Formats
Conversion to raster formats is no longer directly supported. In javascript
code, SVG badges can be converted to raster formats using a library like
[gm](https://www.npmjs.com/package/gm). On the console, the output of `badge`
can be piped to a utility like
[imagemagick](https://imagemagick.org/script/command-line-processing.php)
e.g: `badge build passed :green | magick svg:- gif:-`.

View File

@@ -2,23 +2,18 @@
'use strict'
const makeBadge = require('./make-badge')
const svg2img = require('./svg-to-img')
const { namedColors } = require('./color')
const { makeBadge } = require('./index')
if (process.argv.length < 4) {
console.log('Usage: badge subject status [:color] [.output] [@style]')
console.log(
'Or: badge subject status color [labelColor] [.output] [@style]'
)
console.log('Usage: badge label message [:color] [@style]')
console.log('Or: badge label message color [labelColor] [@style]')
console.log()
console.log(' color, labelColor:')
console.log(` one of ${Object.keys(namedColors).join(', ')}.`)
console.log(' #xxx (three hex digits)')
console.log(' #xxxxxx (six hex digits)')
console.log(' color (CSS color)')
console.log(' output:')
console.log(' svg, png, jpg, or gif')
console.log()
console.log('Eg: badge cactus grown :green @flat')
console.log()
@@ -26,14 +21,8 @@ if (process.argv.length < 4) {
}
// Find a format specifier.
let format = 'svg'
let style = ''
for (let i = 4; i < process.argv.length; i++) {
if (process.argv[i][0] === '.') {
format = process.argv[i].slice(1)
process.argv.splice(i, 1)
continue
}
if (process.argv[i][0] === '@') {
style = process.argv[i].slice(1)
process.argv.splice(i, 1)
@@ -41,14 +30,14 @@ for (let i = 4; i < process.argv.length; i++) {
}
}
const subject = process.argv[2]
const status = process.argv[3]
const label = process.argv[2]
const message = process.argv[3]
let color = process.argv[4] || ':green'
const colorA = process.argv[5]
const labelColor = process.argv[5]
const badgeData = { text: [subject, status], format }
const badgeData = { label, message }
if (style) {
badgeData.template = style
badgeData.style = style
}
if (color[0] === ':') {
@@ -58,28 +47,17 @@ if (color[0] === ':') {
console.error('Invalid color scheme.')
process.exit(1)
}
badgeData.colorscheme = color
badgeData.color = color
} else {
badgeData.colorB = color
if (colorA) {
badgeData.colorA = colorA
badgeData.color = color
if (labelColor) {
badgeData.labelColor = labelColor
}
}
async function main() {
const svg = makeBadge(badgeData)
if (/png|jpg|gif/.test(format)) {
const data = await svg2img(svg, format)
process.stdout.write(data)
} else {
console.log(svg)
}
}
;(async () => {
;(() => {
try {
await main()
console.log(makeBadge(badgeData))
} catch (e) {
console.error(e)
process.exit(1)

View File

@@ -1,7 +1,6 @@
'use strict'
const path = require('path')
const isPng = require('is-png')
const isSvg = require('is-svg')
const { spawn } = require('child-process-promise')
const { expect, use } = require('chai')
@@ -14,13 +13,13 @@ function runCli(args) {
})
}
describe('The CLI', function() {
it('should provide a help message', async function() {
describe('The CLI', function () {
it('should provide a help message', async function () {
const { stdout } = await runCli([])
expect(stdout).to.startWith('Usage')
})
it('should produce default badges', async function() {
it('should produce default badges', async function () {
const { stdout } = await runCli(['cactus', 'grown'])
expect(stdout)
.to.satisfy(isSvg)
@@ -28,30 +27,13 @@ describe('The CLI', function() {
.and.to.include('grown')
})
it('should produce colorschemed badges', async function() {
it('should produce colorschemed badges', async function () {
const { stdout } = await runCli(['cactus', 'grown', ':green'])
expect(stdout).to.satisfy(isSvg)
})
it('should produce right-color badges', async function() {
it('should produce right-color badges', async function () {
const { stdout } = await runCli(['cactus', 'grown', '#abcdef'])
expect(stdout)
.to.satisfy(isSvg)
.and.to.include('#abcdef')
})
it('should produce PNG badges', async function() {
const child = runCli(['cactus', 'grown', '.png'])
// The buffering done by `child-process-promise` doesn't seem correctly to
// handle binary data.
let chunk
child.childProcess.stdout.once('data', data => {
chunk = data
})
await child
expect(chunk).to.satisfy(isPng)
expect(stdout).to.satisfy(isSvg).and.to.include('#abcdef')
})
})

View File

@@ -0,0 +1,610 @@
'use strict'
const anafanafo = require('anafanafo')
const fontFamily = 'font-family="Verdana,Geneva,DejaVu Sans,sans-serif"'
const socialFontFamily =
'font-family="Helvetica Neue,Helvetica,Arial,sans-serif"'
function capitalize(s) {
return `${s.charAt(0).toUpperCase()}${s.slice(1)}`
}
function escapeXml(s) {
if (s === undefined || typeof s !== 'string') {
return undefined
} else {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
}
function roundUpToOdd(val) {
// Increase chances of pixel grid alignment.
return val % 2 === 0 ? val + 1 : val
}
function preferredWidthOf(str) {
return roundUpToOdd((anafanafo(str) / 10) | 0)
}
function computeWidths({ label, message }) {
return {
labelWidth: preferredWidthOf(label),
messageWidth: preferredWidthOf(message),
}
}
function renderLogo({
logo,
badgeHeight,
horizPadding,
logoWidth = 14,
logoPadding = 0,
}) {
if (!logo) {
return {
hasLogo: false,
totalLogoWidth: 0,
renderedLogo: '',
}
}
const logoHeight = 14
const y = (badgeHeight - logoHeight) / 2
const x = horizPadding
return {
hasLogo: true,
totalLogoWidth: logoWidth + logoPadding,
renderedLogo: `<image x="${x}" y="${y}" width="${logoWidth}" height="14" xlink:href="${escapeXml(
logo
)}"/>`,
}
}
function renderText({
leftMargin,
horizPadding = 0,
content,
verticalMargin = 0,
shadow = false,
}) {
if (!content.length) {
return { renderedText: '', width: 0 }
}
const textLength = preferredWidthOf(content)
const escapedContent = escapeXml(content)
const shadowMargin = 150 + verticalMargin
const textMargin = 140 + verticalMargin
const outTextLength = 10 * textLength
const x = 10 * (leftMargin + 0.5 * textLength + horizPadding)
let renderedText = ''
if (shadow) {
renderedText = `<text x="${x}" y="${shadowMargin}" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="${outTextLength}">${escapedContent}</text>`
}
renderedText += `<text x="${x}" y="${textMargin}" transform="scale(.1)" textLength="${outTextLength}">${escapedContent}</text>`
return {
renderedText,
width: textLength,
}
}
function renderLinks({
links: [leftLink, rightLink] = [],
leftWidth,
rightWidth,
height,
}) {
leftLink = escapeXml(leftLink)
rightLink = escapeXml(rightLink)
const hasLeftLink = leftLink && leftLink.length
const hasRightLink = rightLink && rightLink.length
const leftLinkWidth = hasRightLink ? leftWidth : leftWidth + rightWidth
function render({ link, width }) {
return `<a target="_blank" xlink:href="${link}"><rect width="${width}" height="${height}" fill="rgba(0,0,0,0)"/></a>`
}
return (
(hasRightLink
? render({ link: rightLink, width: leftWidth + rightWidth })
: '') +
(hasLeftLink ? render({ link: leftLink, width: leftLinkWidth }) : '')
)
}
function renderBadge({ links, leftWidth, rightWidth, height }, main) {
const width = leftWidth + rightWidth
return `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">
${main}
${renderLinks({ links, leftWidth, rightWidth, height })}
</svg>`
}
function stripXmlWhitespace(xml) {
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
}
class Badge {
static get fontFamily() {
throw new Error('Not implemented')
}
static get height() {
throw new Error('Not implemented')
}
static get verticalMargin() {
throw new Error('Not implemented')
}
static get shadow() {
throw new Error('Not implemented')
}
constructor({
label,
message,
links,
logo,
logoWidth,
logoPadding,
color = '#4c1',
labelColor,
}) {
const horizPadding = 5
const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
logo,
badgeHeight: this.constructor.height,
horizPadding,
logoWidth,
logoPadding,
})
const hasLabel = label.length || labelColor
if (labelColor == null) {
labelColor = '#555'
}
labelColor = hasLabel || hasLogo ? labelColor : color
labelColor = escapeXml(labelColor)
color = escapeXml(color)
const labelMargin = totalLogoWidth + 1
const { renderedText: renderedLabel, width: labelWidth } = renderText({
leftMargin: labelMargin,
horizPadding,
content: label,
verticalMargin: this.constructor.verticalMargin,
shadow: this.constructor.shadow,
})
const leftWidth = hasLabel
? labelWidth + 2 * horizPadding + totalLogoWidth
: 0
let messageMargin = leftWidth - (message.length ? 1 : 0)
if (!hasLabel) {
if (hasLogo) {
messageMargin = messageMargin + totalLogoWidth + horizPadding
} else {
messageMargin = messageMargin + 1
}
}
const { renderedText: renderedMessage, width: messageWidth } = renderText({
leftMargin: messageMargin,
horizPadding,
content: message,
verticalMargin: this.constructor.verticalMargin,
shadow: this.constructor.shadow,
})
let rightWidth = messageWidth + 2 * horizPadding
if (hasLogo && !hasLabel) {
rightWidth += totalLogoWidth + horizPadding - 1
}
const width = leftWidth + rightWidth
this.links = links
this.leftWidth = leftWidth
this.rightWidth = rightWidth
this.width = width
this.labelColor = labelColor
this.color = color
this.renderedLogo = renderedLogo
this.renderedLabel = renderedLabel
this.renderedMessage = renderedMessage
}
render() {
throw new Error('Not implemented')
}
}
class Plastic extends Badge {
static get fontFamily() {
return fontFamily
}
static get height() {
return 18
}
static get verticalMargin() {
return -10
}
static get shadow() {
return true
}
render() {
return renderBadge(
{
links: this.links,
leftWidth: this.leftWidth,
rightWidth: this.rightWidth,
height: this.constructor.height,
},
`
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
<stop offset=".1" stop-color="#aaa" stop-opacity=".1"/>
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
<stop offset="1" stop-color="#000" stop-opacity=".5"/>
</linearGradient>
<clipPath id="r">
<rect width="${this.width}" height="${this.constructor.height}" rx="4" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
<rect width="${this.width}" height="${this.constructor.height}" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} text-rendering="geometricPrecision" font-size="110">
${this.renderedLogo}
${this.renderedLabel}
${this.renderedMessage}
</g>`
)
}
}
class Flat extends Badge {
static get fontFamily() {
return fontFamily
}
static get height() {
return 20
}
static get verticalMargin() {
return 0
}
static get shadow() {
return true
}
render() {
return renderBadge(
{
links: this.links,
leftWidth: this.leftWidth,
rightWidth: this.rightWidth,
height: this.constructor.height,
},
`
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="${this.width}" height="${this.constructor.height}" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
<rect width="${this.width}" height="${this.constructor.height}" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} text-rendering="geometricPrecision" font-size="110">
${this.renderedLogo}
${this.renderedLabel}
${this.renderedMessage}
</g>`
)
}
}
class FlatSquare extends Badge {
static get fontFamily() {
return fontFamily
}
static get height() {
return 20
}
static get verticalMargin() {
return 0
}
static get shadow() {
return false
}
render() {
return renderBadge(
{
links: this.links,
leftWidth: this.leftWidth,
rightWidth: this.rightWidth,
height: this.constructor.height,
},
`
<g shape-rendering="crispEdges">
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
</g>
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} text-rendering="geometricPrecision" font-size="110">
${this.renderedLogo}
${this.renderedLabel}
${this.renderedMessage}
</g>`
)
}
}
function plastic(params) {
const badge = new Plastic(params)
if (params.minify) {
return stripXmlWhitespace(badge.render())
}
return badge.render()
}
function flat(params) {
const badge = new Flat(params)
if (params.minify) {
return stripXmlWhitespace(badge.render())
}
return badge.render()
}
function flatSquare(params) {
const badge = new FlatSquare(params)
if (params.minify) {
return stripXmlWhitespace(badge.render())
}
return badge.render()
}
function social({
label,
message,
links = [],
logo,
logoWidth,
logoPadding,
color = '#4c1',
labelColor = '#555',
minify,
}) {
// Social label is styled with a leading capital. Convert to caps here so
// width can be measured using the correct characters.
label = capitalize(label)
const externalHeight = 20
const internalHeight = 19
const horizPadding = 5
const { totalLogoWidth, renderedLogo } = renderLogo({
logo,
badgeHeight: externalHeight,
horizPadding,
logoWidth,
logoPadding,
})
const hasMessage = message.length
let { labelWidth, messageWidth } = computeWidths({ label, message })
labelWidth += 10 + totalLogoWidth
messageWidth += 10
messageWidth -= 4
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
const labelTextLength = (labelWidth - (10 + totalLogoWidth)) * 10
const escapedLabel = escapeXml(label)
let [leftLink, rightLink] = links
leftLink = escapeXml(leftLink)
rightLink = escapeXml(rightLink)
const hasLeftLink = leftLink && leftLink.length
const hasRightLink = rightLink && rightLink.length
function renderMessageBubble() {
const messageBubbleMainX = labelWidth + 6.5
const messageBubbleNotchX = labelWidth + 6
return `
<rect x="${messageBubbleMainX}" y="0.5" width="${messageWidth}" height="${internalHeight}" rx="2" fill="#fafafa"/>
<rect x="${messageBubbleNotchX}" y="7.5" width="0.5" height="5" stroke="#fafafa"/>
<path d="M${messageBubbleMainX} 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/>
`
}
function renderMessageText() {
const messageTextX = (labelWidth + messageWidth / 2 + 6) * 10
const messageTextLength = (messageWidth - 8) * 10
const escapedMessage = escapeXml(message)
const shadow = `<text x="${messageTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
const text = `<text id="rlink" x="${messageTextX}" y="140" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
if (hasRightLink) {
return `
${shadow}
<a target="_blank" xlink:href="${rightLink}">${text}</a>
`
}
return `
${shadow}
${text}
`
}
function renderLeftLink() {
const rect = `<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="${labelWidth}" height="${internalHeight}" rx="2" />`
if (hasLeftLink) {
return `<a target="_blank" xlink:href="${leftLink}">${rect}</a>`
}
return rect
}
const badge = renderBadge(
{
links: [],
leftWidth: labelWidth + 1,
rightWidth: hasMessage ? messageWidth + 6 : 0,
height: externalHeight,
},
`
<style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style>
<linearGradient id="a" x2="0" y2="100%">
<stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#ccc" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<g stroke="#d5d5d5">
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="${labelWidth}" height="${internalHeight}" rx="2"/>
${hasMessage ? renderMessageBubble() : ''}
</g>
${renderedLogo}
<g fill="#333" text-anchor="middle" ${socialFontFamily} text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px">
<text x="${labelTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>
<text x="${labelTextX}" y="140" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>
${hasMessage ? renderMessageText() : ''}
</g>
${renderLeftLink()}
`
)
if (minify) {
return stripXmlWhitespace(badge)
}
return badge
}
function forTheBadge({
label,
message,
links,
logo,
logoWidth,
logoPadding,
color = '#4c1',
labelColor,
minify,
}) {
// For the Badge is styled in all caps. Convert to caps here so widths can
// be measured using the correct characters.
label = label.toUpperCase()
message = message.toUpperCase()
let { labelWidth, messageWidth } = computeWidths({ label, message })
const height = 28
const hasLabel = label.length || labelColor
if (labelColor == null) {
labelColor = '#555'
}
const horizPadding = 9
const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
logo,
badgeHeight: height,
horizPadding,
logoWidth,
logoPadding,
})
labelWidth += 10 + totalLogoWidth
if (label.length) {
labelWidth += 10 + label.length * 1.5
} else if (hasLogo) {
if (hasLabel) {
labelWidth += 7
} else {
labelWidth -= 7
}
} else {
labelWidth -= 11
}
messageWidth += 10
messageWidth += 10 + message.length * 2
const leftWidth = hasLogo && !hasLabel ? 0 : labelWidth
const rightWidth =
hasLogo && !hasLabel ? messageWidth + labelWidth : messageWidth
labelColor = hasLabel || hasLogo ? labelColor : color
color = escapeXml(color)
labelColor = escapeXml(labelColor)
function renderLabelText() {
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
const labelTextLength = (labelWidth - (24 + totalLogoWidth)) * 10
const escapedLabel = escapeXml(label)
return `
<text x="${labelTextX}" y="175" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>
`
}
const badge = renderBadge(
{
links,
leftWidth,
rightWidth,
height,
},
`
<g shape-rendering="crispEdges">
<rect width="${leftWidth}" height="${height}" fill="${labelColor}"/>
<rect x="${leftWidth}" width="${rightWidth}" height="${height}" fill="${color}"/>
</g>
<g fill="#fff" text-anchor="middle" ${fontFamily} text-rendering="geometricPrecision" font-size="100">
${renderedLogo}
${hasLabel ? renderLabelText() : ''}
<text x="${
(labelWidth + messageWidth / 2) * 10
}" y="175" font-weight="bold" transform="scale(.1)" textLength="${
(messageWidth - 24) * 10
}">
${escapeXml(message)}</text>
</g>`
)
if (minify) {
return stripXmlWhitespace(badge)
}
return badge
}
module.exports = { plastic, flat, flatSquare, social, forTheBadge }

View File

@@ -2,7 +2,7 @@
const isCSSColor = require('is-css-color')
// When updating these, be sure also to update the list in `gh-badges/README.md`.
// When updating these, be sure also to update the list in `badge-maker/README.md`.
const namedColors = {
brightgreen: '#4c1',
green: '#97ca00',

87
badge-maker/lib/index.js Normal file
View File

@@ -0,0 +1,87 @@
'use strict'
/**
* @module badge-maker
*/
const _makeBadge = require('./make-badge')
class ValidationError extends Error {}
function _validate(format) {
if (format !== Object(format)) {
throw new ValidationError('makeBadge takes an argument of type object')
}
if (!('message' in format)) {
throw new ValidationError('Field `message` is required')
}
const stringFields = ['labelColor', 'color', 'message', 'label']
stringFields.forEach(function (field) {
if (field in format && typeof format[field] !== 'string') {
throw new ValidationError(`Field \`${field}\` must be of type string`)
}
})
const styleValues = [
'plastic',
'flat',
'flat-square',
'for-the-badge',
'social',
]
if ('style' in format && !styleValues.includes(format.style)) {
throw new ValidationError(
`Field \`style\` must be one of (${styleValues.toString()})`
)
}
}
function _clean(format) {
const expectedKeys = ['label', 'message', 'labelColor', 'color', 'style']
const cleaned = {}
Object.keys(format).forEach(key => {
if (format[key] != null && expectedKeys.includes(key)) {
cleaned[key] = format[key]
} else {
throw new ValidationError(
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
)
}
})
// convert "public" format to "internal" format
cleaned.text = [cleaned.label || '', cleaned.message]
delete cleaned.label
delete cleaned.message
if ('style' in cleaned) {
cleaned.template = cleaned.style
delete cleaned.style
}
return cleaned
}
/**
* Create a badge
*
* @param {object} format Object specifying badge data
* @param {string} format.label (Optional) Badge label (e.g: 'build')
* @param {string} format.message (Required) Badge message (e.g: 'passing')
* @param {string} format.labelColor (Optional) Label color
* @param {string} format.color (Optional) Message color
* @param {string} format.style (Optional) Visual style e.g: 'flat'
* @returns {string} Badge in SVG format
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
*/
function makeBadge(format) {
_validate(format)
const cleanedFormat = _clean(format)
return _makeBadge(cleanedFormat)
}
module.exports = {
makeBadge,
ValidationError,
}

View File

@@ -0,0 +1,75 @@
'use strict'
const { expect } = require('chai')
const isSvg = require('is-svg')
const { makeBadge, ValidationError } = require('.')
describe('makeBadge function', function () {
it('should produce badge with valid input', function () {
expect(
makeBadge({
label: 'build',
message: 'passed',
})
).to.satisfy(isSvg)
expect(
makeBadge({
message: 'passed',
})
).to.satisfy(isSvg)
expect(
makeBadge({
label: 'build',
message: 'passed',
color: 'green',
style: 'flat',
})
).to.satisfy(isSvg)
})
it('should throw a ValidationError with invalid inputs', function () {
;[null, undefined, 7, 'foo', 4.25].forEach(x => {
console.log(x)
expect(() => makeBadge(x)).to.throw(
ValidationError,
'makeBadge takes an argument of type object'
)
})
expect(() => makeBadge({})).to.throw(
ValidationError,
'Field `message` is required'
)
expect(() => makeBadge({ label: 'build' })).to.throw(
ValidationError,
'Field `message` is required'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', labelColor: 7 })
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
expect(() =>
makeBadge({ label: 'build', message: 'passed', format: 'png' })
).to.throw(ValidationError, "Unexpected field 'format'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', template: 'flat' })
).to.throw(ValidationError, "Unexpected field 'template'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', foo: 'bar' })
).to.throw(ValidationError, "Unexpected field 'foo'")
expect(() =>
makeBadge({
label: 'build',
message: 'passed',
style: 'something else',
})
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', style: 'popout' })
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
})
})

View File

@@ -0,0 +1,64 @@
'use strict'
const camelcase = require('camelcase')
const { normalizeColor, toSvgColor } = require('./color')
const badgeRenderers = require('./badge-renderers')
/*
note: makeBadge() is fairly thinly wrapped so if we are making changes here
it is likely this will impact on the package's public interface in index.js
*/
module.exports = function makeBadge({
format,
template = 'flat',
text,
color,
labelColor,
logo,
logoPosition,
logoWidth,
links = ['', ''],
}) {
// String coercion and whitespace removal.
text = text.map(value => `${value}`.trim())
const [label, message] = text
color = normalizeColor(color)
labelColor = normalizeColor(labelColor)
// This ought to be the responsibility of the server, not `makeBadge`.
if (format === 'json') {
return JSON.stringify({
label,
message,
logoWidth,
color,
labelColor,
link: links,
name: label,
value: message,
})
}
const methodName = camelcase(template)
if (!(methodName in badgeRenderers)) {
throw new Error(`Unknown template: '${template}'`)
}
const render = badgeRenderers[methodName]
logoWidth = +logoWidth || (logo ? 14 : 0)
return render({
label,
message,
links,
logo,
logoPosition,
logoWidth,
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),
minify: true,
})
}

View File

@@ -0,0 +1,563 @@
'use strict'
const { test, given, forCases } = require('sazerac')
const { expect } = require('chai')
const snapshot = require('snap-shot-it')
const isSvg = require('is-svg')
const makeBadge = require('./make-badge')
function testColor(color = '', colorAttr = 'color') {
return JSON.parse(
makeBadge({
text: ['name', 'Bob'],
[colorAttr]: color,
format: 'json',
})
).color
}
describe('The badge generator', function () {
describe('color test', function () {
test(testColor, () => {
// valid hex
forCases([
given('#4c1'),
given('#4C1'),
given('4C1'),
given('4c1'),
]).expect('#4c1')
forCases([
given('#abc123'),
given('#ABC123'),
given('abc123'),
given('ABC123'),
]).expect('#abc123')
// valid rgb(a)
given('rgb(0,128,255)').expect('rgb(0,128,255)')
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)')
// valid hsl(a)
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)')
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)')
// CSS named color.
given('papayawhip').expect('papayawhip')
// Shields named color.
given('red').expect('red')
given('green').expect('green')
given('blue').expect('blue')
given('yellow').expect('yellow')
// Semantic color alias
given('success').expect('brightgreen')
given('informational').expect('blue')
forCases(
// invalid hex
given('#123red'), // contains letter above F
given('#red'), // contains letter above F
// invalid rgb(a)
given('rgb(220,128,255,0.5)'), // has alpha
given('rgba(0,0,255)'), // no alpha
// invalid hsl(a)
given('hsl(360,50%,50%,0.5)'), // has alpha
given('hsla(0,50%,101%)'), // no alpha
// neither a css named color nor colorscheme
given('notacolor'),
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus')
).expect(undefined)
})
})
describe('color aliases', function () {
test(testColor, () => {
forCases([given('#4c1', 'color')]).expect('#4c1')
})
})
describe('SVG', function () {
it('should produce SVG', function () {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
expect(svg)
.to.satisfy(isSvg)
.and.to.include('cactus')
.and.to.include('grown')
})
it('should match snapshot', function () {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
snapshot(svg)
})
})
describe('JSON', function () {
it('should produce the expected JSON', function () {
const json = makeBadge({
text: ['cactus', 'grown'],
format: 'json',
links: ['https://example.com/', 'https://other.example.com/'],
})
expect(JSON.parse(json)).to.deep.equal({
name: 'cactus',
label: 'cactus',
value: 'grown',
message: 'grown',
link: ['https://example.com/', 'https://other.example.com/'],
})
})
it('should replace undefined svg template with "flat"', function () {
const jsonBadgeWithUnknownStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
})
const jsonBadgeWithDefaultStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'flat',
})
expect(jsonBadgeWithUnknownStyle)
.to.equal(jsonBadgeWithDefaultStyle)
.and.to.satisfy(isSvg)
})
it('should fail with unknown svg template', function () {
expect(() =>
makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'unknown_style',
})
).to.throw(Error, "Unknown template: 'unknown_style'")
})
})
describe('"flat" template badge generation', function () {
it('should match snapshots: message/label, no logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"flat-square" template badge generation', function () {
it('should match snapshots: message/label, no logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"plastic" template badge generation', function () {
it('should match snapshots: message/label, no logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"for-the-badge" template badge generation', function () {
// https://github.com/badges/shields/issues/1280
it('numbers should produce a string', function () {
const svg = makeBadge({
text: [1998, 1999],
format: 'svg',
template: 'for-the-badge',
})
expect(svg).to.include('1998').and.to.include('1999')
})
it('lowercase/mixedcase string should produce uppercase string', function () {
const svg = makeBadge({
text: ['Label', '1 string'],
format: 'svg',
template: 'for-the-badge',
})
expect(svg).to.include('LABEL').and.to.include('1 STRING')
})
it('should match snapshots: message/label, no logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"social" template badge generation', function () {
it('should produce capitalized string for badge key', function () {
const svg = makeBadge({
text: ['some-key', 'some-value'],
format: 'svg',
template: 'social',
})
expect(svg).to.include('Some-key').and.to.include('some-value')
})
// https://github.com/badges/shields/issues/1606
it('should handle empty strings used as badge keys', function () {
const svg = makeBadge({
text: ['', 'some-value'],
format: 'json',
template: 'social',
})
expect(svg).to.include('""').and.to.include('some-value')
})
it('should match snapshots: message/label, no logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function () {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function () {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('badges with logos should always produce the same badge', function () {
it('badge with logo', function () {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
snapshot(svg)
})
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "gh-badges",
"version": "2.2.1",
"name": "badge-maker",
"version": "3.0.0",
"description": "Shields.io badge library",
"keywords": [
"GitHub",
@@ -13,7 +13,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/badges/shields.git",
"directory": "gh-badges"
"directory": "badge-maker"
},
"author": "Thaddée Tyl <thaddee.tyl@gmail.com>",
"license": "CC0-1.0",
@@ -25,7 +25,7 @@
"badge": "lib/badge-cli.js"
},
"engines": {
"node": ">= 8",
"node": ">= 10",
"npm": ">= 5"
},
"collective": {
@@ -35,10 +35,7 @@
},
"dependencies": {
"anafanafo": "^1.0.0",
"dot": "^1.1.2",
"gm": "^1.23.0",
"is-css-color": "^1.0.0",
"svgo": "^1.1.1"
"is-css-color": "^1.0.0"
},
"scripts": {
"test": "echo 'Run tests from parent dir'; false"

View File

@@ -6,6 +6,15 @@ public:
metrics:
prometheus:
enabled: 'METRICS_PROMETHEUS_ENABLED'
endpointEnabled: 'METRICS_PROMETHEUS_ENDPOINT_ENABLED'
influx:
enabled: 'METRICS_INFLUX_ENABLED'
url: 'METRICS_INFLUX_URL'
timeoutMilliseconds: 'METRICS_INFLUX_TIMEOUT_MILLISECONDS'
intervalSeconds: 'METRICS_INFLUX_INTERVAL_SECONDS'
instanceIdFrom: 'METRICS_INFLUX_INSTANCE_ID_FROM'
instanceIdEnvVarName: 'METRICS_INFLUX_INSTANCE_ID_ENV_VAR_NAME'
envLabel: 'METRICS_INFLUX_ENV_LABEL'
ssl:
isSecure: 'HTTPS'
@@ -55,6 +64,8 @@ public:
fetchLimit: 'FETCH_LIMIT'
shieldsProductionHerokuHacks: 'SHIELDS_PRODUCTION_HEROKU_HACKS'
private:
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
bintray_user: 'BINTRAY_USER'
@@ -85,3 +96,5 @@ private:
twitch_client_id: 'TWITCH_CLIENT_ID'
twitch_client_secret: 'TWITCH_CLIENT_SECRET'
wheelmap_token: 'WHEELMAP_TOKEN'
influx_username: 'INFLUX_USERNAME'
influx_password: 'INFLUX_PASSWORD'

View File

@@ -5,7 +5,11 @@ public:
metrics:
prometheus:
enabled: false
endpointEnabled: false
influx:
enabled: false
timeoutMilliseconds: 1000
intervalSeconds: 15
ssl:
isSecure: false
@@ -32,4 +36,6 @@ public:
fetchLimit: '10MB'
shieldsProductionHerokuHacks: false
private: {}

View File

@@ -2,6 +2,12 @@ public:
metrics:
prometheus:
enabled: true
influx:
enabled: true
url: https://metrics.shields.io/telegraf
instanceIdFrom: env-var
instanceIdEnvVarName: HEROKU_DYNO_ID
envLabel: shields-production
ssl:
isSecure: true

View File

@@ -38,18 +38,22 @@ 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({

View File

@@ -59,15 +59,19 @@ 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}` : ''

View File

@@ -10,7 +10,7 @@ const {
dynamicBadgeUrl,
} = require('./make-badge-url')
describe('Badge URL generation functions', function() {
describe('Badge URL generation functions', function () {
test(badgeUrlFromPath, () => {
given({
baseUrl: 'http://example.com',

View File

@@ -5,20 +5,20 @@ const { test, given, forCases } = require('sazerac')
const { AuthHelper } = require('./auth-helper')
const { InvalidParameter } = require('./errors')
describe('AuthHelper', function() {
describe('constructor checks', function() {
it('throws without userKey or passKey', function() {
describe('AuthHelper', function () {
describe('constructor checks', function () {
it('throws without userKey or passKey', function () {
expect(() => new AuthHelper({}, {})).to.throw(
Error,
'Expected userKey or passKey to be set'
)
})
it('throws without serviceKey or authorizedOrigins', function() {
it('throws without serviceKey or authorizedOrigins', function () {
expect(
() => new AuthHelper({ userKey: 'myci_user', passKey: 'myci_pass' }, {})
).to.throw(Error, 'Expected authorizedOrigins or serviceKey to be set')
})
it('throws when authorizedOrigins is not an array', function() {
it('throws when authorizedOrigins is not an array', function () {
expect(
() =>
new AuthHelper(
@@ -33,7 +33,7 @@ describe('AuthHelper', function() {
})
})
describe('isValid', function() {
describe('isValid', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
@@ -89,7 +89,7 @@ describe('AuthHelper', function() {
})
})
describe('_basicAuth', function() {
describe('_basicAuth', function () {
function validate(config, privateConfig) {
return new AuthHelper(
{ authorizedOrigins: ['https://example.test'], ...config },
@@ -128,7 +128,7 @@ describe('AuthHelper', function() {
})
})
describe('_isInsecureSslRequest', function() {
describe('_isInsecureSslRequest', function () {
test(AuthHelper._isInsecureSslRequest, () => {
forCases([
given({ url: 'http://example.test' }),
@@ -146,31 +146,31 @@ describe('AuthHelper', function() {
})
})
describe('enforceStrictSsl', function() {
describe('enforceStrictSsl', function () {
const authConfig = {
userKey: 'myci_user',
passKey: 'myci_pass',
serviceKey: 'myci',
}
context('by default', function() {
context('by default', function () {
const authHelper = new AuthHelper(authConfig, {
public: {
services: { myci: { authorizedOrigins: ['http://myci.test'] } },
},
private: { myci_user: 'admin', myci_pass: 'abc123' },
})
it('does not throw for secure requests', function() {
it('does not throw for secure requests', function () {
expect(() => authHelper.enforceStrictSsl({})).not.to.throw()
})
it('throws for insecure requests', function() {
it('throws for insecure requests', function () {
expect(() =>
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
).to.throw(InvalidParameter)
})
})
context("when strict SSL isn't required", function() {
context("when strict SSL isn't required", function () {
const authHelper = new AuthHelper(authConfig, {
public: {
services: {
@@ -182,10 +182,10 @@ describe('AuthHelper', function() {
},
private: { myci_user: 'admin', myci_pass: 'abc123' },
})
it('does not throw for secure requests', function() {
it('does not throw for secure requests', function () {
expect(() => authHelper.enforceStrictSsl({})).not.to.throw()
})
it('does not throw for insecure requests', function() {
it('does not throw for insecure requests', function () {
expect(() =>
authHelper.enforceStrictSsl({ options: { strictSSL: false } })
).not.to.throw()
@@ -193,14 +193,14 @@ describe('AuthHelper', function() {
})
})
describe('shouldAuthenticateRequest', function() {
describe('shouldAuthenticateRequest', function () {
const authConfig = {
userKey: 'myci_user',
passKey: 'myci_pass',
serviceKey: 'myci',
}
context('by default', function() {
context('by default', function () {
const authHelper = new AuthHelper(authConfig, {
public: {
services: {
@@ -213,12 +213,12 @@ describe('AuthHelper', function() {
})
const shouldAuthenticateRequest = requestOptions =>
authHelper.shouldAuthenticateRequest(requestOptions)
describe('a secure request to an authorized origin', function() {
describe('a secure request to an authorized origin', function () {
test(shouldAuthenticateRequest, () => {
given({ url: 'https://myci.test/api' }).expect(true)
})
})
describe('an insecure request', function() {
describe('an insecure request', function () {
test(shouldAuthenticateRequest, () => {
given({
url: 'https://myci.test/api',
@@ -226,7 +226,7 @@ describe('AuthHelper', function() {
}).expect(false)
})
})
describe('a request to an unauthorized origin', function() {
describe('a request to an unauthorized origin', function () {
test(shouldAuthenticateRequest, () => {
forCases([
given({ url: 'http://myci.test/api' }),
@@ -237,7 +237,7 @@ describe('AuthHelper', function() {
})
})
context('when auth over insecure SSL is allowed', function() {
context('when auth over insecure SSL is allowed', function () {
const authHelper = new AuthHelper(authConfig, {
public: {
services: {
@@ -251,12 +251,12 @@ describe('AuthHelper', function() {
})
const shouldAuthenticateRequest = requestOptions =>
authHelper.shouldAuthenticateRequest(requestOptions)
describe('a secure request to an authorized origin', function() {
describe('a secure request to an authorized origin', function () {
test(shouldAuthenticateRequest, () => {
given({ url: 'https://myci.test' }).expect(true)
})
})
describe('an insecure request', function() {
describe('an insecure request', function () {
test(shouldAuthenticateRequest, () => {
given({
url: 'https://myci.test',
@@ -264,7 +264,7 @@ describe('AuthHelper', function() {
}).expect(true)
})
})
describe('a request to an unauthorized origin', function() {
describe('a request to an unauthorized origin', function () {
test(shouldAuthenticateRequest, () => {
forCases([
given({ url: 'http://myci.test' }),
@@ -275,7 +275,7 @@ describe('AuthHelper', function() {
})
})
context('when the service is partly configured', function() {
context('when the service is partly configured', function () {
const authHelper = new AuthHelper(authConfig, {
public: {
services: {
@@ -289,7 +289,7 @@ describe('AuthHelper', function() {
})
const shouldAuthenticateRequest = requestOptions =>
authHelper.shouldAuthenticateRequest(requestOptions)
describe('a secure request to an authorized origin', function() {
describe('a secure request to an authorized origin', function () {
test(shouldAuthenticateRequest, () => {
given({ url: 'https://myci.test' }).expect(false)
})
@@ -297,7 +297,7 @@ describe('AuthHelper', function() {
})
})
describe('withBasicAuth', function() {
describe('withBasicAuth', function () {
const authHelper = new AuthHelper(
{
userKey: 'myci_user',
@@ -318,7 +318,7 @@ describe('AuthHelper', function() {
const withBasicAuth = requestOptions =>
authHelper.withBasicAuth(requestOptions)
describe('authenticates a secure request to an authorized origin', function() {
describe('authenticates a secure request to an authorized origin', function () {
test(withBasicAuth, () => {
given({
url: 'https://myci.test/api',
@@ -343,7 +343,7 @@ describe('AuthHelper', function() {
})
})
describe('does not authenticate a request to an unauthorized origin', function() {
describe('does not authenticate a request to an unauthorized origin', function () {
test(withBasicAuth, () => {
given({
url: 'https://other.test/api',
@@ -364,7 +364,7 @@ describe('AuthHelper', function() {
})
})
describe('throws on an insecure SSL request', function() {
describe('throws on an insecure SSL request', function () {
expect(() =>
withBasicAuth({
url: 'https://myci.test/api',

View File

@@ -36,10 +36,10 @@ class DummyGraphqlService extends BaseGraphqlService {
}
}
describe('BaseGraphqlService', function() {
describe('Making requests', function() {
describe('BaseGraphqlService', function () {
describe('Making requests', function () {
let sendAndCacheRequest
beforeEach(function() {
beforeEach(function () {
sendAndCacheRequest = sinon.stub().returns(
Promise.resolve({
buffer: '{"some": "json"}',
@@ -48,7 +48,7 @@ describe('BaseGraphqlService', function() {
)
})
it('invokes _sendAndCacheRequest', async function() {
it('invokes _sendAndCacheRequest', async function () {
await DummyGraphqlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
@@ -64,7 +64,7 @@ describe('BaseGraphqlService', function() {
)
})
it('forwards options to _sendAndCacheRequest', async function() {
it('forwards options to _sendAndCacheRequest', async function () {
class WithOptions extends DummyGraphqlService {
async handle() {
const { value } = await this._requestGraphql({
@@ -98,8 +98,8 @@ describe('BaseGraphqlService', function() {
})
})
describe('Making badges', function() {
it('handles valid json responses', async function() {
describe('Making badges', function () {
it('handles valid json responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: '{"requiredString": "some-string"}',
res: { statusCode: 200 },
@@ -114,7 +114,7 @@ describe('BaseGraphqlService', function() {
})
})
it('handles json responses which do not match the schema', async function() {
it('handles json responses which do not match the schema', async function () {
const sendAndCacheRequest = async () => ({
buffer: '{"unexpectedKey": "some-string"}',
res: { statusCode: 200 },
@@ -131,7 +131,7 @@ describe('BaseGraphqlService', function() {
})
})
it('handles unparseable json responses', async function() {
it('handles unparseable json responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: 'not json',
res: { statusCode: 200 },
@@ -149,8 +149,8 @@ describe('BaseGraphqlService', function() {
})
})
describe('Error handling', function() {
it('handles generic error', async function() {
describe('Error handling', function () {
it('handles generic error', async function () {
const sendAndCacheRequest = async () => ({
buffer: '{ "errors": [ { "message": "oh noes!!" } ] }',
res: { statusCode: 200 },
@@ -167,7 +167,7 @@ describe('BaseGraphqlService', function() {
})
})
it('handles custom error', async function() {
it('handles custom error', async function () {
class WithErrorHandler extends DummyGraphqlService {
async handle() {
const { requiredString } = await this._requestGraphql({
@@ -178,7 +178,7 @@ describe('BaseGraphqlService', function() {
requiredString
}
`,
transformErrors: function(errors) {
transformErrors: function (errors) {
if (errors[0].message === 'oh noes!!') {
return new InvalidResponse({
prettyMessage: 'a terrible thing has happened',

View File

@@ -29,10 +29,10 @@ class DummyJsonService extends BaseJsonService {
}
}
describe('BaseJsonService', function() {
describe('Making requests', function() {
describe('BaseJsonService', function () {
describe('Making requests', function () {
let sendAndCacheRequest
beforeEach(function() {
beforeEach(function () {
sendAndCacheRequest = sinon.stub().returns(
Promise.resolve({
buffer: '{"some": "json"}',
@@ -41,7 +41,7 @@ describe('BaseJsonService', function() {
)
})
it('invokes _sendAndCacheRequest', async function() {
it('invokes _sendAndCacheRequest', async function () {
await DummyJsonService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
@@ -55,7 +55,7 @@ describe('BaseJsonService', function() {
)
})
it('forwards options to _sendAndCacheRequest', async function() {
it('forwards options to _sendAndCacheRequest', async function () {
class WithOptions extends DummyJsonService {
async handle() {
const { value } = await this._requestJson({
@@ -83,8 +83,8 @@ describe('BaseJsonService', function() {
})
})
describe('Making badges', function() {
it('handles valid json responses', async function() {
describe('Making badges', function () {
it('handles valid json responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: '{"requiredString": "some-string"}',
res: { statusCode: 200 },
@@ -99,7 +99,7 @@ describe('BaseJsonService', function() {
})
})
it('handles json responses which do not match the schema', async function() {
it('handles json responses which do not match the schema', async function () {
const sendAndCacheRequest = async () => ({
buffer: '{"unexpectedKey": "some-string"}',
res: { statusCode: 200 },
@@ -116,7 +116,7 @@ describe('BaseJsonService', function() {
})
})
it('handles unparseable json responses', async function() {
it('handles unparseable json responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: 'not json',
res: { statusCode: 200 },

View File

@@ -1,6 +1,6 @@
'use strict'
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const BaseService = require('./base')
const { MetricHelper } = require('./metric-helper')
const { setCacheHeaders } = require('./cache-headers')

View File

@@ -1,6 +1,6 @@
'use strict'
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const BaseService = require('./base')
const {
serverHasBeenUpSinceResourceCached,

View File

@@ -3,7 +3,7 @@
const { expect } = require('chai')
const sinon = require('sinon')
const Joi = require('@hapi/joi')
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const BaseSvgScrapingService = require('./base-svg-scraping')
function makeExampleSvg({ label, message }) {
@@ -33,7 +33,7 @@ class DummySvgScrapingService extends BaseSvgScrapingService {
}
}
describe('BaseSvgScrapingService', function() {
describe('BaseSvgScrapingService', function () {
const exampleLabel = 'this is the label'
const exampleMessage = 'this is the result!'
const exampleSvg = makeExampleSvg({
@@ -41,17 +41,17 @@ describe('BaseSvgScrapingService', function() {
message: exampleMessage,
})
describe('valueFromSvgBadge', function() {
it('should find the correct value', function() {
describe('valueFromSvgBadge', function () {
it('should find the correct value', function () {
expect(BaseSvgScrapingService.valueFromSvgBadge(exampleSvg)).to.equal(
exampleMessage
)
})
})
describe('Making requests', function() {
describe('Making requests', function () {
let sendAndCacheRequest
beforeEach(function() {
beforeEach(function () {
sendAndCacheRequest = sinon.stub().returns(
Promise.resolve({
buffer: exampleSvg,
@@ -60,7 +60,7 @@ describe('BaseSvgScrapingService', function() {
)
})
it('invokes _sendAndCacheRequest with the expected header', async function() {
it('invokes _sendAndCacheRequest with the expected header', async function () {
await DummySvgScrapingService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
@@ -74,7 +74,7 @@ describe('BaseSvgScrapingService', function() {
)
})
it('forwards options to _sendAndCacheRequest', async function() {
it('forwards options to _sendAndCacheRequest', async function () {
class WithCustomOptions extends DummySvgScrapingService {
async handle() {
const { message } = await this._requestSvg({
@@ -105,8 +105,8 @@ describe('BaseSvgScrapingService', function() {
})
})
describe('Making badges', function() {
it('handles valid svg responses', async function() {
describe('Making badges', function () {
it('handles valid svg responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: exampleSvg,
res: { statusCode: 200 },
@@ -121,7 +121,7 @@ describe('BaseSvgScrapingService', function() {
})
})
it('allows overriding the valueMatcher', async function() {
it('allows overriding the valueMatcher', async function () {
class WithValueMatcher extends BaseSvgScrapingService {
static get route() {
return {}
@@ -149,7 +149,7 @@ describe('BaseSvgScrapingService', function() {
})
})
it('handles unparseable svg responses', async function() {
it('handles unparseable svg responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: 'not svg yo',
res: { statusCode: 200 },

View File

@@ -29,10 +29,10 @@ class DummyXmlService extends BaseXmlService {
}
}
describe('BaseXmlService', function() {
describe('Making requests', function() {
describe('BaseXmlService', function () {
describe('Making requests', function () {
let sendAndCacheRequest
beforeEach(function() {
beforeEach(function () {
sendAndCacheRequest = sinon.stub().returns(
Promise.resolve({
buffer: '<requiredString>some-string</requiredString>',
@@ -41,7 +41,7 @@ describe('BaseXmlService', function() {
)
})
it('invokes _sendAndCacheRequest', async function() {
it('invokes _sendAndCacheRequest', async function () {
await DummyXmlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
@@ -55,7 +55,7 @@ describe('BaseXmlService', function() {
)
})
it('forwards options to _sendAndCacheRequest', async function() {
it('forwards options to _sendAndCacheRequest', async function () {
class WithCustomOptions extends BaseXmlService {
static get route() {
return {}
@@ -87,8 +87,8 @@ describe('BaseXmlService', function() {
})
})
describe('Making badges', function() {
it('handles valid xml responses', async function() {
describe('Making badges', function () {
it('handles valid xml responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: '<requiredString>some-string</requiredString>',
res: { statusCode: 200 },
@@ -103,7 +103,7 @@ describe('BaseXmlService', function() {
})
})
it('parses XML response with custom parser options', async function() {
it('parses XML response with custom parser options', async function () {
const customParserOption = { trimValues: false }
class DummyXmlServiceWithParserOption extends DummyXmlService {
async handle() {
@@ -130,7 +130,7 @@ describe('BaseXmlService', function() {
})
})
it('handles xml responses which do not match the schema', async function() {
it('handles xml responses which do not match the schema', async function () {
const sendAndCacheRequest = async () => ({
buffer: '<unexpectedAttribute>some-string</unexpectedAttribute>',
res: { statusCode: 200 },
@@ -147,7 +147,7 @@ describe('BaseXmlService', function() {
})
})
it('handles unparseable xml responses', async function() {
it('handles unparseable xml responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: 'not xml',
res: { statusCode: 200 },

View File

@@ -45,10 +45,10 @@ foo: bar
foo: baz
`
describe('BaseYamlService', function() {
describe('Making requests', function() {
describe('BaseYamlService', function () {
describe('Making requests', function () {
let sendAndCacheRequest
beforeEach(function() {
beforeEach(function () {
sendAndCacheRequest = sinon.stub().returns(
Promise.resolve({
buffer: expectedYaml,
@@ -57,7 +57,7 @@ describe('BaseYamlService', function() {
)
})
it('invokes _sendAndCacheRequest', async function() {
it('invokes _sendAndCacheRequest', async function () {
await DummyYamlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
@@ -74,7 +74,7 @@ describe('BaseYamlService', function() {
)
})
it('forwards options to _sendAndCacheRequest', async function() {
it('forwards options to _sendAndCacheRequest', async function () {
class WithOptions extends DummyYamlService {
async handle() {
const { requiredString } = await this._requestYaml({
@@ -105,8 +105,8 @@ describe('BaseYamlService', function() {
})
})
describe('Making badges', function() {
it('handles valid yaml responses', async function() {
describe('Making badges', function () {
it('handles valid yaml responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: expectedYaml,
res: { statusCode: 200 },
@@ -121,7 +121,7 @@ describe('BaseYamlService', function() {
})
})
it('handles yaml responses which do not match the schema', async function() {
it('handles yaml responses which do not match the schema', async function () {
const sendAndCacheRequest = async () => ({
buffer: unexpectedYaml,
res: { statusCode: 200 },
@@ -138,7 +138,7 @@ describe('BaseYamlService', function() {
})
})
it('handles unparseable yaml responses', async function() {
it('handles unparseable yaml responses', async function () {
const sendAndCacheRequest = async () => ({
buffer: invalidYaml,
res: { statusCode: 200 },

View File

@@ -58,10 +58,7 @@ const serviceDataSchema = Joi.object({
// `render()` to always return a string.
message: Joi.alternatives(Joi.string().allow(''), Joi.number()).required(),
color: Joi.string(),
link: Joi.array()
.items(Joi.string().uri())
.single()
.max(2),
link: Joi.array().items(Joi.string().uri()).single().max(2),
// Generally services should not use these options, which are provided to
// support the Endpoint badge.
labelColor: Joi.string(),
@@ -70,9 +67,7 @@ const serviceDataSchema = Joi.object({
logoColor: optionalStringWhenNamedLogoPresent,
logoWidth: optionalNumberWhenAnyLogoPresent,
logoPosition: optionalNumberWhenAnyLogoPresent,
cacheSeconds: Joi.number()
.integer()
.min(0),
cacheSeconds: Joi.number().integer().min(0),
style: Joi.string(),
})
.oxor('namedLogo', 'logoSvg')

View File

@@ -73,7 +73,7 @@ class DummyServiceWithServiceResponseSizeMetricEnabled extends DummyService {
}
}
describe('BaseService', function() {
describe('BaseService', function () {
const defaultConfig = {
public: {
handleInternalErrors: false,
@@ -82,7 +82,7 @@ describe('BaseService', function() {
private: {},
}
it('Invokes the handler as expected', async function() {
it('Invokes the handler as expected', async function () {
expect(
await DummyService.invoke(
{},
@@ -95,7 +95,7 @@ describe('BaseService', function() {
})
})
it('Validates query params', async function() {
it('Validates query params', async function () {
expect(
await DummyService.invoke(
{},
@@ -110,14 +110,14 @@ describe('BaseService', function() {
})
})
describe('Required overrides', function() {
it('Should throw if render() is not overridden', function() {
describe('Required overrides', function () {
it('Should throw if render() is not overridden', function () {
expect(() => BaseService.render()).to.throw(
/^render\(\) function not implemented for BaseService$/
)
})
it('Should throw if route is not overridden', function() {
it('Should throw if route is not overridden', function () {
return expect(BaseService.invoke({}, {}, {})).to.be.rejectedWith(
/^Route not defined for BaseService$/
)
@@ -128,31 +128,31 @@ describe('BaseService', function() {
return {}
}
}
it('Should throw if handle() is not overridden', function() {
it('Should throw if handle() is not overridden', function () {
return expect(WithRoute.invoke({}, {}, {})).to.be.rejectedWith(
/^Handler not implemented for WithRoute$/
)
})
it('Should throw if category is not overridden', function() {
it('Should throw if category is not overridden', function () {
expect(() => BaseService.category).to.throw(
/^Category not set for BaseService$/
)
})
})
describe('Logging', function() {
describe('Logging', function () {
let sandbox
beforeEach(function() {
beforeEach(function () {
sandbox = sinon.createSandbox()
})
afterEach(function() {
afterEach(function () {
sandbox.restore()
})
beforeEach(function() {
beforeEach(function () {
sandbox.stub(trace, 'logTrace')
})
it('Invokes the logger as expected', async function() {
it('Invokes the logger as expected', async function () {
await DummyService.invoke(
{},
defaultConfig,
@@ -180,8 +180,8 @@ describe('BaseService', function() {
})
})
describe('Service data validation', function() {
it('Allows a link array', async function() {
describe('Service data validation', function () {
it('Allows a link array', async function () {
const message = 'hello'
const link = ['https://example.com/', 'https://other.example.com/']
class LinkService extends DummyService {
@@ -202,7 +202,7 @@ describe('BaseService', function() {
})
})
context('On invalid data', function() {
context('On invalid data', function () {
class ThrowingService extends DummyService {
async handle() {
return {
@@ -211,7 +211,7 @@ describe('BaseService', function() {
}
}
it('Throws a validation error on invalid data', async function() {
it('Throws a validation error on invalid data', async function () {
try {
await ThrowingService.invoke(
{},
@@ -229,7 +229,7 @@ describe('BaseService', function() {
// Ensure debuggabillity.
// https://github.com/badges/shields/issues/3784
it('Includes the service class in the stack trace', async function() {
it('Includes the service class in the stack trace', async function () {
try {
await ThrowingService.invoke(
{},
@@ -244,8 +244,8 @@ describe('BaseService', function() {
})
})
describe('Error handling', function() {
it('Handles internal errors', async function() {
describe('Error handling', function () {
it('Handles internal errors', async function () {
class ThrowingService extends DummyService {
async handle() {
throw Error("I've made a huge mistake")
@@ -265,8 +265,8 @@ describe('BaseService', function() {
})
})
describe('Handles known subtypes of ShieldsInternalError', function() {
it('handles NotFound errors', async function() {
describe('Handles known subtypes of ShieldsInternalError', function () {
it('handles NotFound errors', async function () {
class ThrowingService extends DummyService {
async handle() {
throw new NotFound()
@@ -281,7 +281,7 @@ describe('BaseService', function() {
})
})
it('handles Inaccessible errors', async function() {
it('handles Inaccessible errors', async function () {
class ThrowingService extends DummyService {
async handle() {
throw new Inaccessible()
@@ -296,7 +296,7 @@ describe('BaseService', function() {
})
})
it('handles InvalidResponse errors', async function() {
it('handles InvalidResponse errors', async function () {
class ThrowingService extends DummyService {
async handle() {
throw new InvalidResponse()
@@ -311,7 +311,7 @@ describe('BaseService', function() {
})
})
it('handles Deprecated', async function() {
it('handles Deprecated', async function () {
class ThrowingService extends DummyService {
async handle() {
throw new Deprecated()
@@ -326,7 +326,7 @@ describe('BaseService', function() {
})
})
it('handles InvalidParameter errors', async function() {
it('handles InvalidParameter errors', async function () {
class ThrowingService extends DummyService {
async handle() {
throw new InvalidParameter()
@@ -343,7 +343,7 @@ describe('BaseService', function() {
})
})
describe('ScoutCamp integration', function() {
describe('ScoutCamp integration', function () {
// TODO Strangly, without the useless escape the regexes do not match in Node 12.
// eslint-disable-next-line no-useless-escape
const expectedRouteRegex = /^\/foo\/([^\/]+?)(|\.svg|\.json)$/
@@ -351,7 +351,7 @@ describe('BaseService', function() {
let mockCamp
let mockHandleRequest
beforeEach(function() {
beforeEach(function () {
mockCamp = {
route: sinon.spy(),
}
@@ -362,12 +362,12 @@ describe('BaseService', function() {
)
})
it('registers the service', function() {
it('registers the service', function () {
expect(mockCamp.route).to.have.been.calledOnce
expect(mockCamp.route).to.have.been.calledWith(expectedRouteRegex)
})
it('handles the request', async function() {
it('handles the request', async function () {
expect(mockHandleRequest).to.have.been.calledOnce
const {
@@ -392,7 +392,7 @@ describe('BaseService', function() {
expect(mockSendBadge).to.have.been.calledWith(expectedFormat, {
text: ['cat', 'Hello namedParamA: bar with queryParamA: ?'],
color: 'lightgrey',
template: undefined,
template: 'flat',
namedLogo: undefined,
logo: undefined,
logoWidth: undefined,
@@ -404,8 +404,8 @@ describe('BaseService', function() {
})
})
describe('getDefinition', function() {
it('returns the expected result', function() {
describe('getDefinition', function () {
it('returns the expected result', function () {
const {
category,
name,
@@ -432,12 +432,12 @@ describe('BaseService', function() {
})
})
describe('validate', function() {
describe('validate', function () {
const dummySchema = Joi.object({
requiredString: Joi.string().required(),
}).required()
it('throws error for invalid responses', function() {
it('throws error for invalid responses', function () {
expect(() =>
DummyService._validate(
{ requiredString: ['this', "shouldn't", 'work'] },
@@ -449,19 +449,19 @@ describe('BaseService', function() {
})
})
describe('request', function() {
describe('request', function () {
let sandbox
beforeEach(function() {
beforeEach(function () {
sandbox = sinon.createSandbox()
})
afterEach(function() {
afterEach(function () {
sandbox.restore()
})
beforeEach(function() {
beforeEach(function () {
sandbox.stub(trace, 'logTrace')
})
it('logs appropriate information', async function() {
it('logs appropriate information', async function () {
const sendAndCacheRequest = async () => ({
buffer: '',
res: { statusCode: 200 },
@@ -491,7 +491,7 @@ describe('BaseService', function() {
)
})
it('handles errors', async function() {
it('handles errors', async function () {
const sendAndCacheRequest = async () => ({
buffer: '',
res: { statusCode: 404 },
@@ -512,14 +512,14 @@ describe('BaseService', function() {
})
})
describe('Metrics', function() {
describe('Metrics', function () {
let register
beforeEach(function() {
beforeEach(function () {
register = new prometheus.Registry()
})
const url = 'some-url'
it('service response size metric is optional', async function() {
it('service response size metric is optional', async function () {
const metricHelper = MetricHelper.create({
metricInstance: new PrometheusMetrics({ register }),
ServiceClass: DummyServiceWithServiceResponseSizeMetricEnabled,
@@ -544,7 +544,7 @@ describe('BaseService', function() {
)
})
it('service response size metric is disabled by default', async function() {
it('service response size metric is disabled by default', async function () {
const metricHelper = MetricHelper.create({
metricInstance: new PrometheusMetrics({ register }),
ServiceClass: DummyService,
@@ -565,7 +565,7 @@ describe('BaseService', function() {
).to.not.contain('service_response_bytes_bucket')
})
})
describe('auth', function() {
describe('auth', function () {
class AuthService extends DummyService {
static get auth() {
return {
@@ -582,7 +582,7 @@ describe('BaseService', function() {
}
}
it('when auth is configured properly, invoke() sets authHelper', async function() {
it('when auth is configured properly, invoke() sets authHelper', async function () {
expect(
await AuthService.invoke(
{},
@@ -598,7 +598,7 @@ describe('BaseService', function() {
).to.deep.equal({ message: 'The CI password is abc123' })
})
it('when auth is not configured properly, invoke() returns inacessible', async function() {
it('when auth is not configured properly, invoke() returns inacessible', async function () {
expect(
await AuthService.invoke(
{},

View File

@@ -7,9 +7,7 @@ const coalesce = require('./coalesce')
const serverStartTimeGMTString = new Date().toGMTString()
const serverStartTimestamp = Date.now()
const isOptionalNonNegativeInteger = Joi.number()
.integer()
.min(0)
const isOptionalNonNegativeInteger = Joi.number().integer().min(0)
const queryParamSchema = Joi.object({
cacheSeconds: isOptionalNonNegativeInteger,
@@ -69,7 +67,7 @@ function setHeadersForCacheLength(res, cacheLengthSeconds) {
cacheControl = 'no-cache, no-store, must-revalidate'
expires = nowGMTString
} else {
cacheControl = `max-age=${cacheLengthSeconds}`
cacheControl = `max-age=${cacheLengthSeconds} s-maxage=${cacheLengthSeconds}`
expires = new Date(now.getTime() + cacheLengthSeconds * 1000).toGMTString()
}
@@ -94,7 +92,7 @@ function setCacheHeaders({
setHeadersForCacheLength(res, cacheLengthSeconds)
}
const staticCacheControlHeader = `max-age=${24 * 3600}` // 1 day.
const staticCacheControlHeader = `max-age=${24 * 3600} s-maxage=${24 * 3600}` // 1 day.
function setCacheHeadersForStaticResource(res) {
res.setHeader('Cache-Control', staticCacheControlHeader)
res.setHeader('Last-Modified', serverStartTimeGMTString)

View File

@@ -15,13 +15,13 @@ const {
chai.use(require('chai-datetime'))
describe('Cache header functions', function() {
describe('Cache header functions', function () {
let res
beforeEach(function() {
beforeEach(function () {
res = httpMocks.createResponse()
})
describe('coalesceCacheLength', function() {
describe('coalesceCacheLength', function () {
const cacheHeaderConfig = { defaultCacheLengthSeconds: 777 }
test(coalesceCacheLength, () => {
given({ cacheHeaderConfig, queryParams: {} }).expect(777)
@@ -101,18 +101,18 @@ describe('Cache header functions', function() {
})
})
describe('setHeadersForCacheLength', function() {
describe('setHeadersForCacheLength', function () {
let sandbox
beforeEach(function() {
beforeEach(function () {
sandbox = sinon.createSandbox()
sandbox.useFakeTimers()
})
afterEach(function() {
afterEach(function () {
sandbox.restore()
sandbox = undefined
})
it('should set the correct Date header', function() {
it('should set the correct Date header', function () {
// Confidence check.
expect(res._headers.date).to.equal(undefined)
@@ -124,40 +124,42 @@ describe('Cache header functions', function() {
expect(res._headers.date).to.equal(now)
})
context('cacheLengthSeconds is zero', function() {
beforeEach(function() {
context('cacheLengthSeconds is zero', function () {
beforeEach(function () {
setHeadersForCacheLength(res, 0)
})
it('should set the expected Cache-Control header', function() {
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'no-cache, no-store, must-revalidate'
)
})
it('should set the expected Expires header', function() {
it('should set the expected Expires header', function () {
expect(res._headers.expires).to.equal(new Date().toGMTString())
})
})
context('cacheLengthSeconds is nonzero', function() {
beforeEach(function() {
context('cacheLengthSeconds is nonzero', function () {
beforeEach(function () {
setHeadersForCacheLength(res, 123)
})
it('should set the expected Cache-Control header', function() {
expect(res._headers['cache-control']).to.equal('max-age=123')
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
'max-age=123 s-maxage=123'
)
})
it('should set the expected Expires header', function() {
it('should set the expected Expires header', function () {
const expires = new Date(Date.now() + 123 * 1000).toGMTString()
expect(res._headers.expires).to.equal(expires)
})
})
})
describe('setCacheHeaders', function() {
it('sets the expected fields', function() {
describe('setCacheHeaders', function () {
it('sets the expected fields', function () {
const expectedFields = ['date', 'cache-control', 'expires']
expectedFields.forEach(field =>
expect(res._headers[field]).to.equal(undefined)
@@ -178,16 +180,18 @@ describe('Cache header functions', function() {
})
})
describe('setCacheHeadersForStaticResource', function() {
beforeEach(function() {
describe('setCacheHeadersForStaticResource', function () {
beforeEach(function () {
setCacheHeadersForStaticResource(res)
})
it('should set the expected Cache-Control header', function() {
expect(res._headers['cache-control']).to.equal(`max-age=${24 * 3600}`)
it('should set the expected Cache-Control header', function () {
expect(res._headers['cache-control']).to.equal(
`max-age=${24 * 3600} s-maxage=${24 * 3600}`
)
})
it('should set the expected Last-Modified header', function() {
it('should set the expected Last-Modified header', function () {
const lastModified = res._headers['last-modified']
expect(new Date(lastModified)).to.be.withinTime(
// Within the last 60 seconds.
@@ -197,17 +201,17 @@ describe('Cache header functions', function() {
})
})
describe('serverHasBeenUpSinceResourceCached', function() {
describe('serverHasBeenUpSinceResourceCached', function () {
// The stringified req's are hard to understand. I thought Sazerac
// provided a way to override the describe message, though I can't find it.
context('when there is no If-Modified-Since header', function() {
it('returns false', function() {
context('when there is no If-Modified-Since header', function () {
it('returns false', function () {
const req = httpMocks.createRequest()
expect(serverHasBeenUpSinceResourceCached(req)).to.equal(false)
})
})
context('when the If-Modified-Since header is invalid', function() {
it('returns false', function() {
context('when the If-Modified-Since header is invalid', function () {
it('returns false', function () {
const req = httpMocks.createRequest({
headers: { 'If-Modified-Since': 'this-is-not-a-date' },
})
@@ -216,8 +220,8 @@ describe('Cache header functions', function() {
})
context(
'when the If-Modified-Since header is before the process started',
function() {
it('returns false', function() {
function () {
it('returns false', function () {
const req = httpMocks.createRequest({
headers: { 'If-Modified-Since': '2018-02-01T05:00:00.000Z' },
})
@@ -227,8 +231,8 @@ describe('Cache header functions', function() {
)
context(
'when the If-Modified-Since header is after the process started',
function() {
it('returns true', function() {
function () {
it('returns true', function () {
const modifiedTimeStamp = new Date(Date.now() + 1800000)
const req = httpMocks.createRequest({
headers: { 'If-Modified-Since': modifiedTimeStamp.toISOString() },

View File

@@ -7,7 +7,7 @@ const defaultErrorMessages = {
}
module.exports = function checkErrorResponse(errorMessages = {}) {
return async function({ buffer, res }) {
return async function ({ buffer, res }) {
let error
errorMessages = { ...defaultErrorMessages, ...errorMessages }
if (res.statusCode === 404) {

View File

@@ -4,11 +4,11 @@ const { expect } = require('chai')
const { NotFound, InvalidResponse, Inaccessible } = require('./errors')
const checkErrorResponse = require('./check-error-response')
describe('async error handler', function() {
describe('async error handler', function () {
const buffer = Buffer.from('some stuff')
context('when status is 200', function() {
it('passes through the inputs', async function() {
context('when status is 200', function () {
it('passes through the inputs', async function () {
const res = { statusCode: 200 }
expect(await checkErrorResponse()({ res, buffer })).to.deep.equal({
res,
@@ -17,11 +17,11 @@ describe('async error handler', function() {
})
})
context('when status is 404', function() {
context('when status is 404', function () {
const buffer = Buffer.from('some stuff')
const res = { statusCode: 404 }
it('throws NotFound', async function() {
it('throws NotFound', async function () {
try {
await checkErrorResponse()({ res, buffer })
expect.fail('Expected to throw')
@@ -34,7 +34,7 @@ describe('async error handler', function() {
}
})
it('displays the custom not found message', async function() {
it('displays the custom not found message', async function () {
const notFoundMessage = 'no goblins found'
try {
await checkErrorResponse({ 404: notFoundMessage })({ res, buffer })
@@ -47,8 +47,8 @@ describe('async error handler', function() {
})
})
context('when status is 4xx', function() {
it('throws InvalidResponse', async function() {
context('when status is 4xx', function () {
it('throws InvalidResponse', async function () {
const res = { statusCode: 499 }
try {
await checkErrorResponse()({ res, buffer })
@@ -64,7 +64,7 @@ describe('async error handler', function() {
}
})
it('displays the custom error message', async function() {
it('displays the custom error message', async function () {
const res = { statusCode: 403 }
try {
await checkErrorResponse({ 403: 'access denied' })({ res })
@@ -79,8 +79,8 @@ describe('async error handler', function() {
})
})
context('when status is 5xx', function() {
it('throws Inaccessible', async function() {
context('when status is 5xx', function () {
it('throws Inaccessible', async function () {
const res = { statusCode: 503 }
try {
await checkErrorResponse()({ res, buffer })
@@ -96,7 +96,7 @@ describe('async error handler', function() {
}
})
it('displays the custom error message', async function() {
it('displays the custom error message', async function () {
const res = { statusCode: 500 }
try {
await checkErrorResponse({ 500: 'server overloaded' })({ res, buffer })

View File

@@ -104,7 +104,23 @@ module.exports = function coalesceBadge(
labelColor: defaultLabelColor,
} = defaultBadgeData
const style = coalesce(overrideStyle, serviceStyle)
let style = coalesce(overrideStyle, serviceStyle)
if (typeof style !== 'string') {
style = 'flat'
}
if (style.startsWith('popout')) {
style = style.replace('popout', 'flat')
}
const styleValues = [
'plastic',
'flat',
'flat-square',
'for-the-badge',
'social',
]
if (!styleValues.includes(style)) {
style = 'flat'
}
let namedLogo, namedLogoColor, logoWidth, logoPosition, logoSvgBase64
if (overrideLogo) {

View File

@@ -4,9 +4,9 @@ const { expect } = require('chai')
const { getShieldsIcon, getSimpleIcon } = require('../../lib/logos')
const coalesceBadge = require('./coalesce-badge')
describe('coalesceBadge', function() {
describe('Label', function() {
it('uses the default label', function() {
describe('coalesceBadge', function () {
describe('Label', function () {
it('uses the default label', function () {
expect(coalesceBadge({}, {}, { label: 'heyo' }).text).to.deep.equal([
'heyo',
'n/a',
@@ -14,34 +14,34 @@ describe('coalesceBadge', function() {
})
// This behavior isn't great and we might want to remove it.
it('uses the category as a default label', function() {
it('uses the category as a default label', function () {
expect(
coalesceBadge({}, {}, {}, { category: 'cat' }).text
).to.deep.equal(['cat', 'n/a'])
})
it('preserves an empty label', function() {
it('preserves an empty label', function () {
expect(
coalesceBadge({}, { label: '', message: '10k' }, {}).text
).to.deep.equal(['', '10k'])
})
it('overrides the label', function() {
it('overrides the label', function () {
expect(
coalesceBadge({ label: 'purr count' }, { label: 'purrs' }, {}).text
).to.deep.equal(['purr count', 'n/a'])
})
})
describe('Message', function() {
it('applies the service message', function() {
describe('Message', function () {
it('applies the service message', function () {
expect(coalesceBadge({}, { message: '10k' }, {}).text).to.deep.equal([
undefined,
'10k',
])
})
it('applies a numeric service message', function() {
it('applies a numeric service message', function () {
// While a number of badges use this, in the long run we may want
// `render()` to always return a string.
expect(coalesceBadge({}, { message: 10 }, {}).text).to.deep.equal([
@@ -51,12 +51,12 @@ describe('coalesceBadge', function() {
})
})
describe('Right color', function() {
it('uses the default color', function() {
describe('Right color', function () {
it('uses the default color', function () {
expect(coalesceBadge({}, {}, {}).color).to.equal('lightgrey')
})
it('overrides the color', function() {
it('overrides the color', function () {
expect(
coalesceBadge({ color: '10ADED' }, { color: 'red' }, {}).color
).to.equal('10ADED')
@@ -66,8 +66,8 @@ describe('coalesceBadge', function() {
).to.equal('B0ADED')
})
context('In case of an error', function() {
it('does not override the color', function() {
context('In case of an error', function () {
it('does not override the color', function () {
expect(
coalesceBadge(
{ color: '10ADED' },
@@ -86,23 +86,23 @@ describe('coalesceBadge', function() {
})
})
it('applies the service color', function() {
it('applies the service color', function () {
expect(coalesceBadge({}, { color: 'red' }, {}).color).to.equal('red')
})
})
describe('Left color', function() {
it('provides no default label color', function() {
describe('Left color', function () {
it('provides no default label color', function () {
expect(coalesceBadge({}, {}, {}).labelColor).to.be.undefined
})
it('applies the service label color', function() {
it('applies the service label color', function () {
expect(coalesceBadge({}, { labelColor: 'red' }, {}).labelColor).to.equal(
'red'
)
})
it('overrides the label color', function() {
it('overrides the label color', function () {
expect(
coalesceBadge({ labelColor: '42f483' }, { color: 'green' }, {})
.labelColor
@@ -113,7 +113,7 @@ describe('coalesceBadge', function() {
).to.equal('B2f483')
})
it('converts a query-string numeric color to a string', function() {
it('converts a query-string numeric color to a string', function () {
expect(
coalesceBadge(
// Scoutcamp converts numeric query params to numbers.
@@ -134,20 +134,20 @@ describe('coalesceBadge', function() {
})
})
describe('Named logos', function() {
it('when not a social badge, ignores the default named logo', function() {
describe('Named logos', function () {
it('when not a social badge, ignores the default named logo', function () {
expect(coalesceBadge({}, {}, { namedLogo: 'appveyor' }).logo).to.be
.undefined
})
it('when a social badge, uses the default named logo', function() {
it('when a social badge, uses the default named logo', function () {
// .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
expect(
coalesceBadge({ style: 'social' }, {}, { namedLogo: 'appveyor' }).logo
).to.equal(getSimpleIcon({ name: 'appveyor' })).and.not.be.empty
})
it('applies the named logo', function() {
it('applies the named logo', function () {
expect(coalesceBadge({}, { namedLogo: 'npm' }, {}).namedLogo).to.equal(
'npm'
)
@@ -156,20 +156,20 @@ describe('coalesceBadge', function() {
).and.not.to.be.empty
})
it('applies the named logo with color', function() {
it('applies the named logo with color', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoColor: 'blue' }, {}).logo
).to.equal(getShieldsIcon({ name: 'npm', color: 'blue' })).and.not.to.be
.empty
})
it('overrides the logo', function() {
it('overrides the logo', function () {
expect(
coalesceBadge({ logo: 'npm' }, { namedLogo: 'appveyor' }, {}).logo
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
})
it('overrides the logo with a color', function() {
it('overrides the logo with a color', function () {
expect(
coalesceBadge(
{ logo: 'npm', logoColor: 'blue' },
@@ -180,7 +180,7 @@ describe('coalesceBadge', function() {
.empty
})
it("when the logo is overridden, it ignores the service's logo color, position, and width", function() {
it("when the logo is overridden, it ignores the service's logo color, position, and width", function () {
expect(
coalesceBadge(
{ logo: 'npm' },
@@ -195,7 +195,7 @@ describe('coalesceBadge', function() {
).to.equal(getShieldsIcon({ name: 'npm' })).and.not.be.empty
})
it("overrides the service logo's color", function() {
it("overrides the service logo's color", function () {
expect(
coalesceBadge(
{ logoColor: 'blue' },
@@ -207,7 +207,7 @@ describe('coalesceBadge', function() {
})
// https://github.com/badges/shields/issues/2998
it('overrides logoSvg', function() {
it('overrides logoSvg', function () {
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
expect(coalesceBadge({ logo: 'npm' }, { logoSvg }, {}).logo).to.equal(
getShieldsIcon({ name: 'npm' })
@@ -215,15 +215,15 @@ describe('coalesceBadge', function() {
})
})
describe('Custom logos', function() {
it('overrides the logo with custom svg', function() {
describe('Custom logos', function () {
it('overrides the logo with custom svg', function () {
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
expect(
coalesceBadge({ logo: logoSvg }, { namedLogo: 'appveyor' }, {}).logo
).to.equal(logoSvg)
})
it('ignores the color when custom svg is provided', function() {
it('ignores the color when custom svg is provided', function () {
const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
expect(
coalesceBadge(
@@ -235,26 +235,26 @@ describe('coalesceBadge', function() {
})
})
describe('Logo width', function() {
it('overrides the logoWidth', function() {
describe('Logo width', function () {
it('overrides the logoWidth', function () {
expect(coalesceBadge({ logoWidth: 20 }, {}, {}).logoWidth).to.equal(20)
})
it('applies the logo width', function() {
it('applies the logo width', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoWidth: 275 }, {}).logoWidth
).to.equal(275)
})
})
describe('Logo position', function() {
it('overrides the logoPosition', function() {
describe('Logo position', function () {
it('overrides the logoPosition', function () {
expect(
coalesceBadge({ logoPosition: -10 }, {}, {}).logoPosition
).to.equal(-10)
})
it('applies the logo position', function() {
it('applies the logo position', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoPosition: -10 }, {})
.logoPosition
@@ -262,8 +262,8 @@ describe('coalesceBadge', function() {
})
})
describe('Links', function() {
it('overrides the links', function() {
describe('Links', function () {
it('overrides the links', function () {
expect(
coalesceBadge(
{ link: 'https://circleci.com/gh/badges/daily-tests' },
@@ -277,14 +277,27 @@ describe('coalesceBadge', function() {
})
})
describe('Style', function() {
it('overrides the template', function() {
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('pill')
describe('Style', function () {
it('falls back to flat with invalid style', function () {
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('flat')
expect(coalesceBadge({ style: 7 }, {}, {}).template).to.equal('flat')
expect(coalesceBadge({ style: undefined }, {}, {}).template).to.equal(
'flat'
)
})
it('replaces legacy popout styles', function () {
expect(coalesceBadge({ style: 'popout' }, {}, {}).template).to.equal(
'flat'
)
expect(
coalesceBadge({ style: 'popout-square' }, {}, {}).template
).to.equal('flat-square')
})
})
describe('Cache length', function() {
it('overrides the cache length', function() {
describe('Cache length', function () {
it('overrides the cache length', function () {
expect(
coalesceBadge({ style: 'pill' }, { cacheSeconds: 123 }, {})
.cacheLengthSeconds

View File

@@ -7,8 +7,8 @@ const coalesce = require('./coalesce')
// `undefined` instead of `null`, though h/t to
// https://github.com/royriojas/coalescy for these tests!
describe('coalesce', function() {
test(coalesce, function() {
describe('coalesce', function () {
test(coalesce, function () {
given().expect(undefined)
given(null, []).expect([])
given(null, [], {}).expect([])

View File

@@ -3,7 +3,7 @@
const { expect } = require('chai')
const deprecatedService = require('./deprecated-service')
describe('DeprecatedService', function() {
describe('DeprecatedService', function () {
const route = {
base: 'service/that/no/longer/exists',
format: '(?:.+)',
@@ -12,33 +12,33 @@ describe('DeprecatedService', function() {
const dateAdded = new Date()
const commonAttrs = { route, category, dateAdded }
it('returns true on isDeprecated', function() {
it('returns true on isDeprecated', function () {
const service = deprecatedService({ ...commonAttrs })
expect(service.isDeprecated).to.be.true
})
it('has the expected name', function() {
it('has the expected name', function () {
const service = deprecatedService({ ...commonAttrs })
expect(service.name).to.equal('DeprecatedServiceThatNoLongerExists')
})
it('sets specified route', function() {
it('sets specified route', function () {
const service = deprecatedService({ ...commonAttrs })
expect(service.route).to.deep.equal(route)
})
it('sets specified label', function() {
it('sets specified label', function () {
const label = 'coverity'
const service = deprecatedService({ ...commonAttrs, label })
expect(service.defaultBadgeData.label).to.equal(label)
})
it('sets specified category', function() {
it('sets specified category', function () {
const service = deprecatedService({ ...commonAttrs })
expect(service.category).to.equal(category)
})
it('sets specified examples', function() {
it('sets specified examples', function () {
const examples = [
{
title: 'Not sure we would have examples',
@@ -48,7 +48,7 @@ describe('DeprecatedService', function() {
expect(service.examples).to.deep.equal(examples)
})
it('uses default deprecation message when no message specified', async function() {
it('uses default deprecation message when no message specified', async function () {
const service = deprecatedService({ ...commonAttrs })
expect(await service.invoke()).to.deep.equal({
isError: true,
@@ -57,7 +57,7 @@ describe('DeprecatedService', function() {
})
})
it('uses custom deprecation message when specified', async function() {
it('uses custom deprecation message when specified', async function () {
const message = 'extended outage'
const service = deprecatedService({ ...commonAttrs, message })
expect(await service.invoke()).to.deep.equal({

View File

@@ -21,19 +21,12 @@ const schema = Joi.object({
staticPreview: Joi.object({
label: Joi.string(),
message: Joi.alternatives()
.try(
Joi.string()
.allow('')
.required(),
Joi.number()
)
.try(Joi.string().allow('').required(), Joi.number())
.required(),
color: Joi.string(),
style: Joi.string(),
}).required(),
keywords: Joi.array()
.items(Joi.string())
.default([]),
keywords: Joi.array().items(Joi.string()).default([]),
documentation: Joi.string(), // Valid HTML.
}).required()

View File

@@ -4,8 +4,8 @@ const { expect } = require('chai')
const { test, given } = require('sazerac')
const { validateExample, transformExample } = require('./examples')
describe('validateExample function', function() {
it('passes valid examples', function() {
describe('validateExample function', function () {
it('passes valid examples', function () {
const validExamples = [
{
title: 'Package manager versioning badge',
@@ -23,7 +23,7 @@ describe('validateExample function', function() {
})
})
it('rejects invalid examples', function() {
it('rejects invalid examples', function () {
const invalidExamples = [
{},
{ staticPreview: { message: '123' } },
@@ -74,7 +74,7 @@ describe('validateExample function', function() {
})
})
test(transformExample, function() {
test(transformExample, function () {
const ExampleService = {
name: 'ExampleService',
route: {

View File

@@ -7,8 +7,8 @@ const { mergeQueries } = require('./graphql')
require('../register-chai-plugins.spec')
describe('mergeQueries function', function() {
it('merges valid gql queries', function() {
describe('mergeQueries function', function () {
it('merges valid gql queries', function () {
expect(
print(
mergeQueries(
@@ -86,7 +86,7 @@ describe('mergeQueries function', function() {
).to.equalIgnoreSpaces('{ foo bar }')
})
it('throws an error when passed invalid params', function() {
it('throws an error when passed invalid params', function () {
expect(() => mergeQueries('', '')).to.throw(Error)
expect(() => mergeQueries(undefined, 17, true)).to.throw(Error)
expect(() => mergeQueries(gql``, gql`foo`)).to.throw(Error)

View File

@@ -2,7 +2,7 @@
const request = require('request')
const queryString = require('query-string')
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const { setCacheHeaders } = require('./cache-headers')
const {
Inaccessible,

View File

@@ -3,7 +3,7 @@
const { expect } = require('chai')
const nock = require('nock')
const portfinder = require('portfinder')
const Camp = require('camp')
const Camp = require('@shields_io/camp')
const got = require('../got-test-client')
const coalesceBadge = require('./coalesce-badge')
const {
@@ -70,19 +70,19 @@ function fakeHandlerWithNetworkIo(queryParams, match, sendBadge, request) {
})
}
describe('The request handler', function() {
describe('The request handler', function () {
let port, baseUrl
beforeEach(async function() {
beforeEach(async function () {
port = await portfinder.getPortPromise()
baseUrl = `http://127.0.0.1:${port}`
})
let camp
beforeEach(function(done) {
beforeEach(function (done) {
camp = Camp.start({ port, hostname: '::' })
camp.on('listening', () => done())
})
afterEach(function(done) {
afterEach(function (done) {
clearRequestCache()
if (camp) {
camp.close(() => done())
@@ -92,17 +92,17 @@ describe('The request handler', function() {
const standardCacheHeaders = { defaultCacheLengthSeconds: 120 }
describe('the options object calling style', function() {
beforeEach(function() {
describe('the options object calling style', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, { handler: fakeHandler })
)
})
it('should return the expected response', async function() {
it('should return the expected response', async function () {
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
json: true,
responseType: 'json',
})
expect(statusCode).to.equal(200)
expect(body).to.deep.equal({
@@ -116,17 +116,17 @@ describe('The request handler', function() {
})
})
describe('the function shorthand calling style', function() {
beforeEach(function() {
describe('the function shorthand calling style', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, fakeHandler)
)
})
it('should return the expected response', async function() {
it('should return the expected response', async function () {
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
json: true,
responseType: 'json',
})
expect(statusCode).to.equal(200)
expect(body).to.deep.equal({
@@ -140,8 +140,8 @@ describe('The request handler', function() {
})
})
describe('the response size limit', function() {
beforeEach(function() {
describe('the response size limit', function () {
beforeEach(function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, {
@@ -151,13 +151,13 @@ describe('The request handler', function() {
)
})
it('should not throw an error if the response <= fetchLimitBytes', async function() {
it('should not throw an error if the response <= fetchLimitBytes', async function () {
nock('https://www.google.com')
.get('/foo/bar')
.once()
.reply(200, 'x'.repeat(100))
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
json: true,
responseType: 'json',
})
expect(statusCode).to.equal(200)
expect(body).to.deep.equal({
@@ -170,13 +170,13 @@ describe('The request handler', function() {
})
})
it('should throw an error if the response is > fetchLimitBytes', async function() {
it('should throw an error if the response is > fetchLimitBytes', async function () {
nock('https://www.google.com')
.get('/foo/bar')
.once()
.reply(200, 'x'.repeat(101))
const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
json: true,
responseType: 'json',
})
expect(statusCode).to.equal(200)
expect(body).to.deep.equal({
@@ -189,15 +189,15 @@ describe('The request handler', function() {
})
})
afterEach(function() {
afterEach(function () {
nock.cleanAll()
})
})
describe('caching', function() {
describe('standard query parameters', function() {
describe('caching', function () {
describe('standard query parameters', function () {
let handlerCallCount
beforeEach(function() {
beforeEach(function () {
handlerCallCount = 0
})
@@ -214,12 +214,12 @@ describe('The request handler', function() {
)
}
context('With standard cache settings', function() {
beforeEach(function() {
context('With standard cache settings', function () {
beforeEach(function () {
register({ cacheHeaderConfig: standardCacheHeaders })
})
it('should cache identical requests', async function() {
it('should cache identical requests', async function () {
await performTwoRequests(
baseUrl,
'/testing/123.svg',
@@ -228,7 +228,7 @@ describe('The request handler', function() {
expect(handlerCallCount).to.equal(1)
})
it('should differentiate known query parameters', async function() {
it('should differentiate known query parameters', async function () {
await performTwoRequests(
baseUrl,
'/testing/123.svg?label=foo',
@@ -237,7 +237,7 @@ describe('The request handler', function() {
expect(handlerCallCount).to.equal(2)
})
it('should ignore unknown query parameters', async function() {
it('should ignore unknown query parameters', async function () {
await performTwoRequests(
baseUrl,
'/testing/123.svg?foo=1',
@@ -247,17 +247,17 @@ describe('The request handler', function() {
})
})
it('should set the expires header to current time + defaultCacheLengthSeconds', async function() {
it('should set the expires header to current time + defaultCacheLengthSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
const { headers } = await got(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(headers.date) + 900000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=900')
expect(headers['cache-control']).to.equal('max-age=900 s-maxage=900')
})
it('should set the expected cache headers on cached responses', async function() {
it('should set the expected cache headers on cached responses', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
// Make first request.
@@ -268,10 +268,10 @@ describe('The request handler', function() {
+new Date(headers.date) + 900000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=900')
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 let live service data override the default cache headers with longer value', async function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
@@ -289,10 +289,10 @@ describe('The request handler', function() {
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
expect(headers['cache-control']).to.equal('max-age=400')
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 not let live service data override the default cache headers with shorter value', async function () {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
@@ -310,10 +310,10 @@ describe('The request handler', function() {
)
const { headers } = await got(`${baseUrl}/testing/123.json`)
expect(headers['cache-control']).to.equal('max-age=300')
expect(headers['cache-control']).to.equal('max-age=300 s-maxage=300')
})
it('should set the expires header to current time + cacheSeconds', async function() {
it('should set the expires header to current time + cacheSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const { headers } = await got(
`${baseUrl}/testing/123.json?cacheSeconds=3600`
@@ -322,10 +322,10 @@ describe('The request handler', function() {
+new Date(headers.date) + 3600000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=3600')
expect(headers['cache-control']).to.equal('max-age=3600 s-maxage=3600')
})
it('should ignore cacheSeconds when shorter than defaultCacheLengthSeconds', async function() {
it('should ignore cacheSeconds when shorter than defaultCacheLengthSeconds', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 600 } })
const { headers } = await got(
`${baseUrl}/testing/123.json?cacheSeconds=300`
@@ -334,10 +334,10 @@ describe('The request handler', function() {
+new Date(headers.date) + 600000
).toGMTString()
expect(headers.expires).to.equal(expectedExpiry)
expect(headers['cache-control']).to.equal('max-age=600')
expect(headers['cache-control']).to.equal('max-age=600 s-maxage=600')
})
it('should set Cache-Control: no-cache, no-store, must-revalidate if cache seconds is 0', async function() {
it('should set Cache-Control: no-cache, no-store, must-revalidate if cache seconds is 0', async function () {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const { headers } = await got(`${baseUrl}/testing/123.json`)
expect(headers.expires).to.equal(headers.date)
@@ -346,25 +346,25 @@ describe('The request handler', function() {
)
})
describe('the cache key', function() {
beforeEach(function() {
describe('the cache key', function () {
beforeEach(function () {
register({ cacheHeaderConfig: standardCacheHeaders })
})
const expectedCacheKey = '/testing/123.json?color=123&label=foo'
it('should match expected and use canonical order - 1', async function() {
it('should match expected and use canonical order - 1', async function () {
await got(`${baseUrl}/testing/123.json?color=123&label=foo`)
expect(_requestCache.cache).to.have.keys(expectedCacheKey)
})
it('should match expected and use canonical order - 2', async function() {
it('should match expected and use canonical order - 2', async function () {
await got(`${baseUrl}/testing/123.json?label=foo&color=123`)
expect(_requestCache.cache).to.have.keys(expectedCacheKey)
})
})
})
describe('custom query parameters', function() {
describe('custom query parameters', function () {
let handlerCallCount
beforeEach(function() {
beforeEach(function () {
handlerCallCount = 0
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
@@ -378,7 +378,7 @@ describe('The request handler', function() {
)
})
it('should differentiate them', async function() {
it('should differentiate them', async function () {
await performTwoRequests(
baseUrl,
'/testing/123.svg?foo=1',

View File

@@ -3,8 +3,8 @@
const { expect } = require('chai')
const { loadServiceClasses, InvalidService } = require('./loader')
describe('loadServiceClasses function', function() {
it('throws if module exports empty', function() {
describe('loadServiceClasses function', function () {
it('throws if module exports empty', function () {
expect(() =>
loadServiceClasses(['./loader-test-fixtures/empty-undefined.fixture.js'])
).to.throw(InvalidService)
@@ -26,7 +26,7 @@ describe('loadServiceClasses function', function() {
).to.throw(InvalidService)
})
it('throws if module exports invalid', function() {
it('throws if module exports invalid', function () {
expect(() =>
loadServiceClasses(['./loader-test-fixtures/invalid-no-base.fixture.js'])
).to.throw(InvalidService)
@@ -47,7 +47,7 @@ describe('loadServiceClasses function', function() {
).to.throw(InvalidService)
})
it('registers services if module exports valid service classes', function() {
it('registers services if module exports valid service classes', function () {
expect(
loadServiceClasses([
'./loader-test-fixtures/valid-array.fixture.js',

View File

@@ -126,7 +126,7 @@ Cache.prototype = {
}
},
clear: function() {
clear: function () {
this.cache.clear()
this.newest = null
this.oldest = null

View File

@@ -25,14 +25,14 @@ function expectCacheSlots(cache, keys) {
}
}
describe('The LRU cache', function() {
it('should support a zero capacity', function() {
describe('The LRU cache', function () {
it('should support a zero capacity', function () {
const cache = new LRU(0)
cache.set('key', 'value')
expect(cache.cache.size).to.equal(0)
})
it('should support a one capacity', function() {
it('should support a one capacity', function () {
const cache = new LRU(1)
cache.set('key1', 'value1')
expectCacheSlots(cache, ['key1'])
@@ -42,7 +42,7 @@ describe('The LRU cache', function() {
expect(cache.get('key2')).to.equal('value2')
})
it('should remove the oldest element when reaching capacity', function() {
it('should remove the oldest element when reaching capacity', function () {
const cache = new LRU(2)
cache.set('key1', 'value1')
@@ -57,7 +57,7 @@ describe('The LRU cache', function() {
expect(cache.get('key3')).to.equal('value3')
})
it('should make sure that resetting a key in cache makes it newest', function() {
it('should make sure that resetting a key in cache makes it newest', function () {
const cache = new LRU(2)
cache.set('key', 'value')
@@ -70,9 +70,9 @@ describe('The LRU cache', function() {
expectCacheSlots(cache, ['key2', 'key'])
})
describe('getting a key in the cache', function() {
context('when the requested key is oldest', function() {
it('should leave the keys in the expected order', function() {
describe('getting a key in the cache', function () {
context('when the requested key is oldest', function () {
it('should leave the keys in the expected order', function () {
const cache = new LRU(2)
cache.set('key1', 'value1')
cache.set('key2', 'value2')
@@ -85,8 +85,8 @@ describe('The LRU cache', function() {
})
})
context('when the requested key is newest', function() {
it('should leave the keys in the expected order', function() {
context('when the requested key is newest', function () {
it('should leave the keys in the expected order', function () {
const cache = new LRU(2)
cache.set('key1', 'value1')
cache.set('key2', 'value2')
@@ -97,8 +97,8 @@ describe('The LRU cache', function() {
})
})
context('when the requested key is in the middle', function() {
it('should leave the keys in the expected order', function() {
context('when the requested key is in the middle', function () {
it('should leave the keys in the expected order', function () {
const cache = new LRU(3)
cache.set('key1', 'value1')
cache.set('key2', 'value2')
@@ -113,7 +113,7 @@ describe('The LRU cache', function() {
})
})
it('should clear', function() {
it('should clear', function () {
// Set up.
const cache = new LRU(2)
cache.set('key1', 'value1')

View File

@@ -1,12 +1,12 @@
'use strict'
const Camp = require('camp')
const Camp = require('@shields_io/camp')
const portfinder = require('portfinder')
const { expect } = require('chai')
const got = require('../got-test-client')
const redirector = require('./redirector')
describe('Redirector', function() {
describe('Redirector', function () {
const route = {
base: 'very/old/service',
pattern: ':namedParamA',
@@ -16,15 +16,15 @@ describe('Redirector', function() {
const dateAdded = new Date()
const attrs = { category, route, transformPath, dateAdded }
it('returns true on isDeprecated', function() {
it('returns true on isDeprecated', function () {
expect(redirector(attrs).isDeprecated).to.be.true
})
it('has the expected name', function() {
it('has the expected name', function () {
expect(redirector(attrs).name).to.equal('VeryOldServiceRedirect')
})
it('overrides the name', function() {
it('overrides the name', function () {
expect(
redirector({
...attrs,
@@ -33,33 +33,33 @@ describe('Redirector', function() {
).to.equal('ShinyRedirect')
})
it('sets specified route', function() {
it('sets specified route', function () {
expect(redirector(attrs).route).to.deep.equal(route)
})
it('sets specified category', function() {
it('sets specified category', function () {
expect(redirector(attrs).category).to.equal(category)
})
it('throws the expected error when dateAdded is missing', function() {
it('throws the expected error when dateAdded is missing', function () {
expect(() =>
redirector({ route, category, transformPath }).validateDefinition()
).to.throw('"dateAdded" is required')
})
describe('ScoutCamp integration', function() {
describe('ScoutCamp integration', function () {
let port, baseUrl
beforeEach(async function() {
beforeEach(async function () {
port = await portfinder.getPortPromise()
baseUrl = `http://127.0.0.1:${port}`
})
let camp
beforeEach(async function() {
beforeEach(async function () {
camp = Camp.start({ port, hostname: '::' })
await new Promise(resolve => camp.on('listening', () => resolve()))
})
afterEach(async function() {
afterEach(async function () {
if (camp) {
await new Promise(resolve => camp.close(resolve))
camp = undefined
@@ -68,7 +68,7 @@ describe('Redirector', function() {
const transformPath = ({ namedParamA }) => `/new/service/${namedParamA}`
beforeEach(function() {
beforeEach(function () {
const ServiceClass = redirector({
category,
route,
@@ -81,7 +81,7 @@ describe('Redirector', function() {
)
})
it('should redirect as configured', async function() {
it('should redirect as configured', async function () {
const { statusCode, headers } = await got(
`${baseUrl}/very/old/service/hello-world.svg`,
{
@@ -93,7 +93,7 @@ describe('Redirector', function() {
expect(headers.location).to.equal('/new/service/hello-world.svg')
})
it('should redirect raster extensions to the canonical path as configured', async function() {
it('should redirect raster extensions to the canonical path as configured', async function () {
const { statusCode, headers } = await got(
`${baseUrl}/very/old/service/hello-world.png`,
{
@@ -107,7 +107,7 @@ describe('Redirector', function() {
)
})
it('should forward the query params', async function() {
it('should forward the query params', async function () {
const { statusCode, headers } = await got(
`${baseUrl}/very/old/service/hello-world.svg?color=123&style=flat-square`,
{
@@ -121,14 +121,14 @@ describe('Redirector', function() {
)
})
describe('transformQueryParams', function() {
describe('transformQueryParams', function () {
const route = {
base: 'another/old/service',
pattern: 'token/:token/:namedParamA',
}
const transformQueryParams = ({ token }) => ({ token })
beforeEach(function() {
beforeEach(function () {
const ServiceClass = redirector({
category,
route,
@@ -139,7 +139,7 @@ describe('Redirector', function() {
ServiceClass.register({ camp }, {})
})
it('should forward the transformed query params', async function() {
it('should forward the transformed query params', async function () {
const { statusCode, headers } = await got(
`${baseUrl}/another/old/service/token/abc123/hello-world.svg`,
{
@@ -153,7 +153,7 @@ describe('Redirector', function() {
)
})
it('should forward the specified and transformed query params', async function() {
it('should forward the specified and transformed query params', async function () {
const { statusCode, headers } = await got(
`${baseUrl}/another/old/service/token/abc123/hello-world.svg?color=123&style=flat-square`,
{
@@ -167,7 +167,7 @@ describe('Redirector', function() {
)
})
it('should use transformed query params on param conflicts by default', async function() {
it('should use transformed query params on param conflicts by default', async function () {
const { statusCode, headers } = await got(
`${baseUrl}/another/old/service/token/abc123/hello-world.svg?color=123&style=flat-square&token=def456`,
{
@@ -181,7 +181,7 @@ describe('Redirector', function() {
)
})
it('should use specified query params on param conflicts when configured', async function() {
it('should use specified query params on param conflicts when configured', async function () {
const route = {
base: 'override/service',
pattern: 'token/:token/:namedParamA',

View File

@@ -9,9 +9,7 @@ function makeFullUrl(base, partialUrl) {
}
const isValidRoute = Joi.object({
base: Joi.string()
.allow('')
.required(),
base: Joi.string().allow('').required(),
pattern: Joi.string().allow(''),
format: Joi.string(),
capture: Joi.alternatives().conditional('format', {

View File

@@ -9,8 +9,8 @@ const {
getQueryParamNames,
} = require('./route')
describe('Route helpers', function() {
context('A `pattern` with a named param is declared', function() {
describe('Route helpers', function () {
context('A `pattern` with a named param is declared', function () {
const { regex, captureNames } = prepareRoute({
base: 'foo',
pattern: ':namedParamA',
@@ -36,7 +36,7 @@ describe('Route helpers', function() {
})
})
context('A `format` with a named param is declared', function() {
context('A `format` with a named param is declared', function () {
const { regex, captureNames } = prepareRoute({
base: 'foo',
format: '([^/]+?)',
@@ -62,7 +62,7 @@ describe('Route helpers', function() {
})
})
context('No named params are declared', function() {
context('No named params are declared', function () {
const { regex, captureNames } = prepareRoute({
base: 'foo',
format: '(?:[^/]+)',
@@ -78,7 +78,7 @@ describe('Route helpers', function() {
})
})
context('The wrong number of params are declared', function() {
context('The wrong number of params are declared', function () {
const { regex, captureNames } = prepareRoute({
base: 'foo',
format: '([^/]+)/([^/]+)',
@@ -94,7 +94,7 @@ describe('Route helpers', function() {
)
})
it('getQueryParamNames', function() {
it('getQueryParamNames', function () {
expect(
getQueryParamNames({
queryParamSchema: Joi.object({ foo: Joi.string() }).required(),

View File

@@ -5,10 +5,7 @@ const Joi = require('@hapi/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 arrayOfStrings = Joi.array().items(Joi.string()).min(0).required()
const objectOfKeyValues = Joi.object()
.pattern(/./, Joi.string().allow(null))
@@ -39,9 +36,7 @@ const serviceDefinition = Joi.object({
}).required(),
preview: Joi.object({
label: Joi.string(),
message: Joi.string()
.allow('')
.required(),
message: Joi.string().allow('').required(),
color: Joi.string().required(),
style: Joi.string(),
namedLogo: Joi.string(),
@@ -70,9 +65,7 @@ const serviceDefinitionExport = Joi.object({
})
)
.required(),
services: Joi.array()
.items(serviceDefinition)
.required(),
services: Joi.array().items(serviceDefinition).required(),
}).required()
function assertValidServiceDefinitionExport(examples, message = undefined) {

View File

@@ -7,19 +7,19 @@ const trace = require('./trace')
const { InvalidParameter } = require('./errors')
const validate = require('./validate')
describe('validate', function() {
describe('validate', function () {
const schema = Joi.object({
requiredString: Joi.string().required(),
}).required()
let sandbox
beforeEach(function() {
beforeEach(function () {
sandbox = sinon.createSandbox()
})
afterEach(function() {
afterEach(function () {
sandbox.restore()
})
beforeEach(function() {
beforeEach(function () {
sandbox.stub(trace, 'logTrace')
})
@@ -35,8 +35,8 @@ describe('validate', function() {
traceSuccessMessage,
}
context('schema is not provided', function() {
it('throws the expected programmer error', function() {
context('schema is not provided', function () {
it('throws the expected programmer error', function () {
try {
validate(options, { requiredString: 'bar' }, undefined)
expect.fail('Expected to throw')
@@ -47,8 +47,8 @@ describe('validate', function() {
})
})
context('data matches schema', function() {
it('logs the data', function() {
context('data matches schema', function () {
it('logs the data', function () {
validate(options, { requiredString: 'bar' }, schema)
expect(trace.logTrace).to.be.calledWithMatch(
'validate',
@@ -60,8 +60,8 @@ describe('validate', function() {
})
})
context('data does not match schema', function() {
it('logs the data and throws the expected error', function() {
context('data does not match schema', function () {
it('logs the data and throws the expected error', function () {
try {
validate(
options,
@@ -84,8 +84,8 @@ describe('validate', function() {
)
})
context('with includeKeys: true', function() {
it('includes keys in the error text', function() {
context('with includeKeys: true', function () {
it('includes keys in the error text', function () {
try {
validate(
{ ...options, includeKeys: true },
@@ -108,7 +108,7 @@ describe('validate', function() {
})
})
it('allowAndStripUnknownKeys', function() {
it('allowAndStripUnknownKeys', function () {
try {
validate(
{ ...options, allowAndStripUnknownKeys: false, includeKeys: true },

View File

@@ -1,21 +1,16 @@
'use strict'
const merge = require('deepmerge')
const config = require('config').util.toObject()
const portfinder = require('portfinder')
const Server = require('./server')
function createTestServer({ port }) {
const serverConfig = {
...config,
public: {
...config.public,
bind: {
...config.public.bind,
port,
},
},
async function createTestServer(customConfig = {}) {
const mergedConfig = merge(config, customConfig)
if (!mergedConfig.public.bind.port) {
mergedConfig.public.bind.port = await portfinder.getPortPromise()
}
return new Server(serverConfig)
return new Server(mergedConfig)
}
module.exports = {

View File

@@ -0,0 +1,86 @@
'use strict'
const os = require('os')
const { promisify } = require('util')
const { post } = require('request')
const postAsync = promisify(post)
const generateInstanceId = require('./instance-id-generator')
const { promClientJsonToInfluxV2 } = require('./metrics/format-converters')
const log = require('./log')
module.exports = class InfluxMetrics {
constructor(metricInstance, config) {
this._metricInstance = metricInstance
this._config = config
this._instanceId = this.getInstanceId()
}
async sendMetrics() {
const auth = {
user: this._config.username,
pass: this._config.password,
}
const request = {
uri: this._config.url,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: this.metrics(),
timeout: this._config.timeoutMillseconds,
auth,
}
let response
try {
response = await postAsync(request)
} catch (error) {
log.error(
new Error(`Cannot push metrics. Cause: ${error.name}: ${error.message}`)
)
}
if (response && response.statusCode >= 300) {
log.error(
new Error(
`Cannot push metrics. ${response.request.href} responded with status code ${response.statusCode}`
)
)
}
}
startPushingMetrics() {
this._intervalId = setInterval(
() => this.sendMetrics(),
this._config.intervalSeconds * 1000
)
}
metrics() {
return promClientJsonToInfluxV2(this._metricInstance.metrics(), {
env: this._config.envLabel,
application: 'shields',
instance: this._instanceId,
})
}
getInstanceId() {
const {
hostnameAliases = {},
instanceIdFrom,
instanceIdEnvVarName,
} = this._config
let instance
if (instanceIdFrom === 'env-var') {
instance = process.env[instanceIdEnvVarName]
} else if (instanceIdFrom === 'hostname') {
const hostname = os.hostname()
instance = hostnameAliases[hostname] || hostname
} else if (instanceIdFrom === 'random') {
instance = generateInstanceId()
}
return instance
}
stopPushingMetrics() {
if (this._intervalId) {
clearInterval(this._intervalId)
this._intervalId = undefined
}
}
}

View File

@@ -0,0 +1,174 @@
'use strict'
const os = require('os')
const nock = require('nock')
const sinon = require('sinon')
const { expect } = require('chai')
const log = require('./log')
const InfluxMetrics = require('./influx-metrics')
require('../register-chai-plugins.spec')
describe('Influx metrics', function () {
const metricInstance = {
metrics() {
return [
{
help: 'counter 1 help',
name: 'counter1',
type: 'counter',
values: [{ value: 11, labels: {} }],
aggregator: 'sum',
},
]
},
}
describe('"metrics" function', function () {
let osHostnameStub
afterEach(function () {
nock.enableNetConnect()
delete process.env.INSTANCE_ID
if (osHostnameStub) {
osHostnameStub.restore()
}
})
it('should use an environment variable value as an instance label', async function () {
process.env.INSTANCE_ID = 'instance3'
const influxMetrics = new InfluxMetrics(metricInstance, {
instanceIdFrom: 'env-var',
instanceIdEnvVarName: 'INSTANCE_ID',
})
expect(influxMetrics.metrics()).to.contain('instance=instance3')
})
it('should use a hostname as an instance label', async function () {
osHostnameStub = sinon.stub(os, 'hostname').returns('test-hostname')
const customConfig = {
instanceIdFrom: 'hostname',
}
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(influxMetrics.metrics()).to.be.contain('instance=test-hostname')
})
it('should use a random string as an instance label', async function () {
const customConfig = {
instanceIdFrom: 'random',
}
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(influxMetrics.metrics()).to.be.match(/instance=\w+ /)
})
it('should use a hostname alias as an instance label', async function () {
osHostnameStub = sinon.stub(os, 'hostname').returns('test-hostname')
const customConfig = {
instanceIdFrom: 'hostname',
hostnameAliases: { 'test-hostname': 'test-hostname-alias' },
}
const influxMetrics = new InfluxMetrics(metricInstance, customConfig)
expect(influxMetrics.metrics()).to.be.contain(
'instance=test-hostname-alias'
)
})
})
describe('startPushingMetrics', function () {
let influxMetrics, clock
beforeEach(function () {
clock = sinon.useFakeTimers()
})
afterEach(function () {
influxMetrics.stopPushingMetrics()
nock.cleanAll()
nock.enableNetConnect()
delete process.env.INSTANCE_ID
clock.restore()
})
it('should send metrics', async function () {
const scope = nock('http://shields-metrics.io/', {
reqheaders: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
.persist()
.post(
'/metrics',
'prometheus,application=shields,env=test-env,instance=instance2 counter1=11'
)
.basicAuth({ user: 'metrics-username', pass: 'metrics-password' })
.reply(200)
process.env.INSTANCE_ID = 'instance2'
influxMetrics = new InfluxMetrics(metricInstance, {
url: 'http://shields-metrics.io/metrics',
timeoutMillseconds: 100,
intervalSeconds: 0.001,
username: 'metrics-username',
password: 'metrics-password',
instanceIdFrom: 'env-var',
instanceIdEnvVarName: 'INSTANCE_ID',
envLabel: 'test-env',
})
influxMetrics.startPushingMetrics()
await clock.tickAsync(10)
expect(scope.isDone()).to.be.equal(
true,
`pending mocks: ${scope.pendingMocks()}`
)
})
})
describe('sendMetrics', function () {
beforeEach(function () {
sinon.stub(log, 'error')
})
afterEach(function () {
log.error.restore()
nock.cleanAll()
nock.enableNetConnect()
})
const influxMetrics = new InfluxMetrics(metricInstance, {
url: 'http://shields-metrics.io/metrics',
timeoutMillseconds: 50,
intervalSeconds: 0,
username: 'metrics-username',
password: 'metrics-password',
})
it('should log errors', async function () {
nock.disableNetConnect()
await influxMetrics.sendMetrics()
expect(log.error).to.be.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'Cannot push metrics. Cause: NetConnectNotAllowedError: Nock: Disallowed net connect for "shields-metrics.io:80/metrics"'
)
)
)
})
it('should log error responses', async function () {
nock('http://shields-metrics.io/').persist().post('/metrics').reply(400)
await influxMetrics.sendMetrics()
expect(log.error).to.be.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'Cannot push metrics. http://shields-metrics.io/metrics responded with status code 400'
)
)
)
})
})
})

View File

@@ -0,0 +1,8 @@
'use strict'
function generateInstanceId() {
// from https://gist.github.com/gordonbrander/2230317
return Math.random().toString(36).substr(2, 9)
}
module.exports = generateInstanceId

View File

@@ -0,0 +1,27 @@
'use strict'
const groupBy = require('lodash.groupby')
function promClientJsonToInfluxV2(metrics, extraLabels = {}) {
// TODO Replace with Array.prototype.flatMap() after migrating to Node.js >= 11
const flatMap = (f, arr) => arr.reduce((acc, x) => acc.concat(f(x)), [])
return flatMap(metric => {
const valuesByLabels = groupBy(metric.values, value =>
JSON.stringify(Object.entries(value.labels).sort())
)
return Object.values(valuesByLabels).map(metricsWithSameLabel => {
const labels = Object.entries(metricsWithSameLabel[0].labels)
.concat(Object.entries(extraLabels))
.sort((a, b) => a[0].localeCompare(b[0]))
.map(labelEntry => `${labelEntry[0]}=${labelEntry[1]}`)
.join(',')
const labelsFormatted = labels ? `,${labels}` : ''
const values = metricsWithSameLabel
.sort((a, b) => a.metricName.localeCompare(b.metricName))
.map(value => `${value.metricName || metric.name}=${value.value}`)
.join(',')
return `prometheus${labelsFormatted} ${values}`
})
}, metrics).join('\n')
}
module.exports = { promClientJsonToInfluxV2 }

View File

@@ -0,0 +1,213 @@
'use strict'
const { expect } = require('chai')
const prometheus = require('prom-client')
const { promClientJsonToInfluxV2 } = require('./format-converters')
describe('Metric format converters', function () {
describe('prom-client JSON to InfluxDB line protocol (version 2)', function () {
it('converts a counter', function () {
const json = [
{
help: 'counter 1 help',
name: 'counter1',
type: 'counter',
values: [{ value: 11, labels: {} }],
aggregator: 'sum',
},
]
const influx = promClientJsonToInfluxV2(json)
expect(influx).to.be.equal('prometheus counter1=11')
})
it('converts a counter (from prometheus registry)', function () {
const register = new prometheus.Registry()
const counter = new prometheus.Counter({
name: 'counter1',
help: 'counter 1 help',
registers: [register],
})
counter.inc(11)
const influx = promClientJsonToInfluxV2(register.getMetricsAsJSON())
expect(influx).to.be.equal('prometheus counter1=11')
})
it('converts a gauge', function () {
const json = [
{
help: 'gause 1 help',
name: 'gauge1',
type: 'gauge',
values: [{ value: 20, labels: {} }],
aggregator: 'sum',
},
]
const influx = promClientJsonToInfluxV2(json)
expect(influx).to.be.equal('prometheus gauge1=20')
})
it('converts a gauge (from prometheus registry)', function () {
const register = new prometheus.Registry()
const gauge = new prometheus.Gauge({
name: 'gauge1',
help: 'gauge 1 help',
registers: [register],
})
gauge.inc(20)
const influx = promClientJsonToInfluxV2(register.getMetricsAsJSON())
expect(influx).to.be.equal('prometheus gauge1=20')
})
const sortLines = text => text.split('\n').sort().join('\n')
it('converts a histogram', function () {
const json = [
{
name: 'histogram1',
help: 'histogram 1 help',
type: 'histogram',
values: [
{ labels: { le: 5 }, value: 1, metricName: 'histogram1_bucket' },
{ labels: { le: 15 }, value: 2, metricName: 'histogram1_bucket' },
{ labels: { le: 50 }, value: 2, metricName: 'histogram1_bucket' },
{
labels: { le: '+Inf' },
value: 3,
metricName: 'histogram1_bucket',
},
{ labels: {}, value: 111, metricName: 'histogram1_sum' },
{ labels: {}, value: 3, metricName: 'histogram1_count' },
],
aggregator: 'sum',
},
]
const influx = promClientJsonToInfluxV2(json)
expect(sortLines(influx)).to.be.equal(
sortLines(`prometheus,le=+Inf histogram1_bucket=3
prometheus,le=50 histogram1_bucket=2
prometheus,le=15 histogram1_bucket=2
prometheus,le=5 histogram1_bucket=1
prometheus histogram1_count=3,histogram1_sum=111`)
)
})
it('converts a histogram (from prometheus registry)', function () {
const register = new prometheus.Registry()
const histogram = new prometheus.Histogram({
name: 'histogram1',
help: 'histogram 1 help',
buckets: [5, 15, 50],
registers: [register],
})
histogram.observe(100)
histogram.observe(10)
histogram.observe(1)
const influx = promClientJsonToInfluxV2(register.getMetricsAsJSON())
expect(sortLines(influx)).to.be.equal(
sortLines(`prometheus,le=+Inf histogram1_bucket=3
prometheus,le=50 histogram1_bucket=2
prometheus,le=15 histogram1_bucket=2
prometheus,le=5 histogram1_bucket=1
prometheus histogram1_count=3,histogram1_sum=111`)
)
})
it('converts a summary', function () {
const json = [
{
name: 'summary1',
help: 'summary 1 help',
type: 'summary',
values: [
{ labels: { quantile: 0.1 }, value: 1 },
{ labels: { quantile: 0.9 }, value: 100 },
{ labels: { quantile: 0.99 }, value: 100 },
{ metricName: 'summary1_sum', labels: {}, value: 111 },
{ metricName: 'summary1_count', labels: {}, value: 3 },
],
aggregator: 'sum',
},
]
const influx = promClientJsonToInfluxV2(json)
expect(sortLines(influx)).to.be.equal(
sortLines(`prometheus,quantile=0.99 summary1=100
prometheus,quantile=0.9 summary1=100
prometheus,quantile=0.1 summary1=1
prometheus summary1_count=3,summary1_sum=111`)
)
})
it('converts a summary (from prometheus registry)', function () {
const register = new prometheus.Registry()
const summary = new prometheus.Summary({
name: 'summary1',
help: 'summary 1 help',
percentiles: [0.1, 0.9, 0.99],
registers: [register],
})
summary.observe(100)
summary.observe(10)
summary.observe(1)
const influx = promClientJsonToInfluxV2(register.getMetricsAsJSON())
expect(sortLines(influx)).to.be.equal(
sortLines(`prometheus,quantile=0.99 summary1=100
prometheus,quantile=0.9 summary1=100
prometheus,quantile=0.1 summary1=1
prometheus summary1_count=3,summary1_sum=111`)
)
})
it('converts a counter and skip a timestamp', function () {
const json = [
{
help: 'counter 4 help',
name: 'counter4',
type: 'counter',
values: [{ value: 11, labels: {}, timestamp: 1581850552292 }],
aggregator: 'sum',
},
]
const influx = promClientJsonToInfluxV2(json)
expect(influx).to.be.equal('prometheus counter4=11')
})
it('converts a counter and adds extra labels', function () {
const json = [
{
help: 'counter 1 help',
name: 'counter1',
type: 'counter',
values: [{ value: 11, labels: {} }],
aggregator: 'sum',
},
]
const influx = promClientJsonToInfluxV2(json, {
instance: 'instance1',
env: 'production',
})
expect(influx).to.be.equal(
'prometheus,env=production,instance=instance1 counter1=11'
)
})
})
})

View File

@@ -39,10 +39,7 @@ function setRoutes({ rateLimit }, { server, metricInstance }) {
const ip =
(req.headers['x-forwarded-for'] || '').split(', ')[0] ||
req.socket.remoteAddress
const badgeType = req.url
.split(/[/-]/)
.slice(0, 3)
.join('')
const badgeType = req.url.split(/[/-]/).slice(0, 3).join('')
const referer = req.headers.referer
if (ipRateLimit.isBanned(ip, req, res)) {
@@ -91,7 +88,7 @@ function setRoutes({ rateLimit }, { server, metricInstance }) {
})
})
return function() {
return function () {
ipRateLimit.stop()
badgeTypeRateLimit.stop()
refererRateLimit.stop()

View File

@@ -68,11 +68,13 @@ module.exports = class PrometheusMetrics {
registers: [this.register],
}),
}
this.interval = prometheus.collectDefaultMetrics({
register: this.register,
})
}
async initialize(server) {
async registerMetricsEndpoint(server) {
const { register } = this
this.interval = prometheus.collectDefaultMetrics({ register })
server.route(/^\/metrics$/, (data, match, end, ask) => {
ask.res.setHeader('Content-Type', register.contentType)
@@ -88,6 +90,10 @@ module.exports = class PrometheusMetrics {
}
}
metrics() {
return this.register.getMetricsAsJSON()
}
/**
* @returns {object} `{ inc() {} }`.
*/

View File

@@ -1,32 +1,32 @@
'use strict'
const { expect } = require('chai')
const Camp = require('camp')
const Camp = require('@shields_io/camp')
const portfinder = require('portfinder')
const got = require('../got-test-client')
const Metrics = require('./prometheus-metrics')
describe('Prometheus metrics route', function() {
let port, baseUrl
beforeEach(async function() {
describe('Prometheus metrics route', function () {
let port, baseUrl, camp, metrics
beforeEach(async function () {
port = await portfinder.getPortPromise()
baseUrl = `http://127.0.0.1:${port}`
})
let camp
beforeEach(async function() {
camp = Camp.start({ port, hostname: '::' })
await new Promise(resolve => camp.on('listening', () => resolve()))
})
afterEach(async function() {
afterEach(async function () {
if (metrics) {
metrics.stop()
}
if (camp) {
await new Promise(resolve => camp.close(resolve))
camp = undefined
}
})
it('returns metrics', async function() {
new Metrics({ enabled: true }).initialize(camp)
it('returns default metrics', async function () {
metrics = new Metrics()
metrics.registerMetricsEndpoint(camp)
const { statusCode, body } = await got(`${baseUrl}/metrics`)

View File

@@ -7,9 +7,9 @@ const path = require('path')
const url = require('url')
const { URL } = url
const bytes = require('bytes')
const Camp = require('camp')
const Camp = require('@shields_io/camp')
const originalJoi = require('@hapi/joi')
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const GithubConstellation = require('../../services/github/github-constellation')
const suggest = require('../../services/suggest')
const { loadServiceClasses } = require('../base-service/loader')
@@ -23,6 +23,7 @@ const { rasterRedirectUrl } = require('../badge-urls/make-badge-url')
const log = require('./log')
const sysMonitor = require('./monitor')
const PrometheusMetrics = require('./prometheus-metrics')
const InfluxMetrics = require('./influx-metrics')
const Joi = originalJoi
.extend(base => ({
@@ -70,17 +71,40 @@ const publicConfigSchema = Joi.object({
Joi.string().pattern(/^\\\\\.\\pipe\\.+$/)
),
address: Joi.alternatives().try(
Joi.string()
.ip()
.required(),
Joi.string()
.hostname()
.required()
Joi.string().ip().required(),
Joi.string().hostname().required()
),
},
metrics: {
prometheus: {
enabled: Joi.boolean().required(),
endpointEnabled: Joi.boolean().required(),
},
influx: {
enabled: Joi.boolean().required(),
url: Joi.string()
.uri()
.when('enabled', { is: true, then: Joi.required() }),
timeoutMilliseconds: Joi.number()
.integer()
.min(1)
.when('enabled', { is: true, then: Joi.required() }),
intervalSeconds: Joi.number()
.integer()
.min(1)
.when('enabled', { is: true, then: Joi.required() }),
instanceIdFrom: Joi.string()
.equal('hostname', 'env-var', 'random')
.when('enabled', { is: true, then: Joi.required() }),
instanceIdEnvVarName: Joi.string().when('instanceIdFrom', {
is: 'env-var',
then: Joi.required(),
}),
envLabel: Joi.string().when('enabled', {
is: true,
then: Joi.required(),
}),
hostnameAliases: Joi.object(),
},
},
ssl: {
@@ -91,9 +115,7 @@ const publicConfigSchema = Joi.object({
redirectUrl: optionalUrl,
rasterUrl: optionalUrl,
cors: {
allowedOrigin: Joi.array()
.items(optionalUrl)
.required(),
allowedOrigin: Joi.array().items(optionalUrl).required(),
},
persistence: {
dir: Joi.string().required(),
@@ -105,10 +127,7 @@ const publicConfigSchema = Joi.object({
baseUri: requiredUrl,
debug: {
enabled: Joi.boolean().required(),
intervalSeconds: Joi.number()
.integer()
.min(1)
.required(),
intervalSeconds: Joi.number().integer().min(1).required(),
},
},
jira: defaultService,
@@ -124,13 +143,12 @@ const publicConfigSchema = Joi.object({
trace: Joi.boolean().required(),
}).required(),
cacheHeaders: {
defaultCacheLengthSeconds: Joi.number()
.integer()
.required(),
defaultCacheLengthSeconds: Joi.number().integer().required(),
},
rateLimit: Joi.boolean().required(),
handleInternalErrors: Joi.boolean().required(),
fetchLimit: Joi.string().regex(/^[0-9]+(b|kb|mb|gb|tb)$/i),
shieldsProductionHerokuHacks: Joi.boolean(),
}).required()
const privateConfigSchema = Joi.object({
@@ -160,8 +178,13 @@ const privateConfigSchema = Joi.object({
twitch_client_id: Joi.string(),
twitch_client_secret: Joi.string(),
wheelmap_token: Joi.string(),
influx_username: Joi.string(),
influx_password: Joi.string(),
}).required()
const privateMetricsInfluxConfigSchema = privateConfigSchema.append({
influx_username: Joi.string().required(),
influx_password: Joi.string().required(),
})
/**
* The Server is based on the web framework Scoutcamp. It creates
* an http server, sets up helpers for token persistence and monitoring.
@@ -173,22 +196,25 @@ class Server {
* Badge Server Constructor
*
* @param {object} config Configuration object read from config yaml files
* by https://www.npmjs.com/package/config and validated against
* publicConfigSchema and privateConfigSchema
* by https://www.npmjs.com/package/config and validated against
* publicConfigSchema and privateConfigSchema
* @see https://github.com/badges/shields/blob/master/doc/production-hosting.md#configuration
* @see https://github.com/badges/shields/blob/master/doc/server-secrets.md
*/
constructor(config) {
const publicConfig = Joi.attempt(config.public, publicConfigSchema)
let privateConfig
try {
privateConfig = Joi.attempt(config.private, privateConfigSchema)
} catch (e) {
const badPaths = e.details.map(({ path }) => path)
throw Error(
`Private configuration is invalid. Check these paths: ${badPaths.join(
','
)}`
const privateConfig = this.validatePrivateConfig(
config.private,
privateConfigSchema
)
// We want to require an username and a password for the influx metrics
// only if the influx metrics are enabled. The private config schema
// and the public config schema are two separate schemas so we have to run
// validation manually.
if (publicConfig.metrics.influx && publicConfig.metrics.influx.enabled) {
this.validatePrivateConfig(
config.private,
privateMetricsInfluxConfigSchema
)
}
this.config = {
@@ -201,8 +227,31 @@ class Server {
service: publicConfig.services.github,
private: privateConfig,
})
if (publicConfig.metrics.prometheus.enabled) {
this.metricInstance = new PrometheusMetrics()
if (publicConfig.metrics.influx.enabled) {
this.influxMetrics = new InfluxMetrics(
this.metricInstance,
Object.assign({}, publicConfig.metrics.influx, {
username: privateConfig.influx_username,
password: privateConfig.influx_password,
})
)
}
}
}
validatePrivateConfig(privateConfig, privateConfigSchema) {
try {
return Joi.attempt(privateConfig, privateConfigSchema)
} catch (e) {
const badPaths = e.details.map(({ path }) => path)
throw Error(
`Private configuration is invalid. Check these paths: ${badPaths.join(
','
)}`
)
}
}
@@ -342,6 +391,8 @@ class Server {
rasterUrl: config.public.rasterUrl,
private: config.private,
public: config.public,
shieldsProductionHerokuHacks:
config.public.shieldsProductionHerokuHacks,
}
)
)
@@ -363,7 +414,7 @@ class Server {
log(`Server is starting up: ${this.baseUrl}`)
const camp = (this.camp = Camp.start({
const camp = (this.camp = Camp.create({
documentRoot: path.resolve(__dirname, '..', '..', 'public'),
port,
hostname,
@@ -379,9 +430,14 @@ class Server {
)
const { githubConstellation } = this
githubConstellation.initialize(camp)
await githubConstellation.initialize(camp)
if (metricInstance) {
metricInstance.initialize(camp)
if (this.config.public.metrics.prometheus.endpointEnabled) {
metricInstance.registerMetricsEndpoint(camp)
}
if (this.influxMetrics) {
this.influxMetrics.startPushingMetrics()
}
}
const { apiProvider: githubApiProvider } = this.githubConstellation
@@ -391,6 +447,8 @@ class Server {
this.registerRedirects()
this.registerServices()
camp.listenAsConfigured()
await new Promise(resolve => camp.on('listening', () => resolve()))
}
@@ -425,6 +483,9 @@ class Server {
}
if (this.metricInstance) {
if (this.influxMetrics) {
this.influxMetrics.stopPushingMetrics()
}
this.metricInstance.stop()
}
}

View File

@@ -2,163 +2,331 @@
const { expect } = require('chai')
const isSvg = require('is-svg')
const portfinder = require('portfinder')
const config = require('config')
const got = require('../got-test-client')
const Server = require('./server')
const { createTestServer } = require('./in-process-server-test-helpers')
describe('The server', function() {
let server, baseUrl
before('Start the server', async function() {
// Fixes https://github.com/badges/shields/issues/2611
this.timeout(10000)
const port = await portfinder.getPortPromise()
server = createTestServer({ port })
baseUrl = server.baseUrl
await server.start()
})
after('Shut down the server', async function() {
if (server) {
await server.stop()
}
server = undefined
})
it('should allow strings for port', async function() {
// fixes #4391 - This allows the app to be run using iisnode, which uses a named pipe for the port.
const pipeServer = createTestServer({
port: '\\\\.\\pipe\\9c137306-7c4d-461e-b7cf-5213a3939ad6',
describe('The server', function () {
describe('running', function () {
let server, baseUrl
before('Start the server', async function () {
// Fixes https://github.com/badges/shields/issues/2611
this.timeout(10000)
server = await createTestServer()
baseUrl = server.baseUrl
await server.start()
})
after('Shut down the server', async function () {
if (server) {
await server.stop()
}
server = undefined
})
expect(pipeServer).to.not.be.undefined
})
it('should produce colorscheme badges', async function() {
const { statusCode, body } = await got(`${baseUrl}:fruit-apple-green.svg`)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should allow strings for port', async function () {
// fixes #4391 - This allows the app to be run using iisnode, which uses a named pipe for the port.
const pipeServer = await createTestServer({
public: {
bind: {
port: '\\\\.\\pipe\\9c137306-7c4d-461e-b7cf-5213a3939ad6',
},
},
})
expect(pipeServer).to.not.be.undefined
})
it('should redirect colorscheme PNG badges as configured', async function() {
const { statusCode, headers } = await got(
`${baseUrl}:fruit-apple-green.png`,
{
it('should produce colorscheme badges', async function () {
const { statusCode, body } = await got(`${baseUrl}:fruit-apple-green.svg`)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should redirect colorscheme PNG badges as configured', async function () {
const { statusCode, headers } = await got(
`${baseUrl}:fruit-apple-green.png`,
{
followRedirect: false,
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/:fruit-apple-green.png'
)
})
it('should redirect modern PNG badges as configured', async function () {
const { statusCode, headers } = await got(`${baseUrl}npm/v/express.png`, {
followRedirect: false,
}
)
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/:fruit-apple-green.png'
)
})
it('should redirect modern PNG badges as configured', async function() {
const { statusCode, headers } = await got(`${baseUrl}npm/v/express.png`, {
followRedirect: false,
})
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/npm/v/express.png'
)
})
expect(statusCode).to.equal(301)
expect(headers.location).to.equal(
'http://raster.example.test/npm/v/express.png'
)
})
it('should produce json badges', async function() {
const { statusCode, body, headers } = await got(
`${baseUrl}twitter/follow/_Pyves.json`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('application/json')
expect(() => JSON.parse(body)).not.to.throw()
})
it('should produce json badges', async function () {
const { statusCode, body, headers } = await got(
`${baseUrl}twitter/follow/_Pyves.json`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('application/json')
expect(() => JSON.parse(body)).not.to.throw()
})
it('should preserve label case', async function() {
const { statusCode, body } = await got(`${baseUrl}:fRuiT-apple-green.svg`)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fRuiT')
})
it('should preserve label case', async function () {
const { statusCode, body } = await got(`${baseUrl}:fRuiT-apple-green.svg`)
expect(statusCode).to.equal(200)
expect(body).to.satisfy(isSvg).and.to.include('fRuiT')
})
// https://github.com/badges/shields/pull/1319
it('should not crash with a numeric logo', async function() {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?logo=1`
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
// https://github.com/badges/shields/pull/1319
it('should not crash with a numeric logo', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?logo=1`
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should not crash with a numeric link', async function() {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=1`
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should not crash with a numeric link', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=1`
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should not crash with a boolean link', async function() {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=true`
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should not crash with a boolean link', async function () {
const { statusCode, body } = await got(
`${baseUrl}:fruit-apple-green.svg?link=true`
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
})
it('should return the 404 badge for unknown badges', async function() {
const { statusCode, body } = await got(
`${baseUrl}this/is/not/a/badge.svg`,
{ throwHttpErrors: false }
)
expect(statusCode).to.equal(404)
expect(body)
.to.satisfy(isSvg)
.and.to.include('404')
.and.to.include('badge not found')
})
it('should return the 404 badge for unknown badges', async function () {
const { statusCode, body } = await got(
`${baseUrl}this/is/not/a/badge.svg`,
{
throwHttpErrors: false,
}
)
expect(statusCode).to.equal(404)
expect(body)
.to.satisfy(isSvg)
.and.to.include('404')
.and.to.include('badge not found')
})
it('should return the 404 badge page for rando links', async function() {
const { statusCode, body } = await got(
`${baseUrl}this/is/most/definitely/not/a/badge.js`,
{
it('should return the 404 badge page for rando links', async function () {
const { statusCode, body } = await got(
`${baseUrl}this/is/most/definitely/not/a/badge.js`,
{
throwHttpErrors: false,
}
)
expect(statusCode).to.equal(404)
expect(body)
.to.satisfy(isSvg)
.and.to.include('404')
.and.to.include('badge not found')
})
it('should redirect the root as configured', async function () {
const { statusCode, headers } = await got(baseUrl, {
followRedirect: false,
})
expect(statusCode).to.equal(302)
// This value is set in `config/test.yml`
expect(headers.location).to.equal('http://frontend.example.test')
})
it('should return the 410 badge for obsolete formats', async function () {
const { statusCode, body } = await got(`${baseUrl}npm/v/express.jpg`, {
throwHttpErrors: false,
})
// TODO It would be nice if this were 404 or 410.
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('410')
.and.to.include('jpg no longer available')
})
})
describe('configuration', function () {
let server
afterEach(async function () {
if (server) {
server.stop()
}
)
expect(statusCode).to.equal(404)
expect(body)
.to.satisfy(isSvg)
.and.to.include('404')
.and.to.include('badge not found')
})
it('should redirect the root as configured', async function() {
const { statusCode, headers } = await got(baseUrl, {
followRedirect: false,
})
expect(statusCode).to.equal(302)
// This value is set in `config/test.yml`
expect(headers.location).to.equal('http://frontend.example.test')
it('should allow to enable prometheus metrics', async function () {
// Fixes https://github.com/badges/shields/issues/2611
this.timeout(10000)
server = await createTestServer({
public: {
metrics: { prometheus: { enabled: true, endpointEnabled: true } },
},
})
await server.start()
const { statusCode } = await got(`${server.baseUrl}metrics`)
expect(statusCode).to.be.equal(200)
})
it('should allow to disable prometheus metrics', async function () {
// Fixes https://github.com/badges/shields/issues/2611
this.timeout(10000)
server = await createTestServer({
public: {
metrics: { prometheus: { enabled: true, endpointEnabled: false } },
},
})
await server.start()
const { statusCode } = await got(`${server.baseUrl}metrics`, {
throwHttpErrors: false,
})
expect(statusCode).to.be.equal(404)
})
})
it('should return the 410 badge for obsolete formats', async function() {
const { statusCode, body } = await got(`${baseUrl}npm/v/express.jpg`, {
throwHttpErrors: false,
describe('configuration validation', function () {
describe('influx', function () {
let customConfig
beforeEach(function () {
customConfig = config.util.toObject()
customConfig.public.metrics.influx = {
enabled: true,
url: 'http://localhost:8081/telegraf',
timeoutMilliseconds: 1000,
intervalSeconds: 2,
instanceIdFrom: 'random',
instanceIdEnvVarName: 'INSTANCE_ID',
hostnameAliases: { 'metrics-hostname': 'metrics-hostname-alias' },
envLabel: 'test-env',
}
customConfig.private = {
influx_username: 'telegraf',
influx_password: 'telegrafpass',
}
})
it('should not require influx configuration', function () {
delete customConfig.public.metrics.influx
expect(() => new Server(config.util.toObject())).to.not.throw()
})
it('should require url when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.url
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.url" is required'
)
})
it('should not require url when influx configuration is disabled', function () {
customConfig.public.metrics.influx.enabled = false
delete customConfig.public.metrics.influx.url
expect(() => new Server(customConfig)).to.not.throw()
})
it('should require timeoutMilliseconds when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.timeoutMilliseconds
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.timeoutMilliseconds" is required'
)
})
it('should require intervalSeconds when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.intervalSeconds
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.intervalSeconds" is required'
)
})
it('should require instanceIdFrom when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.instanceIdFrom
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.instanceIdFrom" is required'
)
})
it('should require instanceIdEnvVarName when instanceIdFrom is env-var', function () {
customConfig.public.metrics.influx.instanceIdFrom = 'env-var'
delete customConfig.public.metrics.influx.instanceIdEnvVarName
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.instanceIdEnvVarName" is required'
)
})
it('should allow instanceIdFrom = hostname', function () {
customConfig.public.metrics.influx.instanceIdFrom = 'hostname'
expect(() => new Server(customConfig)).to.not.throw()
})
it('should allow instanceIdFrom = env-var', function () {
customConfig.public.metrics.influx.instanceIdFrom = 'env-var'
expect(() => new Server(customConfig)).to.not.throw()
})
it('should allow instanceIdFrom = random', function () {
customConfig.public.metrics.influx.instanceIdFrom = 'random'
expect(() => new Server(customConfig)).to.not.throw()
})
it('should require envLabel when influx configuration is enabled', function () {
delete customConfig.public.metrics.influx.envLabel
expect(() => new Server(customConfig)).to.throw(
'"metrics.influx.envLabel" is required'
)
})
it('should not require hostnameAliases', function () {
delete customConfig.public.metrics.influx.hostnameAliases
expect(() => new Server(customConfig)).to.not.throw()
})
it('should allow empty hostnameAliases', function () {
customConfig.public.metrics.influx.hostnameAliases = {}
expect(() => new Server(customConfig)).to.not.throw()
})
it('should require username when influx configuration is enabled', function () {
delete customConfig.private.influx_username
expect(() => new Server(customConfig)).to.throw(
'Private configuration is invalid. Check these paths: influx_username'
)
})
it('should require password when influx configuration is enabled', function () {
delete customConfig.private.influx_password
expect(() => new Server(customConfig)).to.throw(
'Private configuration is invalid. Check these paths: influx_password'
)
})
it('should allow other private keys', function () {
customConfig.private.gh_token = 'my-token'
expect(() => new Server(customConfig)).to.not.throw()
})
})
// TODO It would be nice if this were 404 or 410.
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('410')
.and.to.include('jpg no longer available')
})
})

View File

@@ -43,11 +43,11 @@
// 1. Generating the list of services to test is necessarily asynchronous, and
// in Mocha, exclusive tests (`it.only` and `describe.only`) can only be
// applied synchronously. In other words, if you try to add exclusive tests
// in an asynchronous callback, all the tests will run. This is true even
// when using `_mocha --delay`, as we are. Undoubtedly this could be fixed,
// though it's not worth it. The problem is obscure and therefore low
// for Mocha, which is quite backlogged. There is an easy workaround, which
// is to generate the list of services to test in a separate process.
// in an asynchronous callback, all the tests will run. Undoubtedly this
// could be fixed, though it's not worth it. The problem is obscure and
// therefore low for Mocha, which is quite backlogged. There is an easy
// workaround, which is to generate the list of services to test in a
// separate process.
// 2. Executing these two steps of the test runner separately makes the process
// easier to reason about and much easier to debug on a dev machine.
// 3. Getting "pipefail" to work cross platform with an npm script seems tricky.
@@ -73,11 +73,17 @@ if (process.env.TESTED_SERVER_URL) {
} else {
const port = 1111
baseUrl = 'http://localhost:1111'
before('Start running the server', function() {
server = createTestServer({ port })
before('Start running the server', async function () {
server = await createTestServer({
public: {
bind: {
port,
},
},
})
server.start()
})
after('Shut down the server', async function() {
after('Shut down the server', async function () {
if (server) {
await server.stop()
}
@@ -125,5 +131,3 @@ if (typeof onlyServices === 'undefined' || onlyServices.includes('*****')) {
}
runner.toss()
// Invoke run() asynchronously, because Mocha will not start otherwise.
process.nextTick(run)

View File

@@ -6,7 +6,7 @@ const {
inferPullRequest,
} = require('./infer-pull-request')
describe('Pull request inference', function() {
describe('Pull request inference', function () {
test(parseGithubPullRequestUrl, () => {
forCases([
given('https://github.com/badges/shields/pull/1234'),

View File

@@ -29,7 +29,7 @@ async function getTitle(owner, repo, pullRequest) {
'User-Agent': 'badges/shields',
Authorization: `token ${process.env.GITHUB_TOKEN}`,
},
json: true,
responseType: 'json',
}
)
return title

View File

@@ -85,7 +85,7 @@ class ServiceTester {
this.beforeEach()
})
// eslint-disable-next-line mocha/prefer-arrow-callback
.finally(function() {
.finally(function () {
// `this` is the IcedFrisby instance.
let responseBody
try {
@@ -125,7 +125,7 @@ class ServiceTester {
const fn = this._only ? describe.only : describe
// eslint-disable-next-line mocha/prefer-arrow-callback
fn(this.title, function() {
fn(this.title, function () {
specs.forEach(spec => {
spec._message = `[${spec.hasIntercept ? 'mocked' : 'live'}] ${
spec._message

View File

@@ -3,7 +3,7 @@
const { test, given } = require('sazerac')
const servicesForTitle = require('./services-for-title')
describe('Services from PR title', function() {
describe('Services from PR title', function () {
test(servicesForTitle, () => {
given('[Travis] Fix timeout issues').expect(['travis'])
given('[Travis Sonar] Support user token authentication').expect([

View File

@@ -6,20 +6,20 @@ const readFile = require('fs-readfile-promise')
const { expect } = require('chai')
const FsTokenPersistence = require('./fs-token-persistence')
describe('File system token persistence', function() {
describe('File system token persistence', function () {
let path, persistence
beforeEach(function() {
beforeEach(function () {
path = tmp.tmpNameSync()
persistence = new FsTokenPersistence({ path })
})
context('when the file does not exist', function() {
it('does nothing', async function() {
context('when the file does not exist', function () {
it('does nothing', async function () {
const tokens = await persistence.initialize()
expect(tokens).to.deep.equal([])
})
it('saving creates an empty file', async function() {
it('saving creates an empty file', async function () {
await persistence.initialize()
await persistence.save()
@@ -29,20 +29,20 @@ describe('File system token persistence', function() {
})
})
context('when the file exists', function() {
context('when the file exists', function () {
const initialTokens = ['a', 'b', 'c'].map(char => char.repeat(40))
beforeEach(async function() {
beforeEach(async function () {
fs.writeFileSync(path, JSON.stringify(initialTokens))
})
it('loads the contents', async function() {
it('loads the contents', async function () {
const tokens = await persistence.initialize()
expect(tokens).to.deep.equal(initialTokens)
})
context('when tokens are added', function() {
it('saves the change', async function() {
context('when tokens are added', function () {
it('saves the change', async function () {
const newToken = 'e'.repeat(40)
const expected = Array.from(initialTokens)
expected.push(newToken)
@@ -55,8 +55,8 @@ describe('File system token persistence', function() {
})
})
context('when tokens are removed', function() {
it('saves the change', async function() {
context('when tokens are removed', function () {
it('saves the change', async function () {
const expected = Array.from(initialTokens)
const toRemove = expected.pop()

View File

@@ -5,11 +5,11 @@ const Redis = require('ioredis')
const { expect } = require('chai')
const RedisTokenPersistence = require('./redis-token-persistence')
describe('Redis token persistence', function() {
describe('Redis token persistence', function () {
let server
// In CI, expect redis already to be running.
if (!process.env.CI) {
beforeEach(async function() {
beforeEach(async function () {
server = new RedisServer({ config: { host: 'localhost' } })
await server.open()
})
@@ -18,11 +18,11 @@ describe('Redis token persistence', function() {
const key = 'tokenPersistenceIntegrationTest'
let redis
beforeEach(async function() {
beforeEach(async function () {
redis = new Redis()
await redis.del(key)
})
afterEach(async function() {
afterEach(async function () {
if (redis) {
await redis.quit()
redis = undefined
@@ -30,44 +30,44 @@ describe('Redis token persistence', function() {
})
if (!process.env.CI) {
afterEach(async function() {
afterEach(async function () {
await server.close()
server = undefined
})
}
let persistence
beforeEach(function() {
beforeEach(function () {
persistence = new RedisTokenPersistence({ key })
})
afterEach(async function() {
afterEach(async function () {
if (persistence) {
await persistence.stop()
persistence = undefined
}
})
context('when the key does not exist', function() {
it('does nothing', async function() {
context('when the key does not exist', function () {
it('does nothing', async function () {
const tokens = await persistence.initialize()
expect(tokens).to.deep.equal([])
})
})
context('when the key exists', function() {
context('when the key exists', function () {
const initialTokens = ['a', 'b', 'c'].map(char => char.repeat(40))
beforeEach(async function() {
beforeEach(async function () {
await redis.sadd(key, initialTokens)
})
it('loads the contents', async function() {
it('loads the contents', async function () {
const tokens = await persistence.initialize()
expect(tokens.sort()).to.deep.equal(initialTokens)
})
context('when tokens are added', function() {
it('saves the change', async function() {
context('when tokens are added', function () {
it('saves the change', async function () {
const newToken = 'e'.repeat(40)
const expected = initialTokens.slice()
expected.push(newToken)
@@ -80,8 +80,8 @@ describe('Redis token persistence', function() {
})
})
context('when tokens are removed', function() {
it('saves the change', async function() {
context('when tokens are removed', function () {
it('saves the change', async function () {
const expected = Array.from(initialTokens)
const toRemove = expected.pop()

View File

@@ -13,10 +13,7 @@ const PriorityQueue = require('priorityqueuejs')
* @returns {string} hash
*/
function sanitizeToken(id) {
return crypto
.createHash('sha256')
.update(id, 'utf-8')
.digest('hex')
return crypto.createHash('sha256').update(id, 'utf-8').digest('hex')
}
function getUtcEpochSeconds() {

View File

@@ -11,27 +11,27 @@ function expectPoolToBeExhausted(pool) {
}).to.throw(Error, /^Token pool is exhausted$/)
}
describe('The token pool', function() {
describe('The token pool', function () {
const ids = ['1', '2', '3', '4', '5']
const batchSize = 3
let tokenPool
beforeEach(function() {
beforeEach(function () {
tokenPool = new TokenPool({ batchSize })
ids.forEach(id => tokenPool.add(id))
})
it('allValidTokenIds() should return the full list', function() {
it('allValidTokenIds() should return the full list', function () {
expect(tokenPool.allValidTokenIds()).to.deep.equal(ids)
})
it('should yield the expected tokens', function() {
it('should yield the expected tokens', function () {
ids.forEach(id =>
times(batchSize, () => expect(tokenPool.next().id).to.equal(id))
)
})
it('should repeat when reaching the end', function() {
it('should repeat when reaching the end', function () {
ids.forEach(id =>
times(batchSize, () => expect(tokenPool.next().id).to.equal(id))
)
@@ -40,17 +40,17 @@ describe('The token pool', function() {
)
})
describe('serializeDebugInfo should initially return the expected', function() {
beforeEach(function() {
describe('serializeDebugInfo should initially return the expected', function () {
beforeEach(function () {
sinon.useFakeTimers({ now: 1544307744484 })
})
afterEach(function() {
afterEach(function () {
sinon.restore()
})
context('sanitize is not specified', function() {
it('returns fully sanitized results', function() {
context('sanitize is not specified', function () {
it('returns fully sanitized results', function () {
// This is `sha()` of '1', '2', '3', '4', '5'. These are written
// literally for avoidance of doubt as to whether sanitization is
// happening.
@@ -79,8 +79,8 @@ describe('The token pool', function() {
})
})
context('with sanitize: false', function() {
it('returns unsanitized results', function() {
context('with sanitize: false', function () {
it('returns unsanitized results', function () {
expect(tokenPool.serializeDebugInfo({ sanitize: false })).to.deep.equal(
{
allValidTokenIds: ids,
@@ -101,8 +101,8 @@ describe('The token pool', function() {
})
})
context('tokens are marked exhausted immediately', function() {
it('should be exhausted', function() {
context('tokens are marked exhausted immediately', function () {
it('should be exhausted', function () {
ids.forEach(() => {
const token = tokenPool.next()
token.update(0, Token.nextResetNever)
@@ -112,8 +112,8 @@ describe('The token pool', function() {
})
})
context('tokens are marked after the last request', function() {
it('should be exhausted', function() {
context('tokens are marked after the last request', function () {
it('should be exhausted', function () {
ids.forEach(() => {
const token = times(batchSize, () => tokenPool.next()).pop()
token.update(0, Token.nextResetNever)
@@ -123,8 +123,8 @@ describe('The token pool', function() {
})
})
context('tokens are renewed', function() {
it('should keep using them', function() {
context('tokens are renewed', function () {
it('should keep using them', function () {
const tokensToRenew = ['2', '4']
const renewalCount = 3
@@ -149,16 +149,16 @@ describe('The token pool', function() {
})
})
context('tokens reset', function() {
context('tokens reset', function () {
let clock
beforeEach(function() {
beforeEach(function () {
clock = sinon.useFakeTimers()
})
afterEach(function() {
afterEach(function () {
clock.restore()
})
it('should start using them', function() {
it('should start using them', function () {
const tokensToReset = ['2', '4']
const futureTime = 1440
@@ -183,8 +183,8 @@ describe('The token pool', function() {
})
})
context('when empty', function() {
it('next() should return the expected error', function() {
context('when empty', function () {
it('next() should return the expected error', function () {
const tokenPool = new TokenPool()
expect(() => tokenPool.next()).to.throw('Token pool is exhausted')
})

View File

@@ -1,19 +1,17 @@
'use strict'
describe('Main page', function() {
describe('Main page', function () {
const backendUrl = Cypress.env('backend_url')
const SEARCH_INPUT = 'input[placeholder="search / project URL"]'
function expectBadgeExample(title, previewUrl, pattern) {
cy.contains('tr', `${title}:`)
.find('code')
.should('have.text', pattern)
cy.contains('tr', `${title}:`).find('code').should('have.text', pattern)
cy.contains('tr', `${title}:`)
.find('img')
.should('have.attr', 'src', previewUrl)
}
it('Search for badges', function() {
it('Search for badges', function () {
cy.visit('/')
cy.get(SEARCH_INPUT).type('pypi')
@@ -21,7 +19,7 @@ describe('Main page', function() {
cy.contains('PyPI - License')
})
it('Shows badge from category', function() {
it('Shows badge from category', function () {
cy.visit('/category/chat')
expectBadgeExample(
@@ -31,7 +29,7 @@ describe('Main page', function() {
)
})
it('Suggest badges', function() {
it('Suggest badges', function () {
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
cy.visit('/')
@@ -41,7 +39,7 @@ describe('Main page', function() {
expectBadgeExample('GitHub issues', badgeUrl, badgeUrl)
})
it('Customization form is filled with suggested badge details', function() {
it('Customization form is filled with suggested badge details', function () {
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
@@ -53,7 +51,7 @@ describe('Main page', function() {
cy.get('input[name="repo"]').should('have.value', 'shields')
})
it('Customizate suggested badge', function() {
it('Customizate suggested badge', function () {
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')

View File

@@ -25,7 +25,7 @@ and learn about the [Github workflow](http://try.github.io/).
#### Node, NPM
Node 8 or later is required. If you don't already have them,
Node 10 or later is required. If you don't already have them,
install node and npm: https://nodejs.org/en/download/
### Setup a dev install

View File

@@ -7,7 +7,7 @@ The Shields codebase is divided into several parts:
1. The frontend (about 7% of the code)
1. [`frontend`][frontend]
2. The badge renderer (which is available as an npm package)
1. [`gh-badges`][gh-badges]
1. [`badge-maker`][badge-maker]
3. The base service classes (about 8% of the code, and probably the most important
code in the codebase)
1. [`core/base-service`][base-service]
@@ -24,7 +24,7 @@ The Shields codebase is divided into several parts:
1. [`lib/suggest.js`][suggest]
[frontend]: https://github.com/badges/shields/tree/master/frontend
[gh-badges]: https://github.com/badges/shields/tree/master/gh-badges
[badge-maker]: https://github.com/badges/shields/tree/master/badge-maker
[base-service]: https://github.com/badges/shields/tree/master/core/base-service
[server]: https://github.com/badges/shields/tree/master/core/server
[token-pooling]: https://github.com/badges/shields/tree/master/core/token-pooling
@@ -36,7 +36,7 @@ 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. `gh-badges/**/*.spec.js`
1. `badge-maker/**/*.spec.js`
3. Unit and functional tests of the core code
1. `core/**/*.spec.js`
4. Unit and functional tests of the service helper functions

View File

@@ -36,38 +36,7 @@
There are [too many bottlenecks][issue 2577]!
## Badge servers
There are three public badge servers on OVH VPSs.
| Cname | Hostname | Type | IP | Location |
| --------------------------- | -------------------- | ---- | -------------- | ------------------ |
| [s0.servers.shields.io][s0] | vps71670.vps.ovh.ca | VPS | 192.99.59.72 | Quebec, Canada |
| [s1.servers.shields.io][s1] | vps244529.ovh.net | VPS | 51.254.114.150 | Gravelines, France |
| [s2.servers.shields.io][s2] | vps117870.vps.ovh.ca | VPS | 149.56.96.133 | Quebec, Canada |
- These are single-core virtual hosts with 2 GB RAM [VPS SSD 1][].
- The Node version (v9.4.0 at time of writing) and dependency versions on the
servers can be inspected in Sentry, but only when an error occurs.
- The servers use self-signed SSL certificates. ([#1460][issue 1460])
- After accepting the certificate, you can debug an individual server using
the links above.
- The scripts that start the server live in the [ServerScript][] repo. However
updates must be pulled manually. They are not updated as part of the deploy process.
- The server runs SSH.
- Deploys are made using a git post-receive hook.
- The server uses systemd to automatically restart the server when it crashes.
- Provisioning additional servers is a manual process which is yet to been
documented.
- The public servers _do not_ use docker. The `Dockerfile` is included for
self-hosting (including on a Docker-capable PaaS).
[s0]: https://s0.servers.shields.io/index.html
[s1]: https://s1.servers.shields.io/index.html
[s2]: https://s2.servers.shields.io/index.html
[vps ssd 1]: https://www.ovh.com/world/vps/vps-ssd.xml
[issue 1460]: https://github.com/badges/shields/issues/1460
[serverscript]: https://github.com/badges/ServerScript
[issue 2577]: https://github.com/badges/shields/issues/2577
## Attached state
@@ -147,64 +116,25 @@ hosted on [Zeit Now][]. It's managed in the
## Deployment
To set things up for deployment:
The deployment is done in two stages: the badge server (heroku) and the front-end (gh-pages).
1. Get your SSH key added to the server.
2. Clone a fresh copy of the repository, dedicated for deployment.
(Not required, but recommended; and lets you use `npm ci` below.)
3. Add remotes:
### Heroku
After merging a commit to master, heroku should create a staging deploy. Check this has deployed correctly in the `shields-staging` pipeline and review http://shields-staging.herokuapp.com/
If we're happy with it, "promote to production". This will deploy what's on staging to the `shields-production-eu` and `shields-production-us` pieplines.
### Frontend
To deploy the front-end to GH pages, use a clean clone of the shields repo.
```sh
git remote add s0 root@s0.servers.shields.io:/home/m/shields.git
git remote add s1 root@s1.servers.shields.io:/home/m/shields.git
git remote add s2 root@s2.servers.shields.io:/home/m/shields.git
$ git pull # update the working copy
$ npm ci # install dependencies (devDependencies are needed to build the frontend)
$ make deploy-gh-pages # build the frontend and push it to the gh-pages branch
```
`origin` should point to GitHub as usual.
4. Since the deploy uses `git worktree`, make sure you have git 2.5 or later.
To deploy:
1. Use `git fetch` to obtain a current copy of
`local-shields-io-production.yml` from the server (or obtain the current
version of that file some other way). Save it in `config/`.
2. Check out the commit you want to deploy.
3. Run `npm ci`. **This is super important for the frontend build!**
4. Run `make deploy-s0` to make a canary deploy.
5. Check the canary deploy:
- [Visit the server][s0]. Don't forget that most of the preview badges
are static!
- Look for errors in [Sentry][].
- Keep an eye on the [status page][status].
6. After a little while (usually 1060 minutes), finish the deploy:
`make push-s1 push-s2 deploy-gh-pages`.
To roll back, check out the commit you want to roll back to and repeat those
steps.
To see which commit is deployed to a server run `git ls-remote` and then
`git log` on the `HEAD` ref. There will be two deploy commits preceded by the
commit which was deployed.
Be careful not to push the deploy commits to GitHub.
`make deploy-s0` does the following:
1. Creates a working tree in `/tmp`.
2. In that tree, runs `features` and `examples` to generate data files
needed for the frontend.
3. Builds and checks in the built frontend.
4. Checks in `local-shields-io-production.yml`.
5. Pushes to s0, which updates dependencies and then restarts itself.
`make push-s1 push-s2 deploy-gh-pages` does the following:
1. Pushes the same working tree to s1 and s2.
2. Creates a new working tree for the frontend.
3. Adds a commit cleaning out the index.
4. Adds another commit with the build frontend.
5. Pushes to `gh-pages`.
No secrets are required to build or deploy the frontend.
## DNS
@@ -214,7 +144,7 @@ DNS is registered with [DNSimple][].
## Logs
Logs are available on the individual servers via SSH.
Logs can be retrieved [from heroku](https://devcenter.heroku.com/articles/logging#log-retrieval).
## Error reporting
@@ -248,11 +178,18 @@ Request performance is monitored in two places:
[notifications]: http://shields.redsparr0w.com/discord_notification
[monitor discord]: https://discordapp.com/channels/308323056592486420/470700909182320646
## Known limitations
## Legacy servers
1. The only way to inspect the commit on the server is with `git ls-remote`.
2. The production deploy installs `devDependencies`. It does not honor
`package-lock.json`. ([#1988][issue 1988])
There are three legacy servers on OVH VPSs which are currently used for proxying.
[issue 2577]: https://github.com/badges/shields/issues/2577
[issue 1988]: https://github.com/badges/shields/issues/1988
| Cname | Hostname | Type | IP | Location |
| --------------------------- | -------------------- | ---- | -------------- | ------------------ |
| [s0.servers.shields.io][s0] | vps71670.vps.ovh.ca | VPS | 192.99.59.72 | Quebec, Canada |
| [s1.servers.shields.io][s1] | vps244529.ovh.net | VPS | 51.254.114.150 | Gravelines, France |
| [s2.servers.shields.io][s2] | vps117870.vps.ovh.ca | VPS | 149.56.96.133 | Quebec, Canada |
[s0]: https://s0.servers.shields.io/index.html
[s1]: https://s1.servers.shields.io/index.html
[s2]: https://s2.servers.shields.io/index.html
The only way to inspect the commit on the server is with `git ls-remote`.

View File

@@ -24,7 +24,7 @@ module.exports = class ExampleService extends LegacyService {
static registerLegacyRouteHandler({ camp, cache }) {
camp.route(
/^\/example\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
cache(function(data, match, sendBadge, request) {
cache(function (data, match, sendBadge, request) {
var first = match[1]
var second = match[2]
var format = match[3]

View File

@@ -2,13 +2,13 @@
## Installation
You will need Node 8 or later, which you can install using a
You will need Node 10 or later, which you can install using a
[package manager][].
On Ubuntu / Debian:
```sh
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -; sudo apt-get install -y nodejs
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -; sudo apt-get install -y nodejs
```
```sh
@@ -78,7 +78,7 @@ $ docker run --rm -p 8080:80 --name shields shields
# or if you have shields.env file, run the following instead
$ docker run --rm -p 8080:80 --env-file shields.env --name shields shields
> gh-badges@1.1.2 start /usr/src/app
> badge-maker@3.0.0 start /usr/src/app
> node server.js
http://[::1]/

View File

@@ -5,7 +5,7 @@ const isSvg = require('is-svg')
const got = require('./core/got-test-client')
let server
before(function() {
before(function () {
this.timeout('5s')
// remove args comming from mocha
// https://github.com/badges/shields/issues/3365
@@ -13,17 +13,14 @@ before(function() {
server = require('./server')
})
after('shut down the server', async function() {
after('shut down the server', async function () {
await server.stop()
})
it('should render a badge', async function() {
it('should render a badge', async function () {
const { statusCode, body } = await got(
'http://localhost:1111/badge/fruit-apple-green.svg'
)
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('fruit')
.and.to.include('apple')
expect(body).to.satisfy(isSvg).and.to.include('fruit').and.to.include('apple')
})

View File

@@ -65,6 +65,7 @@ interface BadgeProps extends React.HTMLAttributes<HTMLImageElement> {
display?: 'inline' | 'block' | 'inline-block'
height?: string
clickable?: boolean
object?: boolean
}
export function Badge({
@@ -73,11 +74,20 @@ export function Badge({
display = 'inline',
height = '20px',
clickable = false,
object = false,
...rest
}: BadgeProps): JSX.Element {
return (
<BadgeWrapper clickable={clickable} display={display} height={height}>
{src ? <img alt={alt} src={src} {...rest} /> : nonBreakingSpace}
{src ? (
object ? (
<object data={src}>alt</object>
) : (
<img alt={alt} src={src} {...rest} />
)
) : (
nonBreakingSpace
)}
</BadgeWrapper>
)
}

View File

@@ -0,0 +1,171 @@
import React, { Fragment } from 'react'
import styled from 'styled-components'
// @ts-ingnore
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { baseUrl } from '../../constants'
import Meta from '../meta'
// @ts-ignore
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>
))}
</>
)
}
interface StyleExamples {
title: string
badges: BadgeData[]
}
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 {
return (
<StyledTable>
<thead>
<tr>
<td>Description</td>
<td>Badges (new)</td>
<td>Badges (old)</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="http://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

@@ -345,7 +345,15 @@ export default function Usage({ baseUrl }: { baseUrl: string }): JSX.Element {
snippet="?logo=appveyor"
/>
<QueryParam
documentation={<span>Insert custom logo image ( 14px high)</span>}
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,…"
/>

View File

@@ -1,7 +1,7 @@
import { test, given } from 'sazerac'
import { patternToOptions, removeRegexpFromPattern } from './pattern-helpers'
describe('Badge URL functions', function() {
describe('Badge URL functions', function () {
test(patternToOptions, () => {
given('[^\\/]+?').expect(undefined)
given('abc|[^\\/]+').expect(undefined)

View File

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

View File

@@ -2,7 +2,7 @@ import { test, given, forCases } from 'sazerac'
import { predicateFromQuery } from './service-definition-set-helper'
import { Example } from '.'
describe('Badge example functions', function() {
describe('Badge example functions', function () {
function exampleMatchesQuery(
{ examples }: { examples: Example[] },
query: string

View File

@@ -21,6 +21,12 @@ const { categories } = yaml.safeLoad(
// 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(
'./frontend/components/development/style-page.tsx'
),
})
createPage({
path: '/dev/logos',
component: require.resolve(

View File

@@ -1,43 +0,0 @@
'use strict'
/**
* @module gh-badges
*/
const makeBadge = require('./make-badge')
/**
* BadgeFactory
*/
class BadgeFactory {
constructor(options) {
if (options !== undefined) {
console.error(
'BadgeFactory: Constructor options are deprecated and will be ignored'
)
}
}
/**
* Create a badge
*
* @param {object} format Object specifying badge data
* @param {string[]} format.text Badge text in an array e.g: ['build', 'passing']
* @param {string} format.labelColor (Optional) Label color
* @param {string} format.color (Optional) Message color
* @param {string} format.colorA (Deprecated, Optional) alias for `labelColor`
* @param {string} format.colorscheme (Deprecated, Optional) alias for `color`
* @param {string} format.colorB (Deprecated, Optional) alias for `color`
* @param {string} format.format (Optional) Output format: 'svg' or 'json'
* @param {string} format.template (Optional) Visual template e.g: 'flat'
* see {@link https://github.com/badges/shields/tree/master/gh-badges/templates}
* @returns {string} Badge in SVG or JSON format
* @see https://github.com/badges/shields/tree/master/gh-badges/README.md
*/
create(format) {
return makeBadge(format)
}
}
module.exports = {
BadgeFactory,
}

View File

@@ -1,20 +0,0 @@
'use strict'
const { expect } = require('chai')
const isSvg = require('is-svg')
const { BadgeFactory } = require('.')
const bf = new BadgeFactory()
describe('BadgeFactory class', function() {
it('should produce badge with valid input', function() {
expect(
bf.create({
text: ['build', 'passed'],
format: 'svg',
colorscheme: 'green',
template: 'flat',
})
).to.satisfy(isSvg)
})
})

View File

@@ -1,185 +0,0 @@
'use strict'
const fs = require('fs')
const path = require('path')
const SVGO = require('svgo')
const dot = require('dot')
const anafanafo = require('anafanafo')
const { normalizeColor, toSvgColor } = require('./color')
// cache templates.
const templates = {}
const templateFiles = fs.readdirSync(path.join(__dirname, '..', 'templates'))
dot.templateSettings.strip = false // Do not strip whitespace.
templateFiles.forEach(async filename => {
if (filename[0] === '.') {
return
}
const templateData = fs
.readFileSync(path.join(__dirname, '..', 'templates', filename))
.toString()
const extension = path.extname(filename).slice(1)
const style = filename.slice(0, -`-template.${extension}`.length)
// Compile the template. Necessary to always have a working template.
templates[style] = dot.template(templateData)
// Substitute dot code.
const mapping = new Map()
let mappingIndex = 1
const untemplatedSvg = templateData.replace(/{{.*?}}/g, match => {
// Weird substitution that currently works for all templates.
const mapKey = `99999990${mappingIndex}.1`
mappingIndex++
mapping.set(mapKey, match)
return mapKey
})
const svgo = new SVGO()
const { data, error } = await svgo.optimize(untemplatedSvg)
if (error !== undefined) {
console.error(
`Template ${filename}: ${error}\n` +
' Generated untemplated SVG:\n' +
`---\n${untemplatedSvg}---\n`
)
return
}
// Substitute dot code back.
let svg = data
const unmappedKeys = []
mapping.forEach((value, key) => {
let keySubstituted = false
svg = svg.replace(RegExp(key, 'g'), () => {
keySubstituted = true
return value
})
if (!keySubstituted) {
unmappedKeys.push(key)
}
})
if (unmappedKeys.length > 0) {
console.error(
`Template ${filename} has unmapped keys ` +
`${unmappedKeys.join(', ')}.\n` +
' Generated untemplated SVG:\n' +
`---\n${untemplatedSvg}\n---\n` +
' Generated template:\n' +
`---\n${svg}\n---\n`
)
return
}
templates[style] = dot.template(svg)
})
function escapeXml(s) {
if (s === undefined || typeof s !== 'string') {
return undefined
} else {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
}
function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
/*
note: makeBadge() is fairly thinly wrapped so if we are making changes here
it is likely this will impact on the package's public interface in index.js
*/
module.exports = function makeBadge({
format,
template,
text,
colorscheme,
color,
colorA,
colorB,
labelColor,
logo,
logoPosition,
logoWidth,
links = ['', ''],
}) {
// String coercion and whitespace removal.
text = text.map(value => `${value}`.trim())
let [left, right] = text
color = normalizeColor(color || colorB || colorscheme)
labelColor = normalizeColor(labelColor || colorA)
// This ought to be the responsibility of the server, not `makeBadge`.
if (format === 'json') {
return JSON.stringify({
label: left,
message: right,
logoWidth,
color,
labelColor,
link: links,
name: left,
value: right,
})
}
if (!(template in templates)) {
if (template === 'popout-square') {
template = 'flat-square'
} else {
template = 'flat'
}
}
if (template === 'social') {
left = capitalize(left)
} else if (template === 'for-the-badge') {
left = left.toUpperCase()
right = right.toUpperCase()
}
let leftWidth = (anafanafo(left) / 10) | 0
// Increase chances of pixel grid alignment.
if (leftWidth % 2 === 0) {
leftWidth++
}
let rightWidth = (anafanafo(right) / 10) | 0
// Increase chances of pixel grid alignment.
if (rightWidth % 2 === 0) {
rightWidth++
}
logoWidth = +logoWidth || (logo ? 14 : 0)
let logoPadding
if (left.length === 0) {
logoPadding = 0
} else {
logoPadding = logo ? 3 : 0
}
const context = {
text: [left, right],
escapedText: [left, right].map(escapeXml),
widths: [leftWidth + 10 + logoWidth + logoPadding, rightWidth + 10],
links: links.map(escapeXml),
logo: escapeXml(logo),
logoPosition,
logoWidth,
logoPadding,
colorA: toSvgColor(labelColor),
colorB: toSvgColor(color),
escapeXml,
}
const templateFn = templates[template]
// The call to template() can raise an exception.
return templateFn(context)
}

View File

@@ -1,231 +0,0 @@
'use strict'
const { test, given, forCases } = require('sazerac')
const { expect } = require('chai')
const snapshot = require('snap-shot-it')
const isSvg = require('is-svg')
const makeBadge = require('./make-badge')
function testColor(color = '', colorAttr = 'colorB') {
return JSON.parse(
makeBadge({
text: ['name', 'Bob'],
[colorAttr]: color,
format: 'json',
})
).color
}
describe('The badge generator', function() {
describe('color test', function() {
test(testColor, () => {
// valid hex
forCases([
given('#4c1'),
given('#4C1'),
given('4C1'),
given('4c1'),
]).expect('#4c1')
forCases([
given('#abc123'),
given('#ABC123'),
given('abc123'),
given('ABC123'),
]).expect('#abc123')
// valid rgb(a)
given('rgb(0,128,255)').expect('rgb(0,128,255)')
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)')
// valid hsl(a)
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)')
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)')
// CSS named color.
given('papayawhip').expect('papayawhip')
// Shields named color.
given('red').expect('red')
given('green').expect('green')
given('blue').expect('blue')
given('yellow').expect('yellow')
forCases(
// invalid hex
given('#123red'), // contains letter above F
given('#red'), // contains letter above F
// invalid rgb(a)
given('rgb(220,128,255,0.5)'), // has alpha
given('rgba(0,0,255)'), // no alpha
// invalid hsl(a)
given('hsl(360,50%,50%,0.5)'), // has alpha
given('hsla(0,50%,101%)'), // no alpha
// neither a css named color nor colorscheme
given('notacolor'),
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus')
).expect(undefined)
})
})
describe('color aliases', function() {
test(testColor, () => {
forCases([
given('#4c1', 'color'),
given('#4c1', 'colorB'),
given('#4c1', 'colorscheme'),
]).expect('#4c1')
})
})
describe('SVG', function() {
it('should produce SVG', function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
expect(svg)
.to.satisfy(isSvg)
.and.to.include('cactus')
.and.to.include('grown')
})
it('should always produce the same SVG (unless we have changed something!)', function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
snapshot(svg)
})
})
describe('JSON', function() {
it('should produce the expected JSON', function() {
const json = makeBadge({
text: ['cactus', 'grown'],
format: 'json',
links: ['https://example.com/', 'https://other.example.com/'],
})
expect(JSON.parse(json)).to.deep.equal({
name: 'cactus',
label: 'cactus',
value: 'grown',
message: 'grown',
link: ['https://example.com/', 'https://other.example.com/'],
})
})
it('should replace unknown svg template with "flat"', function() {
const jsonBadgeWithUnknownStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'unknown_style',
})
const jsonBadgeWithDefaultStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'flat',
})
expect(jsonBadgeWithUnknownStyle)
.to.equal(jsonBadgeWithDefaultStyle)
.and.to.satisfy(isSvg)
})
it('should replace "popout-square" svg template with "flat-square"', function() {
const jsonBadgeWithUnknownStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'popout-square',
})
const jsonBadgeWithDefaultStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'flat-square',
})
expect(jsonBadgeWithUnknownStyle)
.to.equal(jsonBadgeWithDefaultStyle)
.and.to.satisfy(isSvg)
})
})
describe('"for-the-badge" template badge generation', function() {
// https://github.com/badges/shields/issues/1280
it('numbers should produce a string', function() {
const svg = makeBadge({
text: [1998, 1999],
format: 'svg',
template: 'for-the-badge',
})
expect(svg)
.to.include('1998')
.and.to.include('1999')
})
it('lowercase/mixedcase string should produce uppercase string', function() {
const svg = makeBadge({
text: ['Label', '1 string'],
format: 'svg',
template: 'for-the-badge',
})
expect(svg)
.to.include('LABEL')
.and.to.include('1 STRING')
})
})
describe('"social" template badge generation', function() {
it('should produce capitalized string for badge key', function() {
const svg = makeBadge({
text: ['some-key', 'some-value'],
format: 'svg',
template: 'social',
})
expect(svg)
.to.include('Some-key')
.and.to.include('some-value')
})
// https://github.com/badges/shields/issues/1606
it('should handle empty strings used as badge keys', function() {
const svg = makeBadge({
text: ['', 'some-value'],
format: 'json',
template: 'social',
})
expect(svg)
.to.include('""')
.and.to.include('some-value')
})
})
describe('badges with logos should always produce the same badge', function() {
it('shields GitHub logo default color (#333333)', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'github',
})
snapshot(svg)
})
it('shields GitHub logo custom color (whitesmoke)', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'github',
logoColor: 'whitesmoke',
})
snapshot(svg)
})
it('simple-icons javascript logo default color (#F7DF1E)', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'javascript',
})
snapshot(svg)
})
it('simple-icons javascript logo custom color (rgba(46,204,113,0.8))', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'javascript',
logoColor: 'rgba(46,204,113,0.8)',
})
snapshot(svg)
})
})
})

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