File: README.md
This is home to Shields.io, a service for concise, consistent, and legible badges in SVG and raster format, which can easily be included in GitHub readmes or any other web page. The service supports dozens of continuous integration services, package registries, distributions, app stores, social networks, code coverage services, and code analysis services. Every month it serves over 1.6 billion images and is used by some of the world's most popular open-source projects, VS Code, Vue.js and Bootstrap to name a few.
This repo hosts:
- The Shields.io frontend and server code
- An NPM library for generating badges
- The badge design specification
Examples
- code coverage percentage:
- stable release version:
- package manager release:
- status of third-party dependencies:
- static code analysis grade:
- SemVer version observance:
- amount of Liberapay donations per week:
- Python package downloads:
- Chrome Web Store extension rating:
- Uptime Robot uptime percentage:
Make your own badges! (Quick example: https://img.shields.io/badge/left-right-f39f37)
Quickstart
Browse a complete list of badges and locate a particular badge by using the search bar or by browsing the categories. Click on the badge to fill in required data elements for that badge type (like your username or repo) and optionally customize (label, colors etc.). And it's ready for use!
Use the button at the bottom to copy your badge url or snippet, which can then be added to places like your GitHub readme files or other web pages.
Contributing
Shields is a community project. We invite your participation through issues and pull requests! You can peruse the contributing guidelines.
When adding or changing a service please add tests.
This project has quite a backlog of suggestions! If you're new to the project, maybe you'd like to open a pull request to address one of them.
You can read a tutorial on how to add a badge.
If you intend on reporting or contributing a fix related to security vulnerabilities, please first refer to our security policy.
Development
- Install Node 24. You can use the package manager of your choice.
- Clone this repository.
- Run
npm cito install the dependencies. - Run
npm startto start the badge server and the frontend dev server. - Open
http://localhost:3000/to view the frontend.
When server source files change, the badge server should automatically restart itself (using nodemon). When the frontend files change, the frontend dev server (docusaurus start) should also automatically reload. However the badge definitions are built only before the server first starts. To regenerate those, either run npm run prestart or manually restart the server.
To debug a badge from the command line, run npm run badge -- /npm/v/nock. It also works with full URLs like npm run badge -- https://img.shields.io/npm/v/nock.
Use npm run debug:server to start server in debug mode. This recipe shows how to debug Node.js application in VS Code.
Snapshot tests ensure we don't inadvertently make changes that affect the SVG or JSON output. When deliberately changing the output, run SNAPSHOT_DRY=1 npm run test:package to preview changes to the saved snapshots, and SNAPSHOT_UPDATE=1 npm run test:package to update them.
The server can be configured to use Sentry (configuration) and Prometheus (configuration).
Our full test suite as well as code coverage are run on a daily basis.
Hosting your own server
There is documentation about hosting your own server.
Related projects
Status badges are used widely across open-source and private software projects. Academics have studied the "signal" badges provide about software project quality. There are many existing libraries for rendering these badges, and alternatives to the hosted Shields badge service. awesome-badges is a curated collection of such resources. Contributions may be considered there. (The presence of a project in that collection should not be interpreted as an endorsement nor promotion from the Shields project)
History
b.adge.me was the original website for this service. Heroku back then had a thing which made it hard to use a toplevel domain with it, hence the odd domain. It used code developed in 2013 from a library called gh-badges, both developed by Thaddée Tyl. The project merged with shields.io by making it use the b.adge.me code and closed b.adge.me.
The original badge specification was developed in 2013 by Olivier Lacan. It was inspired by the Travis CI and similar badges (there were a lot fewer, back then). In 2014 Thaddée Tyl redesigned it with help from a Travis CI employee and convinced everyone to switch to it. The old design is what today is called the plastic style; the new one is the flat style.
You can read more about the project's inception, the motivation of the SVG badge specification, and the specification itself.
Project leaders
Maintainers:
Alumni:
License
Shields.io is dual licensed and distributed under the terms of both the MIT license and the Apache license (Version 2.0).
Community
Thanks to the people and companies who donate money, services or time to keep the project running. https://shields.io/community
File: CHANGELOG.md
Changelog
Note: this changelog is for the shields.io server. The changelog for the badge-maker NPM package is at https://github.com/badges/shields/blob/master/badge-maker/CHANGELOG.md
server-2026-08-15
New configuration:
- Open-ended Dynamic and Endpoint badges can now be disabled via the
public.dynamicAndEndpointBadgesEnabled/DYNAMIC_AND_ENDPOINT_BADGES_ENABLEDconfigurations #12064
Retired badges:
- Retire [WhatPulse] badges #12087
Other changes:
- Disable broken server SPDY mode #12108
- Add [OutageDeck] provider status badge #12096
- Add [DockerLastUpdated] badge for newest image date #12105
- Support [AzureDevOpsCoverage] branch coverage via metric query param #12099
- Show halted status for halted [BitbucketPipelines] pipelines #12098
- Add [ReactNativeDirectory] supported platforms badge #12066
- Move to readthedocs API v3 #11995
- Add [GithubIssues GithubPullRequests] draft filters without changing unfiltered counts #12026
- Fix [ChromeWebStoreUsers] 1M+ counts rendering as "12000,000" not "12M" #12028
- Store [GitHub] accepted token scopes #11915
- Add [Buildbot] build status badge #11938
- Dependency updates
server-2026-07-23
- [readthedocs] Move to readthedocs API v3 #11995
- Remove decamelize and humanize-string Dependabot exclusions #12032
- [GithubIssues GithubPullRequests] Add draft filters without changing unfiltered counts #12026
- [ChromeWebStoreUsers] Fix 1M+ counts rendering as "12000,000" not "12M" #12028
- [GitHub] Store accepted token scopes #11915
- Migrate [Jenkins] tests and examples from Eclipse to FreeBSD #12016
- [Buildbot] Add Buildbot build status badge #11938
- Fix [Symfony] tests #12017
- Fix [Codecov] tests #12019
- Fix [Codacy] tests #12018
- Update simple-icons to 16.26.0 #12020
- Dependency updates
server-2026-07-15
Important notice:
- The project was switched to MIT and Apache 2.0 licenses. See our blog post for more information.
- Node.js 22 is now the minimum supported version to run Shields.io.
Retired badges:
- Retire [Cirrus] badges #11924
Other changes:
- Add [GithubCheckSuites] badge and update [GithubCheckRuns] #11839
- Add an answered/unanswered variant of [GitHubDiscussions] #11951
- Make the [AzureDevops] build badge send the PAT so it works on private projects #11945
- Support multiple licenses in [NpmLicense] #11956
- Add [Macports] package version badge #11936
- Add [WingetReleaseDate] badge #11919
- Add OpenGraph image #11987
- Remake Shields.io favicon #11947
- Stop disallowing unknown keys for [Endpoint] badges #11942
- Dependency updates
server-2026-06-15
- Optimise SVG badge sizes #11860
- Add [GnomeExtensions] version badges #11875
- Add [Cangjie] version badges #11789
- Streamline [JenkinsCoverage] support #11867
- Delete old retired services #11863
- Update simple-icons to 16.21.0 #11880
- Other dependency updates
server-2026-05-15
- feat: add [SdkmanVersion] badge #11806
- feat: improve badges color contrast #11783
- Dependency updates
server-2026-04-15
Noteable changes:
- change deprecated badge messages to 'retired badge' [Bit Codeclimate Codeship Coincap DockerCloud GithubWorkflowStatus Hackage NostrBand Pub Security-Headers VisualStudioAppCenter VisualStudioMarketplace Wikiapiary] #11798
- Migrate [Polymart] to [VoxelShop] #11786
Deprecation:
- deprecate [VisualStudioMarketplace] #11792
Other changes:
- fix(nuget): fix double URL encoding for non-ASCII package names; test [Nuget MyGet] #11795
- Validate [WordPress] versions schema #11787
- fix: strip build metadata suffix from packageManager field (fixes #9514) [GithubPackageJson] #11782
- feat: add GitHub branch count badge (fixes #11283) #11781
- production-hosting: Update Sentry #11778
- Update [Cookbook] host #11735
- Tests updates
- Dependency updates
server-2026-03-15
Important notice:
- The
cors.allowedOriginandfetchLimitconfiguration options have been be removed. Please make sure they're no longer present in your configuration.
Deprecation:
Other changes:
- Replace @sentry/node with @sentry/node-core/light #11731
- Fix [Reuse] unregistered handling #11729
- Dependency updates
server-2026-02-15
- Add not-planned and duplicate to issue state to [GithubIssueDetail] #11666
- Fix: remove null allowance from schema in [GitLabIssues] #11665
- Used the package version list to get version from [Feedz] #11660
- Fix [GitHub] issue detail merged state for PRs #11642
- Add subpath exports support for [bundlejs] #11639
- Update [crates] dependents badge description to include timeout issue #11635
- Increase cache time for [crates] dependents badge #11636
- Dependency updates
server-2026-01-15
Important notice:
- The
cors.allowedOriginconfiguration option is no longer supported and will soon be removed. Please remove it from your configuration. - Several badges URLs have been updated, see #11584 for more information.
Other changes:
- Add link to [GithubFollowers] badge #11590
- Improve GitHub check runs documentation #11612
- Encourage repository stars #11613
- Adapt [YouTube] channel not found handling #11615
- Remove [Reuse] 'Not a Git repository' handling #11614
- Increase PyPI caches #11621
- Prefer
published_atin github-release-date service #11616 - Make badge builder sticky #11593
- Cache some badges for longer #11588
- Cache deprecated badges for a day #11582
- Fix [Symfony] schema when no violations are found #11578
- Handle [ClearlyDefined] empty response #11573
- Treat
img.shields.io/favicon.icoas 404 resource #11576 - Set checking-in-out attribute to
text=auto#11574 - Fix [CondaLicense, GreasyForkInstalls, MyGet, mbin] examples
- Dependency updates
server-2025-12-01
Important notice: The fetchLimit configuration option is being replaced by fetchLimitBytes. If you were overriding the default value of fetchLimit, please update your configuration accordingly. See #11430 for more information.
Deprecation:
- Deprecate [NostrBand] bagdes #11519
- Deprecate [DockerCloud] badges #11501
- Deprecate [CodeClimate] service #11480
- Deprecate [BitComponents] service #11446
Other changes:
- Return 200 status code for not found badges #11479
- Cache defaultpypiBaseUrl [PyPI] value #11532
- Fix [SwaggerValidator] badges by switching to HTTPS #11518
- Migrate [FlathubVersion] badge to v2 API #11507
- Enhance static badge documentation with examples for style, color, and logo options #11263
- Allow to run on Node.js 24 #11505
- Fix GitHub token pool debug logging and add metrics #11500
- Remove legacy configuration checks #11424
- Increase [NpmDownloads] cache #11488
- Dependency updates and removals
server-2025-11-01
- Add [GnomeExtensions] Downloads Badge #11443
- Fix [Sonar] examples #11440
- [BundleJS] Add format query parameter support #11427
- Remove footer dark style to enable theme switching #11431
- Replace query-string with qs #11425
- [BundleJS] Add externals query parameter support #11400
- Add displayAssetName option to [GitHubDownloads] badge #11415
- Increase [VisualStudioMarketplace] cache multiplier #11413
- Handle [Aur] empty license arrays #11412
- Fix [ChromeWebStore] add null check for extension not found in ChromeWebStore #11414
- [Bluesky] Add followers and posts badges #11405
- Replace all instances of twitter.com with x.com #11409
- Document [ChromeWebStore] region exclusion #11407
- Dependency updates and removals
server-2025-10-02
Deprecation:
- Deprecate [JenkinsPluginInstalls] per version #10747
Other changes:
- update Mbin magazine param description #11389
- Update [Fedora] not found handling #11385
- respect arch param when sort=semver in [DockerVersion] badge #10905
- Handle invalid [Crates] version #11384
- Update [FreeCodeCamp] API #11386
- Update labels of deprecated services [GithubWorkflowStatus PubPopularity] #11369
- Add support for codecov components #11370
- Drop popout style fallback #11367
- [GitHubHacktoberfest] 2025 support #11353
- Delete old deprecated services #11352
- feat: Add GitLab go.mod Go version service #11312
- documentation updates #10849, #11324
- devop updates #11378, #11379, #11351, #11365
- CI tests fixes #11387, #11388, #11381, #11380, #11366
- Dependency updates
server-2025-09-02
- deprecate [VisualStudioAppCenter] #11321
- update [StackExchangeMonthlyQuestions] example and live test #11320
- increase cache length for [VisualStudioMarketplace] #11311
- Dependency updates
server-2025-09-01
- docs: add note to [GithubContributors] about co-authors limitations #11260
- Add [UptimeObserver] monitoring shields support #11147
- Dependency updates
server-2025-08-03
- increase [SourceforgeDownloads] cache time #11226
- Dependency updates
server-2025-07-01
Important notice: Updated node from 20 to 22.
- fix/improve maven metadata version badges; affects [maven gradle] #11077
- fix PHP version parsing #11128
- update [polymart] schema #11073
- deploy on node 22 #11070
- improve performance of lookup simple-icons #11129
- [DepsRs] Add Deps.rs dependency status badges #11107
- add icon svg #11126
- Dependency updates
server-2025-06-01
- Remove Non-Functional SVG Stroke Attribute in Social Badge Renderer #11095
- deprecate [pub] popularity badge #11074
- deprecate [coincap] service #11075
- Support [F-Droid] custom repos #10792
- fix: use decimal rating for Firefox extension #11068
- set shorter upstream request timeout on [endpoint] and [dynamic] badges #10996
- Dependency updates
server-2025-05-01
- New experimental [DynamicRegex] badge #10985
- limit label and message to 255 chars #10998
- [PyPI] Fix license for packages following PEP 639 #11001
- Dependency updates
server-2025-04-01
- fix(#10970): codecov branch badge #10971
- docker: cleanup some building #10957
- graceful shutdown #10958
- Update [SourceForge] commit count badge to support repository parameter #10954
- feat: add new badges for new [ansible] collection APIs #10938
- [sourceforge] add repo param to last commit service #10935
- tweak default cache lengths by category #10919
- [Packagist] Update default domain for repo metadata #10920
- use SVG2 hrefs #10918
- remove
logoWidthparam #10878 - Dependency updates
server-2025-03-02
- time out long running requests more aggressively #10833
- Dependency updates
server-2025-02-02
- Mark Stubs-only packages with [PypiTypes] badge #10864
- fix badge style when logo only #10794
- pass matching mime type to xmldom; test [dynamicxml] #10830
- allow [chromewebstore] size to contain decimal point #10812
- Add auth support to [Reddit] badges #10790
- Fixed mixed up Code climate endpoints #10813
- feat: add terraform registry providers and modules downloads #10793
- Renew [Mastodon] docs and improve parameter handling #10789
- Support [Matrix] summary endpoint #10782
- use metric() in [coderabbit] badge #10779
- cache matrix badges for 4 hours #10778
- Dependency updates
server-2025-01-01
- Add [PypiTypes] badge #10774
- feat(endpoint-badge): add logoSize support #10132
- fix auto-sized logo sizes #10764
- Add [Coderabbit] PR Stats service and tests #10749
- add [PUB] downloads badge #10745
- Add [GitLab] Top Language Badge #10750
- provide a non-repository scoped version of [githubcodesearch] #10733
- [ReproducibleCentral] add Reproducible Central in Dependencies #10705
- Add ability to format bytes as metric or IEC; affects [bundlejs bundlephobia ChromeWebStoreSize CratesSize DockerSize GithubRepoSize GithubCodeSize GithubSize NpmUnpackedSize SpigetDownloadSize steam VisualStudioAppCenterReleasesSize whatpulse] #10547
- Dependency updates
server-2024-12-01
- add [WingetVersion] Badge #10245
- Fix broken URL for pingpong.one #10655
- [npm] - Last update badge added #10641
- reduce overhead of NPM Last Update badge; test [npm] #10666
- Add YouTube-specific privacy notes #10646
- Dependency updates
server-2024-11-02
- cleanly handle null or undefined result from jsonpath-plus #10645
- add content security policy header to SVG responses #10642
- [Scoop] Added scoop-license badge. #10627
- [Chromewebstore] Extension size & last updated #10613
- Deprecate HackageDeps service #10618
- Add [CratesUserDownloads] service and tester #10619
- [Snapcraft] - Added snapcraft last update badge #10610
- [GitHubHacktoberfest] 2024 support #10612
- add [homebrew] cask download badge #10595
- remove prefix v for commit hash version #10597
- [Maven] Added badge for Maven-Cenral last-update (#10301) #10585
- [DynamicXml] parse doc as html if served with text/html content type #10607
- Revert "Use old.stats.jenkins.io for JSON data (#10522)" #10537
- catch queries that cause TypeError #10556
- Dependency updates
server-2024-09-25
This release includes an important security fix. See
- https://github.com/badges/shields/security/advisories/GHSA-rxvx-x284-4445
- https://github.com/badges/shields/issues/10553
for more details
- [dynamicjson dynamicyaml dynamictoml] switch to jsonpath-plus #10551
- [Snapcraft] license #10520
- deprecate [wheelmap] service #10538
- Use old.stats.jenkins.io for JSON data #10522
- catch xml ParseError #10516
- migrate [MozillaObservatory] to /scan endpoint #10491
- fix incorrect codecov config link #10511
- [OSSLifecycle OSSLifecycleRedirect] Add file_url param to pull from non-github sources #10489
- perf: improve logoSize performance #10488
- perf: faster
resetIconPositionavoiding to parse path twice #10497 - perf: limit logoSize precision to 3 #10521
- Dependency updates
server-2024-09-02
- Publish linux/amd64 docker images for snapshot builds #10476
- Fix Gitea not having credentials/authorizedOrigins in Docker environments #10486
- fix typo in pepy downloads #10475
- ignore a couple of docusaurus warnings #10469
- Use Ecologi API to power Treeware badges #10467
- move go version badge to platform support category #10444
- [Crates] Implement Dependents Badge #10438
- [Crates] Added crate size badge #10421
- Dependency updates
server-2024-08-01
- send Cross-Origin-Resource-Policy header on all responses #10420
- migrate [MozillaObservatory] to new API #10402
- use metric() for [discord] and [revolt] badges #10406
- Cache text only static badges for longer #10403
- Fix [FreeCodeCampPoints] not found handling #10377
- Fix [Gitea] not found message #10373
- Deprecate [Bountysource] service #10371
- Sunset Shields custom logos #10347
- Use ellipsis when many versions returned for [ModrinthGameVersions] #10350
- deprecate [tokei] service #9581
- Add CF-Ray header value to Sentry errors if available #10339
- Use XML for Chocolatey, affects [Chocolatey Resharper PowershellGallery] #10344
- include github contributors badge in docs site #10337
- Dependency updates
server-2024-07-01
- Add [AUR] Popularity Badge #10304
- fix npm badges when
maintainersnot in response #10286 - Expose
logoBase64andlinksin badge-maker NPM package #10283 - Remove
logoPosition#10284 - [MBIN] Add subscribers badge #10270
- Add [Docker] support for loong64 arch #10241
- Add puppetforge quality score badges #10201
- Dependency updates
server-2024-06-01
- Remove namedLogo from defaultBadgeData of non-social badges #10195
- Update number of badges served each month #10197
- Delete old deprecated services #10196
- handle [BitbucketPipelines] responses with missing result key #10163
- Update description of GitHub commit status badge #10198
- chore: fix spelling of GitHub in badge descriptions #10199
- Add [GithubCheckRuns] service #7759
- feat: add Revolt badge #10093
- ensure color is string before calling toLowerCase() #10129
- instruct dependabot to monitor composite actions #10139
- run tests on node 22 #10127
- tweaks to libraries.io token pooling code #10074
- fix [pypi] status badge when package has no 'Development Status' classifier #10107
- clarify yml paths in server-secrets docs #10106
- Update region flag name in flyctl deploy command #10134
- Dependency updates
server-2024-05-01
- [Hexpm] Fix badges for pre-release only versions #10112
- feat(logos): support auto-sizing mode #9191 #10110 #10125
- support setting pypiBaseUrl by environment variables and queryParameters; affects [pypi] #10044
- Add 0BSD license to licenseTypes and [PypiLicense] #10092
- Update Mastodon profile URL #10082
- [GitHubGoMod] Ignore comment after version (fixes #10079) #10080
- Perf: Librariesio repo dependencies #10062
- [Chocolatey Nuget] Fix "not found" error for chocolatey badge #10060
- Dependency updates
server-2024-04-01
- improve performance of [GithubLastCommit] [GitlabLastCommit] [GiteaLastCommit] #10046
- [BitbucketLastCommit] Add Bitbucket last commit #10043
- [GithubLastCommit] [GitlabLastCommit] [GiteaLastCommit] Support file path for last commit #10041
- upgrade to docusaurus 3 #9820
- redirect [npm] /dt to /d18m #10033
- Add [JSR] version service #10030
- Add [snapcraft] version badge #9976
- Dependency updates
server-2024-03-01
- feat(gitea): add last commit badge #9995
- [GithubCreatedAt] Add Created At Badge for Github #9981
- Added custom bucket url support for [Scoop] #9984
- [NpmUnpackedSize] Unpacked Size Badge #9954
- [Website] Render
status: downbadge if website is unresponsive #9966 - deprecate TAS #9932
- [GITEA] add forks, stars, issues and pr badges #9923
- tolerate missing short_version in [visualstudioappcenter] #9951
- [Crates] Only use non-yanked crate versions (ready for merge) #9949
- Dependency updates
server-2024-02-01
- feat: added up_message and down_message to [uptimerobotstatus] #9662
- Add [Hangar] Badges #9800
- sort categories by title (except core) #9888
- Add Support for [Nostr] Followers #9870
- [thunderstore] replace experimental API usage with newly available v1 API #9886
- Update [Gitea] defaults to gitea.com #9872
- [crates] MSRV Badge #9871
- Add [galaxytoolshed] Version #8249
- fix default style docs for social badges #9869
- Dependency updates
server-2024-01-01
The most important changes in this release for users hosting their own instance are:
The shields docker image is now based on node 20:
- deploy on node 20 #9799
It is now possible to use authentication for DockerHub to allow higher API rate limit or access to private repos:
- call [docker] with auth #9803
New Badges
- [Thunderstore] Add Thunderstore Badges #9782
- Add [Raycast] Badge #9801
- [GITEA] add new gitea service (release/languages) #9781
- Add [NpmStatDownloads] Badge #9783
Frontend Changes
- improve documentation for [dynamicxml] service #9798
- add description to interval enums #9854
- convert 'style' param to enum #9853
- Ensure social category badges are rendered with social style and logo; affects [gitlab keybase lemmy modrinth thunderstore twitch] gist github reddit #9859
Fixes
- [pub] Use official version endpoint for pub-service #9802
- cache weblate badges for longer #9786
- [Discourse] Update schema keys to use plural form (
topic_count->topics_count) #9778 - cache some badges for longer #9785
- increase page size for github release badge by semver #9818
- Dependency updates
server-2023-12-04
- move from @renovate/pep440 to @renovatebot/pep440 #9614
- deprecate/fix [ansible] galaxy services #9648
- call [pepy] with auth #9748
- add meaningful descriptions including keywords #9715
- Dependency updates
server-2023-11-01
- fix greasyfork 404 bug #9632
- Hacktoberfest 2023 support - resolves #9636 #9637
- switch to fixed OpenCollective images #9615
- Dependency updates
server-2023-10-02
- add python package total downloads from [pepy] badge #9564
- deprecate [redmine] plugin rating badges #9568
- fix [bower] version badge #9567
- Add [PythonVersionFromToml] shield #9516
- Add [dub] score badge service #9549
- Dependency updates
server-2023-09-04
- Fix [testspace] badges #9525
- fix rSt code example #9528
- Add dynamic TOML support via [DynamicToml] Service #9517
- cache [pypi] downloads for longer #9522
- [twitter] --> x #9496
- [bundlejs] add badge for the npm package size #9055
- Switch [OpenCollective] badges to use GraphQL and auth #9387
- [Pulsar] Add Pulsar Badges for Stargazers & Downloads #8767
- Add [CurseForge] badges #9252
- deploy on node 18 #9385
- allow calling [github] without auth #9427
- Dependency updates
server-2023-08-01
- Convert
examplesarrays toopenApiobjects (part 1) #9320 - Migrate from docs.rs' builds API to status API #9422
- [OpenVSX] Fix OpenVSX API call for unversioned package URLs #9408
- Add support for [Lemmy] #9368
- upgrade to npm 9 #9323
- Go back to default YouTube cache #9372
- Add [GitHubDiscussionsSearch] and GitHubRepoDiscussionsSearch service #9340
- Allow user to filter github tags and releases #9193
- don't URL encode slash in [githubactionsworkflow] badge #9322
- add a bit of border to select boxes #9348
- deprecate [snyk] badges #9349
- increase max-age on [docker] badges, again #9350 #9369
- Dependency updates
server-2023-07-02
By far the most significant change in this release is the long-awaited launch of the re-designed frontend:
- migrate frontend to docusaurus #9014
- fix a load of spacing issues in frontend content #9281
- set a sensible meta description #9283
- chore(frontend): open homepage feature links in new tab #9300
- adapt opencollective images to theme background #9298
- temp fix: wrap code examples tabs in narrow browser windows #9302
- add a bit of border to text boxes #9324
Other changes in this release:
- cache [dockerpulls] badges for an hour #9343
- Mention YouTube API services and link to Google Privacy Policy #9339
- allow negative timestamps in relative [date] badge #9321
- upgrade to graphql 16 #9290
- remove obsolete travis .org examples #9284
- increase max age on reddit badges #9282
- feat: Add author filter option for [GithubCommitActivity] #9251
- Fix: [GithubCommitActivity] invalid branch error handling #9258
- Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] #9233
- Delete old deprecated services #9254
- feat: add 'canceled' status to netlify deploy badge #9240
- increase default cache on youtube badges #9238
- embiggen youtube cache, again #9250
- Dependency updates
server-2023-06-01
- feat: Add total commits to [GitHubCommitActivity] #9196
- set a custom error on 429 #9159
- deprecate [travis].org badges #9171
- count private sponsors on [GithubSponsors] badge #9170
- Dependency updates
server-2023-05-01
Removal: For users who need to maintain a Github Token pool, storage has been provided via the RedisTokenPersistence and REDIS_URL settings. This feature was deprecated in server-2023-03-01. As of this release, the RedisTokenPersistence backend is now removed. If you are using this feature, you will need to migrate to using the SQLTokenPersistence backend for storage and provide a postgres connection string via the POSTGRES_URL setting. #8922
- fail to start server if there are duplicate service names #9099
- [SourceForge] Added badges for SourceForge #9078 #9102
- crates: Use
?include=to reduce crates.io backend load #9081 - Dependency updates
server-2023-04-02
- [JenkinsCoverage] Update Jenkins Code Coverage API for new plugin version #9010
- [CTAN] fallback to date if version is empty #9036
- Update to [CTAN] API version 2.0 #9016
- handle missing statistics array in [VisualStudioMarketplace] badges #8985
- [Netlify] upgrade colors for SVG parsing #8971
- Fix [Vcpkg] version service for different version fields #8945
- only try to close pool if one exists #8947
- misc minor fixes to [githubsize node pypi] #8946
- Dependency updates
server-2023-03-01
Deprecation: For users who need to maintain a Github Token pool, storage has been provided via the RedisTokenPersistence and REDIS_URL settings. As of this release, the RedisTokenPersistence backend is now deprecated and will be removed in a future release. If you are using this feature, you will need to migrate to using the SQLTokenPersistence backend for storage and provide a postgres connection string via the POSTGRES_URL setting. #8922
- fix: for crates.io versions, use max_stable_version if it exists #8687
- don't autofocus search #8927
- Add [Vcpkg] version service #8923
- fix: Set uid/gid in docker image to 0 #8908
- expose port 443 in Dockerfile #8889
- Dependency updates
server-2023-02-01
- replace [twitter] badge with static fallback #8842
- Add various [Polymart] badges #8811
- update [githubpipenv] tests/examples #8797
- deprecate [apm] service #8773
- deprecate lgtm #8771
- Dependency updates
server-2023-01-01
- Breaking change: Routes for GitHub workflows badge have changed. See https://github.com/badges/shields/issues/8671 for more details
- Behaviour change: In this release we fixed a long standing bug. GitHub badges were previously not reading the base URL from the
config.service.baseUri. This release fixes that bug, bringing the code into line with the documented behaviour. This should not cause a behaviour change for most users, but users who had previously set a value inconfig.service.baseUriwhich was previously ignored could see this now have an effect. Users who configure their instance using env vars rather than yaml should see no change. - Send
X-GitHub-Api-Versionwhen calling [GitHub] v3 API #8669 - add [VpmVersion] badge #8755
- Add [modrinth] game versions #8673
- fix debug logging of undefined query params #8540, #8757
- fall back to classifiers if [pypi] license text is really long #8690
- allow passing key to [stackexchange] #8539
- Dependency updates
server-2022-12-01
- fix: support logoColor to shield icons. #8263
- handle missing properties array in [VisualStudioMarketplaceVersion] #8603
- deprecate [wercker] service #8642
- Add [Coincap] Cryptocurrency badges #8623
- Add [modrinth] version #8604
- [factorio-mod-portal] services #8625
- [Coveralls] for GitLab #8584, #8644
- Remove 'suggest badges' feature #8311
- Add [modrinth] followers #8601
- Update the [modrinth] API to v2 #8600
- tidy up [GitHubGist] routes #8510
- fix [flathub] version error handling #8500
- Dependency updates
server-2022-11-01
- [Ansible] Add collection badge #8578
- [VisualStudioMarketplace] Add support to prerelease extensions version (Issue #8207) #8561
- feat: add [GitlabLastCommit] service #8508
- fix [swagger] service tests (allow 0 items in array) #8564
- fix codecov badge for non-default branch #8565
- Add [GitHubLastCommit] by committer badge #8537
- [GitHubReleaseDate] - published_at field #8543
- Fix [Testspace] with new "untested" value in case_counts array #8544
- fix: Support WAITING status for GitHub deployments #8521
- [Whatpulse] badge for a user and for a team #8466
- deprecate [pkgreview] service #8499
- Dependency updates
server-2022-10-08
- deprecate [criterion] service #8501
- fix formatRelativeDate error handling; run [date] #8497
- allow/validate bitbucket_username / bitbucket_password in private config schema #8472
- fix [pub] points badge test and example #8498
- feat: add [GitlabLanguageCount] service #8377
- [GitHubGistStars] add GitHub Gist Stars #8471
- fix display/search of CII badge examples #8473
- feat: add 2022 support to GitHub Hacktoberfest #8468
- fix [GitLabCoverage] subgroup bug #8401
- implement ruby gems-specific version sort/color functions #8434
- Add
rcto pre-release identifiers #8435 - add [GitHub] Number of commits between branches/tags/commits #8394
- add [Packagist] dependency version #8371
- fix Docker build status invalid response data bug #8392
- Dependency updates
server-2022-09-04
- fix frontend compile for users running on Windows #8350
- [DockerSize] Docker image size multi arch #8290
- upgrade gatsby #8334
- Custom domains for [JitPack] artifacts #8333
- fix [dockerstars] service #8316
- [BountySource] Fix: Broken Badge generation for decimal activity values #8315
- feat: add [gitlabmergerequests] service #8166
- Fix terminology for [ROS] version service #8292
- feat: add [GitlabStars] service #8209
- Fix invalid
rstformat whenaltortargetis present #8275 - [GithubGistLastCommit] GitHub gist last commit #8272
- [GitHub] GitHub file size for a specific branch #8262
- Dependency updates
server-2022-08-01
- [pypi] Add Framework Version Badges support #8261
- feat: add [GitlabForks] server #8208
- Update PyPI api according to https://warehouse.pypa.io/api-reference/json.html #8251
- Add [galaxytoolshed] Activity #8164
- [greasyfork] Add Greasy Fork rating badges #8087
- refactor(deps): Replace moment with dayjs #8192
- add spaces round pipe in [conda] badge #8189
- Add [ROS] version service #8169
- feat: add [gitlabissues] service #8108
- Dependency updates
server-2022-07-03
- Add [galaxytoolshed] services #8114
- fix [gitlab] auth #8145 #8162
- increase cache length on AUR version badge, run [AUR] #8110
- Use GraphQL to fix GitHub file count badges [github] #8112
- feat: add [gitlab] contributors service #8084
- [greasyfork] Add Greasy Fork service badges #8080
- Add [gitlablicense] services #8024
- [Spack] Package Manager: Update Domain #8046
- switch [jitpack] to use latestOk endpoint #8041
- Dependency updates
server-2022-06-01
- Update GitLab logo (2022) #7984
- [GitHub] Added milestone property to GitHub issue details service #7864
- [Spack] Package Manager: Update Endpoint #7957
- Update Chocolatey API endpoint URL #7952
- [Flathub]Add downloads badge #7724
- replace the outdated Telegram logo with the newest #7831
- add [PUB] points badge #7918
- add [PUB] popularity badge #7920
- add [PUB] likes badge #7916
- Dependency updates
server-2022-05-03
- [OSSFScorecard] Create scorecard badge service #7687
- Stringify [githublanguagecount] message #7881
- Stringify and trim whitespace from a few services #7880
- add labels to Dockerfile #7862
- handle missing 'fly-client-ip' #7814
- Dependency updates
server-2022-04-03
- Breaking change: This release updates ioredis from v4 to v5. If you are using redis for GitHub token pooling, redis connection strings of the form
redis://junkusername:[email protected]:1234will need to be updated toredis://:[email protected]:1234. See the ioredis upgrade guide for further details. - fix installation issue on npm >= 8.5.5 #7809
- two fixes for [packagist] schemas #7782
- allow requireCloudflare setting to work when hosted on fly.io #7781
- fix [pypi] badges when package has null license #7761
- Add a [pub] publisher badge #7715
- Switch Steam file size badge to informational color #7722
- Make W3C and Youtube documentation links clickable #7721
- Improve Wercker examples #7720
- Improve Cirrus CI examples #7719
- Support [CodeClimate] responses with multiple data items #7716
- Delete [TeamCityCoverage] and [BowerVersion] redirectors #7718
- Deprecate [Shippable] service #7717
- fix: restore version comparison updates from #4173 #4254
- [piwheels], filter out versions with no files #7696
- set a longer cacheLength on [librariesio] badges #7692
- improve python version formatting #7682
- Clarify GitHub All Contributors badge #7690
- Support [HexPM] packages with no stable release #7685
- Add Test at Scale Badge #7612
- [packagist] api v2 support #7681
- Add [piwheels] version badge #7656
- Dependency updates
server-2022-03-01
- Add [Conan] version service (#7460)
- remove suspended [github] tokens from the pool #7654
- generate links without trailing : if port not set #7655
- Use the latest build status when checking docs.rs #7613
- Remove no download handling and add API warning to [Wordpress] badges #7606
- set a higher default cacheLength on rating/star category #7587
- Update [amo] to use v4 API, set custom
cacheLengths #7586 - fix(amo): include trailing slash in API call #7585
- fix docker image user agent #7582
- Delete deprecated Codetally and continuousphp services #7572
- Deprecate [Requires] service #7571
- [AUR] Fix RPC URL #7570
- Dependency updates
server-2022-02-01
- [Depfu] Add support for Gitlab #7475
- replace label in hn-user-karma with U/ #7500
- Support [Feedz] response with multiple pages without items #7476
- revert decamelize and humanize-string to old versions #7449
- Dependency updates
server-2022-01-01
- minor [reddit] improvements #7436
- [HackerNews] Show User Karma #7411
- [YouTube] Drop support for removed dislikes #7410
- change closed GitHub issue color to purple #7374
- restore cors header injection from #4171 #4255
- [GithubPackageJson] Get version from monorepo subfolder package.json #7350
- Dependency updates
server-2021-12-01
- Send better user-agent values #7309 Self-hosting users now send a user agent which indicates the server version and starts
shields (self-hosted)/by default. This can be configured using the env varUSER_AGENT_BASE - upgrade to node 16 #7271
- feat: deprecate dependabot badges #7274
- fix: npmversion tagged service test #7269
- feat: create new Test Results category #7218
- Migration from Request to Got for all HTTP requests is completed in this release
- Dependency updates
server-2021-11-04
- migrate regularUpdate() from request-->got #7215
- migrate github badges to use got instead of request; affects [github librariesio] #7212
- deprecate David badges #7197
- fix: ensure libraries.io header values are processed numerically #7196
- Add authentication for Libraries.io-based badges, run [Libraries Bower] #7080
- fixes and tests for pipenv helpers #7194
- add GitLab Release badge, run all [GitLab] #7021
- set content-length header on badge responses #7179
- fix [github] release/tag/download schema #7170
- Supported nested groups on [GitLabPipeline] badge #7159
- Support nested groups on [GitLabTag] badge #7158
- Fixing incorrect JetBrains Plugin rating values for [JetBrainsRating] #7140
- support using release or tag name in [GitHub] Release version badge #7075
- feat: support branches in sonar badges #7065
- Add [Modrinth] total downloads badge #7132
- remove [github] admin routes #7105
- Dependency updates
server-2021-10-04
- feat: add 2021 support to GitHub Hacktoberfest #7086
- Add [ClearlyDefined] service #6944
- handle null licenses in crates.io response schema, run [crates] #7074
- [OBS] add Open Build Service service-badge #6993
- Correction of badges url in self-hosting configuration with a custom port. Issue 7025 #7036
- fix: support gitlab token via env var #7023
- Add API-based support for [GitLab] badges, add new GitLab Tag badge #6988
- [freecodecamp]: allow + symbol in username #7016
- Rename Riot to Element in Matrix badge help #6996
- Fixed Reddit Negative Karma Issue #6992
- Dependency updates
server-2021-09-01
- use multi-stage build to reduce size of docker images #6938
- remove disableStrictSsl param from [jenkins] #6887
- refactor(GitHubCommitActivity): switch to v4/GraphQL API #6959
- feat: add freecodecamp badge #6958
- use the right version of NPM in docker build #6941
- [TwitchExtensionVersion] New badge #6900
- enforce strict SSL checking for [coverity] #6886
- Update self hosting docs #6877
- Support optionalDependencies in [GithubPackageJson] #6749
- Dependency updates
server-2021-08-01
- use v5 API for [AUR] badges #6836
- [Sonar] Fix invalid fetch query to sonarqube >=6.6 #6636
- Delegate discord logo to simple-icons, which matches the current branding #6764
- Re-apply 'Migrate request to got (part 1)' #6755
- Delete old deprecated badges #6756
- Replace opn-cli with open-cli #6747
- Verify that Node 14 is installed in development #6748
- Migrate from CommonJS to ESM #6651
- Add Wikiapiary Extension Badge [WikiapiaryInstalls] #6678
- deprecate [beerpay] #6708
- deprecate [microbadger] #6709
- [npmsioscore] Support npm score #6630
- Add [Weblate] badges #6677
- Dependency updates
server-2021-07-01
- improve [MavenCentral], [MavenMetadata], and [GradlePluginPortal] #6628
- fix: fix regex to match [codecov]'s flags #6655
- fix usage style #6638
- update simple-icons to v5 with by-name lookup backwards compatibility #6591
- [GradlePluginPortal] add gradle plugin portal #6449
- upgrade some vulnerable packages #6569
- increase max-age for download and social badges #6567
- Dependency updates
server-2021-06-01
- Changed creating badges to open a new Window/Tab #6536
- Make for-the-badge letter spacing more predictable, and rewrite layout logic #5754
- deprecate DockerBuild service #6529
- Remove rate limiting functionality #6513
- [GitHub] Move to 'funding' category #5846
- Add GitHub discussions total badge [GithubTotalDiscussions] #6472
- Add optional query parameter (include_prereleases) to [GemVersion] #6451
- Add [PingPong] Service #6327
- Dependency updates
server-2021-05-01
- Add setting which allows setting a timeout on HTTP requests This is configured with the new
REQUEST_TIMEOUT_SECONDSsetting. If a request takes longer than this number of seconds a408 Request Timeoutresponse will be returned. - Deprecate [Bintray] service #6423
- Support git hash in [nexus] SNAPSHOT version #6369
- Replace 4183C4 with blue #6366
- [Youtube] Added channel view count and subscriber count badges #6333
- Fix Netlify badge by adding new color schema #6340
- [REUSE] Add service badges #6330
- Dependency updates
server-2021-04-01
- Use NPM packages to provide fonts instead of Google Fonts #6274
- Prevent duplication of parameters in badge examples #6272
- Add docs for all types of releases #6210
- refresh self-hosting docs #6273
- allow missing 'goal' key in [liberapay] badges #6258
- use got to push influx metrics #6257
- Dependency updates
server-2021-03-01
- ensure redirect target path is correctly encoded #6229
- [SecurityHeaders] Added a possibility for no follow redirects #6212
- catch URL parse error in [endpoint] badge #6214
- [Homebrew] Add homebrew downloads badge #6209
- update [pkgreview] url #6189
- Make [Twitch] a social badge #6183
- update [flathub] error handling #6185
- Add [Testspace] badges #6162
- Dependency updates
server-2021-02-01
- Docs.rs badge (#6098)
- Fix feedz service in case the package page gets paginated (#6101)
- [Bitbucket] Server Adding Auth Tokens and Resolving Pull Request api … (#6076)
- Dependency updates
server-2021-01-18
- Gotta start somewhere
File: CODE_OF_CONDUCT.md
Contributor Covenant Code of Conduct
Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
1. Correction
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
2. Warning
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
3. Temporary Ban
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
File: CONTRIBUTING.md
Contributing to Shields
Shields is a community project. We invite your participation through financial contributions, issues, and pull requests!
Ways you can help
Financial contributions
We welcome financial contributions in full transparency on our open collective.
Contributing code
This project has quite a backlog of suggestions! If you're new to the project, maybe you'd like to open a pull request to address one of them:
Contributing documentation
You can help by improving the project's usage and developer instructions.
Tutorials are in /doc:
- When you read the documentation, you can fix mistakes and add your own thoughts.
- When your pull request follows the documentation but the practice changed, consider pointing this out and change the documentation for the next person.
API documentation is at contributing.shields.io:
- This documentation is generated by annotating the code with JSDoc comments. Example
- Adding a JSDoc comment to some existing code is a great first contribution and a good way to familiarize yourself with the codebase
Helping others
You can help with code review, which reduces bugs, and over time has a wonderful side effect of making the code more readable and therefore more approachable. It's also a great way to teach and learn. Feel free to jump in! Be welcoming, appreciative, and helpful. You can perform first reviews of simple changes, like badge additions. These are usually tagged with service badge.
Please review these impeccable guidelines.
You can monitor issues, discussions and the chat room, and help other people who have questions about contributing to Shields, or using it for their projects.
Feel free to reach out to one of the maintainers if you need help getting started.
Suggesting improvements
There are a lot of suggestions on file. You can help by weighing in on these suggestions, which helps convey community need to other contributors who might pick them up.
There is no need to post a new comment. Just add a :thumbsup: or :heart: to the top post.
If you have a suggestion of your own, search the open issues. If you don't see it, feel free to open a new issue.
Requesting new logos
We consume logos via the SimpleIcons project, and encourage you to contribute logos there. Please review their guidance before doing so.
Spreading the word
Feel free to star the repository. This will help increase the visibility of the project, therefore attracting more users and contributors to Shields!
We're also asking for donations from developers who use and love Shields, please spread the word!
Getting help
There are three places to get help:
- If you're new to the project, a good place to start is the tutorial.
- If you need help getting started or implementing a change, start a discussion with your question. We promise it's okay to do that. If there is already an issue open for the feature you're working on, you can post there directly.
- You can also join the chat room and ask your question there.
Badge guidelines
- Shields.io hosts integrations for services which are primarily used by developers or which are widely used by developers.
- The left-hand side of a badge should not advertise. It should be a lowercase noun succinctly describing the meaning of the right-hand side.
- Except for badges using the
socialstyle, logos and links should be turned off by default. - Badges should not obtain data from undocumented or reverse-engineered API endpoints.
- Badges should not obtain data by scraping web pages - these are likely to break frequently. Whereas API publishers are incentivised to maintain a stable platform for their users, authors of web pages have no such incentive.
- Badges may require users to specify a token in the badge URL as long it is scoped only to fetching information and doesn't expose any sensitive information. Generating a token with the correct scope must be clearly documented.
Badge URLs
- The format of new badges should be of the form
/SERVICE/NOUN/PARAMETERS. - There is further documentation on this in badge-urls
Coding guidelines
Prettier
This project formats its source code using Prettier. The most enjoyable way to use Prettier is to let it format code for you when you save. You can integrate it into your editor.
Whether you integrate it into your editor or not, a pre-commit hook will run Prettier before a commit by default.
Coding style
This project uses lower camelCase for variable names and function names and client facing parameters. We use PascalCase for class names and service names.
Tests
When adding or changing a service please write tests, and ensure the title of your Pull Requests follows the required conventions to ensure your tests are executed. When changing other code, please add unit tests.
The integration tests are not run by default. For most contributions it is OK to skip these unless you're working directly on the code for storing the GitHub token pool in postgres.
To run the integration tests:
- You must have PostgreSQL installed. Use
brew install postgresql,apt-get install postgresql, etc. - Set a connection string either with an env var
POSTGRES_URL=postgresql://user:[email protected]:5432/db_nameor by usingin a yaml config file.yamlprivate: postgres_url: 'postgresql://user:[email protected]:5432/db_name' - Run
npm run migrate upto apply DB migrations - Run
npm run test:integrationto run the tests
Code organization
There is a High-level code walkthrough describing the layout of the project.
Pull Requests
All code changes are incorporated via pull requests, and pull requests are always squashed into a single commit on merging. Therefore there's no requirement to squash commits within your PR, but feel free to squash or restructure the commits on your PR branch if you think it will be helpful. PRs with well structured commits are always easier to review!
Because all changes are pulled into the main branch via squash merges from PRs, we do not support overwriting any aspects of the git history once it hits our main branch. Notably this means we do not support amending commit messages, nor adjusting commit author information once merged.
Accordingly, it is the responsibility of contributors to review this type of information and adjust as needed before marking PRs as ready for review and merging.
You can review and modify your local git configuration via git config, and also find more information about amending your commit messages here.
Running service tests in pull requests
The affected service names must be included in square brackets in the pull request title so that the CI engine will run those service tests. When a pull request affects multiple services, they should be separated with spaces. The test runner is case-insensitive, so they should be capitalized for readability.
For example:
- [Travis] Fix timeout issues
- [Travis Sonar] Support user token authentication
- Add tests for [CRAN] and [CPAN]
Note that many services are part of a "family" of related services. Depending on the changes in your PR you may need to run the tests for just a single service, or for all the services within a family.
For example, a PR title of [GitHubForks] Foo will only run the service tests specifically for the GitHub Forks badge, whereas a title of [GitHub] Foo will run the service tests for all of the GitHub badges.
In the rare case when it's necessary to see the output of a full service-test run in a PR (all 2,000+ tests), include [*****] in the title. Unless all the tests pass, the build will fail, so likely it will be necessary to remove it and re-run the tests before merging.
File: jsdoc.json
{
"source": {
"exclude": ["snapshots", "coverage", "node_modules", "public"],
"excludePattern": ".+\.(service|spec|tester)\.js$"
},
"plugins": ["plugins/markdown"],
"opts": {
"destination": "api-docs/",
"readme": "README.md",
"recurse": true,
"tutorials": "doc/"
}
}
File: package.json
{
"name": "shields.io",
"version": "0.0.0",
"description": "Shields.io server and frontend",
"private": true,
"keywords": [
"GitHub",
"badge",
"SVG",
"image",
"shields.io"
],
"homepage": "https://shields.io",
"bugs": {
"url": "https://github.com/badges/shields/issues"
},
"license": "(MIT OR Apache-2.0)",
"author": "Thaddée Tyl ",
"contributors": [
"Paul Melnikow",
"Pierre-Yves Bigourdan"
],
"repository": {
"type": "git",
"url": "https://github.com/badges/shields"
},
"dependencies": {
"@renovatebot/pep440": "^5.0.1",
"@renovatebot/ruby-semver": "^5.0.0",
"@sentry/node-core": "^10.75.2",
"@shields_io/camp": "^18.1.2",
"@xmldom/xmldom": "0.9.12",
"badge-maker": "file:badge-maker",
"byte-size": "^9.0.1",
"camelcase": "^9.0.0",
"check-node-version": "^4.2.1",
"cloudflare-middleware": "^1.0.4",
"config": "^5.0.1",
"cross-env": "^10.1.0",
"dayjs": "^1.11.23",
"decamelize": "^6.0.1",
"emojic": "^1.1.18",
"fast-xml-parser": "^5.11.1",
"global-agent": "^4.1.3",
"got": "^16.0.0",
"graphql": "17.0.2",
"graphql-tag": "^2.12.7",
"joi": "18.2.9",
"joi-extension-semver": "5.0.0",
"js-yaml": "^5.5.0",
"jsonpath-plus": "^11.2.0",
"lodash.countby": "^4.6.0",
"matcher": "^7.0.0",
"mvncmp": "^1.0.0",
"node-pg-migrate": "^9.0.0",
"parse-link-header": "^2.0.0",
"path-to-regexp": "^6.3.0",
"pg": "^8.23.0",
"priorityqueuejs": "^2.0.0",
"prom-client": "^15.1.3",
"qs": "^6.16.1",
"re2": "^1.27.0",
"semver": "~7.8.5",
"simple-icons": "16.35.0",
"smol-toml": "1.9.0",
"svg-path-bbox": "^2.1.0",
"svgpath": "^2.6.0",
"webextension-store-meta": "^1.3.0",
"xpath": "~0.0.34"
},
"scripts": {
"coverage:test:core": "c8 npm run test:core",
"coverage:test:package": "c8 npm run test:package",
"coverage:test:entrypoint": "c8 npm run test:entrypoint",
"coverage:test:integration": "c8 npm run test:integration",
"coverage:test:services": "c8 npm run test:services",
"coverage:clean": "rimraf coverage",
"precoverage:test": "cross-env BASE_URL=http://localhost:8080 run-s --silent coverage:clean defs",
"coverage:test": "run-s --silent --continue-on-error coverage:test:core coverage:test:package coverage:test:entrypoint coverage:test:integration",
"coverage:report:generate": "c8 report",
"coverage:report:open": "open-cli coverage/lcov-report/index.html",
"coverage:report": "run-s --silent coverage:report:generate coverage:report:open",
"lint": "eslint --concurrency auto "/*.@(cjs|mjs|js|ts)"",
"prettier": "prettier --write "/.@(cjs|mjs|js|ts|md|json|yml)"",
"prettier:check": "prettier --check "**/.@(cjs|mjs|js|ts|md|json|yml)"",
"danger": "danger",
"test:e2e": "cypress run",
"test:core": "cross-env TZ=UTC NODE_CONFIG_ENV=test mocha "core//*.spec.js" "lib//.spec.js" "services/**/.spec.js"",
"test:package": "mocha "badge-maker//*.spec.js"",
"test:entrypoint": "cross-env NODE_CONFIG_ENV=test mocha entrypoint.spec.js",
"test:integration": "cross-env NODE_CONFIG_ENV=test mocha "core//.integration.js" "services/**/.integration.js"",
"test:services": "cross-env NODE_CONFIG_ENV=test mocha core/service-test-runner/cli.js",
"test:services:trace": "cross-env NODE_CONFIG_ENV=test TRACE_SERVICES=true mocha core/service-test-runner/cli.js",
"test:services:pr:prepare": "node core/service-test-runner/pull-request-services-cli.js > pull-request-services.log",
"test:services:pr:run": "cross-env NODE_CONFIG_ENV=test mocha core/service-test-runner/cli.js --stdin < pull-request-services.log",
"test:services:pr": "run-s --silent test:services:pr:prepare test:services:pr:run",
"pretest": "cross-env BASE_URL=http://localhost:8080 run-s --silent defs",
"test": "run-s --silent --continue-on-error lint test:package test:core test:entrypoint check-types:package prettier:check",
"check-types:package": "tsd badge-maker",
"depcheck": "check-node-version --node ">= 24.0"",
"prebuild": "run-s --silent depcheck",
"defs": "node scripts/export-openapi-cli.js",
"build": "rimraf public && run-s defs docusaurus:build",
"heroku-postbuild": "run-s --silent build",
"start:server:prod": "node server",
"now-start": "npm run start:server:prod",
"start:server:e2e-on-build": "node server 8080",
"start:server": "cross-env NODE_CONFIG_ENV=development nodemon server 8080",
"debug:server": "cross-env NODE_CONFIG_ENV=development nodemon --inspect server.js 8080",
"profile:server": "cross-env NODE_CONFIG_ENV=development node --prof server 8080",
"benchmark:badge": "cross-env NODE_CONFIG_ENV=test node scripts/benchmark-performance.js --iterations 10100 | node scripts/capture-timings.js --warmup-iterations 100",
"prestart": "cross-env BASE_URL=http://localhost:8080 run-s --silent depcheck defs",
"start": "concurrently --names server,frontend "npm run start:server" "npm run docusaurus:start"",
"e2e": "cross-env BASE_URL=http://localhost:8080 run-s build e2e-on-build",
"e2e-on-build": "cross-env CYPRESS_BASE_URL=http://localhost:8080 start-server-and-test start:server:e2e-on-build http://localhost:8080 test:e2e",
"badge": "cross-env NODE_CONFIG_ENV=test TRACE_SERVICES=true node scripts/badge-cli.js",
"build-docs": "rimraf api-docs/ && jsdoc --pedantic -c ./jsdoc.json . && echo 'contributing.shields.io' > api-docs/CNAME",
"migrate": "node scripts/write-migrations-config.js > migrations-config.json && node-pg-migrate --config-file=migrations-config.json",
"docusaurus:start": "docusaurus start frontend",
"docusaurus:build": "docusaurus build frontend --out-dir ../public",
"docusaurus:swizzle": "cd frontend && docusaurus swizzle",
"docusaurus:clear": "docusaurus clear frontend"
},
"lint-staged": {
"/*.@(cjs|mjs|js|ts)": [
"eslint --fix --concurrency auto",
"prettier --write"
],
"/.@(md|json|yml)": [
"prettier --write"
]
},
"nodemonConfig": {
"verbose": true,
"ext": "js,yml",
"ignore": [
"package.json",
"**/.spec.js",
"/*.tester.js",
"/*.integration.js",
"frontend/",
"build/",
"cypress/",
".github/"
]
},
"devDependencies": {
"@docusaurus/core": "^3.10.2",
"@docusaurus/preset-classic": "^3.10.2",
"@easyops-cn/docusaurus-search-local": "^0.55.3",
"@mdx-js/react": "^3.1.1",
"@typescript-eslint/parser": "^8.70.1",
"c8": "^12.0.0",
"chai": "6.2.2",
"chai-as-promised": "^8.0.2",
"clsx": "^2.1.1",
"concurrently": "^10.0.5",
"cypress": "^16.1.0",
"cypress-wait-for-stable-dom": "^0.1.0",
"danger": "^14.0.7",
"deepmerge": "^4.3.1",
"docusaurus-preset-openapi": "0.8.0",
"eslint": "9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-chai-friendly": "1.2.1",
"eslint-plugin-cypress": "6.4.3",
"eslint-plugin-icedfrisby": "0.3.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jsdoc": "64.5.4",
"eslint-plugin-mocha": "11.3.0",
"eslint-plugin-prettier": "5.5.6",
"eslint-plugin-promise": "7.3.0",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-sort-class-members": "1.22.1",
"globals": "17.12.0",
"http-deceiver": "file:vendor/http-deceiver",
"icedfrisby": "4.0.0",
"icedfrisby-nock": "^2.1.0",
"is-svg": "^6.1.0",
"jsdoc": "^4.0.5",
"lint-staged": "^17.6.0",
"mocha": "^12.0.3",
"neostandard": "0.13.0",
"nock": "13.5.6",
"node-mocks-http": "^1.18.1",
"nodemon": "^3.1.14",
"npm-run-all": "^4.1.5",
"open-cli": "^9.0.0",
"portfinder": "^1.0.38",
"prettier": "3.9.9",
"prism-react-renderer": "^2.4.1",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"rimraf": "^6.1.3",
"sazerac": "^2.0.0",
"simple-git-hooks": "^2.14.0",
"sinon": "^22.1.0",
"sinon-chai": "4.0.1",
"snap-shot-it": "^7.9.10",
"start-server-and-test": "3.0.12",
"tsd": "^0.33.0",
"url": "^0.11.4"
},
"overrides": {
"debug@>=4.0.0 <4.3.1": "4.3.1",
"http-deceiver": "$http-deceiver"
},
"engines": {
"node": "^24 || ^26",
"npm": "^11 || ^12"
},
"type": "module",
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/shields",
"logo": "https://opencollective.com/opencollective/logo.txt"
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
}
}
File: SECURITY.md
Security Policy
Supported Projects
Please follow this guidance when reporting security issues affecting:
- Shields.io
- Raster.shields.io
- Self-hosted Shields instances
- The squint raster proxy
- The badge-maker NPM package
The gh-badges and svg-to-image-proxy NPM packages are now deprecated and will no longer receive fixes for bugs or security issues.
Reporting a Vulnerability
If you find a security vulnerability affecting any of our supported projects, please email [email protected], rather than opening a public issue on GitHub. After receiving the initial report, we will endeavor to keep you informed of the progress towards a fix and full announcement. We may ask you for additional information. You are also welcome to propose a patch or solution.
Report security bugs in third-party modules to the person or team maintaining the module.
Coordinated Disclosure
We aim to patch confirmed vulnerabilities within 90 days or less, disclosing the details of those vulnerabilities when a patch is published. We ask that you refrain from sharing your report with others while we work on our patch.
We may want to coordinate an advisory with you to be published simultaneously with the patch, but you are also welcome to self-disclose after 90 days if you prefer. We will never publish information about you or our communications with you without your permission.
Bounties
Everyone who works on shields is an unpaid volunteer. That includes the core team, contributors and people who report security vulnerabilities. This means we are unable to offer bug or security bounties.
File: .c8rc.json
{
"reporter": ["lcov"],
"all": true,
"silent": true,
"clean": false,
"exclude": [
"/*.spec.js",
"/.integration.js",
"/test-helpers.js",
"/-test-helpers.js",
"/*-fixtures.js",
"/.test-d.ts",
"dangerfile.js",
"core/service-test-runner",
"core/got-test-client.js",
"services/**/.tester.js",
"services/test-validators.js",
"services/tester.js",
"core/base-service/loader-test-fixtures",
"scripts",
"coverage",
".github",
"**/public/",
"cypress",
"frontend",
"migrations"
]
}
File: .prettierrc.yml
semi: false
singleQuote: true
bracketSpacing: true
endOfLine: lf
arrowParens: avoid
proseWrap: never
File: badge-maker/README.md
badge-maker
Installation
npm install badge-makerUsage
On the console
npm install -g badge-maker
badge build passed :brightgreen > mybadge.svgAs a library
import { makeBadge, ValidationError } from 'badge-maker'const format = {
label: 'build',
message: 'passed',
color: 'brightgreen',
}
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 badge-maker supports all currently maintained Node versions. See the Node Release Schedule.
Format
The format is the following:
{
label: 'build', // (Optional) Badge label
message: 'passed', // (Required) Badge message
labelColor: '#555', // (Optional) Label color
color: '#4c0', // (Optional) Message color
logoBase64: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iOCIgZmlsbD0iI2IxY2U1NiIvPjxwYXRoIGQ9Ik04IDBoMjR2NjRIOGMtNC40MzIgMC04LTMuNTY4LTgtOFY4YzAtNC40MzIgMy41NjgtOCA4LTh6IiBmaWxsPSIjNWQ1ZDVkIi8+PC9zdmc+', // (Optional) Any custom logo can be passed in a URL parameter by base64 encoding
links: ['https://example.com', 'https://example.com'], // (Optional) Links array of maximum two links
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
// Each offers a different visual design.
style: 'flat',
// (Optional) A string with only letters, numbers, -, and _. This can be used
// to ensure every element id within the SVG is unique and prevent CSS
// cross-contamination when the SVG badge is rendered inline in HTML pages.
idSuffix: 'dd'
}Colors
There are three ways to specify color and labelColor:
- One of the Shields named colors:
– the default
labelColor– the default
color– the default
color
- A three- or six-character hex color, optionally prefixed with
#:
- etc.
- Any valid CSS color, e.g.
rgb(...),rgba(...)hsl(...),hsla(...)etc.
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. On the console, the output of badge can be piped to a utility like imagemagick e.g: badge build passed :green | magick svg:- gif:-.
File: badge-maker/CHANGELOG.md
Changelog
6.0.0
Visual Changes
- Generated badges have a new standard palette with higher text contrast. See this PR for more information.
Other Changes
- badge-maker was switched from CC0 to MIT and Apache 2.0 licenses. See this blog post for more information.
- The structure of the generated SVGs has been optimised.
5.0.2
Bug Fixes
- Fix export for require
5.0.1
Bug Fixes
- Fix ESM type exports
Other Changes
- Drop use-strict from badge-maker header
5.0.0
Breaking Changes
- Drop compatibility with Node < 20
- Switch from CommonJS to ESModules
Other Changes
- Switching to using
hrefs instead of the oldxlink:hrefsyntax
4.1.0
Features
- Add
idSuffixparam. This can be used to ensure every element id within the SVG is unique
4.0.0
Breaking Changes
- Drop compatibility with Node < 16
Features
- Add
linksandlogoBase64params
3.3.1
- Improve font measuring in for-the-badge and social styles
- Make for-the-badge letter spacing more predictable
3.3.0
- Readability improvements: a dark font color is automatically used when the badge's background is too light. For example:
- Better CSS color compliance: thanks to a switch from is-css-color to css-color-converter, you can use a wider range of color formats from the latest CSS specification, for example
rgb(0 255 0) - Less dependencies: badge-maker no longer depends on camelcase
3.2.0
- Accessibility improvements: Help users of assistive technologies to read the badges when used inline
3.1.0
- Add TypeScript definitions
3.0.1
- Fix missing dependency
3.0.0
Breaking Changes
- Dropped support for node < 10
- Package name has changed to
badge-makerand moved to https://www.npmjs.com/package/badge-maker BadgeFactoryclass is removed and replaced bymakeBadge()function.- Deprecated parameters have been removed. In version 2.2.0 the
colorA,colorBandcolorschemeparams 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
formatkey has been removed. - The
textarray has been replaced bylabelandmessagekeys. - The
templatekey has been renamedstyle. To upgrade from v2.1.1, change your code from:to:jsconst { BadgeFactory } = require('gh-badges') const bf = new BadgeFactory() const svg = bf.create({ text: ['build', 'passed'], format: 'svg', template: 'flat-square', })jsconst { makeBadge } = require('badge-maker') const svg = makeBadge({ label: 'build', message: 'passed', style: 'flat-square', }) ValidationErrorhad been added and inputs are now validated. In previous releases, invalid inputs would be discarded and replaced with defaults. For example, in 2.2.1would generate an SVG badge. In version >=3jsconst { BadgeFactory } = require('gh-badges') const bf = new BadgeFactory() const svg = bf.create({ text: ['build', 'passed'], template: 'some invalid value', })will throw ajsconst { makeBadge } = require('badge-maker') const svg = makeBadge({ label: 'build', message: 'passed', style: 'some invalid value', })ValidationError.- Raster support has been removed from the CLI. It will now only output SVG. On the console, the output of
badgecan be piped to a utility like imagemagick. If you were previously usingthis could be replaced byshbadge build passed :green .gifshbadge build passed :green | magick svg:- gif:-
Security
- Removed dependency on doT library which has known vulnerabilities.
2.2.1 - 2019-05-30
Fixes
- Escape logos to prevent XSS vulnerability
- Update docblock for BadgeFactory.create()
2.2.0 - 2019-05-29
Deprecations
labelColorandcolorare now the recommended attribute names for label color and message color.colorA(now an alias forlabelColor),colorB(now an alias forcolor) andcolorscheme(now an alias forcolor)
are now deprecated and will be removed in some future release.
New Features
Semantic color aliases. Add support for:
- Add directory field to package.json (to help tools find this package in the repo)
Bug Fixes
- Prevent bad letter spacing when whitespace surrounds badge text
Dependencies
- Bump anafanafo
- Use caret instead of tilde for dependencies
Internals
- Generate JSON badges without using a template
- Refactoring
- Testing improvements
Node support
- Declare support for all currently maintained Node versions
- Explicitly test on all supported versions
2.1.0 - 2018-11-18
gh-badges v2.1.0 implements a new text width measurer which uses a lookup table, removing the dependency on PDFKit. It is no longer necessary to provide a local copy of Verdana for accurate text width computation.
As such, the fontPath and precomputeWidths parameters are now deprecated. The recommended call to create an instance of BadgeFactory is now
const bf = new BadgeFactory()For backwards compatibility you can still construct an instance of BadgeFactory with a call like
const bf = new BadgeFactory({
fontPath: '/path/to/Verdana.ttf',
precomputeWidths: true,
})However, the function will issue a warning.
To clear the warning, change the code to:
const bf = new BadgeFactory()These arguments will be removed in a future release.
To upgrade from v1.3.0, change your code from:
const badge = require('gh-badges')
const format = {
text: ['build', 'passed'],
colorscheme: 'green',
template: 'flat',
}
badge.loadFont('/path/to/Verdana.ttf', err => {
badge(format, (svg, err) => {
// svg is a string containing your badge
})
})to:
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const format = {
text: ['build', 'passed'],
colorscheme: 'green',
template: 'flat',
}
const svg = bf.create(format)Other changes in this release:
- Remove unnecessary dependencies
- Documentation improvements
2.0.0 - 2018-11-09
gh-badges v2.0.0 declares a new public interface which is synchronous. If your version 1.3.0 code looked like this:
const badge = require('gh-badges')
const format = {
text: ['build', 'passed'],
colorscheme: 'green',
template: 'flat',
}
badge.loadFont('/path/to/Verdana.ttf', err => {
badge(format, (svg, err) => {
// svg is a string containing your badge
})
})To upgrade to version 2.0.0, refactor you code to:
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory({ fontPath: '/path/to/Verdana.ttf' })
const format = {
text: ['build', 'passed'],
colorscheme: 'green',
template: 'flat',
}
const svg = bf.create(format)You can generate badges without a copy of Verdana, however font width computation is approximate and badges may be distorted.
const bf = new BadgeFactory({ fallbackFontPath: 'Helvetica' })1.3.0 - 2016-09-07
Add support for optionally specifying the path to Verdana.ttf. In earlier versions, the file needed to be in the directory containing Shields.
Without font path:
const badge = require('gh-badges')
badge({ text: ['build', 'passed'], colorscheme: 'green' }, (svg, err) => {
// svg is a string containing your badge
})With font path:
const badge = require('gh-badges')
// Optional step, to have accurate text width computation.
badge.loadFont('/path/to/Verdana.ttf', err => {
badge(
{ text: ['build', 'passed'], colorscheme: 'green', template: 'flat' },
(svg, err) => {
// svg is a string containing your badge
},
)
})File: badge-maker/package.json
{
"name": "badge-maker",
"version": "6.0.0",
"type": "module",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./lib/index.js",
"default": "./lib/index.js"
}
},
"description": "Shields.io badge library",
"keywords": [
"GitHub",
"badge",
"SVG",
"image",
"shields.io"
],
"types": "index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/badges/shields.git",
"directory": "badge-maker"
},
"author": "Thaddée Tyl ",
"contributors": [
"Paul Melnikow",
"Pierre-Yves Bigourdan"
],
"license": "(MIT OR Apache-2.0)",
"bugs": {
"url": "https://github.com/badges/shields/issues"
},
"homepage": "https://shields.io",
"bin": {
"badge": "lib/badge-cli.js"
},
"engines": {
"node": ">=22"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/shields",
"logo": "https://opencollective.com/opencollective/logo.txt"
},
"dependencies": {
"anafanafo": "2.0.0",
"css-color-converter": "^2.0.0"
},
"scripts": {
"test": "echo 'Run tests from parent dir'; false"
}
}
File: config/custom-environment-variables.yml
public:
bind:
address: 'BIND_ADDRESS'
port: 'PORT'
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'
key: 'HTTPS_KEY'
cert: 'HTTPS_CRT'
redirectUrl: 'REDIRECT_URI'
rasterUrl: 'RASTER_URL'
services:
bitbucket:
authorizedOrigins: 'BITBUCKET_ORIGINS'
bitbucketServer:
authorizedOrigins: 'BITBUCKET_SERVER_ORIGINS'
drone:
authorizedOrigins: 'DRONE_ORIGINS'
gitea:
authorizedOrigins: 'GITEA_ORIGINS'
github:
baseUri: 'GITHUB_URL'
debug:
enabled: 'GITHUB_DEBUG_ENABLED'
intervalSeconds: 'GITHUB_DEBUG_INTERVAL_SECONDS'
gitlab:
authorizedOrigins: 'GITLAB_ORIGINS'
jenkins:
authorizedOrigins: 'JENKINS_ORIGINS'
jira:
authorizedOrigins: 'JIRA_ORIGINS'
nexus:
authorizedOrigins: 'NEXUS_ORIGINS'
npm:
authorizedOrigins: 'NPM_ORIGINS'
obs:
authorizedOrigins: 'OBS_ORIGINS'
pypi:
baseUri: 'PYPI_URL'
sonar:
authorizedOrigins: 'SONAR_ORIGINS'
teamcity:
authorizedOrigins: 'TEAMCITY_ORIGINS'
weblate:
authorizedOrigins: 'WEBLATE_ORIGINS'
trace: 'TRACE_SERVICES'
cacheHeaders:
defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS'
fetchLimitBytes: 'FETCH_LIMIT_BYTES'
userAgentBase: 'USER_AGENT_BASE'
requestTimeoutSeconds: 'REQUEST_TIMEOUT_SECONDS'
requestTimeoutMaxAgeSeconds: 'REQUEST_TIMEOUT_MAX_AGE_SECONDS'
dynamicAndEndpointBadgesEnabled: 'DYNAMIC_AND_ENDPOINT_BADGES_ENABLED'
requireCloudflare: 'REQUIRE_CLOUDFLARE'
private:
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
bitbucket_username: 'BITBUCKET_USER'
bitbucket_password: 'BITBUCKET_PASS'
bitbucket_server_username: 'BITBUCKET_SERVER_USER'
bitbucket_server_password: 'BITBUCKET_SERVER_PASS'
curseforge_api_key: 'CURSEFORGE_API_KEY'
discord_bot_token: 'DISCORD_BOT_TOKEN'
dockerhub_username: 'DOCKERHUB_USER'
dockerhub_pat: 'DOCKERHUB_PAT'
drone_token: 'DRONE_TOKEN'
gitea_token: 'GITEA_TOKEN'
gh_client_id: 'GH_CLIENT_ID'
gh_client_secret: 'GH_CLIENT_SECRET'
gh_token: 'GH_TOKEN'
gitlab_token: 'GITLAB_TOKEN'
jenkins_user: 'JENKINS_USER'
jenkins_pass: 'JENKINS_PASS'
jira_user: 'JIRA_USER'
jira_pass: 'JIRA_PASS'
librariesio_tokens: 'LIBRARIESIO_TOKENS'
nexus_user: 'NEXUS_USER'
nexus_pass: 'NEXUS_PASS'
npm_token: 'NPM_TOKEN'
obs_user: 'OBS_USER'
obs_pass: 'OBS_PASS'
opencollective_token: 'OPENCOLLECTIVE_TOKEN'
outagedeck_api_key: 'OUTAGEDECK_API_KEY'
pepy_key: 'PEPY_KEY'
postgres_url: 'POSTGRES_URL'
readthedocs_token: 'READTHEDOCS_TOKEN'
reddit_client_id: 'REDDIT_CLIENT_ID'
reddit_client_secret: 'REDDIT_CLIENT_SECRET'
sentry_dsn: 'SENTRY_DSN'
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'
sl_insight_apiToken: 'SL_INSIGHT_API_TOKEN'
sonarqube_token: 'SONARQUBE_TOKEN'
stackapps_api_key: 'STACKAPPS_API_KEY'
teamcity_user: 'TEAMCITY_USER'
teamcity_pass: 'TEAMCITY_PASS'
twitch_client_id: 'TWITCH_CLIENT_ID'
twitch_client_secret: 'TWITCH_CLIENT_SECRET'
influx_username: 'INFLUX_USERNAME'
influx_password: 'INFLUX_PASSWORD'
weblate_api_key: 'WEBLATE_API_KEY'
File: config/default.yml
public:
bind:
address: '::'
metrics:
prometheus:
enabled: false
endpointEnabled: false
influx:
enabled: false
timeoutMilliseconds: 1000
intervalSeconds: 15
ssl:
isSecure: false
services:
bitbucket:
authorizedOrigins: 'https://bitbucket.org'
github:
baseUri: 'https://api.github.com'
debug:
enabled: false
intervalSeconds: 200
restApiVersion: '2026-03-10'
obs:
authorizedOrigins: 'https://api.opensuse.org'
pypi:
baseUri: 'https://pypi.org'
weblate:
authorizedOrigins: 'https://hosted.weblate.org'
trace: false
cacheHeaders:
defaultCacheLengthSeconds: 120
handleInternalErrors: true
fetchLimitBytes: 10485760 # 10MB
userAgentBase: 'shields (self-hosted)'
requestTimeoutSeconds: 120
requestTimeoutMaxAgeSeconds: 30
allowUnsecuredEndpointRequests: false
dynamicAndEndpointBadgesEnabled: true
requireCloudflare: false
private: {}
File: config/development.yml
public:
bind:
address: 'localhost'
handleInternalErrors: false
File: config/local-shields-io-production.template.yml
private:
These are the keys which are set on the production servers.
curseforge_api_key: ...
discord_bot_token: ...
gh_client_id: ...
gh_client_secret: ...
gitlab_token: ...
outagedeck_api_key: ...
readthedocs_token: ...
reddit_client_id: ...
reddit_client_secret: ...
sentry_dsn: ...
shields_secret: ...
sl_insight_userUuid: ...
sl_insight_apiToken: ...
twitch_client_id: ...
twitch_client_secret: ...
weblate_api_key: ...
wheelmap_token: ...
File: config/local.template.yml
Copy this file to `config/local.yml` and fill it in! It will be gitignored.
private:
The possible values are documented in `doc/server-secrets.md`. Note that
you can also set these values through environment variables, which may be
preferable for self hosting.
curseforge_api_key: '...'
gh_token: '...'
gitlab_token: '...'
obs_user: '...'
obs_pass: '...'
readthedocs_token: '...'
reddit_client_id: '...'
reddit_client_secret: '...'
twitch_client_id: '...'
twitch_client_secret: '...'
weblate_api_key: '...'
wheelmap_token: '...'
File: config/production.yml
public:
bind:
address: '0.0.0.0'
File: config/shields-io-production.yml
public:
metrics:
prometheus:
enabled: true
influx:
enabled: true
url: https://metrics.shields.io/telegraf
instanceIdFrom: env-var
instanceIdEnvVarName: FLY_ALLOC_ID
envLabel: shields-production
ssl:
isSecure: false
services:
gitlab:
authorizedOrigins: 'https://gitlab.com'
rasterUrl: 'https://raster.shields.io'
userAgentBase: 'Shields.io'
requireCloudflare: true
requestTimeoutSeconds: 8
File: config/test.yml
public:
bind:
address: 'localhost'
port: 1111
redirectUrl: 'http://frontend.example.test'
rasterUrl: 'http://raster.example.test'
handleInternalErrors: false
allowUnsecuredEndpointRequests: false
File: cypress/.eslintrc.yml
plugins:
- cypress
env:
cypress/globals: true
File: doc/adding-new-config-values.md
Adding New Config Values
The Badge Server supports a variety of methods for defining configuration settings and secrets, and provides a framework for loading those values during bootstrapping.
Any new configuration setting or secret must be correctly registered so that it will be loaded at startup along with the others.
This generally includes adding the corresponding information for your new setting(s)/secret(s) to the following locations:
- core/server/server.js - Add the new values to the schemas. Secrets/tokens/etc. should go in the
privateConfigSchemawhile non-secret configuration settings should go in thepublicConfigSchema. - config/custom-environment-variables.yml
- docs/server-secrets.md (only applicable for secrets)
- config/default.yml (optional)
- Any other template config files (e.g.
config/local.template.yml) (optional)
The exact values needed will depend on what type of secret/setting you are adding, but for reference a few commits are included below which added secrets and or settings:
- (secret) 4a75cf09a786c5e93232ae699af985e5bc89440e
- (secret) bd6f4ee1465d14a8f188c37823748a21b6a46762
- (secret) 0fd557d7bb623e3852c92cebac586d5f6d6d89d8
- (configuration setting) b1fc4925928c061234e9492f3794c0797467e123
Don't hesitate to reach out if you're unsure of the exact values needed for your new secret/setting, or have any other questions. Feel free to post questions on your corresponding Issue/Pull Request, and/or ping us on the contributing channel on our Discord server.
File: doc/authentication.md
Badges Requiring Authentication
There are two patterns for how shields.io can interact with APIs that require auth:
- We can store one token at the service level which allows us to read public data for everyone's projects, or lift a rate limit. If you are looking for information on configuring credentials for a self-hosted instance see https://github.com/badges/shields/blob/master/doc/server-secrets.md
- If every user needs to provide their own token, that has to be a token which can be passed to us as a query param in the badge URL. This means it must be possible to generate a key or token that can be exposed in a public github README public with no negative consequences. (i.e: that key or token only exposes public metrics).
If every user would need to supply their own token for some particular service and it is only possible to generate a key or token which allows access to sensitive data or allows write access to resources, we can't provide an integration for this service.
File: doc/badge-redirectors.md
Badge Redirectors
When a badge URL pattern needs to change, we should ensure that existing badges continue to work. This is achieved through redirectors, which automatically redirect old URLs to their new equivalents.
Redirectors issue HTTP 301 (permanent redirect) responses, allowing browsers and clients to follow the redirect to the new URL while maintaining backward compatibility for all existing badge usages.
Creating a Redirector
Redirectors are created using the redirector() function from core/base-service/redirector.js.
Required Properties
Every redirector must specify:
category- The badge category (e.g.'build','version','downloads'). Here is the list of valid categories.route- An object defining the old URL pattern to match:base- The first part of the old URL pathpattern- The variable part of the route (using path-to-regexp syntax)
transformPath- A function that transforms the old path parameters into the new badge pathdateAdded- The date when this redirector was added (e.g.,new Date('2025-11-23'))
Optional Properties
name- Custom name for the service class (auto-generated from route if not specified)transformQueryParams- A function to transform path parameters into query parametersoverrideTransformedQueryParams- Boolean (default:false). Whentrue, query params from the URL take precedence over transformed params in case of conflictsisRetired- Boolean (default:true). Set tofalsefor non-retired redirectors that should appear in the API documentationopenApi- OpenAPI documentation object. Only needed for non-retired redirectors (isRetired: false) that should appear in the user-facing documentation
Examples
Example 1: Simple Path Redirect
When simply redirecting from an old URL pattern to a new one:
import { redirector } from '../index.js'
export default redirector({
category: 'other',
route: {
base: 'badge/endpoint',
pattern: '',
},
transformPath: () => '/endpoint',
dateAdded: new Date('2025-01-01'),
})This redirects /badge/endpoint to /endpoint.
Example 2: Redirecting with Path Parameters
When the old URL has parameters that need to be mapped to a new structure:
import { redirector } from '../index.js'
export default redirector({
category: 'analysis',
route: {
base: 'scrutinizer',
pattern: ':vcs/:user/:repo/:branch*',
},
routeEnum: ['g', 'b'],
transformPath: ({ vcs, user, repo, branch }) =>
`/scrutinizer/quality/${vcs}/${user}/${repo}${branch ? `/${branch}` : ''}`,
dateAdded: new Date('2025-02-02'),
})This redirects patterns like /scrutinizer/g/user/repo to /scrutinizer/quality/g/user/repo.
Example 3: Converting Path Parameters to Query Parameters
When migrating from path-based to query-based parameters:
import { redirector } from '../index.js'
export default redirector({
category: 'monitoring',
route: {
base: 'website',
pattern: ':protocol/:hostAndPath+',
},
routeEnum: ['https', 'http'],
transformPath: () => '/website',
transformQueryParams: ({ protocol, hostAndPath }) => ({
url: `${protocol}://${hostAndPath}`,
}),
dateAdded: new Date('2025-03-03'),
})This redirects /website/https/example.com to /website?url=https://example.com.
Example 4: Handling Query Parameter Conflicts
When both path-based and query-string parameters exist, conflicts can occur. By default, transformed query parameters take precedence over user-provided ones. Use overrideTransformedQueryParams: true to reverse this behavior and let user-provided query parameters win:
import { redirector } from '../index.js'
export default redirector({
category: 'build',
route: {
base: 'old/service',
pattern: 'token/:token/:param',
},
transformPath: ({ param }) => `/new/service/${param}`,
transformQueryParams: ({ token }) => ({ token }),
overrideTransformedQueryParams: true,
dateAdded: new Date('2025-04-04'),
})If a user specifies /old/service/token/abc123/foo?token=xyz789, xyz789 takes precedence due to overrideTransformedQueryParams: true.
Example 5: Non-Deprecated Redirector with Documentation
In some cases, a badged based on a redirector may not be retired and should appear on the website. This requires setting isRetired: false and providing an openApi specification:
import { redirector, pathParam } from '../index.js'
import { commonParams } from '../maven-metadata/maven-metadata.js'
export default redirector({
category: 'version',
isRetired: false,
route: {
base: 'gradle-plugin-portal/v',
pattern: ':pluginId',
},
openApi: {
'/gradle-plugin-portal/v/{pluginId}': {
get: {
summary: 'Gradle Plugin Portal Version',
parameters: [
pathParam({ name: 'pluginId', example: 'com.gradle.plugin-publish' }),
...commonParams,
],
},
},
},
transformPath: () => '/maven-metadata/v',
transformQueryParams: ({ pluginId }) => ({
metadataUrl: `https://plugins.gradle.org/m2/...`,
label: 'plugin portal',
}),
overrideTransformedQueryParams: true,
dateAdded: new Date('2025-05-05'),
})This pattern is uncommon and should only be used when the redirect is part of the public API rather than a legacy compatibility layer.
File Naming Convention
Redirectors should preferably be specified in a separate file named using the *-redirect.service.js pattern, and placed in the appropriate service directory. For example:
services/
npm/
my-service.service.js
my-service.tester.js
my-service-redirect.service.js
my-service-redirect.tester.jsTesting Redirectors
Basic Test Structure
Tests for redirectors should verify that the old URL correctly redirects to the new URL.
Example redirect tests from npm-downloads.tester.js:
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Total downloads redirect: unscoped package')
.get('/dt/left-pad.svg')
.expectRedirect('/npm/d18m/left-pad.svg')
t.create('Total downloads redirect: scoped package')
.get('/dt/@cycle/core.svg')
.expectRedirect('/npm/d18m/@cycle/core.svg')The expectRedirect() helper verifies that:
- The response is an HTTP 301 (permanent redirect)
- The
Locationheader points to the expected new URL
Running Redirector Tests
Run tests the same way as other service tests:
npm run test:services -- --only=npmOr for more specific tests:
npm run test:services -- --only=npm --fgrep="redirect"What Happens Next?
We'll keep the redirector for a minimum of one year. It may stay in place for significantly longer, and can only be sunset if one or both of the following conditions are met:
- The redirector led to less than 100 badge renders on a weekday.
- The redirector serves less than 1% of the traffic of the new badge URL it points to.
It can then be removed and replaced with a retired badge linking to an issue that explains the migration path, for example:
import { retiredService } from '../index.js'
export default retiredService({
category: 'build',
route: {
base: 'github/workflow/status',
pattern: ':various+',
},
label: 'githubworkflowstatus',
issueUrl: 'https://github.com/badges/shields/issues/8671',
dateAdded: new Date('2025-11-29'),
})Doing so will render badges similar to the following:
Removal of the resulting retired badge follows the process documented here.
File: doc/badge-urls.md
Badge URL Conventions
- The format of new badges should be of the form
/SERVICE/NOUN/PARAMETERS?QUERYSTRINGe.g:/github/issues/:user/:repo. The service is github, the badge is for issues, and the parameters are:user/:repo. - The
NOUNpart of the route is:- singular if the badge message represents a single entity, such as the current status of a build (e.g:
/build), or a more abstract or aggregate representation of the thing (e.g.:/coverage,/quality) - plural if there are (or may) be many of the thing (e.g:
/dependencies,/stars)
- singular if the badge message represents a single entity, such as the current status of a build (e.g:
- Parameters should always be part of the route if they are required to display a badge e.g:
:packageNameand should be lower camelCase. routeEnumsupport: when a service defines a finite set of allowed values for a parameter, the service can declarestatic routeEnum = [...]. In that case the first named parameter in the routepatternis treated as the enum to validate againstrouteEnum.- Common optional params like,
:branchor:tagshould also be passed as part of the route. - Query string parameters should be used when:
- The parameter is related to formatting. e.g:
/appveyor/tests/:user/:repo?compact_message. - The parameter is for an uncommon optional attribute, like an alternate registry URL.
- The parameter triggers application of alternative logic, like version semantics. e.g:
/github/v/tag/:user/:repo?sort=semver. - Services which require a url/hostname parameter always should use a query string parameter to accept that value. e.g:
/discourse/topics?server=https://meta.discourse.org. - Similar to parameters, query string parameters should be lower camelCase e.g: 'labelColor' and not 'label_color'.
- The parameter is related to formatting. e.g:
- Query parameters for an alternative host of upstream data (like registry, repository, selfhost instance, etc) should be called
baseUrland include a full URL with protocol and path (if required).
It is convention to use the following standard routes and abbreviations across services:
- Coverage:
/coverage - Downloads or Installs:
- Total:
/dt- Use this even for services that only provide the total download/install data - Per month:
/dm - Per week:
/dw - Per day:
/dd
- Total:
- Rating:
- Numeric:
/rating - Stars:
/stars
- Numeric:
- License:
/l - Version or Release:
/v
File: doc/code-walkthrough.md
High-level code walkthrough
Code inventory and testing strategy
The Shields codebase is divided into several parts:
- The frontend
- The badge renderer (which is available as an npm package)
- The base service classes (about 8% of the code, and probably the most important code in the codebase)
- The server code and a few related odds and ends
- Helper code for token pooling and persistence (used to avoid GitHub rate limiting)
- Service common helper functions (about 7% of the code, and fairly important since it’s shared across much of the service code)
*.jsin the root ofservices
- The services themselves (about 80% of the code)
*.jsin the folders ofservices
The tests are also divided into several parts:
- Unit and functional tests of the badge renderer
badge-maker/**/*.spec.js
- Unit and functional tests of the core code
core/**/*.spec.js
- Unit and functional tests of the service helper functions
services/*.spec.js
- Unit and functional tests of the service code (we have only a few of these)
services/*/**/*.spec.js
- End-to-end tests for the frontend
cypress/e2e/*.cy.js
- The service tester and service test runner
- The service tests themselves live integration tests of the services, and some mocked tests
*.tester.jsin subfolders ofservices
- Integration tests of PostgreSQL-backed persistence code
- Integration tests of the GitHub authorization code
Our goal is to reach 100% coverage of the code in the frontend, core, and service helper functions when the unit and functional tests are run.
Our test strategy for the service code is a bit different. It’s primarily based on live integration tests. That’s because service response formats can change, and when they do the badges break. We want our tests to fail when this happens. That way we can fix the problems proactively instead of waiting for users to report them. There’s a good discussion about this decision in #927. It’s acceptable to write mocked tests of logic that is difficult to reach using live tests, however where possible, it’s preferred to test this kind of logic through unit tests (e.g. of render() and transform() functions).
Server initialization
The server entrypoint is
server.jswhich sets up error reporting, loads config, and creates an instance of the server.The Server, which is defined in
core/server/server.js, is based on the web framework Scoutcamp. It creates an http server, sets up helpers for token persistence and monitoring. Then it loads all the services, injecting dependencies as it asks each one to register its route with Scoutcamp.The service registration continues in
BaseService.register. From itsrouteproperty, it derives a regular expression to match the route path, and invokescamp.routewith this value.At this point the situation gets gnarly and hard to follow. For the purpose of initialization, suffice it to say that
camp.routeinvokes a callback with the four parameters( queryParams, match, end, ask )which is created in a legacy helper function inlegacy-request-handler.js. This callback delegates to a callback inBaseService.registerwith three different parameters( queryParams, match, sendBadge ), which then runsBaseService.invoke.BaseService.invokeinstantiates the service and runsBaseService#handle.
Downstream caching
- In production, the majority of requests are served from caches, including the browser cache, GitHub’s camo proxy server, and other downstream caches.
- The Shields servers sit behind the Cloudflare CDN. The CDN itself handles about 40% of the HTTPS requests that come in.
- The remaining requests are proxied to one of the servers.
- See the production hosting documentation for a fuller discussion of the production architecture.
How the server makes a badge
- An HTTPS request arrives. Scoutcamp inspects the URL path and matches it against the regexes for all the registered routes until it finds one that matches. (See Initialization above for an explanation of how routes are registered.)
- Scoutcamp invokes a callback with the four parameters:
( queryParams, match, end, ask ). This callback is defined inlegacy-request-handler. A timeout is set to handle unresponsive service code and the next callback is invoked: the legacy handler function. - The legacy handler function receives
( queryParams, match, sendBadge ). Its job is to extract data from the regexmatchandqueryParams, and then invokesendBadgewith the result. - The implementation of this function is in
BaseService.register. It works by runningBaseService.invoke, which instantiates the service, injects more dependencies, and invokesBaseService.handlewhich is implemented by the service subclass. - The job of
handle(), which should be implemented by each service subclass, is to return an object which partially describes a badge or throw one of the handled error classes. "Partially rendered" most commonly means a non-empty message and an optional color. In the case of the Endpoint badge, it could include many other parameters. At the time of writing the handled error classes were NotFound, InvalidResponse, Inaccessible, and InvalidParameter. Throwing any other error is a programmer error which will be reported and described to the user as a shields internal error. - A typical
handle()function delegates to one or more helpers to handle stages of the request:- fetch: load the needed data from the upstream service and validate it
- transform: pluck, convert, or summarize the response format into a few properties which will be displayed on the badge
- render: given a few properties, return a message, optional color, and optional label.
- When an error is thrown, BaseService steps in and converts the error object to renderable properties:
{ isError, message, color }. - The service invokes
coalesceBadgewhose job is to coalesce query string overrides with values from the service and the service’s defaults to produce an object that fully describes the badge to be rendered. sendBadgeis invoked with that object. It does some housekeeping on the timeout. Then it renders the badge to svg or raster and pushes out the result over the HTTPS connection.
File: doc/input-validation.md
Input Data Validation
When we receive input data from an upstream API, we perform input validation to:
- Ensure we won't throw a runtime error trying to render a badge
- Ensure we won't render badges with spurious or unexpected output e.g: etc
- Express and document our understanding of the input data
Writing schemas and validation
The default validation mechanism should be to use Joi to define a schema for the input data. Validation against Joi schemas is implemented in the base classes and inherited by every service class that extends them. Sometimes additional manual validation is needed which can't be covered by Joi and plugins in which case we implement it by hand.
If validation is implemented manually (because we need to enforce a constraint that can't be expressed with Joi), invalid data should throw an InvalidResponse exception.
Our definition of "valid" should not be stricter than the upstream API's definition of "valid".
The schema/validation we choose is informed by the assumptions we're making about the data. e.g:
- If we're going to use a value, make sure it exists.
- If we need to multiply it by something, we check it's a number.
- If we're going to call
.split()on it, we make sure it's a string. - If we're going to address
foo[0],foomust be an array. - If we're going to sort a version on the assumption it is a semver, check it's a semver
We don't need to validate characteristics we don't rely on. For example, if we're just going to render a version on a badge with the same exact value from the API response and do not need to sort or transform the value, then it doesn't matter what format the version number is in. We can use a very relaxed schema to validate in this case, e.g.
Joi.string().required()https://joi.dev/tester/ is a tool that can be used to reverse engineer a schema from an API response. This can be a great starting point to tweak from. If using this as a starting point, remember to remove fields we don't rely on to render a badge.
If theory (docs) and practice (real-world API responses) conflict, real-world outputs take precedence over documented behaviour. e.g: if the docs say version is a semver but we learn that there are real-world packages where the version number is
0.3bor1.2.1.27then we should accept those values in preference to enforcing the documented API behaviour.Shields is descriptive rather than prescriptive. We reflect the established norms of the communities we serve.
It is fine to define a single schema which is applied to multiple badges. For example, we could define a schema that says:
jsconst schema = Joi.object({ license: Joi.string().required(), version: Joi.string().required(), }).required()and have both the license and version badges validate the response against that schema.
For build status badges there is a shared isBuildStatus validator. In most cases build status badges should use
isBuildStatusor input validation andrenderBuildStatusBadgeshould be used for rendering. Any additional status values can be added to the relevant color arrays.
Identifying problems
If we know of a real-world example of a package/repo/etc that causes us to render an invalid value on a badge (e.g: ) our input validation is broken and we should fix it.
If we know of a real-world example of a package/repo/etc that causes us to throw an unhandled runtime exception, our input validation is broken and we should fix it.
We should not fail to render a badge because of a validation failure on a field that isn't necessary to render the badge. In the above example of a shared license/version schema: If we become aware of a real-world example of a package/repo/etc that has a
versionkey but not alicensekey then we should split the schema (or makeversionoptional and handle the error in code).
File: doc/json-format.md
JSON Format
Even though Shields is probably best known for its SVG badges, you can also retrieve a JSON payload by replacing the .svg extension with .json.
For instance, hitting this endpoint will generate the following payload:
{
"name": "hello",
"label": "hello",
"value": "world",
"message": "world",
"color": "brightgreen"
}Note that the values of the name and value fields are duplicates of the label and message ones, respectively. As of April 2019, name and value are deprecated and will be removed in a future release, please consider migrating your application to use label and message instead.
Feel free to open an issue if you have any queries regarding the JSON format.
File: doc/performance-testing.md
Performance testing
Shields has some basic tooling available to help you get started with performance testing.
Benchmarking the badge generation
Want to micro-benchmark a section of the code responsible for generating the static badges? Follow these two simple steps:
- Surround the code you want to time with
console.timeandconsole.timeEndstatements. For example:
console.time('makeBadge')
const svg = makeBadge(badgeData)
console.timeEnd('makeBadge')- Run
npm run benchmark:badgein your terminal. An average timing will be displayed!
If you want to change the number of iterations in the benchmark, you can modify the values specified by the benchmark:badge script in package.json. If you want to benchmark a specific code path not covered by the static badge, you can modify the badge URL in scripts/benchmark-performance.js.
Profiling the full code
Want to have an overview of how the entire application is performing? Simply run npm run profile:server in your terminal. This will start the backend server (i.e. without the frontend) in profiling mode and any requests you make on localhost:8080 will generate data in a file with a name similar to isolate-00000244AB6ED3B0-11920-v8.log.
You can then make use of this profiling data in various tools, for example flamebearer:
npm install -g flamebearer
node --prof-process --preprocess -j isolate-00000244AB6ED3B0-11920-v8.log | flamebearerAn example output is the following:
File: doc/production-hosting.md
Production hosting
Production hosting is managed by the Shields ops team:
| Component | Subcomponent | People with access |
|---|---|---|
| Fly.io | Admin access | @calebcartwright, @chris48s, @paulmelnikow, @PyvesB |
| Fly.io | Account access | @calebcartwright, @chris48s, @jNullj, @paulmelnikow, @PyvesB |
| Cloudflare (CDN) | Account owner | @espadrine |
| Cloudflare (CDN) | Admin access | @calebcartwright, @chris48s, @espadrine, @paulmelnikow, @PyvesB |
| DNS | Account owner | @olivierlacan |
| DNS | Read-only account access | @chris48s, @espadrine, @paulmelnikow |
| Sentry | Owner | @paulmelnikow, @jNullj |
| Sentry | Account access | @calebcartwright, @jNullj, @paulmelnikow, @PyvesB |
| Metrics server | Owner | @platan |
| More metrics | Owner | @RedSparr0w |
| Twitch | OAuth app | @PyvesB |
| OAuth app | @chris48s, @PyvesB | |
| Discord | OAuth app | @PyvesB |
| CurseForge | OAuth app | @PyvesB |
| GitLab | Account owner | @calebcartwright |
| GitLab | Account access | @calebcartwright, @chris48s, @paulmelnikow, @PyvesB |
Attached state
Shields has mercifully little persistent state:
- The GitHub tokens we collect are stored in a fly.io postgres database
- The server keeps the resource cache in memory. It is neither persisted nor inspectable.
Configuration
To bootstrap the configuration of non-secret settings, we set a single environment variable:
NODE_CONFIG_ENV=shields-io-productionWith that variable set, the server (using config) reads these files:
local-shields-io-production.yml. This file contains secrets which are checked in with a deploy commit.shields-io-production.yml. This file contains non-secrets which are checked in to the main repo.default.yml. This file contains defaults.
Secrets are supplied directly as environment vars.
Badge CDN
Sitting in front of our servers is a Cloudflare Free account which provides several services:
- Global CDN, caching, and SSL gateway for
img.shields.ioandshields.io - Analytics through the Cloudflare dashboard
- DNS resolution for
shields.io(and subdomains)
Cloudflare is configured to respect the servers' cache headers.
Fly.io Deployment
Both the badge server and frontend are served from Fly.io. Deployments are triggered using GitHub actions in a private repo.
The raster server raster.shields.io (a.k.a. the rasterizing proxy) is also hosted on Fly.io. It's managed in the squint repo.
DNS
DNS is registered with DNSimple.
Error reporting
Error reporting is one of the most useful tools we have for monitoring the server. It's generously donated by Sentry. We bundle @sentry/node into the application, and the Sentry DSN is configured via local-shields-io-production.yml (see documentation).
URLs
The canonical and only recommended domain for badge URLs is img.shields.io. Currently it is possible to request badges on both img.shields.io and shields.io i.e: https://img.shields.io/badge/build-passing-brightgreen and https://shields.io/badge/build-passing-brightgreen will both work. However:
- We never show or generate the
img.-less URL format on https://shields.io/ - We make no guarantees about the
img.-less URL format. At some future point we may remove the ability to serve badges onshields.io(withoutimg.) without any warning.img.shields.ioshould always be used for badge urls.
Monitoring
Overall server performance and requests by service are monitored using Prometheus and Grafana.
Request performance is monitored in two places:
- Status (using NodePing)
- Server metrics using Prometheus and Grafana #monitor chat room
File: doc/releases.md
Releases
Shields is a community project that is stewarded by a handful of core maintainers who contribute on a volunteer basis. We do our best to maintain the availability and reliability of the service, and enhance and improve the project overall. However, if you've spotted something wrong or would like to see a specific feature implemented, please consider helping us resolve it by submitting a pull request. All community contributions, even documentation improvements, are welcome!
https://github.com/badges/shields is a monorepo and hosts the Shields frontend and server code as well as the badge-maker NPM library (and the badge design specification). The packaging and release processes for these items are described in the respective sections below.
badge-maker package
We follow Semantic Versioning for the badge-maker package, and publish git Tags using the form X.Y.Z, and all such tags of this form are badge-maker releases (e.g. 3.3.0)
The badge-maker is also published to the npm.js registry.
Releases of badge-maker are done as and when needed, and not on any predetermined interval.
Shields.io service
The Shields.io Service consists of the frontend https://shields.io website which allows users to browse and discover available badges, as well as the badge server backend that serves up requested badges (e.g. https://img.shields.io/badge/badges-rock-blue).
This is the core, free, anonymous service available for anyone to use and which serves up hundreds of millions of badges every month!
We do not have a fixed schedule for deploying updates to the Shields.io production environment, but we typically deploy the latest version at least once per week.
We do not have any guaranteed SLA for the Shields.io service, but we do have monitoring and observability capabilities in place and our Maintainer team will review and address any availability, performance, etc. issues on a best-effort basis.
More information about the production environment can be found here
Shields server
Some users may wish to host their own instance of Shields so we also make it possible for users to do so. This is particularly useful if you want to serve badges for resources that require authentication or are not exposed to the internet (e.g: inside a corporate network). A variety of options are available either by installing from source or using a docker image.
Important information
This Shields server is the exact same codebase that powers the main Shields.io service; we do not have a separate self-hosted version of Shields. This means that the server codebase is geared towards the development, maintenance, and operation of the Shields.io service so there are a few things to note:
- We often have to reject or de-prioritize feature requests that are only applicable for self-hosting and/or which may be problematic for the core Shields.io offering (e.g. requiring authentication on the badge server)
- We do not accept new badges that cannot be utilized with Shields.io (e.g. those that would always require user-specific authorization)
- We do not do any additional testing nor validation of self-hosted instances (e.g. we test Shields.io with Jira Cloud badges, but we don't test a self-hosted Shields server against a self-hosted Jira server instance with auth)
- We're happy to try to offer some tips and guidance to self-hosters when and where we can, but we don't have the ability to troubleshoot/debug/support/etc. self-hosted Shields servers
- Note that Paul, a core team member, offers some paid support options for those interested in self-hosting support. Contact him at for more information.
We are happy to document and collate any self-hosting patterns/approaches that others have found to be helpful to be able to share with the broader community. If you have any self-hosting material you'd like to share, please feel free to get in contact with us (or even open a PR) and we'll work with you to get that incorporated for the benefit of other self-hosters!
Server Releases
We try to make it as easy as possible for users to self-host a Shields server so we publish a few releases of the server. Please be sure to refer to the self hosting guide for a detailed walk through on how to spin up a server.
- The server uses Calendar Versioning. Tags of the form
server-YYYY-MM-DDare server releases (these are the tags that are relevant to self-hosting users, e.g. server-2021-02-01). - As well as tags on GitHub, server releases are also pushed to DockerHub and GitHub Container Registry. See the self-hosting section on Docker for more details.
- We publish release notes for server releases in the CHANGELOG. There may occasionally be non-backwards compatible changes to be aware of.
- We will normally put out one release per month. If there is a security patch or major bugfix affecting self-hosting users, we may put out an out-of-sequence release.
- Releases are just a snapshot in time. We advise always tracking the latest release to ensure you are up-to-date with the latest bug fixes and security updates. There are no 'patch' releases - we don't backport fixes to old releases. Tagged versions just provide a convenient way to apply upgrades in a controlled way or roll back to an older version if necessary and communicate about versions.
- You can stay on the bleeding edge by tracking the
masterbranch for source installs or thenexttag on DockerHub/GHCR.
File: doc/retiring-badges.md
Retiring Badges
When a service that Shields integrates with shuts down, those badges will no longer work and need to be retired (deprecated) within Shields.
Retiring a badge involves two steps:
- Updating the service code to use the
RetiredServiceclass - Updating the service tests to reflect the new behavior of the retired service
Update Service Implementation
Locate the source file(s) for the service, which can be found in *.service.js files located within the directory for the service (./services/:service-name/) such as ./services/imagelayers/imagelayers.service.js.
Replace the existing service class implementation with the RetiredService class from ./core/base-service/retired-service.js using the respective category, route, and label values for that service.
Set the badge label to the service name. This ensures users can immediately identify which service is no longer available at a glance.
For example:
import { retiredService } from '../index.js'
export default retiredService({
category: 'size',
route: {
base: 'imagelayers',
pattern: ':various+',
},
label: 'imagelayers',
dateAdded: new Date('2019-xx-xx'), // Be sure to update this with today's date!
})Update Service Tests
Locate the test file(s) for the service, which can be found in *.tester.js files located in the service directory (./services/:service-name/), such as ./services/imagelayers/imagelayers.tester.js.
With RetiredService classes we cannot use createServiceTester() so you will need to create the ServiceTester class directly. For example:
import { ServiceTester } from '../tester.js'
export const t = new ServiceTester({
id: 'imagelayers',
title: 'ImageLayers',
})Next you will need to replace/refactor the existing tests to validate the new retired badge behavior for this service. Retired badges always return a message of retired badge (such as imagelayers | retired badge) so the tests need to be updated to reflect that message value. For example:
t.create('retired badge (previously image size)')
.get('/image-size/_/ubuntu/latest.json')
.expectBadge({
label: 'imagelayers',
message: 'retired badge',
})Make sure to have a live (non-mocked) test for each badge the service provides that validates the each badge returns the retired badge message.
Here is an example of what the final result would look like for a test file:
import { ServiceTester } from '../tester.js'
export const t = new ServiceTester({
id: 'imagelayers',
title: 'ImageLayers',
})
t.create('retired badge (previously image size)')
.get('/image-size/_/ubuntu/latest.json')
.expectBadge({
label: 'imagelayers',
message: 'retired badge',
})
t.create('retired badge (previously number of layers)')
.get('/layers/_/ubuntu/latest.json')
.expectBadge({
label: 'imagelayers',
message: 'retired badge',
})What Happens Next?
Once a service is retired, we'll keep the retirement notice for a minimum of one year. During that time, the badge will render as follows:
Past that point, all related code will be deleted, and a not found error will be rendered instead:
Here is a listing of all deleted badges that were once part of the Shields.io service:
- Ansible Collection, Role, Quality
- APM
- Beerpay
- Bintray
- bitHound
- Bountysource
- Cauditor
- Chrome Web Store Price
- CocoaPods Apps
- CocoaPods Downloads
- Codetally
- Coincap
- continuousphp
- Coverity
- Criterion
- David
- dependabot
- Dockbit
- Dotnet Status
- Gemnasium
- Gratipay/Gittip
- Hackage (dependencies)
- ImageLayers
- Issue Stats
- JitPack Downloads
- Leanpub
- LGTM
- Libscore
- Magnum CI
- MicroBadger
- NSP
- pkgreview
- PHP Eye
- Pub (popularity)
- Redmine
- requires.io
- Shippable
- Snap CI
- Snyk
- Tas
- Tokei
- Travis.org
- VersionEye
- Waffle
- Wercker
- Wheelmap
Additional Information
Some other information that may be useful:
- Contributing Docs
- Badge Tutorial
- Service Tests Tutorial
- Previous Pull Requests that retired badges like #10371 and #11075
File: doc/self-hosting.md
Hosting your own Shields server
This document describes how to host your own shields server either from source or using a docker image. See the docs on releases for info on how we version the server and how to choose a release.
Installing from Source
You will need Node 24, which you can install using a package manager.
On Ubuntu / Debian:
curl -sL https://deb.nodesource.com/setup_24.x | sudo -E bash -; sudo apt-get install -y nodejsgit clone https://github.com/badges/shields.git
cd shields
git checkout $(git tag | grep server | tail -n 1) # checkout the latest tag
npm ci # You may need sudo for this.Build the frontend
npm run buildStart the server
sudo node serverThe server uses port 80 by default, which requires sudo permissions.
There are two ways to provide an alternate port:
PORT=8080 node server
node server 8080The root gets redirected to https://shields.io.
For testing purposes, you can go to http://localhost/.
Deploying to Heroku
Once you have installed the Heroku CLI
heroku login
heroku create your-app-name
git push heroku master
heroku openDeploying to Zeit Vercel
To deploy using Zeit Vercel:
npm run build # Not sure why, but this needs to be run before deploying.
vercelDocker
Public Images
We publish images to:
- DockerHub at https://registry.hub.docker.com/r/shieldsio/shields and
- GitHub Container Registry at https://github.com/badges/shields/pkgs/container/shields
The next tag is the latest build from master. These are only available for linux/amd64
# DockerHub
$ docker pull shieldsio/shields:next
$ docker run shieldsio/shields:next# GHCR
$ docker pull ghcr.io/badges/shields:next
$ docker pull ghcr.io/badges/shields:nextTagged snapshot releases are also available:
- https://registry.hub.docker.com/r/shieldsio/shields/tags
- https://github.com/badges/shields/pkgs/container/shields/versions?filters%5Bversion_type%5D=tagged
We push both linux/amd64 and linux/arm64 snapshot images. We use the linux/amd64 image ourselves to host shields.io. We push a linux/arm64 image, but we don't consume it ourselves and it receives no testing beyond ensuring the docker image builds without error.
Building Docker Image Locally
Alternatively, you can build and run the server locally using Docker. First build an image:
$ docker build -t shields .
Sending build context to Docker daemon 3.923 MB
…
Successfully built 4471b442c220Optionally, alter the default values for configuration by setting them via environment variables. See server-secrets.md and config/custom-environment-variables.yml for possible values. In config/custom-environment-variables.yml, environment variable names are specified as the quoted, uppercase key values (e.g. GH_TOKEN).
Then run the container, and be sure to specify the same mapped port as the one Shields is listening on :
$ docker run --rm -p 8080:8080 --env PORT=8080 --name shields shieldsio/shields:next
Configuration:
...
0916211515 Server is starting up: http://0.0.0.0:8080/Assuming Docker is running locally, you should be able to get to the application at http://localhost:8080/.
If you run Docker in a virtual machine (such as boot2docker or Docker Machine) then you will need to replace localhost with the IP address of that virtual machine.
Disabling Dynamic and Endpoint badges
Dynamic and Endpoint badges are enabled by default. These badge families fetch URLs supplied by the requester, which may be unsuitable for an internet-facing self-hosted server that can reach private network resources.
To disable both badge families, set the following public configuration:
public:
dynamicAndEndpointBadgesEnabled: falseAlternatively, set the environment variable DYNAMIC_AND_ENDPOINT_BADGES_ENABLED=false.
When disabled, Dynamic and Endpoint routes are not registered. Requests to those routes receive the standard 404 | badge not found badge response. Other badge services remain available.
Raster server
If you want to host PNG badges, you can also self-host a raster server which points to your badge server. It's a docker container. We host it on Fly.io but should be possible to host on a wide variety of platforms.
- In your raster instance, set
BASE_URLto your Shields instance, e.g.https://shields.example.co. - Optionally, in your Shields, instance, configure
RASTER_URLto the base URL, e.g.https://raster.example.co. This will send 301 redirects for the legacy raster URLs instead of 404's.
If anyone has set this up, more documentation on how to do this would be welcome!
Server secrets
You can add your own server secrets in environment variables or config/local.yml.
These are documented in server-secrets.md
Separate frontend hosting
If you want to host the frontend on a separate server, such as cloud storage or a CDN, you can do that.
First, build the frontend, pointing BASE_URL to your server.
BASE_URL=https://your-server.example.com npm run buildThen copy the contents of the public/ folder to your static hosting / CDN.
There are also a couple settings you should configure on the server.
To help out users, you can make the Shields server redirect the server root. Set the REDIRECT_URI environment variable:
REDIRECT_URI=http://my-custom-shields.s3.amazonaws.com/Sentry
In order to enable integration with Sentry, you need your own Sentry DSN. It’s an URL in format https://{PUBLIC_KEY}:{SECRET_KEY}@sentry.io/{PROJECT_ID}.
How to obtain the Sentry DSN
- Sign up for Sentry
- Log in to Sentry
- Create a new project for Node.js
- You should see Sentry DSN for your project. Sentry DSN can be found by navigating to [Project Name] -> Project Settings -> Client Keys (DSN) as well.
Start the server using the Sentry DSN. You can set it:
- by
SENTRY_DSNenvironment variable
sudo SENTRY_DSN=https://xxx:[email protected]/zzz node serverOr via config as you would do with server secrets:
private:
sentry_dsn: ...sudo node serverPrometheus
Shields uses prom-client to provide default metrics. These metrics are disabled by default. You can enable them by METRICS_PROMETHEUS_ENABLED and METRICS_PROMETHEUS_ENDPOINT_ENABLED environment variables.
METRICS_PROMETHEUS_ENABLED=true METRICS_PROMETHEUS_ENDPOINT_ENABLED=true npm startMetrics are available at /metrics resource.
Cloudflare
Shields.io uses Cloudflare as a downstream CDN. If your installation does the same, you can configure your server to only accept requests coming from Cloudflare's IPs. Set public.requireCloudflare: true.
File: doc/server-secrets.md
Server Secrets
It is possible to provide a token or credentials for a number of external services. These may be used to lift a rate limit or provide access to private resources from a self-hosted instance.
There are two ways of setting secrets:
- Via environment variables. This is a good way to set them in a PaaS environment.
DRONE_TOKEN=...
DRONE_ORIGINS="https://drone.example.com"- Via checked-in
config/local.yml:
public:
services:
drone:
authorizedOrigins: ['https://drone.example.com']
private:
drone_token: '...'For more complex scenarios, configuration files can cascade. See the node-config documentation for details.
Authorized origins
Several of the badges provided by Shields allow users to specify the target URL/server of the upstream instance to use via a query parameter in the badge URL (e.g. https://img.shields.io/nexus/s/com.google.guava/guava?server=https%3A%2F%2Foss.sonatype.org). This supports scenarios where your users may need badges from multiple upstream targets, for example if you have more than one Nexus server.
Accordingly, if you configure credentials for one of these services with your self-hosted Shields instance, you must also specifically authorize the hosts to which the credentials are allowed to be sent. If your self-hosted Shields instance then receives a badge request for a target that does not match any of the authorized origins, one of two things will happen:
- if credentials are required for the targeted service, Shields will render an error badge.
- if credentials are optional for the targeted service, Shields will attempt the request, but without sending any credentials.
When setting authorized origins through an environment variable, use a space to separate multiple origins. Note that failing to define authorized origins for a service will default to an empty list, i.e. no authorized origins.
It is highly recommended to use https origins with valid SSL, to avoid the possibility of exposing your credentials, for example through DNS-based attacks.
It is also recommended to use tokens for a service account having the fewest privileges needed for fetching the relevant status information.
Services
Azure DevOps
AZURE_DEVOPS_TOKEN(yml:private.azure_devops_token)
An Azure DevOps Token (PAT) is required for accessing private Azure DevOps projects.
Create a PAT using an account that has access to your target Azure DevOps projects. Your PAT only needs the following scopes:
Build (read)Release (read)Test Management (read)
Bitbucket (Cloud)
BITBUCKET_USER(yml:private.bitbucket_username)BITBUCKET_PASS(yml:private.bitbucket_password)
Bitbucket badges use basic auth. Provide a username and password to give your self-hosted Shields installation access to private repositories hosted on bitbucket.org.
Bitbucket Server
BITBUCKET_SERVER_ORIGINS(yml:public.services.bitbucketServer.authorizedOrigins)BITBUCKET_SERVER_USER(yml:private.bitbucket_server_username)BITBUCKET_SERVER_PASS(yml:private.bitbucket_server_password)
Bitbucket badges use basic auth. Provide a username and password to give your self-hosted Shields installation access to a private Bitbucket Server instance.
CurseForge
CURSEFORGE_API_KEY(yml:private.curseforge_api_key)
A CurseForge API key is required to use the CurseForge API. To obtain an API key, signup to CurseForge Console with a Google account and create an organization, then go to the API keys page and copy the generated API key.
Discord
Using a token for Discord is optional but will allow higher API rates.
DISCORD_BOT_TOKEN(yml:private.discord_bot_token)
Register an application in the Discord developer console. To obtain a token, simply create a bot for your application.
DockerHub
Using authentication for DockerHub is optional but can be used to allow higher API rates or access to private repos.
DOCKERHUB_USER(yml:private.dockerhub_username)DOCKERHUB_PAT(yml:private.dockerhub_pat)
DOCKERHUB_PAT is a Personal Access Token. Generate a token in your account security settings with "Read-Only" or "Public Repo Read-Only", depending on your needs.
Drone
DRONE_ORIGINS(yml:public.services.drone.authorizedOrigins)DRONE_TOKEN(yml:private.drone_token)
The self-hosted Drone API requires authentication. Log in to your Drone instance and obtain a token from the user profile page.
GitHub
GITHUB_URL(yml:public.services.github.baseUri)GH_TOKEN(yml:private.gh_token)
Because of GitHub rate limits, you will need to provide a token, or else badges will stop working once you hit 60 requests per hour, the unauthenticated rate limit.
You can create a personal access token (PATs) through the GitHub website. When you create the token, you can choose to give read access to your repositories. If you do that, your self-hosted Shields installation will have access to your private repositories.
For most users we recommend using a classic PAT as opposed to a fine-grained PAT. It is possible to request a fairly large subset of the GitHub badge suite using a fine-grained PAT for authentication but there are also some badges that won't work. This is because some of our badges make use of GitHub's v4 GraphQL API and the GraphQL API only supports authentication with a classic PAT.
When a gh_token is specified, it is used in place of the Shields token rotation logic.
GITHUB_URL can be used to optionally send all the GitHub requests to a GitHub Enterprise server. This can be done in conjunction with setting a token, though it's not required.
GH_CLIENT_ID(yml:private.gh_client_id)GH_CLIENT_SECRET(yml:private.gh_client_secret)
These settings are used by shields.io for GitHub OAuth app authorization but will not be necessary for most self-hosted installations. See production-hosting.md.
Gitea
GITEA_ORIGINS(yml:public.services.gitea.authorizedOrigins)GITEA_TOKEN(yml:private.gitea_token)
A Gitea Personal Access Token is required for accessing private content. If you need a Gitea token for your self-hosted Shields server then we recommend limiting the scopes to the minimal set necessary for the badges you are using.
GitLab
GITLAB_ORIGINS(yml:public.services.gitlab.authorizedOrigins)GITLAB_TOKEN(yml:private.gitlab_token)
A GitLab Personal Access Token is required for accessing private content. If you need a GitLab token for your self-hosted Shields server then we recommend limiting the scopes to the minimal set necessary for the badges you are using.
Jenkins CI
JENKINS_ORIGINS(yml:public.services.jenkins.authorizedOrigins)JENKINS_USER(yml:private.jenkins_user)JENKINS_PASS(yml:private.jenkins_pass)
Provide a username and password to give your self-hosted Shields installation access to a private Jenkins CI instance.
Jira
JIRA_ORIGINS(yml:public.services.jira.authorizedOrigins)JIRA_USER(yml:private.jira_user)JIRA_PASS(yml:private.jira_pass)
Provide a username and password to give your self-hosted Shields installation access to a private JIRA instance.
Libraries.io/Bower
LIBRARIESIO_TOKENS(yml:private.librariesio_tokens)
Note that the Bower badges utilize the Libraries.io API, so use this secret for both Libraries.io badges and/or Bower badges.
Just like the *_ORIGINS type secrets, this value can accept a single token as a string, or a group of tokens provided as an array of strings. For example:
private:
librariesio_tokens: my-token
## Or
private:
librariesio_tokens: [my-token some-other-token]When using the environment variable with multiple tokens, be sure to use a space to separate the tokens, e.g. LIBRARIESIO_TOKENS="my-token some-other-token"
Nexus
NEXUS_ORIGINS(yml:public.services.nexus.authorizedOrigins)NEXUS_USER(yml:private.nexus_user)NEXUS_PASS(yml:private.nexus_pass)
Provide a username and password to give your self-hosted Shields installation access to your private nexus repositories.
npm
NPM_ORIGINS(yml:public.services.npm.authorizedOrigins)NPM_TOKEN(yml:private.npm_token)
Generate an npm token to give your self-hosted Shields installation access to private npm packages
Open Build Service
OBS_USER(yml:private.obs_user)OBS_PASS(yml:private.obs_user)
Only authenticated users are allowed to access the Open Build Service API. Authentication is done by sending a Basic HTTP Authorisation header. A user account for the reference instance is a SUSE IdP account, which can be created here.
While OBS supports API tokens, they can only be scoped to execute specific actions on a POST request. This means however, that an actual account is required to read the build status of a package.
OpenCollective
OPENCOLLECTIVE_TOKEN(yml:private.opencollective_token)
OpenCollective's GraphQL API only allows 10 reqs/minute for anonymous users. An API token can be provided to access a higher rate limit of 100 reqs/minute.
OutageDeck
OUTAGEDECK_API_KEY(yml:private.outagedeck_api_key)
An optional Bearer key raises the OutageDeck API rate limit. The hosted Shields.io service can use its complimentary dedicated key; self-hosted instances can leave this unset and use anonymous access, which is limited to 120 requests per IP address per hour.
Pepy
PEPY_KEY(yml:private.pepy_key)
The Pepy API requires authentication. To obtain a key, Create an account, sign in and obtain generate a key on your account page.
PyPI
PYPI_URL(yml:public.pypi.baseUri)
PYPI_URL can be used to optionally send all the PyPI requests to a Self-hosted Pypi registry, users can also override this by query parameter pypiBaseUrl.
Read the Docs
READTHEDOCS_TOKEN(yml:private.readthedocs_token)
Authentication is optional, but a Read the Docs API token increases the API rate limit for Read the Docs badges.
Use a token from a dedicated account that has access only to public projects. Badge endpoints are public, so a token whose account can access private projects could allow badge requests to reveal those projects' build status.
Using a token for Reddit is optional but will allow higher API rates.
REDDIT_CLIENT_ID(yml:private.reddit_client_id)REDDIT_CLIENT_SECRET(yml:private.reddit_client_secret)
Register to use the API using this form and create an app in the Reddit preferences page in order to obtain a client id and a client secret for making Reddit API calls.
SymfonyInsight (formerly Sensiolabs)
SL_INSIGHT_USER_UUID(yml:private.sl_insight_userUuid)SL_INSIGHT_API_TOKEN(yml:private.sl_insight_apiToken)
The SymfonyInsight API requires authentication. To obtain a token, Create an account, sign in and obtain a uuid and token from your account page.
SonarQube
SONAR_ORIGINS(yml:public.services.sonar.authorizedOrigins)SONARQUBE_TOKEN(yml:private.sonarqube_token)
Generate a token to give your self-hosted Shields installation access to a private SonarQube instance or private project on a public instance.
StackApps (for StackExchange and StackOverflow)
STACKAPPS_API_KEY: (yml:private.stackapps_api_key)
Anonymous requests to the stackexchange API are limited to 300 calls per day. To increase your quota to 10,000 calls per day, create an account at StackApps and register an OAuth app. Having registered an OAuth app, you'll be granted a key which can be used to increase your request quota. It is not necessary to perform a full OAuth Flow to gain an access token.
TeamCity
TEAMCITY_ORIGINS(yml:public.services.teamcity.authorizedOrigins)TEAMCITY_USER(yml:private.teamcity_user)TEAMCITY_PASS(yml:private.teamcity_pass)
Provide a username and password to give your self-hosted Shields installation access to your private nexus repositories.
Twitch
TWITCH_CLIENT_ID(yml:private.twitch_client_id)TWITCH_CLIENT_SECRET(yml:private.twitch_client_secret)
Register an application in the Twitch developer console in order to obtain a client id and a client secret for making Twitch API calls.
Weblate
WEBLATE_ORIGINS(yml:public.services.weblate.authorizedOrigins)WEBLATE_API_KEY(yml:private.weblate_api_key)
Weblate heavily throttles unauthenticated request, it is recommended to use an API key for requests.
You can find your Weblate API key in your profile under "API access".
Error reporting
SENTRY_DSN(yml:private.sentry_dsn)
A Sentry DSN may be used to send error reports from your installation to Sentry.io. For more info, see the self hosting docs.
File: doc/service-tests.md
Service tests
When creating a badge for a new service or changing a badge's behavior, automated tests should be included. They serve three purposes:
The contributor and reviewer can easily verify the code works as intended.
When a badge stops working due to an upstream API, maintainers can find out right away.
They speed up future contributors when they are debugging or improving a badge.
Tests should cover:
- Valid behavior
- Optional parameters like tags or branches
- Any customized error handling
- If a non-trivial validator is defined, include tests for malformed responses
Tutorial
Before getting started, set up a development environment by following the setup instructions
We will write some tests for Docs.rs, a service that builds documentation of crates, which are packages in the Rust programming language.
(1) Boilerplate
The code for our badge is in services/docsrs/docsrs.service.js. Tests for this badge should be stored in services/docsrs/docsrs.tester.js.
We'll start by adding some boilerplate to our file:
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()If our .service.js module exports a single class, we can createServiceTester, which uses convention to create a ServiceTester object. Calling this inside services/docsrs/docsrs.tester.js will create a ServiceTester object configured for the service exported in services/docsrs/docsrs.service.js. We will add our tests to this ServiceTester object t, which is exported from the module.
(2) Our First Test Case
First we'll add a test for the typical case:
import Joi from 'joi'
t.create('Docs with no version specified')
.get('/tokio.json')
.expectBadge({
label: 'docs',
message: Joi.equal('passing', 'failing'),
})- The
create()method adds a new test to the tester object. The chained-on calls come from the API testing framework IcedFrisby. Here's a longer example and the complete API guide. - We use the
get()method to request a badge. There are several points to consider here:- We need a real crate to test against. In this case we have used Tokio but we could have chosen any one.
- Note that when we call our badge, we are allowing it to communicate with an external service without mocking the response. We write tests which interact with external services, which is unusual practice in unit testing. We do this because one of the purposes of service tests is to notify us if a badge has broken due to an upstream API change. For this reason it is important for at least one test to call the live API without mocking the interaction.
- All badges on shields can be requested in a number of formats. As well as calling https://img.shields.io/docsrs/tokio.svg to generate we can also call https://img.shields.io/docsrs/tokio.json to request the same content as JSON. When writing service tests, we request the badge in JSON format so it is easier to make assertions about the content.
- We don't need to explicitly call
/docsrs/tokio.jsonhere, only/tokio.json. When we create a tester object withcreateServiceTester()the URL base defined in our service class (in this case/docsrs) is used as the base URL for any requests made by the tester object.
expectBadge()is a helper function which accepts either a string literal, a RegExp or a Joi schema for the different fields. Joi is a validation library that is built into IcedFrisby which you can use to match based on a set of allowed strings, regexes, or specific values. You can refer to their API reference.- We expect
labelto be a string literal"docs". - Because this test depends on a live service, we don't want our test to depend on our API call returning a particular build status. Instead we should perform a "picture check" to assert that the badge data conforms to an expected pattern. Our test should not depend on the status of the example crates's documentation build, but should fail if trying to generate the badge throws an error, or if there is a breaking change to the upstream API. In this case, we specify a list with all possible response values,
Joi.equal('passing', 'failing'). For more complex cases, services/test-validators.js defines a number of useful validators we can use with regular expressions. Many of the common badge types (version, downloads, rank, etc.) already have validators defined there.
When defining an IcedFrisby test, typically you would invoke the toss() method, to register the test. This is not necessary, because the Shields test harness will call it for you.
(3) Running the Tests
Lets run the test we have written:
npm run test:services -- --only=docsrsThe --only= option indicates which service or services you want to test. You can provide a comma-separated list here.
The -- tells the NPM CLI to pass the remaining arguments through to the test runner.
Here's the output:
Server is starting up: http://localhost:1111/
DocsRs
[live] Docs with no version specified
√
[ GET /tokio.json ] (441ms)
1 passing (1s)That's looking good!
Sometimes if we have a failing test, it is useful to be able to see some logging output to help work out why the test is failing. We can do that by calling npm run test:services:trace. Try running
npm run test:services:trace -- --only=docsrsto run the test with some additional debug output.
(4) Writing More Tests
We should write tests cases for valid paths through our code. The Docs.rs badge supports an optional version parameter so we'll add a second test for a branch build. In this case, we know for sure that the documentation for this older version was successfully built, we specify a string literal instead of a Joi schema for message. This narrows down the expectation and gives us a more helpful error message if the test fails.
t.create('Passing docs for version').get('/tokio/1.37.0.json').expectBadge({
label: '[email protected]',
message: 'passing',
color: 'brightgreen',
})Server is starting up: http://localhost:1111/
DocsRs
[live] Docs with no version specified
√
[ GET /tokio.json ] (408ms)
[live] Passing docs for version
√
[ GET /tokio/1.37.0.json ] (171ms)
2 passing (2s)Once we have multiple tests, sometimes it is useful to run only one test. We can do this using the --fgrep argument. For example:
npm run test:services -- --only="docsrs" --fgrep="Passing docs for version"Documentation for tokio version 1.32.1 failed to build, we can also add a corresponding test:
t.create('Failing docs for version').get('/tokio/1.32.1.json').expectBadge({
label: '[email protected]',
message: 'failing',
color: 'red',
})Note that in these tests, we have specified a color parameter in expectBadge. This is helpful in a case like this when we want to test custom color logic, but it is only necessary to explicitly test color values if our badge implements custom logic for setting the badge colors.
Having covered the typical and custom cases, we'll move on to errors. We should include a test for the 'not found' response and also tests for any other custom error handling. When a version is specified, the Docs.rs integration defines a custom error condition for 400 status codes:
httpErrors: version ? { 400: 'malformed version' } : {},First we'll add a test for a crate and a test for a version which will return 404 errors:
t.create('Crate not found')
.get('/not-a-crate/latest.json')
.expectBadge({ label: 'docs', message: 'not found' })
t.create('Version not found')
.get('/tokio/0.8.json')
.expectBadge({ label: 'docs', message: 'not found' })We also want to include a test for a case where a malformed version was specified. For example:
t.create('Malformed version')
.get('/tokio/not-a-version.json')
.expectBadge({ label: 'docs', message: 'malformed version' })(5) Mocking Responses
If we didn't have a stable example of crate version with a failing documentation build, another approach would be to mock the response. An alternative test for the 'Failing docs for version' case might look like:
t.create('Failing docs for version')
.get('/tokio/1.32.1.json')
.intercept(nock =>
nock('https://docs.rs/crate')
.get('/tokio/1.32.1/status.json')
.reply(200, { doc_status: false }),
)
.expectBadge({
label: '[email protected]',
message: 'failing',
color: 'red',
})This will intercept the request and provide our own mock response. We use the intercept() method provided by the icedfrisby-nock plugin. It takes a setup function, which returns an interceptor, and exposes the full API of the HTTP mocking library Nock.
Nock is fussy. All parts of a request must match perfectly for the mock to take effect, including the HTTP method (in this case GET), scheme (https), host, and path.
Code coverage
By checking code coverage, we can make sure we've covered all our bases.
We can generate a coverage report and open it:
npm run coverage:test:services -- -- --only=docsrs
npm run coverage:report:openPull requests
Pull requests must follow the documented conventions in order to execute the correct set of service tests.
Getting help
If you have questions about how to write your tests, please open an issue. If there's already an issue open for the badge you're working on, you can post a comment there instead.
Further reading
File: doc/TUTORIAL.md
Tutorial on how to add a badge for a service
This tutorial should help you add a service to shields.io in form of a badge. You will need to learn to use JavaScript, Git and GitHub, however, this document will guide you through that journey if you are a beginner. Please improve the tutorial while you read it.
(1) Reading
- Contributing Guidance
- Documentation for the Shields Core API
- You can also read previous merged pull-requests with the 'service-badge' label to see how other people implemented their badges.
(2) Setup
Pre-requisites
Git
You should have git installed. If you do not, install git and learn about the GitHub workflow.
Node, NPM
Node 24 and NPM 11.x or 12.x is required. If you don't already have them, install node and npm: https://nodejs.org/en/download/
Setup a dev install
- Fork this repository.
- Clone the fork
git clone [email protected]:YOURGITHUBUSERNAME/shields.git cd shields- Install project dependencies
npm ci - Run the badge server and the frontend dev server
npm start - Visit the website to check the front-end is loaded: http://localhost:3000/.
In case you get the "getaddrinfo ENOTFOUND localhost" error, visit http://127.0.0.1:3000/ instead or take a look at this issue.
(3) Open an Issue
Before you want to implement your service, you may want to open an issue and describe what you have in mind:
- What is the badge for?
- Which API do you want to use?
You may additionally proceed to say what you want to work on. This information allows other humans to help and build on your work.
(4) Implementing
(4.1) Structure and Layout
Service badge code is stored in the /services directory. Each service has a directory for its files:
In files ending with
.service.js, you can find the code which handles incoming requests and generates the badges. Sometimes, code for a service can be re-used. This might be the case when you add a badge for an API which is already used by other badges.Imagine a service that lives at https://img.shields.io/example/some-param-here.
For services with a single badge, the badge code will generally be stored in
/services/example/example.service.js. If you add a badge for a new API, create a new directory.Example: Docs.rs
For service families with multiple badges we usually store the code for each badge in its own file like this:
/services/example/example-downloads.service.js/services/example/example-version.service.jsetc.
Example: ruby gems
In files ending with
.tester.js, you can find the code which uses the shields server to test if the badges are generated correctly. There is a chapter on Tests.
(4.2) Our First Badge
All service badge classes inherit from BaseService or another class which extends it. Other classes implement useful behavior on top of BaseService.
- BaseJsonService implements methods for performing requests to a JSON API and schema validation.
- BaseJsonlService implements methods for performing requests to a JSONL API and schema validation.
- BaseXmlService implements methods for performing requests to an XML API and schema validation.
- BaseYamlService implements methods for performing requests to a YAML API and schema validation.
- BaseTomlService implements methods for performing requests to a TOML API and schema validation.
- BaseSvgScrapingService implements methods for retrieving information from existing third-party badges.
- BaseGraphqlService implements methods for performing requests to a GraphQL API and schema validation.
- If you are contributing to a service family, you may define a common super class for the badges or one may already exist.
As a first step we will look at the code for an example which generates a badge without contacting an API. Note that lower camelCase should be used for variables, functions, and path/query parameters.
// (1)
import { BaseService } from '../index.js'
// (2)
export default class Example extends BaseService {
// (3)
static category = 'build'
// (4)
static route = { base: 'example', pattern: ':text' }
// (5)
async handle({ text }) {
return {
label: 'example',
message: text,
color: 'blue',
}
}
}Description of the code:
- Our service badge class will extend
BaseServiceso we need to import it. - Our module must export a class which extends
BaseService. - Returns the name of the category to sort this badge into (eg. "build"). Used to sort the examples on the main shields.io website. Here is the list of the valid categories. See section 4.4 for more details.
routedeclares the URL path at which the service operates. It also maps components of the URL path to handler parameters.basedefines the first part of the URL that doesn't change, e.g./example/.patterndefines the variable part of the route, everything that comes after/example/. It can include any number of named parameters. These are converted into regular expressions bypath-to-regexp. Because a service instance won't be created until it's time to handle a request, the route and other metadata must be obtained by examining the classes themselves. That's why they're markedstatic.- See designing badge URLs for additional naming and URL conventions.
routeEnum(optional): an array of strings that defines the allowed values for the first named parameter inpattern.- Only the first named parameter is validated by
routeEnum. - Example: for
pattern: ':type/:user/:repo', setstatic routeEnum = ['dt', 'dm', 'dd']; the incomingtypevalue is validated against that array. - If that first parameter value is not present in
routeEnum, the request is rejected beforehandle()is invoked.
- Only the first named parameter is validated by
// Example service that uses routeEnum
export default class Downloads extends BaseService {
static route = { base: 'downloads', pattern: ':type/:user/:repo' }
static routeEnum = ['dt', 'dm', 'dd']
async handle({ type, user, repo }) {
// `type` is guaranteed to be one of 'dt', 'dm', or 'dd'
}
}- All badges must implement the
async handle()function that receives parameters to render the badge. Parameters ofhandle()will match the name defined inrouteBecause we're capturing a single variable calledtextour function signature isasync handle({ text }).asyncis needed to let JavaScript do other things while we are waiting for result from external API. Although in this simple case, we don't make any external calls. Ourhandle()function should return an object with 3 properties:label: the text on the left side of the badgemessage: the text on the right side of the badge - here we are passing through the parameter we captured in the route regexcolor: the background color of the right side of the badge
The process of turning this object into an image is handled automatically by the BaseService class.
To try out this example badge:
- Copy and paste this code into a new file in
/services/example/example.service.js - The server should restart on its own. (If it doesn't for some reason, quit the running server with
Control+C, then start it again withnpm start.) - Visit the badge at http://localhost:8080/example/foo. It should look like this:
(4.3) Querying an API
The example above was completely static. In order to make a useful service badge we will need to get some data from somewhere. The most common case is that we will query an API which serves up some JSON data, but other formats (e.g: XML) may be used.
This example is based on the Ruby Gems version badge:
// (1)
import { renderVersionBadge } from '../version.js'
// (2)
import { BaseJsonService } from '../index.js'
// (3)
import Joi from 'joi'
const schema = Joi.object({
version: Joi.string().required(),
}).required()
// (4)
export default class GemVersion extends BaseJsonService {
// (5)
static category = 'version'
// (6)
static route = { base: 'gem/v', pattern: ':gem' }
// (7)
static defaultBadgeData = { label: 'gem' }
// (10)
static render({ version }) {
return renderVersionBadge({ version })
}
// (9)
async fetch({ gem }) {
return this._requestJson({
schema,
url: `https://rubygems.org/api/v1/gems/${gem}.json`,
})
}
// (8)
async handle({ gem }) {
const { version } = await this.fetch({ gem })
return this.constructor.render({ version })
}
}Description of the code:
In this case we are making a version badge, which is a common pattern. Instead of directly returning an object in this badge we will use a helper function to format our data consistently. There are a variety of helper functions to help with common tasks in
/services. Some useful generic helpers can be found in:Our badge will query a JSON API so we will extend
BaseJsonServiceinstead ofBaseService. This contains some helpers to reduce the need for boilerplate when calling a JSON API.We perform input validation by defining a schema which we expect the JSON we receive to conform to. This is done using Joi. Defining a schema means we can ensure the JSON we receive meets our expectations and throw an error if we receive unexpected input without having to explicitly code validation checks. The schema also acts as a filter on the JSON object. Any properties we're going to reference need to be validated, otherwise they will be filtered out. In this case our schema declares that we expect to receive an object which must have a property called 'version', which is a string. There is further documentation on input validation.
Our module exports a class which extends
BaseJsonServiceReturns the name of the category to sort this badge into (eg. "build"). Used to sort the examples on the main shields.io website. Here is the list of the valid categories. See section 4.4 for more details.
As with our previous badge, we need to declare a route. This time we will capture a variable called
gem.We can use
defaultBadgeDatato set a defaultcolor,logoand/orlabel. Ifhandle()doesn't return any of these keys, we'll use the default. Instead of explicitly setting the label text when we return a badge object, we'll usedefaultBadgeDatahere to define it declaratively.We now jump to the bottom of the example code to the function all badges must implement:
async handle(). This is the function the server will invoke to handle an incoming request. Because our URL pattern captures a variable calledgem, our function signature isasync handle({ gem }). We usually separate the process of generating a badge into 2 stages or concerns: fetch and render. Thefetch()function is responsible for calling an API endpoint to get data. Therender()function formats the data for display. In a case where there is a lot of calculation or intermediate steps, this pattern may be thought of as fetch, transform, render and it might be necessary to define some helper functions to assist with the 'transform' step.Working our way upward, the
async fetch()method is responsible for calling an API endpoint to get data. ExtendingBaseJsonServicegives us the helper function_requestJson(). Note here that we pass the schema we defined in step 3 as an argument._requestJson()will deal with validating the response against the schema and throwing an error if necessary._requestJson()automatically adds an Accept header, checks the status code, parses the response as JSON, and returns the parsed response._requestJson()uses got to perform the HTTP request. Options can be passed to got, including method, query string, and headers. If headers are provided they will override the ones automatically set by_requestJson(). There is no need to specify json, as the JSON parsing is handled by_requestJson(). See thegotdocs for supported options.- Error messages corresponding to each status code can be returned by passing a dictionary of status codes -> messages in
httpErrors. - A more complex call to
_requestJson()might look like this:jsreturn this._requestJson({ schema: mySchema, url, options: { searchParams: { branch: 'master' } }, httpErrors: { 401: 'private application not supported', 404: 'application not found', }, })
Upward still, the
static render()method is responsible for formatting the data for display.render()is a pure function so we can make it astaticmethod. By convention we declare functions which don't referencethisasstatic. We could explicitly return an object here, as we did in the previous example. In this case, we will hand the version string off torenderVersionBadge()which will format it consistently and set an appropriate color. BecauserenderVersionBadge()doesn't return alabelkey, the default label we defined indefaultBadgeDatawill be used when we generate the badge.
This code allows us to call this URL https://img.shields.io/gem/v/formatador to generate this badge:
It is also worth considering the code we haven't written here. Note that our example doesn't contain any explicit error handling code, but our badge handles errors gracefully. For example, if we call https://img.shields.io/gem/v/does-not-exist we render a 'not found' badge because https://rubygems.org/api/v1/gems/this-package-does-not-exist.json returns a 404 Not Found status code. When dealing with well-behaved APIs, some of our error handling will be handled implicitly in BaseJsonService.
Specifically BaseJsonService will handle the following errors for us:
- API does not respond
- API responds with a non-
200 OKstatus code - API returns a response which can't be parsed as JSON
- API returns a response which doesn't validate against our schema
Sometimes it may be necessary to manually throw an exception to deal with a non-standard error condition. If so, there are several standard exceptions that can be used. The errors are documented at errors and can be imported via the import shortcut and then thrown:
import { NotFound } from '../index.js'
throw new NotFound({ prettyMessage: 'package not found' })(4.4) Adding Documentation to the Frontend
To render the shields.io website, we produce an OpenAPI 3 specification which describes the available badge endpoints. Then we use that specification to render the frontend.
Once we have implemented our badge, we want to add it to the index so that users can discover it. We will do this by adding an additional object openApi to our class. This object contains an OpenAPI Paths Object describing the endpoint or endpoints exposed by this service class.
// (1)
import { pathParams } from '../index.js'
export default class GemVersion extends BaseJsonService {
// ...
// (2)
static category = 'version'
static openApi = {
// (3)
'/gem/v/{gem}': {
// (4)
get: {
// (5)
summary: 'Gem Version',
description:
'[Ruby Gems](https://rubygems.org/) is a registry for ruby libraries',
// (6)
parameters: pathParams({
name: 'gem',
description: 'Name of the Ruby Gem',
example: 'formatador',
}),
},
},
}
}There are four helper functions we can use to assemble Open API Parameter objects. These are:
pathParam- returns a single Parameter object describing a single path parameterpathParams- returns an array of path parameter objectsqueryParam- returns a single Parameter object describing a single query parameterqueryParams- returns an array of query parameter objects
These four helper functions are documented in more detail at http://contributing.shields.io/module-core_base-service_openapi.html
We defined category earlier in the tutorial. The
categoryproperty defines which heading in the index our example will appear under.The keys of the
openApiobject are routes. In this case we only need to describe one route. In some cases, a service class can define more than one badge route. Open API doesn't have the concept of optional path parameters (more specifically,in: 'path'impliesrequired: true) so if there are any optional path parameters in our route, ouropenApiobject needs to describe two URLs: one without the optional parameter, and another with it.The HTTP method. Shields only allows GET requests, so this is always
get.summary(required) is a short title or description of the badge.descriptionis an optional longer description or additional documentation. We can use markdown or HTML syntax inside thedescriptionfield.parametersis an array of Open API Parameter objects describing any parameters we can pass to this route. This array should include all path parameters included in the key that this value object describes and all relevant query parameters. As a minimum, we need to supplynameandexample. The example must be a valid example of a value we can provide for this parameter. In this case we need a valid ruby gem, so we've picked formatador. There are also optional keys we can pass. The codejsparameters: pathParams({ name: 'gem', description: 'Name of the Ruby Gem', example: 'formatador', })is equivalent to
jsparameters: [ { name: 'gem', in: 'path', required: true, schema: { type: 'string' }, example: 'formatador', description: 'Name of the Ruby Gem', }, ]but we have used the helper function
pathParamsto imply some defaults and reduce the amount of code we need to write by hand.Path and query parameters should be lower camelCase, for example use
gemNameinstead ofgem_name.
Save, run npm start, and you can see it locally.
If you update openApi, you don't have to restart the server. Run npm run prestart in another terminal window and the frontend will update.
(4.5) Write Tests
When creating a badge for a new service or changing a badge's behavior, tests should be included. They serve several purposes:
- They speed up future contributors when they are debugging or improving a badge.
- If the contributors would like to change your badge, chances are, they forget edge cases and break your code. Tests may give hints in such cases.
- The contributor and reviewer can easily verify the code works as intended.
- When a badge stops working on the live server, maintainers can find out right away.
There is a dedicated tutorial for tests in the service-tests folder. Please follow it to include tests on your pull-request.
(4.6) Update the Docs
If your submission requires an API token or authentication credentials, please update server-secrets.md. You should explain what the token or credentials are for and how to obtain them.
(5) Create a Pull Request
Once you have implemented a new badge:
- Before submitting your changes, please review the coding guidelines.
- Create a pull-request to propose your changes.
- CI will check the tests pass and that your code conforms to our coding standards.
- We also use Danger to check for some common problems. The first comment on your pull request will be posted by a bot. If there are any errors or warnings raised, please review them.
- One of the maintainers will review your contribution.
- We'll work with you to progress your contribution suggesting improvements if necessary. Although there are some occasions where a contribution is not appropriate, if your contribution conforms to our guidelines we'll aim to work towards merging it. The majority of pull requests adding a service badge are merged.
- If your contribution is merged, the final comment on the pull request will be an automated post which you can monitor to tell when your contribution has been deployed to production.
File: frontend/package.json
{
"name": "badge-frontend",
"version": "0.0.0",
"description": "Shields.io frontend",
"private": true,
"homepage": "https://shields.io",
"license": "(MIT OR Apache-2.0)",
"repository": {
"type": "git",
"url": "git+https://github.com/badges/shields.git",
"directory": "frontend"
},
"scripts": {
"test": "echo 'Run tests from parent dir'; false"
}
}
File: frontend/blog/2023-07-03-new-frontend.md
slug: new-frontend title: We launched a new frontend authors: name: chris48s title: Shields.io Core Team url: https://github.com/chris48s image_url: https://avatars.githubusercontent.com/u/6025893 tags: []
Alongside the general visual refresh and improvements to look and feel, our new frontend has allowed us to address a number of long-standing feature requests and enhancements:
- Clearer and more discoverable documentation for our static, dynamic json/xml/yaml and endpoint badges
- Improved badge builder interface, with all optional query parameters included in the builder for each badge
- Each badge now has its own documentation page, which we can link to. e.g: https://shields.io/badges/discord
- Light/dark mode themes
- Improved search
- Documentation for individual path and query parameters
The new site also comes with big maintenance benefits for the core team. We rely heavily on docusaurus, docusaurus-openapi, and docusaurus-search-local. This moves us to a mostly declarative setup, massively reducing the amount of custom frontend code we maintain ourselves.
File: frontend/blog/2023-07-29-tag-filter.md
slug: tag-filter title: Applying filters to GitHub Tag and Release badges authors: name: chris48s title: Shields.io Core Team url: https://github.com/chris48s image_url: https://avatars.githubusercontent.com/u/6025893 tags: []
We recently shipped a feature which allows you to pass an arbitrary filter to the GitHub tag and release badges. The filter param can be used to apply a filter to the project's tag or release names before selecting the latest from the list. Two constructs are available: * is a wildcard matching zero or more characters, and if the pattern starts with a !, the whole pattern is negated.
To give an example of how this might be useful, we create two types of tags on our GitHub repo: https://github.com/badges/shields/tags There are tags in the format major.minor.patch which correspond to our NPM package releases and tags in the format server-YYYY-MM-DD that correspond to our docker snapshot releases.
In our case, this would allow us to make a badge that applies the filter !server-* to filter out the snapshot tags and just select the latest package tag.
File: frontend/blog/2023-11-29-simpleicons10.md
slug: simple-icons-10 title: Simple Icons 10 authors: name: chris48s title: Shields.io Core Team url: https://github.com/chris48s image_url: https://avatars.githubusercontent.com/u/6025893 tags: []
Logos on Shields.io are provided by SimpleIcons. We've recently upgraded to SimpleIcons 10. This release removes 45 icons. A full list of the removals can be found in the release notes.
Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the SimpleIcons project.
File: frontend/blog/2024-01-13-simpleicons11.md
slug: simple-icons-11 title: Simple Icons 11 authors: name: chris48s title: Shields.io Core Team url: https://github.com/chris48s image_url: https://avatars.githubusercontent.com/u/6025893 tags: []
Logos on Shields.io are provided by SimpleIcons. We've recently upgraded to SimpleIcons 11. This release removes the following 4 icons:
- Babylon.js
- Hulu
- Pepsi
- Uno
More details can be found in the release notes.
Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the SimpleIcons project.
File: frontend/blog/2024-06-01-simpleicons12.md
slug: simple-icons-12 title: Simple Icons 12 authors: name: chris48s title: Shields.io Core Team url: https://github.com/chris48s image_url: https://avatars.githubusercontent.com/u/6025893 tags: []
Logos on Shields.io are provided by SimpleIcons. We've recently upgraded to SimpleIcons 12. This release removes the following 10 icons:
- FITE
- Flattr
- Google Bard
- Integromat
- Niantic
- Nintendo Network
- Rome
- Shotcut
- Skynet
And renames the following 3:
- Airbrake.io to Airbrake
- Amazon AWS to Amazon Web Services
- RStudio to RStudio IDE
More details can be found in the release notes.
Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the SimpleIcons project.
File: frontend/blog/2024-07-05-simpleicons13.md
slug: simple-icons-13 title: Simple Icons 13 authors: name: chris48s title: Shields.io Core Team url: https://github.com/chris48s image_url: https://avatars.githubusercontent.com/u/6025893 tags: []
Logos on Shields.io are provided by SimpleIcons. We've recently upgraded to SimpleIcons 13. This release removes 65 icons and renames one. A full list of the changes can be found in the release notes.
Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the SimpleIcons project.