├── .c8rc.json ├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1_Bug_report.yml │ ├── 2_Failing_service_test.md │ ├── 3_Badge_request.yml │ ├── 4_Feature_request.md │ └── config.yml ├── actions │ ├── core-tests │ │ └── action.yml │ ├── docusaurus-swizzled-warning │ │ ├── action.yml │ │ ├── helpers.js │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── draft-release │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── integration-tests │ │ └── action.yml │ ├── package-tests │ │ └── action.yml │ ├── service-tests │ │ └── action.yml │ └── setup │ │ └── action.yml ├── dependabot.yml ├── pull_request_template.md ├── scripts │ ├── cleanup-review-apps.sh │ └── deploy-review-app.sh └── workflows │ ├── build-docker-image.yml │ ├── cleanup-review-apps.yml │ ├── coveralls-code-coverage.yml │ ├── create-release.yml │ ├── daily-tests.yml │ ├── danger.yml │ ├── deploy-docs.yml │ ├── deploy-review-app.yml │ ├── docusaurus-swizzled-warning.yml │ ├── draft-release.yml │ ├── enforce-dependency-review.yml │ ├── publish-docker-next.yml │ ├── test-bug-run-badge.yml │ ├── test-e2e.yml │ ├── test-integration-22.yml │ ├── test-integration.yml │ ├── test-lint.yml │ ├── test-main-22.yml │ ├── test-main.yml │ ├── test-package-cli.yml │ ├── test-package-lib.yml │ ├── test-services-22.yml │ ├── test-services.yml │ └── update-github-api.yml ├── .gitignore ├── .gitpod.yml ├── .mocharc.yml ├── .npmrc ├── .prettierignore ├── .prettierrc.yml ├── .vscode ├── extensions.json └── launch.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── SECURITY.md ├── __snapshots__ └── make-badge.spec.mjs.js ├── badge-maker ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── index.test-d.ts ├── lib │ ├── badge-cli.js │ ├── badge-cli.spec.mjs │ ├── badge-renderers.js │ ├── color.js │ ├── color.spec.js │ ├── constants.js │ ├── index.js │ ├── index.spec.mjs │ ├── make-badge.js │ ├── make-badge.spec.mjs │ ├── xml.js │ └── xml.spec.js └── package.json ├── config ├── custom-environment-variables.yml ├── default.yml ├── development.yml ├── local-shields-io-production.template.yml ├── local.template.yml ├── production.yml ├── shields-io-production.yml └── test.yml ├── core ├── badge-urls │ ├── make-badge-url.js │ ├── path-helpers.js │ └── path-helpers.spec.js ├── base-service │ ├── auth-helper.js │ ├── auth-helper.spec.js │ ├── base-graphql.js │ ├── base-graphql.spec.js │ ├── base-json.js │ ├── base-json.spec.js │ ├── base-static.js │ ├── base-svg-scraping.js │ ├── base-svg-scraping.spec.js │ ├── base-toml.js │ ├── base-toml.spec.js │ ├── base-xml.js │ ├── base-xml.spec.js │ ├── base-yaml.js │ ├── base-yaml.spec.js │ ├── base.js │ ├── base.spec.js │ ├── cache-headers.js │ ├── cache-headers.spec.js │ ├── categories.js │ ├── check-error-response.js │ ├── check-error-response.spec.js │ ├── coalesce-badge.js │ ├── coalesce-badge.spec.js │ ├── coalesce.js │ ├── coalesce.spec.js │ ├── deprecated-service.js │ ├── deprecated-service.spec.js │ ├── errors.js │ ├── got-config.js │ ├── got-config.spec.js │ ├── got.js │ ├── got.spec.js │ ├── graphql.js │ ├── graphql.spec.js │ ├── index.js │ ├── json.js │ ├── legacy-request-handler.js │ ├── legacy-request-handler.spec.js │ ├── legacy-result-sender.js │ ├── loader-test-fixtures │ │ ├── empty-array.fixture.js │ │ ├── empty-no-export.fixture.js │ │ ├── empty-object.fixture.js │ │ ├── empty-undefined.fixture.js │ │ ├── invalid-mixed.fixture.js │ │ ├── invalid-no-base.fixture.js │ │ ├── invalid-wrong-base.fixture.js │ │ ├── valid-array.fixture.js │ │ ├── valid-class.fixture.js │ │ └── valid-object.fixture.js │ ├── loader.js │ ├── loader.spec.js │ ├── metric-helper.js │ ├── openapi.js │ ├── openapi.spec.js │ ├── redirector.js │ ├── redirector.spec.js │ ├── resource-cache.js │ ├── resource-cache.spec.js │ ├── route.js │ ├── route.spec.js │ ├── service-definitions.js │ ├── to-array.js │ ├── trace.js │ ├── validate.js │ └── validate.spec.js ├── got-test-client.js ├── register-chai-plugins.spec.js ├── server │ ├── error-pages │ │ ├── 404.html │ │ └── 500.html │ ├── in-process-server-test-helpers.js │ ├── influx-metrics.js │ ├── influx-metrics.spec.js │ ├── instance-id-generator.js │ ├── log.js │ ├── metrics │ │ ├── format-converters.js │ │ └── format-converters.spec.js │ ├── prometheus-metrics.js │ ├── prometheus-metrics.spec.js │ ├── server.js │ ├── server.spec.js │ └── test-public │ │ ├── img │ │ └── frontend-image.png │ │ └── index.html ├── service-test-runner │ ├── cli.js │ ├── create-service-tester.js │ ├── icedfrisby-shields.js │ ├── pull-request-services-cli.js │ ├── runner.js │ ├── service-tester.js │ ├── services-for-title.js │ └── services-for-title.spec.js ├── token-pooling │ ├── sql-token-persistence.integration.js │ ├── sql-token-persistence.js │ ├── token-pool.js │ └── token-pool.spec.js └── unhandled-rejection.spec.js ├── cypress.config.js ├── cypress ├── .eslintrc.yml └── e2e │ └── main-page.cy.js ├── dangerfile.js ├── doc ├── TUTORIAL.md ├── adding-new-config-values.md ├── authentication.md ├── badge-urls.md ├── code-walkthrough.md ├── deprecating-badges.md ├── flamegraph.png ├── input-validation.md ├── json-format.md ├── performance-testing.md ├── production-hosting.md ├── releases.md ├── self-hosting.md ├── server-secrets.md ├── service-tests.md └── static-badges.md ├── entrypoint.spec.js ├── eslint.config.js ├── fly.toml ├── frontend ├── babel.config.cjs ├── blog │ ├── 2023-07-03-new-frontend.md │ ├── 2023-07-29-tag-filter.md │ ├── 2023-11-29-simpleicons10.md │ ├── 2024-01-13-simpleicons11.md │ ├── 2024-06-01-simpleicons12.md │ ├── 2024-07-05-simpleicons13.md │ ├── 2024-07-10-sunsetting-shields-custom-logos.md │ ├── 2024-09-25-rce.md │ ├── 2024-11-14-token-pool.md │ └── 2024-12-27-simpleicons14.md ├── categories │ └── .gitkeep ├── docs │ ├── index.md │ ├── logos.md │ └── static-badges.md ├── docusaurus.config.cjs ├── package.json ├── sidebars.cjs ├── src │ ├── components │ │ ├── homepage-features.js │ │ └── homepage-features.module.css │ ├── css │ │ └── custom.css │ ├── pages │ │ ├── community.md │ │ ├── donate.md │ │ ├── index.js │ │ ├── index.module.css │ │ └── privacy.md │ ├── plugins │ │ └── strip-code-block-links.js │ └── theme │ │ ├── ApiDemoPanel │ │ ├── Curl │ │ │ └── index.js │ │ └── Response │ │ │ └── index.js │ │ └── DocPaginator │ │ └── index.js └── static │ ├── .nojekyll │ └── img │ ├── builder.png │ ├── favicon.ico │ ├── icon.svg │ └── logo.png ├── jsdoc.json ├── lib ├── load-simple-icons.js ├── load-simple-icons.spec.js ├── logos.js ├── logos.spec.js ├── svg-helpers.js └── svg-helpers.spec.js ├── migrations ├── 1676731511125_initialize.cjs └── 1727809177709_add-created.cjs ├── package-lock.json ├── package.json ├── readme-logo.svg ├── scripts ├── badge-cli.js ├── benchmark-performance.js ├── capture-timings.js ├── export-openapi-cli.js ├── mocha2md.js ├── update-github-api.js └── write-migrations-config.js ├── server.js ├── services ├── amo │ ├── amo-base.js │ ├── amo-downloads.service.js │ ├── amo-downloads.tester.js │ ├── amo-rating.service.js │ ├── amo-rating.tester.js │ ├── amo-users.service.js │ ├── amo-users.tester.js │ ├── amo-version.service.js │ └── amo-version.tester.js ├── ansible │ ├── ansible-collection-downloads.service.js │ ├── ansible-collection-downloads.tester.js │ ├── ansible-collection-version.service.js │ ├── ansible-collection-version.tester.js │ ├── ansible-collection.service.js │ ├── ansible-collection.tester.js │ ├── ansible-quality.service.js │ ├── ansible-quality.tester.js │ ├── ansible-role.service.js │ └── ansible-role.tester.js ├── appveyor │ ├── appveyor-base.js │ ├── appveyor-build-redirect.service.js │ ├── appveyor-build-redirect.tester.js │ ├── appveyor-build.service.js │ ├── appveyor-build.tester.js │ ├── appveyor-job-build.service.js │ ├── appveyor-job-build.spec.js │ ├── appveyor-job-build.tester.js │ ├── appveyor-tests.service.js │ └── appveyor-tests.tester.js ├── archlinux │ ├── archlinux.service.js │ └── archlinux.tester.js ├── aur │ ├── aur.service.js │ ├── aur.spec.js │ └── aur.tester.js ├── azure-devops │ ├── azure-devops-base.js │ ├── azure-devops-build.service.js │ ├── azure-devops-build.tester.js │ ├── azure-devops-coverage.service.js │ ├── azure-devops-coverage.tester.js │ ├── azure-devops-helpers.js │ ├── azure-devops-release.service.js │ ├── azure-devops-release.tester.js │ ├── azure-devops-tests.service.js │ ├── azure-devops-tests.tester.js │ ├── vso-redirect.service.js │ └── vso-redirect.tester.js ├── bit │ ├── bit-components.service.js │ └── bit-components.tester.js ├── bitbucket │ ├── bitbucket-issues.service.js │ ├── bitbucket-issues.tester.js │ ├── bitbucket-last-commit.service.js │ ├── bitbucket-last-commit.tester.js │ ├── bitbucket-pipelines.service.js │ ├── bitbucket-pipelines.tester.js │ ├── bitbucket-pull-request.service.js │ ├── bitbucket-pull-request.spec.js │ └── bitbucket-pull-request.tester.js ├── bitrise │ ├── bitrise.service.js │ └── bitrise.tester.js ├── bountysource │ ├── bountysource.service.js │ └── bountysource.tester.js ├── bower │ ├── bower-base.js │ ├── bower-license.service.js │ ├── bower-license.tester.js │ ├── bower-version.service.js │ ├── bower-version.spec.js │ └── bower-version.tester.js ├── bstats │ ├── bstats-players.service.js │ ├── bstats-players.tester.js │ ├── bstats-servers.service.js │ └── bstats-servers.tester.js ├── bugzilla │ ├── bugzilla.service.js │ ├── bugzilla.spec.js │ └── bugzilla.tester.js ├── build-status.js ├── build-status.spec.js ├── buildkite │ ├── buildkite.service.js │ └── buildkite.tester.js ├── bundlejs │ ├── bundlejs-package.service.js │ └── bundlejs-package.tester.js ├── bundlephobia │ ├── bundlephobia.service.js │ └── bundlephobia.tester.js ├── categories.js ├── cdnjs │ ├── cdnjs.service.js │ └── cdnjs.tester.js ├── check-services.spec.js ├── chocolatey │ ├── chocolatey.service.js │ └── chocolatey.tester.js ├── chrome-web-store │ ├── chrome-web-store-base.js │ ├── chrome-web-store-last-updated.service.js │ ├── chrome-web-store-last-updated.tester.js │ ├── chrome-web-store-price.service.js │ ├── chrome-web-store-price.tester.js │ ├── chrome-web-store-rating.service.js │ ├── chrome-web-store-rating.tester.js │ ├── chrome-web-store-size.service.js │ ├── chrome-web-store-size.spec.js │ ├── chrome-web-store-size.tester.js │ ├── chrome-web-store-users.service.js │ ├── chrome-web-store-users.tester.js │ ├── chrome-web-store-version.service.js │ └── chrome-web-store-version.tester.js ├── cii-best-practices │ ├── cii-best-practices.service.js │ └── cii-best-practices.tester.js ├── circleci │ ├── circleci.service.js │ └── circleci.tester.js ├── cirrus │ ├── cirrus.service.js │ └── cirrus.tester.js ├── clearlydefined │ ├── clearlydefined-score.service.js │ └── clearlydefined-score.tester.js ├── clojars │ ├── clojars-base.js │ ├── clojars-downloads.service.js │ ├── clojars-downloads.tester.js │ ├── clojars-version.service.js │ └── clojars-version.tester.js ├── cocoapods │ ├── cocoapods-base.js │ ├── cocoapods-docs.service.js │ ├── cocoapods-docs.tester.js │ ├── cocoapods-license.service.js │ ├── cocoapods-license.tester.js │ ├── cocoapods-platform.service.js │ ├── cocoapods-platform.tester.js │ ├── cocoapods-version.service.js │ └── cocoapods-version.tester.js ├── codacy │ ├── codacy-coverage.service.js │ ├── codacy-coverage.tester.js │ ├── codacy-grade.service.js │ ├── codacy-grade.tester.js │ └── codacy-helpers.js ├── codeclimate │ ├── codeclimate-analysis-redirector.service.js │ ├── codeclimate-analysis-redirector.tester.js │ ├── codeclimate-analysis.service.js │ ├── codeclimate-analysis.tester.js │ ├── codeclimate-common.js │ ├── codeclimate-coverage-redirector.service.js │ ├── codeclimate-coverage-redirector.tester.js │ ├── codeclimate-coverage.service.js │ └── codeclimate-coverage.tester.js ├── codecov │ ├── codecov-redirect.service.js │ ├── codecov-redirect.tester.js │ ├── codecov.service.js │ ├── codecov.spec.js │ └── codecov.tester.js ├── codefactor │ ├── codefactor-grade.service.js │ ├── codefactor-grade.spec.js │ ├── codefactor-grade.tester.js │ └── codefactor-helpers.js ├── coderabbit │ ├── coderabbit-pull-request.service.js │ └── coderabbit-pull-request.tester.js ├── codeship │ ├── codeship.service.js │ ├── codeship.spec.js │ └── codeship.tester.js ├── coincap │ ├── coincap.service.js │ └── coincap.tester.js ├── color-formatters.js ├── color-formatters.spec.js ├── conan │ ├── conan-version-helpers.js │ ├── conan-version-helpers.spec.js │ ├── conan-version.service.js │ └── conan-version.tester.js ├── conda │ ├── conda-base.js │ ├── conda-downloads.service.js │ ├── conda-downloads.tester.js │ ├── conda-license.service.js │ ├── conda-license.tester.js │ ├── conda-platform.service.js │ ├── conda-platform.tester.js │ ├── conda-version.service.js │ └── conda-version.tester.js ├── contributor-count.js ├── contributor-count.spec.js ├── cookbook │ ├── cookbook.service.js │ └── cookbook.tester.js ├── coveralls │ ├── coveralls-redirector.service.js │ ├── coveralls-redirector.tester.js │ ├── coveralls.service.js │ └── coveralls.tester.js ├── coverity │ ├── coverity-scan.service.js │ └── coverity-scan.tester.js ├── cpan │ ├── cpan-license.service.js │ ├── cpan-license.tester.js │ ├── cpan-version.service.js │ ├── cpan-version.tester.js │ └── cpan.js ├── cran │ ├── cran.service.js │ └── cran.tester.js ├── crates │ ├── crates-base.js │ ├── crates-base.spec.js │ ├── crates-dependents.service.js │ ├── crates-dependents.tester.js │ ├── crates-downloads.service.js │ ├── crates-downloads.tester.js │ ├── crates-license.service.js │ ├── crates-license.spec.js │ ├── crates-license.tester.js │ ├── crates-msrv.service.js │ ├── crates-msrv.tester.js │ ├── crates-size.service.js │ ├── crates-size.tester.js │ ├── crates-user-downloads.service.js │ ├── crates-user-downloads.tester.js │ ├── crates-version.service.js │ └── crates-version.tester.js ├── ctan │ ├── ctan.service.js │ └── ctan.tester.js ├── curseforge │ ├── curseforge-base.js │ ├── curseforge-downloads.service.js │ ├── curseforge-downloads.tester.js │ ├── curseforge-game-versions.service.js │ ├── curseforge-game-versions.tester.js │ ├── curseforge-version.service.js │ └── curseforge-version.tester.js ├── date.js ├── date.spec.js ├── date │ ├── date.service.js │ └── date.tester.js ├── debian │ ├── debian.service.js │ └── debian.tester.js ├── debug │ ├── debug.service.js │ └── debug.tester.js ├── depfu │ ├── depfu.service.js │ └── depfu.tester.js ├── deps-rs │ ├── deps-rs-base.js │ ├── deps-rs-crate.service.js │ ├── deps-rs-crate.tester.js │ ├── deps-rs-repo.service.js │ └── deps-rs-repo.tester.js ├── discord │ ├── discord.service.js │ ├── discord.spec.js │ └── discord.tester.js ├── discourse │ ├── discourse-redirect.service.js │ ├── discourse-redirect.tester.js │ ├── discourse.service.js │ └── discourse.tester.js ├── docker │ ├── docker-automated.service.js │ ├── docker-automated.spec.js │ ├── docker-automated.tester.js │ ├── docker-cloud-automated.service.js │ ├── docker-cloud-automated.tester.js │ ├── docker-cloud-build.service.js │ ├── docker-cloud-build.tester.js │ ├── docker-cloud-common-fetch.js │ ├── docker-cloud-common-fetch.spec.js │ ├── docker-fixtures.js │ ├── docker-helpers.js │ ├── docker-hub-common-fetch.js │ ├── docker-hub-common-fetch.spec.js │ ├── docker-pulls.service.js │ ├── docker-pulls.tester.js │ ├── docker-size.service.js │ ├── docker-size.spec.js │ ├── docker-size.tester.js │ ├── docker-stars.service.js │ ├── docker-stars.tester.js │ ├── docker-version.service.js │ ├── docker-version.spec.js │ └── docker-version.tester.js ├── docsrs │ ├── docsrs.service.js │ └── docsrs.tester.js ├── downloads.js ├── downloads.spec.js ├── drone │ ├── drone-build.service.js │ ├── drone-build.spec.js │ └── drone-build.tester.js ├── dub │ ├── dub-download.service.js │ ├── dub-download.tester.js │ ├── dub-license.service.js │ ├── dub-license.tester.js │ ├── dub-score.service.js │ ├── dub-score.tester.js │ ├── dub-version.service.js │ └── dub-version.tester.js ├── dynamic-common.js ├── dynamic │ ├── dynamic-helpers.js │ ├── dynamic-json.service.js │ ├── dynamic-json.tester.js │ ├── dynamic-regex.service.js │ ├── dynamic-regex.spec.js │ ├── dynamic-regex.tester.js │ ├── dynamic-response-fixtures.js │ ├── dynamic-toml.service.js │ ├── dynamic-toml.tester.js │ ├── dynamic-xml.service.js │ ├── dynamic-xml.spec.js │ ├── dynamic-xml.tester.js │ ├── dynamic-yaml.service.js │ ├── dynamic-yaml.tester.js │ ├── json-path.js │ └── json-path.spec.js ├── eclipse-marketplace │ ├── eclipse-marketplace-base.js │ ├── eclipse-marketplace-downloads.service.js │ ├── eclipse-marketplace-downloads.tester.js │ ├── eclipse-marketplace-favorites.service.js │ ├── eclipse-marketplace-favorites.tester.js │ ├── eclipse-marketplace-license.service.js │ ├── eclipse-marketplace-license.tester.js │ ├── eclipse-marketplace-update.service.js │ ├── eclipse-marketplace-update.tester.js │ ├── eclipse-marketplace-version.service.js │ └── eclipse-marketplace-version.tester.js ├── ecologi │ ├── ecologi-carbon.service.js │ ├── ecologi-carbon.tester.js │ ├── ecologi-trees.service.js │ └── ecologi-trees.tester.js ├── elm-package │ ├── elm-package.service.js │ └── elm-package.tester.js ├── endpoint-common.js ├── endpoint │ ├── endpoint-redirect.service.js │ ├── endpoint-redirect.tester.js │ ├── endpoint.service.js │ └── endpoint.tester.js ├── f-droid │ ├── f-droid.service.js │ └── f-droid.tester.js ├── factorio-mod-portal │ ├── factorio-mod-portal.service.js │ └── factorio-mod-portal.tester.js ├── fedora │ ├── fedora.service.js │ └── fedora.tester.js ├── feedz │ ├── feedz.service.js │ ├── feedz.service.spec.js │ └── feedz.tester.js ├── flathub │ ├── flathub-downloads.service.js │ ├── flathub-downloads.tester.js │ ├── flathub.service.js │ └── flathub.tester.js ├── freecodecamp │ ├── freecodecamp-points.service.js │ └── freecodecamp-points.tester.js ├── galaxytoolshed │ ├── galaxytoolshed-activity.service.js │ ├── galaxytoolshed-activity.tester.js │ ├── galaxytoolshed-base.js │ ├── galaxytoolshed-downloads.service.js │ ├── galaxytoolshed-downloads.tester.js │ ├── galaxytoolshed-version.service.js │ └── galaxytoolshed-version.tester.js ├── gem │ ├── gem-downloads.service.js │ ├── gem-downloads.tester.js │ ├── gem-helpers.js │ ├── gem-helpers.spec.js │ ├── gem-owner.service.js │ ├── gem-owner.tester.js │ ├── gem-rank.service.js │ ├── gem-rank.tester.js │ ├── gem-version.service.js │ └── gem-version.tester.js ├── gerrit │ ├── gerrit.service.js │ └── gerrit.tester.js ├── gitea │ ├── gitea-base.js │ ├── gitea-base.spec.js │ ├── gitea-common-fetch.js │ ├── gitea-forks.service.js │ ├── gitea-forks.tester.js │ ├── gitea-helper.js │ ├── gitea-issues.service.js │ ├── gitea-issues.tester.js │ ├── gitea-languages-count.service.js │ ├── gitea-languages-count.tester.js │ ├── gitea-last-commit.service.js │ ├── gitea-last-commit.tester.js │ ├── gitea-pull-requests.service.js │ ├── gitea-pull-requests.tester.js │ ├── gitea-release.service.js │ ├── gitea-release.tester.js │ ├── gitea-stars.service.js │ └── gitea-stars.tester.js ├── github │ ├── auth │ │ ├── acceptor.js │ │ └── acceptor.spec.js │ ├── gist │ │ ├── github-gist-last-commit-redirect.service.js │ │ ├── github-gist-last-commit-redirect.tester.js │ │ ├── github-gist-last-commit.service.js │ │ ├── github-gist-last-commit.tester.js │ │ ├── github-gist-stars-redirect.service.js │ │ ├── github-gist-stars-redirect.tester.js │ │ ├── github-gist-stars.service.js │ │ └── github-gist-stars.tester.js │ ├── github-actions-workflow-status.service.js │ ├── github-actions-workflow-status.tester.js │ ├── github-all-contributors.service.js │ ├── github-all-contributors.tester.js │ ├── github-api-provider.integration.js │ ├── github-api-provider.js │ ├── github-api-provider.spec.js │ ├── github-auth-service.js │ ├── github-auth-service.spec.js │ ├── github-check-runs.service.js │ ├── github-check-runs.spec.js │ ├── github-check-runs.tester.js │ ├── github-checks-status.service.js │ ├── github-checks-status.tester.js │ ├── github-code-size.service.js │ ├── github-code-size.tester.js │ ├── github-commit-activity.service.js │ ├── github-commit-activity.spec.js │ ├── github-commit-activity.tester.js │ ├── github-commit-status.service.js │ ├── github-commit-status.tester.js │ ├── github-commits-difference.service.js │ ├── github-commits-difference.tester.js │ ├── github-commits-since.service.js │ ├── github-commits-since.tester.js │ ├── github-common-fetch.js │ ├── github-common-release.js │ ├── github-common-release.spec.js │ ├── github-constellation.js │ ├── github-contributors.service.js │ ├── github-contributors.tester.js │ ├── github-created-at.service.js │ ├── github-created-at.tester.js │ ├── github-deployments.service.js │ ├── github-deployments.spec.js │ ├── github-deployments.tester.js │ ├── github-directory-file-count.service.js │ ├── github-directory-file-count.spec.js │ ├── github-directory-file-count.tester.js │ ├── github-discussions-custom-search.service.js │ ├── github-discussions-custom-search.tester.js │ ├── github-discussions-total.service.js │ ├── github-discussions-total.tester.js │ ├── github-downloads.service.js │ ├── github-downloads.tester.js │ ├── github-followers.service.js │ ├── github-followers.tester.js │ ├── github-forks.service.js │ ├── github-forks.tester.js │ ├── github-go-mod.service.js │ ├── github-go-mod.spec.js │ ├── github-go-mod.tester.js │ ├── github-hacktoberfest.service.js │ ├── github-hacktoberfest.spec.js │ ├── github-hacktoberfest.tester.js │ ├── github-helpers.js │ ├── github-issue-detail-redirect.service.js │ ├── github-issue-detail-redirect.tester.js │ ├── github-issue-detail.service.js │ ├── github-issue-detail.spec.js │ ├── github-issue-detail.tester.js │ ├── github-issues-search.service.js │ ├── github-issues-search.tester.js │ ├── github-issues.service.js │ ├── github-issues.tester.js │ ├── github-labels.service.js │ ├── github-labels.tester.js │ ├── github-language-count.service.js │ ├── github-language-count.tester.js │ ├── github-languages-base.js │ ├── github-last-commit.service.js │ ├── github-last-commit.tester.js │ ├── github-lerna-json.service.js │ ├── github-lerna-json.tester.js │ ├── github-license.service.js │ ├── github-license.spec.js │ ├── github-license.tester.js │ ├── github-manifest.service.js │ ├── github-manifest.tester.js │ ├── github-milestone-detail.service.js │ ├── github-milestone-detail.tester.js │ ├── github-milestone.service.js │ ├── github-milestone.tester.js │ ├── github-package-json.service.js │ ├── github-package-json.tester.js │ ├── github-pipenv.service.js │ ├── github-pipenv.tester.js │ ├── github-pull-request-check-state.service.js │ ├── github-pull-request-check-state.tester.js │ ├── github-r-package.service.js │ ├── github-r-package.spec.js │ ├── github-r-package.tester.js │ ├── github-release-date.service.js │ ├── github-release-date.tester.js │ ├── github-release.service.js │ ├── github-release.spec.js │ ├── github-release.tester.js │ ├── github-repo-size.service.js │ ├── github-repo-size.tester.js │ ├── github-search.service.js │ ├── github-search.tester.js │ ├── github-size.service.js │ ├── github-size.tester.js │ ├── github-sponsors.service.js │ ├── github-sponsors.tester.js │ ├── github-stars.service.js │ ├── github-stars.tester.js │ ├── github-tag.service.js │ ├── github-tag.spec.js │ ├── github-tag.tester.js │ ├── github-top-language.service.js │ ├── github-top-language.tester.js │ ├── github-total-star.service.js │ ├── github-total-star.tester.js │ ├── github-watchers.service.js │ ├── github-watchers.tester.js │ ├── github-workflow-status.service.js │ └── github-workflow-status.tester.js ├── gitlab │ ├── gitlab-base.js │ ├── gitlab-contributors-redirect.service.js │ ├── gitlab-contributors-redirect.tester.js │ ├── gitlab-contributors.service.js │ ├── gitlab-contributors.tester.js │ ├── gitlab-coverage-redirect.service.js │ ├── gitlab-coverage-redirect.tester.js │ ├── gitlab-forks.service.js │ ├── gitlab-forks.tester.js │ ├── gitlab-helper.js │ ├── gitlab-issues.service.js │ ├── gitlab-issues.tester.js │ ├── gitlab-languages-count.service.js │ ├── gitlab-languages-count.tester.js │ ├── gitlab-last-commit.service.js │ ├── gitlab-last-commit.tester.js │ ├── gitlab-license-redirect.service.js │ ├── gitlab-license-redirect.tester.js │ ├── gitlab-license.service.js │ ├── gitlab-license.tester.js │ ├── gitlab-merge-requests.service.js │ ├── gitlab-merge-requests.spec.js │ ├── gitlab-merge-requests.tester.js │ ├── gitlab-pipeline-coverage.service.js │ ├── gitlab-pipeline-coverage.tester.js │ ├── gitlab-pipeline-status.service.js │ ├── gitlab-pipeline-status.tester.js │ ├── gitlab-release.service.js │ ├── gitlab-release.spec.js │ ├── gitlab-release.tester.js │ ├── gitlab-stars.service.js │ ├── gitlab-stars.tester.js │ ├── gitlab-tag.service.js │ ├── gitlab-tag.spec.js │ ├── gitlab-tag.tester.js │ ├── gitlab-top-language.service.js │ └── gitlab-top-language.tester.js ├── gitter │ ├── gitter.service.js │ └── gitter.tester.js ├── gradle-plugin-portal │ ├── gradle-plugin-portal.service.js │ └── gradle-plugin-portal.tester.js ├── greasyfork │ ├── greasyfork-base.js │ ├── greasyfork-downloads.service.js │ ├── greasyfork-downloads.tester.js │ ├── greasyfork-license.service.js │ ├── greasyfork-license.tester.js │ ├── greasyfork-rating.service.js │ ├── greasyfork-rating.spec.js │ ├── greasyfork-rating.tester.js │ ├── greasyfork-version.service.js │ └── greasyfork-version.tester.js ├── hackage │ ├── hackage-deps.service.js │ ├── hackage-deps.tester.js │ ├── hackage-version.service.js │ └── hackage-version.tester.js ├── hackernews │ ├── hackernews-user-karma.service.js │ └── hackernews-user-karma.tester.js ├── hangar │ ├── hangar-base.js │ ├── hangar-downloads.service.js │ ├── hangar-downloads.tester.js │ ├── hangar-stars.service.js │ ├── hangar-stars.tester.js │ ├── hangar-views.service.js │ ├── hangar-views.tester.js │ ├── hangar-watchers.service.js │ └── hangar-watchers.tester.js ├── hexpm │ ├── hexpm.service.js │ └── hexpm.tester.js ├── homebrew │ ├── homebrew-cask-downloads.service.js │ ├── homebrew-cask-downloads.tester.js │ ├── homebrew-cask-version.service.js │ ├── homebrew-cask-version.tester.js │ ├── homebrew-formula-downloads.service.js │ ├── homebrew-formula-downloads.tester.js │ ├── homebrew-formula-version.service.js │ └── homebrew-formula-version.tester.js ├── hsts │ ├── hsts.service.js │ └── hsts.tester.js ├── index.js ├── itunes │ ├── itunes.service.js │ └── itunes.tester.js ├── jenkins │ ├── jenkins-base.js │ ├── jenkins-build-redirect.service.js │ ├── jenkins-build-redirect.tester.js │ ├── jenkins-build.service.js │ ├── jenkins-build.spec.js │ ├── jenkins-build.tester.js │ ├── jenkins-common.js │ ├── jenkins-common.spec.js │ ├── jenkins-coverage-redirector.service.js │ ├── jenkins-coverage-redirector.tester.js │ ├── jenkins-coverage.service.js │ ├── jenkins-coverage.tester.js │ ├── jenkins-plugin-installs.service.js │ ├── jenkins-plugin-installs.tester.js │ ├── jenkins-plugin-version.service.js │ ├── jenkins-plugin-version.tester.js │ ├── jenkins-tests-redirector.service.js │ ├── jenkins-tests-redirector.tester.js │ ├── jenkins-tests.service.js │ └── jenkins-tests.tester.js ├── jetbrains │ ├── jetbrains-base.js │ ├── jetbrains-downloads.service.js │ ├── jetbrains-downloads.tester.js │ ├── jetbrains-rating.service.js │ ├── jetbrains-rating.tester.js │ ├── jetbrains-version.service.js │ └── jetbrains-version.tester.js ├── jira │ ├── jira-common.js │ ├── jira-issue-redirect.service.js │ ├── jira-issue-redirect.tester.js │ ├── jira-issue.service.js │ ├── jira-issue.spec.js │ ├── jira-issue.tester.js │ ├── jira-sprint-redirect.service.js │ ├── jira-sprint-redirect.tester.js │ ├── jira-sprint.service.js │ ├── jira-sprint.spec.js │ ├── jira-sprint.tester.js │ └── jira-test-helpers.js ├── jitpack │ ├── jitpack-version-redirector.service.js │ ├── jitpack-version-redirector.tester.js │ ├── jitpack-version.service.js │ └── jitpack-version.tester.js ├── jsdelivr │ ├── jsdelivr-base.js │ ├── jsdelivr-hits-github.service.js │ ├── jsdelivr-hits-github.tester.js │ ├── jsdelivr-hits-npm.service.js │ └── jsdelivr-hits-npm.tester.js ├── jsr │ ├── jsr-version.service.js │ └── jsr-version.tester.js ├── keybase │ ├── keybase-btc.service.js │ ├── keybase-btc.tester.js │ ├── keybase-pgp.service.js │ ├── keybase-pgp.tester.js │ ├── keybase-profile.js │ ├── keybase-xlm.service.js │ ├── keybase-xlm.tester.js │ ├── keybase-zec.service.js │ └── keybase-zec.tester.js ├── lemmy │ ├── lemmy.service.js │ └── lemmy.tester.js ├── liberapay │ ├── liberapay-base.js │ ├── liberapay-gives.service.js │ ├── liberapay-gives.tester.js │ ├── liberapay-goal.service.js │ ├── liberapay-goal.spec.js │ ├── liberapay-goal.tester.js │ ├── liberapay-patrons.service.js │ ├── liberapay-patrons.tester.js │ ├── liberapay-receives.service.js │ └── liberapay-receives.tester.js ├── librariesio │ ├── librariesio-api-provider.js │ ├── librariesio-api-provider.spec.js │ ├── librariesio-base.js │ ├── librariesio-constellation.js │ ├── librariesio-dependencies-helpers.js │ ├── librariesio-dependencies-helpers.spec.js │ ├── librariesio-dependencies.service.js │ ├── librariesio-dependencies.tester.js │ ├── librariesio-dependent-repos.service.js │ ├── librariesio-dependent-repos.tester.js │ ├── librariesio-dependents.service.js │ ├── librariesio-dependents.tester.js │ ├── librariesio-sourcerank.service.js │ ├── librariesio-sourcerank.spec.js │ └── librariesio-sourcerank.tester.js ├── licenses.js ├── licenses.spec.js ├── localizely │ ├── localizely.service.js │ └── localizely.tester.js ├── luarocks │ ├── luarocks-version-helpers.js │ ├── luarocks-version-helpers.spec.js │ ├── luarocks.service.js │ └── luarocks.tester.js ├── maintenance │ ├── maintenance.service.js │ ├── maintenance.spec.js │ └── maintenance.tester.js ├── mastodon │ ├── mastodon-follow.service.js │ └── mastodon-follow.tester.js ├── matrix │ ├── matrix.service.js │ └── matrix.tester.js ├── maven-central │ ├── maven-central-base.js │ ├── maven-central-last-update.service.js │ ├── maven-central-last-update.tester.js │ ├── maven-central.service.js │ └── maven-central.tester.js ├── maven-metadata │ ├── maven-metadata-redirect.service.js │ ├── maven-metadata-redirect.tester.js │ ├── maven-metadata.js │ ├── maven-metadata.service.js │ └── maven-metadata.tester.js ├── mbin │ ├── mbin.service.js │ └── mbin.tester.js ├── modrinth │ ├── modrinth-base.js │ ├── modrinth-downloads.service.js │ ├── modrinth-downloads.tester.js │ ├── modrinth-followers.service.js │ ├── modrinth-followers.tester.js │ ├── modrinth-game-versions.service.js │ ├── modrinth-game-versions.spec.js │ ├── modrinth-game-versions.tester.js │ ├── modrinth-version.service.js │ └── modrinth-version.tester.js ├── mozilla-observatory │ ├── mozilla-observatory.service.js │ ├── mozilla-observatory.spec.js │ └── mozilla-observatory.tester.js ├── myget │ ├── myget.service.js │ └── myget.tester.js ├── netlify │ ├── netlify.service.js │ ├── netlify.spec.js │ └── netlify.tester.js ├── nexus │ ├── nexus-redirect.service.js │ ├── nexus-redirect.tester.js │ ├── nexus-version.js │ ├── nexus.service.js │ ├── nexus.spec.js │ └── nexus.tester.js ├── node │ ├── node-base.js │ ├── node-current.service.js │ ├── node-current.tester.js │ ├── node-lts.service.js │ ├── node-lts.tester.js │ ├── node-version-color.js │ └── testUtils │ │ ├── packageJsonTemplate.json │ │ ├── packageJsonVersionsTemplate.json │ │ └── test-utils.js ├── nodeping │ ├── nodeping-status.service.js │ ├── nodeping-status.tester.js │ ├── nodeping-uptime.service.js │ └── nodeping-uptime.tester.js ├── nostr-band │ ├── nostr-band-followers.service.js │ └── nostr-band-followers.tester.js ├── npm-stat │ ├── npm-stat-downloads.service.js │ ├── npm-stat-downloads.spec.js │ └── npm-stat-downloads.tester.js ├── npm │ ├── npm-base.js │ ├── npm-base.spec.js │ ├── npm-collaborators.service.js │ ├── npm-collaborators.tester.js │ ├── npm-dependency-version.service.js │ ├── npm-dependency-version.tester.js │ ├── npm-downloads-redirect.service.js │ ├── npm-downloads.service.js │ ├── npm-downloads.spec.js │ ├── npm-downloads.tester.js │ ├── npm-last-update.service.js │ ├── npm-last-update.tester.js │ ├── npm-license.service.js │ ├── npm-license.tester.js │ ├── npm-type-definitions.service.js │ ├── npm-type-definitions.spec.js │ ├── npm-type-definitions.tester.js │ ├── npm-unpacked-size.service.js │ ├── npm-unpacked-size.tester.js │ ├── npm-version.service.js │ └── npm-version.tester.js ├── npms-io │ ├── npms-io-score.service.js │ └── npms-io-score.tester.js ├── nuget │ ├── nuget-helpers.js │ ├── nuget-helpers.spec.js │ ├── nuget-v2-service-family.js │ ├── nuget-v3-service-family.js │ ├── nuget-v3-service-family.spec.js │ ├── nuget.service.js │ └── nuget.tester.js ├── nycrc │ ├── nycrc.service.js │ └── nycrc.tester.js ├── obs │ ├── obs-build-status.js │ ├── obs.service.js │ ├── obs.spec.js │ └── obs.tester.js ├── offset-earth │ ├── offset-earth-carbon-redirect.service.js │ ├── offset-earth-carbon-redirect.tester.js │ ├── offset-earth-trees-redirect.service.js │ └── offset-earth-trees-redirect.tester.js ├── open-vsx │ ├── open-vsx-base.js │ ├── open-vsx-downloads.service.js │ ├── open-vsx-downloads.tester.js │ ├── open-vsx-rating.service.js │ ├── open-vsx-rating.spec.js │ ├── open-vsx-rating.tester.js │ ├── open-vsx-release-date.service.js │ ├── open-vsx-release-date.tester.js │ ├── open-vsx-version.service.js │ └── open-vsx-version.tester.js ├── opencollective │ ├── opencollective-all.service.js │ ├── opencollective-all.tester.js │ ├── opencollective-backers.service.js │ ├── opencollective-backers.tester.js │ ├── opencollective-base.js │ ├── opencollective-base.spec.js │ ├── opencollective-by-tier.service.js │ ├── opencollective-by-tier.tester.js │ ├── opencollective-sponsors.service.js │ └── opencollective-sponsors.tester.js ├── opm │ ├── opm-version.service.js │ └── opm-version.tester.js ├── ore │ ├── ore-base.js │ ├── ore-category.service.js │ ├── ore-category.spec.js │ ├── ore-category.tester.js │ ├── ore-downloads.service.js │ ├── ore-downloads.tester.js │ ├── ore-license.service.js │ ├── ore-license.spec.js │ ├── ore-license.tester.js │ ├── ore-sponge-versions.service.js │ ├── ore-sponge-versions.spec.js │ ├── ore-sponge-versions.tester.js │ ├── ore-stars.service.js │ ├── ore-stars.tester.js │ ├── ore-version.service.js │ ├── ore-version.spec.js │ └── ore-version.tester.js ├── ossf-scorecard │ ├── ossf-scorecard.service.js │ └── ossf-scorecard.tester.js ├── osslifecycle │ ├── osslifecycle-redirector.service.js │ ├── osslifecycle-redirector.tester.js │ ├── osslifecycle.service.js │ └── osslifecycle.tester.js ├── package-json-helpers.js ├── package-json-helpers.spec.js ├── packagecontrol │ ├── packagecontrol.service.js │ └── packagecontrol.tester.js ├── packagist │ ├── packagist-base.js │ ├── packagist-base.spec.js │ ├── packagist-dependency-version.service.js │ ├── packagist-dependency-version.spec.js │ ├── packagist-dependency-version.tester.js │ ├── packagist-downloads.service.js │ ├── packagist-downloads.tester.js │ ├── packagist-license.service.js │ ├── packagist-license.spec.js │ ├── packagist-license.tester.js │ ├── packagist-php-version.service.js │ ├── packagist-php-version.tester.js │ ├── packagist-stars.service.js │ ├── packagist-stars.tester.js │ ├── packagist-version.service.js │ └── packagist-version.tester.js ├── pepy │ ├── pepy-downloads.service.js │ ├── pepy-downloads.spec.js │ └── pepy-downloads.tester.js ├── php-version.js ├── php-version.spec.js ├── pingpong │ ├── pingpong-base.js │ ├── pingpong-status.service.js │ ├── pingpong-status.tester.js │ ├── pingpong-uptime.service.js │ └── pingpong-uptime.tester.js ├── pipenv-helpers.js ├── pipenv-helpers.spec.js ├── piwheels │ ├── piwheels-version.service.js │ ├── piwheels-version.spec.js │ └── piwheels-version.tester.js ├── poeditor │ ├── poeditor.service.js │ └── poeditor.tester.js ├── polymart │ ├── polymart-base.js │ ├── polymart-downloads.service.js │ ├── polymart-downloads.tester.js │ ├── polymart-latest-version.service.js │ ├── polymart-latest-version.tester.js │ ├── polymart-rating.service.js │ └── polymart-rating.tester.js ├── powershellgallery │ ├── powershellgallery.service.js │ └── powershellgallery.tester.js ├── pub │ ├── pub-common.js │ ├── pub-downloads.service.js │ ├── pub-downloads.tester.js │ ├── pub-likes.service.js │ ├── pub-likes.tester.js │ ├── pub-points.service.js │ ├── pub-points.tester.js │ ├── pub-popularity.service.js │ ├── pub-popularity.tester.js │ ├── pub-publisher.service.js │ ├── pub-publisher.tester.js │ ├── pub.service.js │ └── pub.tester.js ├── pulsar │ ├── pulsar-downloads.service.js │ ├── pulsar-downloads.tester.js │ ├── pulsar-helper.js │ ├── pulsar-stargazers.service.js │ └── pulsar-stargazers.tester.js ├── puppetforge │ ├── puppetforge-base.js │ ├── puppetforge-module-downloads.service.js │ ├── puppetforge-module-downloads.tester.js │ ├── puppetforge-module-endorsement.service.js │ ├── puppetforge-module-endorsement.tester.js │ ├── puppetforge-module-feedback.service.js │ ├── puppetforge-module-feedback.tester.js │ ├── puppetforge-module-pdk-version.service.js │ ├── puppetforge-module-pdk-version.tester.js │ ├── puppetforge-module-quality-score.service.js │ ├── puppetforge-module-quality-score.tester.js │ ├── puppetforge-module-version.service.js │ ├── puppetforge-module-version.tester.js │ ├── puppetforge-user-module-count.service.js │ ├── puppetforge-user-module-count.tester.js │ ├── puppetforge-user-release-count.service.js │ └── puppetforge-user-release-count.tester.js ├── pypi │ ├── pypi-base.js │ ├── pypi-django-versions.service.js │ ├── pypi-django-versions.tester.js │ ├── pypi-downloads.service.js │ ├── pypi-downloads.tester.js │ ├── pypi-format.service.js │ ├── pypi-format.tester.js │ ├── pypi-framework-versions.service.js │ ├── pypi-framework-versions.tester.js │ ├── pypi-helpers.js │ ├── pypi-helpers.spec.js │ ├── pypi-implementation.service.js │ ├── pypi-implementation.tester.js │ ├── pypi-license.service.js │ ├── pypi-license.tester.js │ ├── pypi-python-versions.service.js │ ├── pypi-python-versions.spec.js │ ├── pypi-python-versions.tester.js │ ├── pypi-status.service.js │ ├── pypi-status.tester.js │ ├── pypi-types.service.js │ ├── pypi-types.tester.js │ ├── pypi-version.service.js │ ├── pypi-version.tester.js │ ├── pypi-wheel.service.js │ └── pypi-wheel.tester.js ├── python │ ├── python-version-from-toml.service.js │ └── python-version-from-toml.tester.js ├── raycast │ ├── installs.service.js │ └── installs.tester.js ├── readthedocs │ ├── readthedocs.service.js │ └── readthedocs.tester.js ├── reddit │ ├── reddit-base.js │ ├── subreddit-subscribers.service.js │ ├── subreddit-subscribers.tester.js │ ├── user-karma.service.js │ └── user-karma.tester.js ├── redmine │ ├── redmine.service.js │ └── redmine.tester.js ├── repology │ ├── repology-repositories.service.js │ └── repology-repositories.tester.js ├── reproducible-central │ ├── reproducible-central.service.js │ └── reproducible-central.tester.js ├── resharper │ ├── resharper.service.js │ └── resharper.tester.js ├── response-fixtures.js ├── reuse │ ├── reuse-compliance-helper.js │ ├── reuse-compliance.service.js │ └── reuse-compliance.tester.js ├── revolt │ ├── revolt.service.js │ └── revolt.tester.js ├── ros │ ├── ros-version.service.js │ ├── ros-version.service.spec.js │ └── ros-version.tester.js ├── route-builder.js ├── scoop │ ├── scoop-base.js │ ├── scoop-license.service.js │ ├── scoop-license.tester.js │ ├── scoop-version.service.js │ └── scoop-version.tester.js ├── scrutinizer │ ├── scrutinizer-base.js │ ├── scrutinizer-build.service.js │ ├── scrutinizer-build.tester.js │ ├── scrutinizer-coverage.service.js │ ├── scrutinizer-coverage.spec.js │ ├── scrutinizer-coverage.tester.js │ ├── scrutinizer-quality.service.js │ ├── scrutinizer-quality.tester.js │ ├── scrutinizer-redirect.service.js │ └── scrutinizer-redirect.tester.js ├── security-headers │ ├── security-headers.service.js │ └── security-headers.tester.js ├── size.js ├── snapcraft │ ├── snapcraft-base.js │ ├── snapcraft-last-update.service.js │ ├── snapcraft-last-update.tester.js │ ├── snapcraft-licence.service.js │ ├── snapcraft-licence.spec.js │ ├── snapcraft-licence.tester.js │ ├── snapcraft-version.service.js │ ├── snapcraft-version.spec.js │ └── snapcraft-version.tester.js ├── snyk │ ├── snyk-vulnerability-github.service.js │ ├── snyk-vulnerability-github.tester.js │ ├── snyk-vulnerability-npm.service.js │ └── snyk-vulnerability-npm.tester.js ├── sonar │ ├── sonar-base.js │ ├── sonar-coverage.service.js │ ├── sonar-coverage.tester.js │ ├── sonar-documented-api-density.service.js │ ├── sonar-documented-api-density.spec.js │ ├── sonar-documented-api-density.tester.js │ ├── sonar-fortify-rating.service.js │ ├── sonar-fortify-rating.spec.js │ ├── sonar-fortify-rating.tester.js │ ├── sonar-generic.service.js │ ├── sonar-generic.tester.js │ ├── sonar-helpers.js │ ├── sonar-quality-gate.service.js │ ├── sonar-quality-gate.spec.js │ ├── sonar-quality-gate.tester.js │ ├── sonar-redirector.service.js │ ├── sonar-redirector.tester.js │ ├── sonar-tech-debt.service.js │ ├── sonar-tech-debt.spec.js │ ├── sonar-tech-debt.tester.js │ ├── sonar-tests.service.js │ ├── sonar-tests.spec.js │ ├── sonar-tests.tester.js │ ├── sonar-violations.service.js │ ├── sonar-violations.spec.js │ └── sonar-violations.tester.js ├── sourceforge │ ├── sourceforge-base.js │ ├── sourceforge-commit-count-redirect.service.js │ ├── sourceforge-commit-count-redirect.tester.js │ ├── sourceforge-commit-count.service.js │ ├── sourceforge-commit-count.tester.js │ ├── sourceforge-contributors.service.js │ ├── sourceforge-contributors.tester.js │ ├── sourceforge-downloads.service.js │ ├── sourceforge-downloads.tester.js │ ├── sourceforge-languages.service.js │ ├── sourceforge-languages.tester.js │ ├── sourceforge-last-commit-redirect.service.js │ ├── sourceforge-last-commit-redirect.tester.js │ ├── sourceforge-last-commit.service.js │ ├── sourceforge-last-commit.tester.js │ ├── sourceforge-open-tickets.service.js │ ├── sourceforge-open-tickets.tester.js │ ├── sourceforge-platform.service.js │ ├── sourceforge-platform.tester.js │ ├── sourceforge-translations.service.js │ └── sourceforge-translations.tester.js ├── sourcegraph │ ├── sourcegraph.service.js │ └── sourcegraph.tester.js ├── spack │ ├── spack.service.js │ └── spack.tester.js ├── spiget │ ├── spiget-base.js │ ├── spiget-download-size.service.js │ ├── spiget-download-size.tester.js │ ├── spiget-downloads.service.js │ ├── spiget-downloads.tester.js │ ├── spiget-latest-version.service.js │ ├── spiget-latest-version.tester.js │ ├── spiget-rating.service.js │ ├── spiget-rating.tester.js │ ├── spiget-tested-versions.service.js │ └── spiget-tested-versions.tester.js ├── stackexchange │ ├── stackexchange-base.js │ ├── stackexchange-monthlyquestions.service.js │ ├── stackexchange-monthlyquestions.spec.js │ ├── stackexchange-monthlyquestions.tester.js │ ├── stackexchange-reputation.service.js │ ├── stackexchange-reputation.spec.js │ ├── stackexchange-reputation.tester.js │ ├── stackexchange-taginfo.service.js │ ├── stackexchange-taginfo.spec.js │ └── stackexchange-taginfo.tester.js ├── static-badge │ ├── query-string-static.service.js │ ├── query-string-static.tester.js │ ├── static-badge.service.js │ └── static-badge.tester.js ├── steam │ ├── steam-base.js │ ├── steam-workshop.service.js │ └── steam-workshop.tester.js ├── swagger │ ├── swagger-redirect.service.js │ ├── swagger-redirect.tester.js │ ├── swagger.service.js │ └── swagger.tester.js ├── symfony │ ├── sensiolabs-redirect.service.js │ ├── sensiolabs-redirect.tester.js │ ├── symfony-insight-base.js │ ├── symfony-insight-base.spec.js │ ├── symfony-insight-grade.service.js │ ├── symfony-insight-grade.tester.js │ ├── symfony-insight-stars.service.js │ ├── symfony-insight-stars.tester.js │ ├── symfony-insight-violations.service.js │ ├── symfony-insight-violations.tester.js │ ├── symfony-insight.spec.js │ └── symfony-test-helpers.js ├── tas │ ├── tas-tests.service.js │ └── tas-tests.tester.js ├── teamcity │ ├── teamcity-base.js │ ├── teamcity-build-redirect.service.js │ ├── teamcity-build-redirect.tester.js │ ├── teamcity-build.service.js │ ├── teamcity-build.spec.js │ ├── teamcity-build.tester.js │ ├── teamcity-coverage.service.js │ ├── teamcity-coverage.spec.js │ ├── teamcity-coverage.tester.js │ └── teamcity-test-helpers.js ├── terraform │ ├── terraform-base.js │ ├── terraform-module-downloads.service.js │ ├── terraform-module-downloads.tester.js │ ├── terraform-provider-downloads.service.js │ └── terraform-provider-downloads.tester.js ├── test-helpers.js ├── test-results.js ├── test-results.spec.js ├── test-validators.js ├── tester.js ├── testspace │ ├── testspace-base.js │ ├── testspace-base.spec.js │ ├── testspace-test-count.service.js │ ├── testspace-test-count.spec.js │ ├── testspace-test-count.tester.js │ ├── testspace-test-pass-ratio.service.js │ ├── testspace-test-pass-ratio.spec.js │ ├── testspace-test-pass-ratio.tester.js │ ├── testspace-test-summary.service.js │ └── testspace-test-summary.tester.js ├── text-formatters.js ├── text-formatters.spec.js ├── thunderstore │ ├── thunderstore-base.js │ ├── thunderstore-downloads.service.js │ ├── thunderstore-downloads.tester.js │ ├── thunderstore-likes.service.js │ ├── thunderstore-likes.tester.js │ ├── thunderstore-version.service.js │ └── thunderstore-version.tester.js ├── tokei │ ├── tokei.service.js │ └── tokei.tester.js ├── travis │ ├── travis-build.service.js │ └── travis-build.tester.js ├── treeware │ ├── treeware-trees.service.js │ └── treeware-trees.tester.js ├── twitch │ ├── twitch-base.js │ ├── twitch-extension-version.service.js │ ├── twitch-extension-version.tester.js │ ├── twitch.service.js │ ├── twitch.spec.js │ └── twitch.tester.js ├── twitter │ ├── twitter-redirect.service.js │ ├── twitter-redirect.tester.js │ ├── twitter.service.js │ └── twitter.tester.js ├── ubuntu │ ├── ubuntu.service.js │ └── ubuntu.tester.js ├── uptimerobot │ ├── uptimerobot-base.js │ ├── uptimerobot-ratio.service.js │ ├── uptimerobot-ratio.tester.js │ ├── uptimerobot-status.service.js │ └── uptimerobot-status.tester.js ├── vaadin-directory │ ├── vaadin-directory-base.js │ ├── vaadin-directory-rating-count.service.js │ ├── vaadin-directory-rating-count.tester.js │ ├── vaadin-directory-rating.service.js │ ├── vaadin-directory-rating.tester.js │ ├── vaadin-directory-release-date.service.js │ ├── vaadin-directory-release-date.tester.js │ ├── vaadin-directory-status.service.js │ ├── vaadin-directory-status.tester.js │ ├── vaadin-directory-version.service.js │ └── vaadin-directory-version.tester.js ├── validators.js ├── vcpkg │ ├── vcpkg-version-helpers.js │ ├── vcpkg-version-helpers.spec.js │ ├── vcpkg-version.service.js │ └── vcpkg-version.tester.js ├── version.js ├── version.spec.js ├── visual-studio-app-center │ ├── visual-studio-app-center-base.js │ ├── visual-studio-app-center-builds.service.js │ ├── visual-studio-app-center-builds.tester.js │ ├── visual-studio-app-center-releases-osversion.service.js │ ├── visual-studio-app-center-releases-osversion.tester.js │ ├── visual-studio-app-center-releases-size.service.js │ ├── visual-studio-app-center-releases-size.tester.js │ ├── visual-studio-app-center-releases-version.service.js │ └── visual-studio-app-center-releases-version.tester.js ├── visual-studio-marketplace │ ├── visual-studio-marketplace-azure-devops-installs.service.js │ ├── visual-studio-marketplace-azure-devops-installs.tester.js │ ├── visual-studio-marketplace-base.js │ ├── visual-studio-marketplace-downloads.service.js │ ├── visual-studio-marketplace-downloads.tester.js │ ├── visual-studio-marketplace-last-updated.service.js │ ├── visual-studio-marketplace-last-updated.tester.js │ ├── visual-studio-marketplace-rating.service.js │ ├── visual-studio-marketplace-rating.tester.js │ ├── visual-studio-marketplace-release-date.service.js │ ├── visual-studio-marketplace-release-date.tester.js │ ├── visual-studio-marketplace-version.service.js │ └── visual-studio-marketplace-version.tester.js ├── vpm │ ├── vpm-version.service.js │ └── vpm-version.tester.js ├── w3c │ ├── w3c-validation-helper.js │ ├── w3c-validation-helper.spec.js │ ├── w3c-validation.service.js │ └── w3c-validation.tester.js ├── weblate │ ├── weblate-base.js │ ├── weblate-component-license.service.js │ ├── weblate-component-license.tester.js │ ├── weblate-entities.service.js │ ├── weblate-entities.tester.js │ ├── weblate-project-translated-percentage.service.js │ ├── weblate-project-translated-percentage.tester.js │ ├── weblate-user-statistic.service.js │ └── weblate-user-statistic.tester.js ├── website-status.js ├── website-status.spec.js ├── website │ ├── website-redirect.service.js │ ├── website-redirect.tester.js │ ├── website.service.js │ └── website.tester.js ├── whatpulse │ ├── whatpulse.service.js │ └── whatpulse.tester.js ├── wheelmap │ ├── wheelmap.service.js │ └── wheelmap.tester.js ├── wikiapiary │ ├── wikiapiary-installs.service.js │ └── wikiapiary-installs.tester.js ├── winget │ ├── version.js │ ├── version.spec.js │ ├── winget-version.service.js │ └── winget-version.tester.js ├── wordpress │ ├── wordpress-base.js │ ├── wordpress-downloads.service.js │ ├── wordpress-downloads.tester.js │ ├── wordpress-last-update.service.js │ ├── wordpress-last-update.tester.js │ ├── wordpress-platform-redirect.service.js │ ├── wordpress-platform-redirect.tester.js │ ├── wordpress-platform.service.js │ ├── wordpress-platform.tester.js │ ├── wordpress-rating.service.js │ ├── wordpress-rating.tester.js │ ├── wordpress-version-color.integration.js │ ├── wordpress-version-color.js │ ├── wordpress-version-color.spec.js │ ├── wordpress-version.service.js │ └── wordpress-version.tester.js └── youtube │ ├── youtube-base.js │ ├── youtube-channel-views.service.js │ ├── youtube-channel-views.tester.js │ ├── youtube-comments.service.js │ ├── youtube-comments.tester.js │ ├── youtube-likes.service.js │ ├── youtube-likes.tester.js │ ├── youtube-subscribers.service.js │ ├── youtube-subscribers.tester.js │ ├── youtube-views.service.js │ └── youtube-views.tester.js ├── spec ├── SPECIFICATION.md ├── motivation.md ├── proportions.png └── users.md └── static └── images ├── nodeping.svg └── sentry-logo-black.svg /.c8rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": ["lcov"], 3 | "all": true, 4 | "silent": true, 5 | "clean": false, 6 | "exclude": [ 7 | "**/*.spec.js", 8 | "**/*.integration.js", 9 | "**/test-helpers.js", 10 | "**/*-test-helpers.js", 11 | "**/*-fixtures.js", 12 | "**/*.test-d.ts", 13 | "dangerfile.js", 14 | "core/service-test-runner", 15 | "core/got-test-client.js", 16 | "services/**/*.tester.js", 17 | "services/test-validators.js", 18 | "services/tester.js", 19 | "core/base-service/loader-test-fixtures", 20 | "scripts", 21 | "coverage", 22 | "build", 23 | ".github", 24 | "**/public/", 25 | "cypress", 26 | "frontend", 27 | "migrations" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node.js", 3 | // "Officially" maintained node devcontainer image from Microsoft 4 | // https://github.com/devcontainers/templates/tree/main/src/javascript-node 5 | "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm", 6 | 7 | // Features to add to the dev container. More info: https://containers.dev/features. 8 | "features": { 9 | "ghcr.io/devcontainers/features/github-cli:1": {} 10 | }, 11 | 12 | // Use 'postCreateCommand' to run commands after the container is created. 13 | "postCreateCommand": "npm ci" 14 | } 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | shields.env 3 | .git/ 4 | .gitignore 5 | .github 6 | .vscode/ 7 | fly.toml 8 | 9 | *.md 10 | doc/ 11 | 12 | # Improve layer cacheability. 13 | Dockerfile 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | 10 | [*.{js,json,html,css}] 11 | charset = utf-8 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: shields 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/4_Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: Ideas for other new features or improvements 4 | --- 5 | 6 | :clipboard: **Description** 7 | 8 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: 🎨 Simple Icons 3 | url: https://github.com/badges/shields/discussions/5369 4 | about: Please read this before posting a question about SimpleIcons 5 | - name: ❓ Support Question 6 | url: https://github.com/badges/shields/discussions 7 | about: Ask a question about Shields.io 8 | -------------------------------------------------------------------------------- /.github/actions/docusaurus-swizzled-warning/action.yml: -------------------------------------------------------------------------------- 1 | name: 'docusaurus-theme-openapi swizzled component changes warning' 2 | description: 'Check for changes in docusaurus-theme-openapi components which are swizzled and prints out a warning' 3 | branding: 4 | icon: 'alert-triangle' 5 | color: 'yellow' 6 | inputs: 7 | github-token: 8 | description: 'The GITHUB_TOKEN secret' 9 | required: true 10 | runs: 11 | using: 'node20' 12 | main: 'index.js' 13 | -------------------------------------------------------------------------------- /.github/actions/docusaurus-swizzled-warning/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docusaurus-swizzled-warning", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "jNullj", 11 | "license": "CC0", 12 | "dependencies": { 13 | "@actions/core": "^1.11.1", 14 | "@actions/github": "^6.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/actions/draft-release/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-bullseye 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y jq 5 | RUN apt-get install -y uuid-runtime 6 | COPY entrypoint.sh /entrypoint.sh 7 | 8 | ENTRYPOINT ["/entrypoint.sh"] 9 | -------------------------------------------------------------------------------- /.github/actions/draft-release/action.yml: -------------------------------------------------------------------------------- 1 | name: 'draft-release' 2 | description: 'Generate a changelog and propose a release PR' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.github/scripts/cleanup-review-apps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | apps=$(flyctl apps list --json | jq -r .[].ID | grep -E "pr-[0-9]+-badges-shields") || exit 0 6 | 7 | for app in $apps 8 | do 9 | flyctl apps destroy "$app" -y 10 | done 11 | -------------------------------------------------------------------------------- /.github/workflows/build-docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker Image 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - 'gh-readonly-queue/**' 7 | 8 | jobs: 9 | build-docker-image: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Docker Buildx 16 | uses: docker/setup-buildx-action@v3 17 | 18 | - name: Set Git Short SHA 19 | run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV 20 | 21 | - name: Build 22 | uses: docker/build-push-action@v6 23 | with: 24 | context: . 25 | push: false 26 | tags: ghcr.io/badges/shields:pr-validation 27 | build-args: | 28 | version=${{ env.SHORT_SHA }} 29 | -------------------------------------------------------------------------------- /.github/workflows/cleanup-review-apps.yml: -------------------------------------------------------------------------------- 1 | name: Cleanup Review Apps 2 | on: 3 | schedule: 4 | - cron: '0 7 * * *' 5 | # At 07:00, daily 6 | workflow_dispatch: 7 | 8 | jobs: 9 | cleanup-review-apps: 10 | runs-on: ubuntu-latest 11 | environment: 'Review Apps' 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: superfly/flyctl-actions/setup-flyctl@master 15 | 16 | - name: install jq 17 | run: | 18 | sudo apt-get -qq update 19 | sudo apt-get install -y jq 20 | 21 | - run: .github/scripts/cleanup-review-apps.sh 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | deploy-docs: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | with: 17 | persist-credentials: false 18 | 19 | - name: Setup 20 | uses: ./.github/actions/setup 21 | with: 22 | node-version: 20 23 | 24 | - name: Build 25 | run: npm run build-docs 26 | 27 | - name: Deploy 28 | uses: JamesIves/github-pages-deploy-action@v4 29 | with: 30 | branch: gh-pages 31 | folder: api-docs 32 | clean: true 33 | -------------------------------------------------------------------------------- /.github/workflows/docusaurus-swizzled-warning.yml: -------------------------------------------------------------------------------- 1 | name: Docusaurus swizzled component changes warning 2 | on: 3 | pull_request: 4 | types: [opened] 5 | 6 | permissions: 7 | pull-requests: write 8 | 9 | jobs: 10 | docusaurus-swizzled-warning: 11 | runs-on: ubuntu-latest 12 | if: github.actor == 'dependabot[bot]' 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Install action dependencies 18 | run: cd .github/actions/docusaurus-swizzled-warning && npm ci 19 | 20 | - uses: ./.github/actions/docusaurus-swizzled-warning 21 | with: 22 | github-token: '${{ secrets.GITHUB_TOKEN }}' 23 | -------------------------------------------------------------------------------- /.github/workflows/draft-release.yml: -------------------------------------------------------------------------------- 1 | name: Draft Release 2 | on: 3 | schedule: 4 | - cron: '0 1 1 * *' 5 | # At 01:00 on the first day of every month 6 | workflow_dispatch: 7 | 8 | permissions: 9 | pull-requests: write 10 | contents: write 11 | 12 | jobs: 13 | draft-release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Draft Release 20 | uses: ./.github/actions/draft-release 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | REPO_NAME: ${{ github.repository }} 24 | -------------------------------------------------------------------------------- /.github/workflows/enforce-dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: 'Dependency Review' 2 | on: 3 | pull_request: 4 | types: [opened, reopened, synchronize] 5 | 6 | jobs: 7 | enforce-dependency-review: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: 'Checkout Repository' 11 | uses: actions/checkout@v4 12 | - name: 'Dependency Review' 13 | uses: actions/dependency-review-action@v4 14 | -------------------------------------------------------------------------------- /.github/workflows/test-lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: 4 | types: [opened, reopened, synchronize] 5 | push: 6 | branches-ignore: 7 | - 'gh-pages' 8 | - 'dependabot/**' 9 | 10 | jobs: 11 | test-lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup 18 | uses: ./.github/actions/setup 19 | with: 20 | node-version: 20 21 | 22 | - name: ESLint 23 | if: always() 24 | run: npm run lint 25 | 26 | - name: 'Prettier check (quick fix: `npm run prettier`)' 27 | if: always() 28 | run: npm run prettier:check 29 | -------------------------------------------------------------------------------- /.github/workflows/test-main-22.yml: -------------------------------------------------------------------------------- 1 | name: Main@node 22 2 | on: 3 | pull_request: 4 | types: [opened, reopened, synchronize] 5 | push: 6 | branches-ignore: 7 | - 'gh-pages' 8 | - 'dependabot/**' 9 | 10 | jobs: 11 | test-main-22: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup 18 | uses: ./.github/actions/setup 19 | with: 20 | node-version: 22 21 | env: 22 | NPM_CONFIG_ENGINE_STRICT: 'false' 23 | 24 | - name: Core tests 25 | uses: ./.github/actions/core-tests 26 | -------------------------------------------------------------------------------- /.github/workflows/test-main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | on: 3 | pull_request: 4 | types: [opened, reopened, synchronize] 5 | push: 6 | branches-ignore: 7 | - 'gh-pages' 8 | - 'dependabot/**' 9 | 10 | jobs: 11 | test-main: 12 | strategy: 13 | matrix: 14 | os: ['ubuntu-latest', 'windows-latest'] 15 | 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup 23 | uses: ./.github/actions/setup 24 | with: 25 | node-version: 20 26 | 27 | - name: Core tests 28 | uses: ./.github/actions/core-tests 29 | -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | reporter: mocha-env-reporter 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | strict-peer-deps=true 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | /__snapshots__ 4 | /.next 5 | .cache 6 | /api-docs 7 | /build 8 | public 9 | /coverage 10 | private/*.json 11 | /.nyc_output 12 | analytics.json 13 | frontend/.docusaurus 14 | frontend/categories 15 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | bracketSpacing: true 4 | endOfLine: lf 5 | arrowParens: avoid 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "Node: Nodemon", 8 | "processId": "${command:PickProcess}", 9 | "restart": true, 10 | "protocol": "inspector" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start:server:prod 2 | -------------------------------------------------------------------------------- /badge-maker/.npmignore: -------------------------------------------------------------------------------- 1 | lib/make-badge-test-helpers.js 2 | lib/**/*.spec.js 3 | index.test-d.ts 4 | -------------------------------------------------------------------------------- /badge-maker/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Format { 2 | label?: string 3 | message: string 4 | labelColor?: string 5 | color?: string 6 | style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social' 7 | logoBase64?: string 8 | links?: Array 9 | idSuffix?: string 10 | } 11 | 12 | export declare class ValidationError extends Error {} 13 | 14 | export declare function makeBadge(format: Format): string 15 | -------------------------------------------------------------------------------- /badge-maker/lib/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_LOGO_HEIGHT = 14 2 | 3 | module.exports = { 4 | DEFAULT_LOGO_HEIGHT, 5 | } 6 | -------------------------------------------------------------------------------- /config/development.yml: -------------------------------------------------------------------------------- 1 | public: 2 | bind: 3 | address: 'localhost' 4 | 5 | cors: 6 | allowedOrigin: ['http://localhost:3000'] 7 | 8 | handleInternalErrors: false 9 | -------------------------------------------------------------------------------- /config/local-shields-io-production.template.yml: -------------------------------------------------------------------------------- 1 | private: 2 | # These are the keys which are set on the production servers. 3 | curseforge_api_key: ... 4 | discord_bot_token: ... 5 | gh_client_id: ... 6 | gh_client_secret: ... 7 | gitlab_token: ... 8 | reddit_client_id: ... 9 | reddit_client_secret: ... 10 | sentry_dsn: ... 11 | shields_secret: ... 12 | sl_insight_userUuid: ... 13 | sl_insight_apiToken: ... 14 | twitch_client_id: ... 15 | twitch_client_secret: ... 16 | weblate_api_key: ... 17 | wheelmap_token: ... 18 | youtube_api_key: ... 19 | -------------------------------------------------------------------------------- /config/local.template.yml: -------------------------------------------------------------------------------- 1 | # Copy this file to `config/local.yml` and fill it in! It will be gitignored. 2 | 3 | private: 4 | # The possible values are documented in `doc/server-secrets.md`. Note that 5 | # you can also set these values through environment variables, which may be 6 | # preferable for self hosting. 7 | curseforge_api_key: '...' 8 | gh_token: '...' 9 | gitlab_token: '...' 10 | obs_user: '...' 11 | obs_pass: '...' 12 | reddit_client_id: '...' 13 | reddit_client_secret: '...' 14 | twitch_client_id: '...' 15 | twitch_client_secret: '...' 16 | weblate_api_key: '...' 17 | wheelmap_token: '...' 18 | youtube_api_key: '...' 19 | -------------------------------------------------------------------------------- /config/production.yml: -------------------------------------------------------------------------------- 1 | public: 2 | bind: 3 | address: '0.0.0.0' 4 | -------------------------------------------------------------------------------- /config/shields-io-production.yml: -------------------------------------------------------------------------------- 1 | public: 2 | metrics: 3 | prometheus: 4 | enabled: true 5 | influx: 6 | enabled: true 7 | url: https://metrics.shields.io/telegraf 8 | instanceIdFrom: env-var 9 | instanceIdEnvVarName: FLY_ALLOC_ID 10 | envLabel: shields-production 11 | 12 | ssl: 13 | isSecure: false 14 | 15 | cors: 16 | allowedOrigin: ['http://shields.io', 'https://shields.io'] 17 | 18 | services: 19 | gitlab: 20 | authorizedOrigins: 'https://gitlab.com' 21 | 22 | rasterUrl: 'https://raster.shields.io' 23 | userAgentBase: 'Shields.io' 24 | requireCloudflare: true 25 | requestTimeoutSeconds: 8 26 | -------------------------------------------------------------------------------- /config/test.yml: -------------------------------------------------------------------------------- 1 | public: 2 | bind: 3 | address: 'localhost' 4 | port: 1111 5 | 6 | redirectUrl: 'http://frontend.example.test' 7 | 8 | rasterUrl: 'http://raster.example.test' 9 | 10 | handleInternalErrors: false 11 | -------------------------------------------------------------------------------- /core/badge-urls/make-badge-url.js: -------------------------------------------------------------------------------- 1 | // Avoid "Attempted import error: 'URL' is not exported from 'url'" in frontend. 2 | import url from 'url' 3 | 4 | function rasterRedirectUrl({ rasterUrl }, badgeUrl) { 5 | // Ensure we're always using the `rasterUrl` by using just the path from 6 | // the request URL. 7 | const { pathname, search } = new url.URL(badgeUrl, 'https://bogus.test') 8 | const result = new url.URL(pathname, rasterUrl) 9 | result.search = search 10 | return result 11 | } 12 | 13 | export { rasterRedirectUrl } 14 | -------------------------------------------------------------------------------- /core/badge-urls/path-helpers.js: -------------------------------------------------------------------------------- 1 | // Escapes `t` using the format specified in 2 | // 3 | function escapeFormat(t) { 4 | return ( 5 | t 6 | // Single underscore. 7 | .replace(/(^|[^_])((?:__)*)_(?!_)/g, '$1$2 ') 8 | // Double underscore and double dash. 9 | .replace(/__/g, '_') 10 | .replace(/--/g, '-') 11 | ) 12 | } 13 | 14 | export { escapeFormat } 15 | -------------------------------------------------------------------------------- /core/base-service/categories.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import categories from '../../services/categories.js' 3 | 4 | const isRealCategory = Joi.equal(...categories.map(({ id }) => id)).required() 5 | 6 | const isValidCategory = Joi.alternatives() 7 | .try(isRealCategory, Joi.equal('debug', 'dynamic', 'static').required()) 8 | .required() 9 | 10 | function assertValidCategory(category, message = undefined) { 11 | Joi.assert(category, isValidCategory, message) 12 | } 13 | 14 | export { isValidCategory, assertValidCategory } 15 | -------------------------------------------------------------------------------- /core/base-service/coalesce.js: -------------------------------------------------------------------------------- 1 | export default function coalesce(...candidates) { 2 | return candidates.find(c => c !== undefined && c !== null) 3 | } 4 | -------------------------------------------------------------------------------- /core/base-service/coalesce.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import coalesce from './coalesce.js' 3 | 4 | // Sticking with our one-line spread implementation, and defaulting to 5 | // `undefined` instead of `null`, though h/t to 6 | // https://github.com/royriojas/coalescy for these tests! 7 | 8 | describe('coalesce', function () { 9 | test(coalesce, () => { 10 | given().expect(undefined) 11 | given(null, []).expect([]) 12 | given(null, [], {}).expect([]) 13 | given(null, undefined, 0, {}).expect(0) 14 | 15 | const a = null 16 | const c = 0 17 | const d = 1 18 | let b 19 | given(a, b, c, d).expect(0) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /core/base-service/json.js: -------------------------------------------------------------------------------- 1 | // See available emoji at http://emoji.muan.co/ 2 | import emojic from 'emojic' 3 | import { InvalidResponse } from './errors.js' 4 | import trace from './trace.js' 5 | 6 | function parseJson(buffer) { 7 | const logTrace = (...args) => trace.logTrace('fetch', ...args) 8 | let json 9 | try { 10 | json = JSON.parse(buffer) 11 | } catch (err) { 12 | logTrace(emojic.dart, 'Response JSON (unparseable)', buffer) 13 | throw new InvalidResponse({ 14 | prettyMessage: 'unparseable json response', 15 | underlyingError: err, 16 | }) 17 | } 18 | logTrace(emojic.dart, 'Response JSON (before validation)', json, { 19 | deep: true, 20 | }) 21 | return json 22 | } 23 | 24 | export { parseJson } 25 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/empty-array.fixture.js: -------------------------------------------------------------------------------- 1 | export default [] 2 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/empty-no-export.fixture.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | class BadService {} // lgtm [js/unused-local-variable] 3 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/empty-object.fixture.js: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/empty-undefined.fixture.js: -------------------------------------------------------------------------------- 1 | export default undefined 2 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/invalid-mixed.fixture.js: -------------------------------------------------------------------------------- 1 | import BaseJsonService from '../base-json.js' 2 | 3 | class BadBaseService {} 4 | class GoodMixedService extends BaseJsonService { 5 | static category = 'build' 6 | static route = { base: 'it/is', pattern: 'good' } 7 | } 8 | class BadMixedService extends BadBaseService {} 9 | 10 | export default [GoodMixedService, BadMixedService] 11 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/invalid-no-base.fixture.js: -------------------------------------------------------------------------------- 1 | class BadNoBaseService {} 2 | 3 | export default BadNoBaseService 4 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/invalid-wrong-base.fixture.js: -------------------------------------------------------------------------------- 1 | class BadBaseService {} 2 | class BadChildService extends BadBaseService {} 3 | 4 | export default BadChildService 5 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/valid-array.fixture.js: -------------------------------------------------------------------------------- 1 | import BaseJsonService from '../base-json.js' 2 | 3 | class GoodServiceArrayOne extends BaseJsonService { 4 | static category = 'build' 5 | static route = { base: 'good', pattern: 'one' } 6 | } 7 | class GoodServiceArrayTwo extends BaseJsonService { 8 | static category = 'build' 9 | static route = { base: 'good', pattern: 'two' } 10 | } 11 | 12 | export default [GoodServiceArrayOne, GoodServiceArrayTwo] 13 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/valid-class.fixture.js: -------------------------------------------------------------------------------- 1 | import BaseJsonService from '../base-json.js' 2 | 3 | class GoodService extends BaseJsonService { 4 | static category = 'build' 5 | static route = { base: 'it/is', pattern: 'good' } 6 | } 7 | 8 | export default GoodService 9 | -------------------------------------------------------------------------------- /core/base-service/loader-test-fixtures/valid-object.fixture.js: -------------------------------------------------------------------------------- 1 | import BaseJsonService from '../base-json.js' 2 | 3 | class GoodServiceObjectOne extends BaseJsonService { 4 | static category = 'build' 5 | static route = { base: 'good', pattern: 'one' } 6 | } 7 | class GoodServiceObjectTwo extends BaseJsonService { 8 | static category = 'build' 9 | static route = { base: 'good', pattern: 'two' } 10 | } 11 | 12 | export { GoodServiceObjectOne, GoodServiceObjectTwo } 13 | -------------------------------------------------------------------------------- /core/base-service/to-array.js: -------------------------------------------------------------------------------- 1 | export default function toArray(val) { 2 | if (val === undefined) { 3 | return [] 4 | } else if (Object(val) instanceof Array) { 5 | return val 6 | } else { 7 | return [val] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /core/got-test-client.js: -------------------------------------------------------------------------------- 1 | import got from 'got' 2 | 3 | // https://github.com/nock/nock/issues/1523 4 | export default got.extend({ retry: { limit: 0 } }) 5 | -------------------------------------------------------------------------------- /core/register-chai-plugins.spec.js: -------------------------------------------------------------------------------- 1 | import { use } from 'chai' 2 | import sinonChai from 'sinon-chai' 3 | use(sinonChai) 4 | -------------------------------------------------------------------------------- /core/server/error-pages/404.html: -------------------------------------------------------------------------------- 1 | These aren't the pages you're looking 2 | for 3 | 4 |

I have nothing to offer for this request

5 |

… but blood, toil, tears and sweat. 6 |

7 |

Fortunately, the main page can offer rainbows instead! 8 |

9 | 10 | 11 | (You probably landed on this page because a link was broken, because someone 12 | mistyped its URL, or because of an irrelevant mistake. Don't worry, though: 13 | there is all of the rest of the Web to explore!) 14 | 15 | -------------------------------------------------------------------------------- /core/server/error-pages/500.html: -------------------------------------------------------------------------------- 1 | A server error occurred 2 | 3 |

I have nothing to offer for this request

4 |

… but blood, toil, tears and sweat. 5 |

6 |

And trying again later.

7 | -------------------------------------------------------------------------------- /core/server/in-process-server-test-helpers.js: -------------------------------------------------------------------------------- 1 | import merge from 'deepmerge' 2 | import config from 'config' 3 | import portfinder from 'portfinder' 4 | import Server from './server.js' 5 | 6 | async function createTestServer(customConfig = {}) { 7 | const mergedConfig = merge(config.util.toObject(), customConfig) 8 | if (!mergedConfig.public.bind.port) { 9 | mergedConfig.public.bind.port = await portfinder.getPortPromise() 10 | } 11 | return new Server(mergedConfig) 12 | } 13 | 14 | export { createTestServer } 15 | -------------------------------------------------------------------------------- /core/server/instance-id-generator.js: -------------------------------------------------------------------------------- 1 | function generateInstanceId() { 2 | // from https://gist.github.com/gordonbrander/2230317 3 | return Math.random().toString(36).substr(2, 9) 4 | } 5 | 6 | export default generateInstanceId 7 | -------------------------------------------------------------------------------- /core/server/test-public/img/frontend-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/core/server/test-public/img/frontend-image.png -------------------------------------------------------------------------------- /core/server/test-public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | shields.io 6 | 7 | 8 | concise, consistent, legible 9 | 10 | 11 | -------------------------------------------------------------------------------- /core/unhandled-rejection.spec.js: -------------------------------------------------------------------------------- 1 | // Cause unhandled promise rejections to fail unit tests, and print with stack 2 | // traces. 3 | process.on('unhandledRejection', error => { 4 | throw error 5 | }) 6 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | fixturesFolder: false, 5 | env: { 6 | backend_url: 'http://localhost:8080', 7 | }, 8 | e2e: { 9 | setupNodeEvents(on, config) {}, 10 | baseUrl: 'http://localhost:3000', 11 | supportFile: false, 12 | }, 13 | video: true, 14 | videoCompression: true, 15 | }) 16 | -------------------------------------------------------------------------------- /cypress/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - cypress 3 | env: 4 | cypress/globals: true 5 | -------------------------------------------------------------------------------- /doc/flamegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/doc/flamegraph.png -------------------------------------------------------------------------------- /doc/static-badges.md: -------------------------------------------------------------------------------- 1 | This documentation has moved to https://shields.io/docs/static-badges 2 | -------------------------------------------------------------------------------- /frontend/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | } 4 | -------------------------------------------------------------------------------- /frontend/blog/2023-11-29-simpleicons10.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: simple-icons-10 3 | title: Simple Icons 10 4 | authors: 5 | name: chris48s 6 | title: Shields.io Core Team 7 | url: https://github.com/chris48s 8 | image_url: https://avatars.githubusercontent.com/u/6025893 9 | tags: [] 10 | --- 11 | 12 | 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](https://github.com/simple-icons/simple-icons/releases/tag/10.0.0). 13 | 14 | Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the [SimpleIcons](https://github.com/simple-icons/simple-icons) project. 15 | -------------------------------------------------------------------------------- /frontend/blog/2024-07-05-simpleicons13.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: simple-icons-13 3 | title: Simple Icons 13 4 | authors: 5 | name: chris48s 6 | title: Shields.io Core Team 7 | url: https://github.com/chris48s 8 | image_url: https://avatars.githubusercontent.com/u/6025893 9 | tags: [] 10 | --- 11 | 12 | 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](https://github.com/simple-icons/simple-icons/releases/tag/13.0.0). 13 | 14 | Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the [SimpleIcons](https://github.com/simple-icons/simple-icons) project. 15 | -------------------------------------------------------------------------------- /frontend/categories/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/frontend/categories/.gitkeep -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "badge-frontend", 3 | "version": "0.0.0", 4 | "description": "Shields.io frontend", 5 | "private": true, 6 | "homepage": "https://shields.io", 7 | "license": "CC0-1.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/badges/shields.git", 11 | "directory": "frontend" 12 | }, 13 | "scripts": { 14 | "test": "echo 'Run tests from parent dir'; false" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/components/homepage-features.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 966px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/theme/DocPaginator/index.js: -------------------------------------------------------------------------------- 1 | export default function DocPaginator(props) { 2 | return '' 3 | } 4 | -------------------------------------------------------------------------------- /frontend/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/frontend/static/.nojekyll -------------------------------------------------------------------------------- /frontend/static/img/builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/frontend/static/img/builder.png -------------------------------------------------------------------------------- /frontend/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/frontend/static/img/favicon.ico -------------------------------------------------------------------------------- /frontend/static/img/icon.svg: -------------------------------------------------------------------------------- 1 | Shields.io -------------------------------------------------------------------------------- /frontend/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/frontend/static/img/logo.png -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "exclude": [ 4 | "__snapshots__", 5 | "build", 6 | "coverage", 7 | "logo", 8 | "node_modules", 9 | "private", 10 | "public" 11 | ], 12 | "excludePattern": ".+\\.(service|spec|tester)\\.js$" 13 | }, 14 | "plugins": ["plugins/markdown"], 15 | "opts": { 16 | "destination": "api-docs/", 17 | "readme": "README.md", 18 | "recurse": true, 19 | "tutorials": "doc/" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/svg-helpers.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import { svg2base64 } from './svg-helpers.js' 3 | 4 | describe('SVG helpers', function () { 5 | test(svg2base64, () => { 6 | given('').expect( 7 | 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=', 8 | ) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /migrations/1676731511125_initialize.cjs: -------------------------------------------------------------------------------- 1 | exports.shorthands = undefined 2 | 3 | exports.up = pgm => { 4 | pgm.createTable('github_user_tokens', { 5 | id: 'id', 6 | token: { type: 'varchar(1000)', notNull: true, unique: true }, 7 | }) 8 | } 9 | 10 | exports.down = pgm => { 11 | pgm.dropTable('github_user_tokens') 12 | } 13 | -------------------------------------------------------------------------------- /migrations/1727809177709_add-created.cjs: -------------------------------------------------------------------------------- 1 | exports.shorthands = undefined 2 | 3 | exports.up = pgm => { 4 | pgm.addColumn('github_user_tokens', { 5 | created: { 6 | type: 'TIMESTAMP', 7 | notNull: true, 8 | default: pgm.func('CURRENT_TIMESTAMP'), 9 | }, 10 | }) 11 | } 12 | 13 | exports.down = pgm => { 14 | pgm.dropColumn('github_user_tokens', 'created') 15 | } 16 | -------------------------------------------------------------------------------- /scripts/benchmark-performance.js: -------------------------------------------------------------------------------- 1 | import config from 'config' 2 | import got from 'got' 3 | import minimist from 'minimist' 4 | import Server from '../core/server/server.js' 5 | 6 | async function main() { 7 | const server = new Server(config.util.toObject()) 8 | await server.start() 9 | const args = minimist(process.argv) 10 | const iterations = parseInt(args.iterations) || 10000 11 | for (let i = 0; i < iterations; ++i) { 12 | await got(`${server.baseUrl}badge/coverage-${i}-green.svg`) 13 | } 14 | await server.stop() 15 | } 16 | 17 | ;(async () => { 18 | try { 19 | await main() 20 | } catch (e) { 21 | console.error(e) 22 | process.exit(1) 23 | } 24 | })() 25 | -------------------------------------------------------------------------------- /scripts/update-github-api.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import got from 'got' 3 | import yaml from 'js-yaml' 4 | 5 | const resp = await got('https://api.github.com/versions').json() 6 | const latestDate = resp.sort()[resp.length - 1] 7 | 8 | const config = yaml.load(await fs.readFile('./config/default.yml', 'utf8')) 9 | 10 | if (latestDate === config.public.services.github.restApiVersion) { 11 | console.log("We're already using the latest version. No change needed.") 12 | process.exit(0) 13 | } 14 | 15 | config.public.services.github.restApiVersion = latestDate 16 | await fs.writeFile( 17 | './config/default.yml', 18 | yaml.dump(config, { forceQuotes: true }), 19 | ) 20 | -------------------------------------------------------------------------------- /scripts/write-migrations-config.js: -------------------------------------------------------------------------------- 1 | import configModule from 'config' 2 | const config = configModule.util.toObject() 3 | 4 | const postgresUrl = config?.private?.postgres_url 5 | 6 | if (!postgresUrl) { 7 | process.exit(1) 8 | } 9 | 10 | process.stdout.write(JSON.stringify({ url: postgresUrl })) 11 | process.exit(0) 12 | -------------------------------------------------------------------------------- /services/amo/amo-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | import { isMetricOverTimePeriod } from '../test-validators.js' 3 | export const t = new ServiceTester({ 4 | id: 'AmoDownloads', 5 | title: 'AmoDownloads', 6 | pathPrefix: '/amo', 7 | }) 8 | 9 | t.create('Weekly Downloads') 10 | .get('/dw/duckduckgo-for-firefox.json') 11 | .expectBadge({ label: 'downloads', message: isMetricOverTimePeriod }) 12 | 13 | t.create('Weekly Downloads (not found)') 14 | .get('/dw/not-a-real-plugin.json') 15 | .expectBadge({ label: 'downloads', message: 'not found' }) 16 | 17 | t.create('/d URL should redirect to /dw') 18 | .get('/d/dustman.svg') 19 | .expectRedirect('/amo/dw/dustman.svg') 20 | -------------------------------------------------------------------------------- /services/amo/amo-rating.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { isStarRating } from '../test-validators.js' 3 | import { createServiceTester } from '../tester.js' 4 | export const t = await createServiceTester() 5 | 6 | t.create('Rating') 7 | .get('/rating/IndieGala-Helper.json') 8 | .expectBadge({ 9 | label: 'rating', 10 | message: Joi.string().regex(/^\d(\.\d)?\/\d$/), 11 | }) 12 | 13 | t.create('Stars') 14 | .get('/stars/IndieGala-Helper.json') 15 | .expectBadge({ label: 'stars', message: isStarRating }) 16 | 17 | t.create('Rating (not found)') 18 | .get('/rating/not-a-real-plugin.json') 19 | .expectBadge({ label: 'mozilla add-on', message: 'not found' }) 20 | -------------------------------------------------------------------------------- /services/amo/amo-users.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Users') 6 | .get('/IndieGala-Helper.json') 7 | .expectBadge({ label: 'users', message: isMetric }) 8 | 9 | t.create('Users (not found)') 10 | .get('/not-a-real-plugin.json') 11 | .expectBadge({ label: 'users', message: 'not found' }) 12 | -------------------------------------------------------------------------------- /services/amo/amo-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Version').get('/night-video-tuner.json').expectBadge({ 6 | label: 'mozilla add-on', 7 | message: isVPlusDottedVersionAtLeastOne, 8 | }) 9 | 10 | t.create('Version (not found)') 11 | .get('/not-a-real-plugin.json') 12 | .expectBadge({ label: 'mozilla add-on', message: 'not found' }) 13 | -------------------------------------------------------------------------------- /services/ansible/ansible-collection-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | 4 | export const t = new ServiceTester({ 5 | id: 'AnsibleGalaxyCollectionDownloads', 6 | title: 'AnsibleGalaxyCollectionDownloads', 7 | pathPrefix: '/ansible/collection/d', 8 | }) 9 | 10 | t.create('collection downloads (valid)') 11 | .get('/community/general.json') 12 | .expectBadge({ label: 'collection downloads', message: isMetric }) 13 | 14 | t.create('collection downloads (not found)') 15 | .get('/not/real.json') 16 | .expectBadge({ label: 'collection downloads', message: 'not found' }) 17 | -------------------------------------------------------------------------------- /services/ansible/ansible-collection-version.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | import { isSemver } from '../test-validators.js' 3 | 4 | export const t = new ServiceTester({ 5 | id: 'AnsibleGalaxyCollectionVersion', 6 | title: 'AnsibleGalaxyCollectionVersion', 7 | pathPrefix: '/ansible/collection/v', 8 | }) 9 | 10 | t.create('collection version (valid)') 11 | .get('/community/general.json') 12 | .expectBadge({ label: 'galaxy', message: isSemver }) 13 | 14 | t.create('collection version (not found)') 15 | .get('/not/real.json') 16 | .expectBadge({ label: 'galaxy', message: 'not found' }) 17 | -------------------------------------------------------------------------------- /services/ansible/ansible-collection.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const AnsibleGalaxyCollectionName = deprecatedService({ 4 | category: 'other', 5 | route: { 6 | base: 'ansible/collection', 7 | pattern: ':collectionId', 8 | }, 9 | label: 'collection', 10 | dateAdded: new Date('2023-10-10'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/ansible/ansible-collection.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'AnsibleGalaxyCollectionName', 5 | title: 'AnsibleGalaxyCollectionName', 6 | pathPrefix: '/ansible/collection', 7 | }) 8 | 9 | t.create('collection name') 10 | .get('/278.json') 11 | .expectBadge({ label: 'collection', message: 'no longer available' }) 12 | -------------------------------------------------------------------------------- /services/ansible/ansible-quality.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const AnsibleGalaxyContentQualityScore = deprecatedService({ 4 | category: 'analysis', 5 | route: { 6 | base: 'ansible/quality', 7 | pattern: ':projectId', 8 | }, 9 | label: 'quality', 10 | dateAdded: new Date('2023-10-10'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/ansible/ansible-quality.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | export const t = new ServiceTester({ 3 | id: 'AnsibleGalaxyContentQualityScore', 4 | title: 'AnsibleGalaxyContentQualityScore', 5 | pathPrefix: '/ansible/quality', 6 | }) 7 | 8 | t.create('quality score') 9 | .get('/432.json') 10 | .expectBadge({ label: 'quality', message: 'no longer available' }) 11 | -------------------------------------------------------------------------------- /services/appveyor/appveyor-build-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'build', 6 | route: { 7 | base: 'appveyor/ci', 8 | pattern: ':user/:repo/:branch*', 9 | }, 10 | transformPath: ({ user, repo, branch }) => 11 | `/appveyor/build/${user}/${repo}${branch ? `/${branch}` : ''}`, 12 | dateAdded: new Date('2019-12-10'), 13 | }), 14 | ] 15 | -------------------------------------------------------------------------------- /services/appveyor/appveyor-build-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'AppveyorBuildRedirect', 5 | title: 'AppveyorBuildRedirect', 6 | pathPrefix: '/appveyor/ci', 7 | }) 8 | 9 | t.create('Appveyor CI') 10 | .get('/gruntjs/grunt', { 11 | followRedirect: false, 12 | }) 13 | .expectStatus(301) 14 | .expectHeader('Location', '/appveyor/build/gruntjs/grunt.svg') 15 | 16 | t.create('Appveyor CI (branch)') 17 | .get('/gruntjs/grunt/develop', { 18 | followRedirect: false, 19 | }) 20 | .expectStatus(301) 21 | .expectHeader('Location', '/appveyor/build/gruntjs/grunt/develop.svg') 22 | -------------------------------------------------------------------------------- /services/aur/aur.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import { AurVersion } from './aur.service.js' 3 | 4 | describe('AurVersion', function () { 5 | test(AurVersion.render, () => { 6 | given({ version: '1:1.1.42.622-1', outOfDate: 1 }).expect({ 7 | color: 'orange', 8 | message: 'v1:1.1.42.622-1', 9 | label: undefined, 10 | }) 11 | 12 | given({ version: '7', outOfDate: null }).expect({ 13 | color: 'blue', 14 | message: 'v7', 15 | label: undefined, 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /services/bit/bit-components.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('collection (valid)').get('/ramda/ramda.json').expectBadge({ 6 | label: 'components', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('collection (valid)') 11 | .get('/bit/no-collection-test.json') 12 | .expectBadge({ label: 'components', message: 'collection not found' }) 13 | -------------------------------------------------------------------------------- /services/bountysource/bountysource.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const Bountysource = deprecatedService({ 4 | category: 'funding', 5 | route: { 6 | base: 'bountysource/team', 7 | pattern: ':team/activity', 8 | }, 9 | label: 'bountysource', 10 | dateAdded: new Date('2024-07-18'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/bountysource/bountysource.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'bountysource', 5 | title: 'Bountysource', 6 | }) 7 | 8 | t.create('bounties').get('/team/mozilla-core/activity.json').expectBadge({ 9 | label: 'bountysource', 10 | message: 'no longer available', 11 | }) 12 | -------------------------------------------------------------------------------- /services/bower/bower-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('licence') 5 | .timeout(10000) 6 | .get('/bootstrap.json') 7 | .expectBadge({ label: 'license', message: 'MIT' }) 8 | 9 | t.create('licence for Invalid Package') 10 | .timeout(10000) 11 | .get('/it-is-a-invalid-package-should-error.json') 12 | .expectBadge({ label: 'license', message: 'package not found' }) 13 | -------------------------------------------------------------------------------- /services/bstats/bstats-players.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Players').get('/1.json').expectBadge({ 6 | label: 'players', 7 | message: isMetric, 8 | }) 9 | -------------------------------------------------------------------------------- /services/bstats/bstats-servers.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Servers').get('/1.json').expectBadge({ 6 | label: 'servers', 7 | message: isMetric, 8 | }) 9 | -------------------------------------------------------------------------------- /services/bugzilla/bugzilla.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import Bugzilla from './bugzilla.service.js' 3 | 4 | describe('getDisplayStatus function', function () { 5 | it('formats status correctly', async function () { 6 | test(Bugzilla.getDisplayStatus, () => { 7 | given({ status: 'RESOLVED', resolution: 'WORKSFORME' }).expect( 8 | 'works for me', 9 | ) 10 | given({ status: 'RESOLVED', resolution: 'WONTFIX' }).expect("won't fix") 11 | given({ status: 'ASSIGNED', resolution: '' }).expect('assigned') 12 | }) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /services/cdnjs/cdnjs.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusTripleDottedVersion } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('cdnjs (valid)').get('/jquery.json').expectBadge({ 6 | label: 'cdnjs', 7 | message: isVPlusTripleDottedVersion, 8 | }) 9 | 10 | t.create('cdnjs (not found)') 11 | .get('/not-a-library.json') 12 | .expectBadge({ label: 'cdnjs', message: 'not found' }) 13 | -------------------------------------------------------------------------------- /services/check-services.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | loadServiceClasses, 3 | collectDefinitions, 4 | } from '../core/base-service/loader.js' 5 | 6 | // When these tests fail, they will throw AssertionErrors. Wrapping them in an 7 | // `expect().not.to.throw()` makes the error output unreadable. 8 | 9 | it('Services have unique names', async function () { 10 | this.timeout(30000) 11 | await loadServiceClasses() 12 | }) 13 | 14 | it('Can collect the service definitions', async function () { 15 | await collectDefinitions() 16 | }) 17 | -------------------------------------------------------------------------------- /services/chocolatey/chocolatey.service.js: -------------------------------------------------------------------------------- 1 | import { createServiceFamily } from '../nuget/nuget-v2-service-family.js' 2 | 3 | export default createServiceFamily({ 4 | defaultLabel: 'chocolatey', 5 | serviceBaseUrl: 'chocolatey', 6 | apiBaseUrl: 'https://community.chocolatey.org/api/v2', 7 | title: 'Chocolatey', 8 | examplePackageName: 'git', 9 | }) 10 | -------------------------------------------------------------------------------- /services/chrome-web-store/chrome-web-store-base.js: -------------------------------------------------------------------------------- 1 | import { ChromeWebStore } from 'webextension-store-meta/lib/chrome-web-store/index.js' 2 | import checkErrorResponse from '../../core/base-service/check-error-response.js' 3 | import { BaseService, Inaccessible } from '../index.js' 4 | 5 | export default class BaseChromeWebStoreService extends BaseService { 6 | async fetch({ storeId }) { 7 | try { 8 | return await ChromeWebStore.load({ id: storeId }) 9 | } catch (e) { 10 | const statusCode = parseInt(e.message) 11 | if (isNaN(statusCode)) { 12 | throw new Inaccessible({ underlyingError: e }) 13 | } 14 | e.statusCode = statusCode 15 | return checkErrorResponse({})({ buffer: '', res: e }) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services/chrome-web-store/chrome-web-store-last-updated.tester.js: -------------------------------------------------------------------------------- 1 | import { isFormattedDate } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Last updated') 7 | .get('/nccfelhkfpbnefflolffkclhenplhiab.json') 8 | .expectBadge({ 9 | label: 'last updated', 10 | message: isFormattedDate, 11 | }) 12 | 13 | t.create('Last updated (not found)') 14 | .get('/invalid-name-of-addon.json') 15 | .expectBadge({ 16 | label: 'last updated', 17 | message: 'not found', 18 | }) 19 | -------------------------------------------------------------------------------- /services/chrome-web-store/chrome-web-store-price.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | const ChromeWebStorePrice = deprecatedService({ 4 | category: 'funding', 5 | route: { base: 'chrome-web-store/price', pattern: ':storeId' }, 6 | label: 'price', 7 | dateAdded: new Date('2024-02-18'), 8 | }) 9 | 10 | export default ChromeWebStorePrice 11 | -------------------------------------------------------------------------------- /services/chrome-web-store/chrome-web-store-price.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'ChromeWebStorePrice', 5 | title: 'ChromeWebStorePrice', 6 | pathPrefix: '/chrome-web-store/price', 7 | }) 8 | 9 | t.create('Price').get('/alhjnofcnnpeaphgeakdhkebafjcpeae.json').expectBadge({ 10 | label: 'price', 11 | message: 'no longer available', 12 | }) 13 | -------------------------------------------------------------------------------- /services/chrome-web-store/chrome-web-store-size.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isIecFileSize } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Size').get('/nccfelhkfpbnefflolffkclhenplhiab.json').expectBadge({ 7 | label: 'extension size', 8 | message: isIecFileSize, 9 | }) 10 | 11 | t.create('Size (not found)') 12 | .get('/invalid-name-of-addon.json') 13 | .expectBadge({ label: 'extension size', message: 'not found' }) 14 | -------------------------------------------------------------------------------- /services/cirrus/cirrus.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { isBuildStatus } from '../build-status.js' 3 | import { createServiceTester } from '../tester.js' 4 | export const t = await createServiceTester() 5 | 6 | t.create('cirrus bad repo') 7 | .get('/github/unknown-identifier/unknown-repo.json') 8 | .expectBadge({ label: 'build', message: 'unknown' }) 9 | 10 | t.create('cirrus fully.valid') 11 | .get('/github/flutter/flutter.json') 12 | .expectBadge({ 13 | label: 'build', 14 | message: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), 15 | }) 16 | -------------------------------------------------------------------------------- /services/clojars/clojars-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('clojars downloads (valid)').get('/prismic.json').expectBadge({ 6 | label: 'downloads', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('clojars downloads (not found)') 11 | .get('/not-a-package.json') 12 | .expectBadge({ label: 'downloads', message: 'not found' }) 13 | -------------------------------------------------------------------------------- /services/cocoapods/cocoapods-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('version (valid)').get('/AFNetworking.json').expectBadge({ 6 | label: 'pod', 7 | message: isVPlusDottedVersionAtLeastOne, 8 | }) 9 | 10 | t.create('version (not found)') 11 | .get('/not-a-package.json') 12 | .expectBadge({ label: 'pod', message: 'not found' }) 13 | -------------------------------------------------------------------------------- /services/codacy/codacy-helpers.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | 3 | const codacyGrade = Joi.equal('A', 'B', 'C', 'D', 'E', 'F') 4 | 5 | export { codacyGrade } 6 | -------------------------------------------------------------------------------- /services/codeclimate/codeclimate-analysis-redirector.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | // http://github.com/badges/shields/issues/1387 5 | // https://github.com/badges/shields/pull/3320#issuecomment-483795000 6 | redirector({ 7 | name: 'CodeclimateCoverageMaintainabilityRedirect', 8 | category: 'analysis', 9 | route: { 10 | base: 'codeclimate/maintainability-letter', 11 | pattern: ':user/:repo', 12 | }, 13 | transformPath: ({ user, repo }) => 14 | `/codeclimate/maintainability/${user}/${repo}`, 15 | dateAdded: new Date('2019-04-16'), 16 | }), 17 | ] 18 | -------------------------------------------------------------------------------- /services/codeclimate/codeclimate-analysis-redirector.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'CodeclimateCoverageRedirector', 5 | title: 'Code Climate Coverage Redirector', 6 | pathPrefix: '/codeclimate', 7 | }) 8 | 9 | t.create('Maintainability letter alias') 10 | .get('/maintainability-letter/jekyll/jekyll.svg') 11 | .expectRedirect('/codeclimate/maintainability/jekyll/jekyll.svg') 12 | -------------------------------------------------------------------------------- /services/codecov/codecov-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | const vcsSNameShortFormMap = { 4 | bb: 'bitbucket', 5 | gh: 'github', 6 | gl: 'gitlab', 7 | } 8 | 9 | export default [ 10 | redirector({ 11 | category: 'coverage', 12 | route: { 13 | base: 'codecov/c', 14 | pattern: 15 | 'token/:token/:vcsName(github|gh|bitbucket|bb|gl|gitlab)/:user/:repo/:branch*', 16 | }, 17 | transformPath: ({ vcsName, user, repo, branch }) => { 18 | const vcs = vcsSNameShortFormMap[vcsName] || vcsName 19 | return `/codecov/c/${vcs}/${user}/${repo}${branch ? `/${branch}` : ''}` 20 | }, 21 | transformQueryParams: ({ token }) => ({ token }), 22 | dateAdded: new Date('2019-03-04'), 23 | }), 24 | ] 25 | -------------------------------------------------------------------------------- /services/codecov/codecov.spec.js: -------------------------------------------------------------------------------- 1 | import { test, forCases, given } from 'sazerac' 2 | import Codecov from './codecov.service.js' 3 | 4 | describe('Codecov', function () { 5 | test(Codecov.prototype.legacyTransform, () => { 6 | forCases([given({ json: {} }), given({ json: { commit: {} } })]).expect({ 7 | coverage: 'unknown', 8 | }) 9 | }) 10 | 11 | test(Codecov.prototype.transform, () => { 12 | forCases([given({ data: { message: 'unknown' } })]).expect({ 13 | coverage: 'unknown', 14 | }) 15 | }) 16 | 17 | test(Codecov.render, () => { 18 | given({ coverage: 'unknown' }).expect({ 19 | message: 'unknown', 20 | color: 'lightgrey', 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /services/codefactor/codefactor-grade.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isValidGrade } from './codefactor-helpers.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Grade').get('/github/microsoft/powertoys.json').expectBadge({ 6 | label: 'code quality', 7 | message: isValidGrade, 8 | }) 9 | 10 | t.create('Grade (branch)') 11 | .get('/github/spring-projects/spring-boot/main.json') 12 | .expectBadge({ 13 | label: 'code quality', 14 | message: isValidGrade, 15 | }) 16 | 17 | t.create('Grade (nonexistent repo)') 18 | .get('/github/badges/asdfasdfasdfasdfasfae.json') 19 | .expectBadge({ 20 | label: 'code quality', 21 | message: 'repo or branch not found', 22 | }) 23 | -------------------------------------------------------------------------------- /services/coincap/coincap.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const Coincap = deprecatedService({ 4 | category: 'other', 5 | route: { 6 | base: 'coincap', 7 | pattern: ':various+', 8 | }, 9 | label: 'coincap', 10 | dateAdded: new Date('2025-05-11'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/coincap/coincap.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'Coincap', 5 | title: 'Coincap', 6 | pathPrefix: '/coincap', 7 | }) 8 | 9 | t.create('coincap change percent 24hr') 10 | .get('/change-percent-24hr/bitcoin.json') 11 | .expectBadge({ 12 | label: 'coincap', 13 | message: 'no longer available', 14 | }) 15 | 16 | t.create('coincap price USD').get('/price-usd/bitcoin.json').expectBadge({ 17 | label: 'coincap', 18 | message: 'no longer available', 19 | }) 20 | 21 | t.create('coincap rank').get('/rank/bitcoin.json').expectBadge({ 22 | label: 'coincap', 23 | message: 'no longer available', 24 | }) 25 | -------------------------------------------------------------------------------- /services/conan/conan-version-helpers.js: -------------------------------------------------------------------------------- 1 | import yaml from 'js-yaml' 2 | import { NotFound, InvalidResponse } from '../index.js' 3 | import { latest } from '../version.js' 4 | 5 | export function parseLatestVersionFromConfig(configYaml) { 6 | let versions 7 | try { 8 | const config = yaml.load(configYaml) 9 | versions = Object.keys(config.versions) 10 | } catch (err) { 11 | throw new InvalidResponse({ 12 | prettyMessage: 'invalid config.yml', 13 | underlyingError: err, 14 | }) 15 | } 16 | const version = latest(versions) 17 | if (version == null) { 18 | throw new NotFound({ prettyMessage: 'no versions found' }) 19 | } 20 | return version 21 | } 22 | -------------------------------------------------------------------------------- /services/conan/conan-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('gets the package version of zeromq') 7 | .get('/zeromq.json') 8 | .expectBadge({ label: 'conan', message: isSemver }) 9 | 10 | t.create('returns not found for invalid package') 11 | .get('/this package does not exist - shields test.json') 12 | .expectBadge({ 13 | label: 'conan', 14 | color: 'red', 15 | message: 16 | 'repo not found, branch not found, or recipes/this package does not exist - shields test/config.yml missing', 17 | }) 18 | -------------------------------------------------------------------------------- /services/conda/conda-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('license') 5 | .get('/l/conda-forge/setuptools.json') 6 | .expectBadge({ label: 'license', message: 'MIT', color: 'green' }) 7 | 8 | t.create('license (invalid)') 9 | .get('/l/conda-forge/some-bogus-package-that-never-exists.json') 10 | .expectBadge({ label: 'license', message: 'not found', color: 'red' }) 11 | -------------------------------------------------------------------------------- /services/conda/conda-platform.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | const isCondaPlatform = Joi.string().regex(/^\w+-[\w\d]+( \| \w+-[\w\d]+)*$/) 4 | export const t = await createServiceTester() 5 | 6 | t.create('platform').get('/p/conda-forge/zlib.json').expectBadge({ 7 | label: 'conda|platform', 8 | message: isCondaPlatform, 9 | }) 10 | 11 | t.create('platform (skip prefix)') 12 | .get('/pn/conda-forge/zlib.json') 13 | .expectBadge({ 14 | label: 'platform', 15 | message: isCondaPlatform, 16 | }) 17 | -------------------------------------------------------------------------------- /services/conda/conda-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusTripleDottedVersion } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('version').get('/v/conda-forge/zlib.json').expectBadge({ 6 | label: 'conda | conda-forge', 7 | message: isVPlusTripleDottedVersion, 8 | }) 9 | 10 | t.create('version (skip prefix)').get('/vn/conda-forge/zlib.json').expectBadge({ 11 | label: 'conda-forge', 12 | message: isVPlusTripleDottedVersion, 13 | }) 14 | -------------------------------------------------------------------------------- /services/contributor-count.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import { renderContributorBadge } from './contributor-count.js' 3 | 4 | describe('Contributor count helpers', function () { 5 | test(renderContributorBadge, () => { 6 | given({ label: 'maintainers', contributorCount: 1 }).expect({ 7 | label: 'maintainers', 8 | message: '1', 9 | color: 'red', 10 | }) 11 | given({ label: 'collaborators', contributorCount: 2 }).expect({ 12 | label: 'collaborators', 13 | message: '2', 14 | color: 'yellow', 15 | }) 16 | given({ label: 'collaborators', contributorCount: 3000 }).expect({ 17 | label: 'collaborators', 18 | message: '3k', 19 | color: 'brightgreen', 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /services/cpan/cpan-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('license (valid)').get('/Config-Augeas.json').expectBadge({ 5 | label: 'license', 6 | message: 'lgpl_2_1', 7 | }) 8 | 9 | t.create('license (not found)').get('/not-a-package.json').expectBadge({ 10 | label: 'cpan', 11 | message: 'not found', 12 | }) 13 | -------------------------------------------------------------------------------- /services/cpan/cpan.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { BaseJsonService } from '../index.js' 3 | 4 | const schema = Joi.object({ 5 | version: Joi.alternatives(Joi.string().required(), Joi.number().required()), 6 | license: Joi.array().items(Joi.string()).min(1).required(), 7 | }).required() 8 | 9 | const description = 10 | '[CPAN](https://www.cpan.org/) is a package registry for Perl' 11 | 12 | export default class BaseCpanService extends BaseJsonService { 13 | static defaultBadgeData = { label: 'cpan' } 14 | 15 | async fetch({ packageName }) { 16 | const url = `https://fastapi.metacpan.org/v1/release/${packageName}` 17 | return this._requestJson({ schema, url }) 18 | } 19 | } 20 | 21 | export { BaseCpanService, description } 22 | -------------------------------------------------------------------------------- /services/crates/crates-dependents.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('dependent count').get('/tokio.json').expectBadge({ 6 | label: 'dependents', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('dependent count (nonexistent package)') 11 | .get('/foobar-is-not-crate.json') 12 | .expectBadge({ 13 | label: 'crates.io', 14 | message: 'not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/crates/crates-msrv.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | // dummy crate i created specifically for this test case 5 | t.create('msrv') 6 | .get('/shields-test-dummy-crate-msrv-3452398210.json') 7 | .expectBadge({ label: 'msrv', message: '1.69' }) 8 | 9 | t.create('msrv (with version)') 10 | .get('/shields-test-dummy-crate-msrv-3452398210/0.69.0.json') 11 | .expectBadge({ label: 'msrv', message: '1.69' }) 12 | 13 | t.create('msrv (not found)') 14 | .get('/not-a-real-package.json') 15 | .expectBadge({ label: 'msrv', message: 'not found' }) 16 | -------------------------------------------------------------------------------- /services/crates/crates-size.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isIecFileSize } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('size') 6 | .get('/tokio.json') 7 | .expectBadge({ label: 'size', message: isIecFileSize }) 8 | 9 | t.create('size (with version)') 10 | .get('/tokio/1.32.0.json') 11 | .expectBadge({ label: 'size', message: '708 KiB' }) 12 | 13 | t.create('size (not found)') 14 | .get('/not-a-crate.json') 15 | .expectBadge({ label: 'crates.io', message: 'not found' }) 16 | -------------------------------------------------------------------------------- /services/crates/crates-user-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('total user downloads') 6 | .get('/udt/3027.json') 7 | .expectBadge({ label: 'downloads', message: isMetric }) 8 | 9 | // non-existent user returns 0 downloads with 200 OK status code rather than 404. 10 | t.create('total user downloads (user not found)') 11 | .get('/udt/2147483647.json') // 2147483647 is the maximum valid user id as API uses i32 12 | .expectBadge({ label: 'downloads', message: '0' }) 13 | 14 | t.create('total user downloads (invalid)') 15 | .get('/udt/999999999999999999999999.json') 16 | .expectBadge({ label: 'crates.io', message: 'invalid' }) 17 | -------------------------------------------------------------------------------- /services/crates/crates-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('version') 6 | .get('/libc.json') 7 | .expectBadge({ label: 'crates.io', message: isSemver }) 8 | 9 | t.create('version (not found)') 10 | .get('/not-a-real-package.json') 11 | .expectBadge({ label: 'crates.io', message: 'not found' }) 12 | -------------------------------------------------------------------------------- /services/date/date.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | import { isRelativeFormattedDate } from '../test-validators.js' 3 | 4 | export const t = new ServiceTester({ 5 | id: 'date', 6 | title: 'Relative Date Tests', 7 | }) 8 | 9 | t.create('Relative date') 10 | .get('/1540814400.json') 11 | .expectBadge({ label: 'date', message: isRelativeFormattedDate }) 12 | 13 | t.create('Relative date (negative)') 14 | .get('/-1.json') 15 | .expectBadge({ label: 'date', message: isRelativeFormattedDate }) 16 | 17 | t.create('Relative date - Invalid') 18 | .get('/9999999999999.json') 19 | .expectBadge({ label: 'date', message: 'invalid date' }) 20 | -------------------------------------------------------------------------------- /services/debug/debug.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('start time') 6 | .get('/starttime.json') 7 | .expectBadge({ label: 'start time', message: Joi.date().required() }) 8 | 9 | t.create('Flip: first request') 10 | .get('/flip.json') 11 | .expectBadge({ label: 'flip', message: 'on' }) 12 | 13 | t.create('Flip: second request') 14 | .get('/flip.json') 15 | .expectBadge({ label: 'flip', message: 'off' }) 16 | -------------------------------------------------------------------------------- /services/discord/discord.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import Discord from './discord.service.js' 3 | 4 | describe('Discord', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth( 8 | Discord, 9 | 'BearerAuthHeader', 10 | { presence_count: 125 }, 11 | { bearerHeaderKey: 'Bot' }, 12 | ) 13 | }) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /services/discourse/discourse-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'chat', 6 | route: { 7 | base: 'discourse', 8 | pattern: ':protocol(http|https)/:hostAndPath(.+)/:metric', 9 | }, 10 | transformPath: ({ metric }) => `/discourse/${metric}`, 11 | transformQueryParams: ({ protocol, hostAndPath }) => ({ 12 | server: `${protocol}://${hostAndPath}`, 13 | }), 14 | dateAdded: new Date('2019-09-15'), 15 | }), 16 | ] 17 | -------------------------------------------------------------------------------- /services/docker/docker-automated.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import DockerAutomatedBuild from './docker-automated.service.js' 3 | 4 | describe('DockerAutomatedBuild', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth( 8 | DockerAutomatedBuild, 9 | 'JwtAuth', 10 | { is_automated: true }, 11 | { jwtLoginEndpoint: 'https://hub.docker.com/v2/users/login/' }, 12 | ) 13 | }) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /services/docker/docker-cloud-common-fetch.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon' 2 | import { expect } from 'chai' 3 | import { fetchBuild } from './docker-cloud-common-fetch.js' 4 | 5 | describe('fetchBuild', function () { 6 | it('invokes withJwtAuth', async function () { 7 | const serviceInstance = { 8 | _requestJson: sinon.stub().resolves('fake-response'), 9 | authHelper: { 10 | withJwtAuth: sinon.stub(), 11 | }, 12 | } 13 | 14 | const resp = await fetchBuild(serviceInstance, { 15 | user: 'user', 16 | repo: 'repo', 17 | }) 18 | 19 | expect(serviceInstance.authHelper.withJwtAuth.calledOnce).to.be.true 20 | expect(serviceInstance._requestJson.calledOnce).to.be.true 21 | expect(resp).to.equal('fake-response') 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /services/docker/docker-hub-common-fetch.js: -------------------------------------------------------------------------------- 1 | async function fetch(serviceInstance, params) { 2 | return serviceInstance._requestJson( 3 | await serviceInstance.authHelper.withJwtAuth( 4 | params, 5 | 'https://hub.docker.com/v2/users/login/', 6 | ), 7 | ) 8 | } 9 | 10 | export { fetch } 11 | -------------------------------------------------------------------------------- /services/docker/docker-hub-common-fetch.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon' 2 | import { expect } from 'chai' 3 | import { fetch } from './docker-hub-common-fetch.js' 4 | 5 | describe('fetch', function () { 6 | it('invokes withJwtAuth', async function () { 7 | const serviceInstance = { 8 | _requestJson: sinon.stub().resolves('fake-response'), 9 | authHelper: { 10 | withJwtAuth: sinon.stub(), 11 | }, 12 | } 13 | 14 | const resp = await fetch(serviceInstance, {}) 15 | 16 | expect(serviceInstance.authHelper.withJwtAuth.calledOnce).to.be.true 17 | expect(serviceInstance._requestJson.calledOnce).to.be.true 18 | expect(resp).to.equal('fake-response') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /services/docker/docker-pulls.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | import { dockerBlue } from './docker-helpers.js' 4 | export const t = await createServiceTester() 5 | 6 | t.create('docker pulls (valid, library)') 7 | .get('/_/ubuntu.json') 8 | .expectBadge({ 9 | label: 'docker pulls', 10 | message: isMetric, 11 | color: `#${dockerBlue}`, 12 | }) 13 | 14 | t.create('docker pulls (valid, user)') 15 | .get('/jrottenberg/ffmpeg.json') 16 | .expectBadge({ 17 | label: 'docker pulls', 18 | message: isMetric, 19 | }) 20 | 21 | t.create('docker pulls (not found)') 22 | .get('/_/not-a-real-repo.json') 23 | .expectBadge({ label: 'docker pulls', message: 'repo not found' }) 24 | -------------------------------------------------------------------------------- /services/docker/docker-stars.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | import { dockerBlue } from './docker-helpers.js' 4 | export const t = await createServiceTester() 5 | 6 | t.create('docker stars (valid, library)') 7 | .get('/_/ubuntu.json') 8 | .expectBadge({ 9 | label: 'docker stars', 10 | message: isMetric, 11 | color: `#${dockerBlue}`, 12 | }) 13 | 14 | t.create('docker stars (valid, user)') 15 | .get('/jrottenberg/ffmpeg.json') 16 | .expectBadge({ 17 | label: 'docker stars', 18 | message: isMetric, 19 | }) 20 | 21 | t.create('docker stars (not found)') 22 | .get('/_/not-a-real-repo.json') 23 | .expectBadge({ label: 'docker stars', message: 'repo not found' }) 24 | -------------------------------------------------------------------------------- /services/dub/dub-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('license (valid)') 5 | .get('/vibe-d.json') 6 | .expectBadge({ label: 'license', message: 'MIT' }) 7 | 8 | t.create('license (not found)') 9 | .get('/not-a-package.json') 10 | .expectBadge({ label: 'license', message: 'not found' }) 11 | -------------------------------------------------------------------------------- /services/dub/dub-score.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | const isScoreColor = Joi.equal( 7 | 'red', 8 | 'orange', 9 | 'yellow', 10 | 'yellowgreen', 11 | 'green', 12 | 'brightgreen', 13 | ) 14 | 15 | t.create('version (valid)') 16 | .get('/vibe-d.json') 17 | .expectBadge({ 18 | label: 'score', 19 | message: Joi.number().min(0).max(5), 20 | color: isScoreColor, 21 | }) 22 | 23 | t.create('version (not found)') 24 | .get('/not-a-package.json') 25 | .expectBadge({ label: 'score', message: 'not found' }) 26 | -------------------------------------------------------------------------------- /services/dub/dub-version.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { isVPlusDottedVersionNClausesWithOptionalSuffix } from '../test-validators.js' 3 | import { createServiceTester } from '../tester.js' 4 | export const t = await createServiceTester() 5 | 6 | t.create('version (valid)') 7 | .get('/vibe-d.json') 8 | .expectBadge({ 9 | label: 'dub', 10 | message: isVPlusDottedVersionNClausesWithOptionalSuffix, 11 | color: Joi.equal('blue', 'orange'), 12 | }) 13 | 14 | t.create('version (not found)') 15 | .get('/not-a-package.json') 16 | .expectBadge({ label: 'dub', message: 'not found' }) 17 | -------------------------------------------------------------------------------- /services/dynamic/dynamic-helpers.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { url } from '../validators.js' 3 | 4 | const queryParamSchema = Joi.object({ 5 | url, 6 | query: Joi.string().required(), 7 | prefix: Joi.alternatives().try(Joi.string(), Joi.number()), 8 | suffix: Joi.alternatives().try(Joi.string(), Joi.number()), 9 | }) 10 | .rename('uri', 'url', { ignoreUndefined: true, override: true }) 11 | .required() 12 | 13 | function createRoute(which) { 14 | return { 15 | base: `badge/dynamic/${which}`, 16 | pattern: '', 17 | queryParamSchema, 18 | } 19 | } 20 | 21 | export { createRoute } 22 | -------------------------------------------------------------------------------- /services/dynamic/json-path.spec.js: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai' 2 | import chaiAsPromised from 'chai-as-promised' 3 | import jsonPath from './json-path.js' 4 | use(chaiAsPromised) 5 | 6 | describe('JSON Path service factory', function () { 7 | describe('fetch()', function () { 8 | it('should throw error if it is not overridden', function () { 9 | class BaseService {} 10 | class JsonPathService extends jsonPath(BaseService) {} 11 | const jsonPathServiceInstance = new JsonPathService() 12 | 13 | return expect(jsonPathServiceInstance.fetch({})).to.be.rejectedWith( 14 | Error, 15 | 'fetch() function not implemented for JsonPathService', 16 | ) 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /services/eclipse-marketplace/eclipse-marketplace-base.js: -------------------------------------------------------------------------------- 1 | import { BaseXmlService } from '../index.js' 2 | 3 | export default class EclipseMarketplaceBase extends BaseXmlService { 4 | async fetch({ name, schema }) { 5 | return this._requestXml({ 6 | schema, 7 | url: `https://marketplace.eclipse.org/content/${name}/api/p`, 8 | httpErrors: { 404: 'solution not found' }, 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /services/eclipse-marketplace/eclipse-marketplace-favorites.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('favorites count').get('/notepad4e.json').expectBadge({ 6 | label: 'favorites', 7 | message: Joi.number().integer().positive(), 8 | }) 9 | 10 | t.create('favorites for unknown solution') 11 | .get('/this-does-not-exist.json') 12 | .expectBadge({ 13 | label: 'favorites', 14 | message: 'solution not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/eclipse-marketplace/eclipse-marketplace-update.tester.js: -------------------------------------------------------------------------------- 1 | import { isFormattedDate } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('last update date').get('/notepad4e.json').expectBadge({ 6 | label: 'updated', 7 | message: isFormattedDate, 8 | }) 9 | 10 | t.create('last update for unknown solution') 11 | .get('/this-does-not-exist.json') 12 | .expectBadge({ 13 | label: 'updated', 14 | message: 'solution not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/eclipse-marketplace/eclipse-marketplace-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('marketplace version').get('/notepad4e.json').expectBadge({ 6 | label: 'eclipse marketplace', 7 | message: isVPlusDottedVersionAtLeastOne, 8 | }) 9 | 10 | t.create('last update for unknown solution') 11 | .get('/this-does-not-exist.json') 12 | .expectBadge({ 13 | label: 'eclipse marketplace', 14 | message: 'solution not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/ecologi/ecologi-carbon.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetricWithPattern } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('request for existing username') 6 | .get('/ecologi.json') 7 | .expectBadge({ 8 | label: 'carbon offset', 9 | message: isMetricWithPattern(/ tonnes/), 10 | }) 11 | 12 | t.create('invalid username').get('/non-existent-username.json').expectBadge({ 13 | label: 'carbon offset', 14 | message: 'username not found', 15 | color: 'red', 16 | }) 17 | -------------------------------------------------------------------------------- /services/elm-package/elm-package.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('gets the package version of elm/core') 6 | .get('/elm/core.json') 7 | .expectBadge({ label: 'elm package', message: isSemver }) 8 | 9 | t.create('invalid package name') 10 | .get('/elm-community/frodo-is-not-a-package.json') 11 | .expectBadge({ label: 'elm package', message: 'package not found' }) 12 | -------------------------------------------------------------------------------- /services/endpoint/endpoint-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | category: 'other', 5 | route: { 6 | base: 'badge/endpoint', 7 | pattern: '', 8 | }, 9 | transformPath: () => '/endpoint', 10 | dateAdded: new Date('2019-02-19'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/endpoint/endpoint-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'EndpointRedirect', 5 | title: 'EndpointRedirect', 6 | pathPrefix: '/badge/endpoint', 7 | }) 8 | 9 | t.create('Build: default branch') 10 | .get('.svg?url=https://example.com/badge.json') 11 | .expectRedirect('/endpoint.svg?url=https://example.com/badge.json') 12 | -------------------------------------------------------------------------------- /services/fedora/fedora.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionNClausesWithOptionalSuffixAndEpoch } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Fedora package (default branch, valid)') 6 | .get('/rpm.json') 7 | .expectBadge({ 8 | label: 'fedora', 9 | message: isVPlusDottedVersionNClausesWithOptionalSuffixAndEpoch, 10 | }) 11 | 12 | t.create('Fedora package (not found)') 13 | .get('/not-a-package/rawhide.json') 14 | .expectBadge({ label: 'fedora', message: 'not found' }) 15 | 16 | t.create('Fedora package (branch not found)') 17 | .get('/not-a-package/not-a-branch.json') 18 | .expectBadge({ label: 'fedora', message: 'branch not found' }) 19 | -------------------------------------------------------------------------------- /services/flathub/flathub-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Flathub Downloads (valid)') 6 | .get('/org.mozilla.firefox.json') 7 | .expectBadge({ 8 | label: 'installs', 9 | message: isMetric, 10 | }) 11 | 12 | t.create('Flathub Downloads (not found)') 13 | .get('/not.a.package.json') 14 | .expectBadge({ label: 'installs', message: 'not found' }) 15 | -------------------------------------------------------------------------------- /services/freecodecamp/freecodecamp-points.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Total Points Valid') 6 | .get('/raisedadead.json') 7 | .expectBadge({ label: 'points', message: isMetric }) 8 | 9 | t.create('Total Points Private') 10 | .get('/set.json') 11 | .expectBadge({ label: 'points', message: 'private' }) 12 | 13 | t.create('Total Points Invalid') 14 | .get('/invalid@username.json') 15 | .expectBadge({ label: 'points', message: 'profile not found' }) 16 | -------------------------------------------------------------------------------- /services/gem/gem-helpers.js: -------------------------------------------------------------------------------- 1 | import { valid, maxSatisfying, prerelease } from '@renovatebot/ruby-semver' 2 | 3 | const description = 4 | '[Ruby Gems](https://rubygems.org/) is a registry for ruby libraries' 5 | 6 | function latest(versions) { 7 | // latest Ruby Gems version, including pre-releases 8 | return maxSatisfying(versions, '>0') 9 | } 10 | 11 | function versionColor(version) { 12 | if (!valid(version)) { 13 | return 'lightgrey' 14 | } 15 | 16 | version = `${version}` 17 | let first = version[0] 18 | if (first === 'v') { 19 | first = version[1] 20 | } 21 | 22 | if (first === '0' || prerelease(version)) { 23 | return 'orange' 24 | } 25 | return 'blue' 26 | } 27 | 28 | export { description, latest, versionColor } 29 | -------------------------------------------------------------------------------- /services/gem/gem-helpers.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import { latest, versionColor } from './gem-helpers.js' 3 | 4 | describe('Gem helpers', function () { 5 | test(latest, () => { 6 | given(['2.0.0', '2.0.0.beta1']).expect('2.0.0') 7 | given(['2.0.0.beta1', '1.9.0']).expect('2.0.0.beta1') 8 | given(['0.0.1', '0.0.2']).expect('0.0.2') 9 | }) 10 | 11 | test(versionColor, () => { 12 | given('1.9.0').expect('blue') 13 | given('2.0.0.beta1').expect('orange') 14 | given('0.0.1').expect('orange') 15 | given('v1').expect('lightgrey') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /services/gem/gem-owner.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('users (valid)') 6 | .get('/raphink.json') 7 | .expectBadge({ 8 | label: 'gems', 9 | message: Joi.string().regex(/^[0-9]+$/), 10 | }) 11 | 12 | t.create('users (not found)') 13 | .get('/not-a-package.json') 14 | .expectBadge({ label: 'gems', message: 'not found' }) 15 | -------------------------------------------------------------------------------- /services/gitea/gitea-base.js: -------------------------------------------------------------------------------- 1 | import { BaseJsonService } from '../index.js' 2 | 3 | export default class GiteaBase extends BaseJsonService { 4 | static auth = { 5 | passKey: 'gitea_token', 6 | serviceKey: 'gitea', 7 | } 8 | 9 | async fetch({ url, options, schema, httpErrors }) { 10 | return this._requestJson( 11 | this.authHelper.withBearerAuthHeader({ 12 | schema, 13 | url, 14 | options, 15 | httpErrors, 16 | }), 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /services/gitea/gitea-common-fetch.js: -------------------------------------------------------------------------------- 1 | async function fetchIssue( 2 | serviceInstance, 3 | { user, repo, baseUrl, options, httpErrors }, 4 | ) { 5 | return serviceInstance._request( 6 | serviceInstance.authHelper.withBearerAuthHeader({ 7 | url: `${baseUrl}/api/v1/repos/${user}/${repo}/issues`, 8 | options, 9 | httpErrors, 10 | }), 11 | ) 12 | } 13 | 14 | export { fetchIssue } 15 | -------------------------------------------------------------------------------- /services/github/gist/github-gist-last-commit-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../../index.js' 2 | 3 | export default redirector({ 4 | category: 'activity', 5 | route: { base: 'github-gist/last-commit', pattern: ':gistId' }, 6 | transformPath: ({ gistId }) => `/github/gist/last-commit/${gistId}`, 7 | dateAdded: new Date('2022-10-09'), 8 | }) 9 | -------------------------------------------------------------------------------- /services/github/gist/github-gist-last-commit-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'GistLastCommitRedirect', 5 | title: 'GitHub Gist Last Commit Redirect', 6 | pathPrefix: '/github-gist', 7 | }) 8 | 9 | t.create('Last Commit redirect') 10 | .get('/last-commit/a8b8c979d200ffde13cc08505f7a6436', { 11 | followRedirect: false, 12 | }) 13 | .expectStatus(301) 14 | .expectHeader( 15 | 'Location', 16 | '/github/gist/last-commit/a8b8c979d200ffde13cc08505f7a6436.svg', 17 | ) 18 | -------------------------------------------------------------------------------- /services/github/gist/github-gist-stars-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../../index.js' 2 | 3 | export default redirector({ 4 | category: 'social', 5 | route: { base: 'github/stars/gists', pattern: ':gistId' }, 6 | transformPath: ({ gistId }) => `/github/gist/stars/${gistId}`, 7 | dateAdded: new Date('2022-10-09'), 8 | }) 9 | -------------------------------------------------------------------------------- /services/github/gist/github-gist-stars-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../../tester.js' 2 | export const t = new ServiceTester({ 3 | id: 'GistStarsRedirect', 4 | title: 'Github Gist Stars Redirect', 5 | pathPrefix: '/github', 6 | }) 7 | 8 | t.create('Stars redirect') 9 | .get('/stars/gists/a8b8c979d200ffde13cc08505f7a6436', { 10 | followRedirect: false, 11 | }) 12 | .expectStatus(301) 13 | .expectHeader( 14 | 'Location', 15 | '/github/gist/stars/a8b8c979d200ffde13cc08505f7a6436.svg', 16 | ) 17 | -------------------------------------------------------------------------------- /services/github/github-all-contributors.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('all-contributors repo') 6 | .get('/all-contributors/all-contributors.json') 7 | .expectBadge({ 8 | label: 'all contributors', 9 | message: isMetric, 10 | }) 11 | 12 | t.create('shields repo (not found)').get('/badges/shields.json').expectBadge({ 13 | label: 'all contributors', 14 | message: 'repo not found, branch not found, or .all-contributorsrc missing', 15 | }) 16 | -------------------------------------------------------------------------------- /services/github/github-checks-status.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isBuildStatus } from '../build-status.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('branch checks (branch)') 6 | .get('/badges/shields/master.json') 7 | .expectBadge({ 8 | label: 'checks', 9 | message: isBuildStatus, 10 | }) 11 | 12 | t.create('checks - nonexistent ref') 13 | .get('/badges/shields/this-ref-does-not-exist.json') 14 | .expectBadge({ 15 | label: 'checks', 16 | message: 'ref or repo not found', 17 | color: 'red', 18 | }) 19 | -------------------------------------------------------------------------------- /services/github/github-contributors.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Contributors').get('/contributors/badges/shields.json').expectBadge({ 6 | label: 'contributors', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('1 contributor') 11 | .get('/contributors/badges/shields-tests.json') 12 | .expectBadge({ 13 | label: 'contributors', 14 | message: '1', 15 | }) 16 | 17 | t.create('Contributors (repo not found)') 18 | .get('/contributors/badges/helmets.json') 19 | .expectBadge({ 20 | label: 'contributors', 21 | message: 'repo not found', 22 | }) 23 | -------------------------------------------------------------------------------- /services/github/github-created-at.tester.js: -------------------------------------------------------------------------------- 1 | import { isFormattedDate } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('created at').get('/erayerdin/firereact.json').expectBadge({ 7 | label: 'created at', 8 | message: isFormattedDate, 9 | }) 10 | 11 | t.create('created at').get('/erayerdin/not-a-valid-repo.json').expectBadge({ 12 | label: 'created at', 13 | message: 'repo not found', 14 | }) 15 | -------------------------------------------------------------------------------- /services/github/github-discussions-total.tester.js: -------------------------------------------------------------------------------- 1 | import { withRegex } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('GitHub Total Discussions (repo not found)') 6 | .get('/not-a-user/not-a-repo.json') 7 | .expectBadge({ label: 'discussions', message: 'repo not found' }) 8 | 9 | // example: 6000 total 10 | const numberSpaceTotal = withRegex(/^\d+ total$/) 11 | 12 | t.create('GitHub Total Discussions (repo having discussions)') 13 | .get('/vercel/next.js.json') 14 | .expectBadge({ label: 'discussions', message: numberSpaceTotal }) 15 | -------------------------------------------------------------------------------- /services/github/github-followers.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Followers').get('/webcaetano.json').expectBadge({ 6 | label: 'followers', 7 | message: isMetric, 8 | color: 'blue', 9 | }) 10 | 11 | t.create('Followers (user not found)').get('/PyvesB2.json').expectBadge({ 12 | label: 'followers', 13 | message: 'user not found', 14 | }) 15 | -------------------------------------------------------------------------------- /services/github/github-forks.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Forks') 6 | .get('/badges/shields.json') 7 | .expectBadge({ 8 | label: 'forks', 9 | message: isMetric, 10 | color: 'blue', 11 | link: [ 12 | 'https://github.com/badges/shields/fork', 13 | 'https://github.com/badges/shields/network', 14 | ], 15 | }) 16 | 17 | t.create('Forks (repo not found)').get('/badges/helmets.json').expectBadge({ 18 | label: 'forks', 19 | message: 'repo not found', 20 | }) 21 | -------------------------------------------------------------------------------- /services/github/github-issue-detail-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | const variantMap = { 4 | s: 'state', 5 | u: 'author', 6 | } 7 | 8 | export default [ 9 | redirector({ 10 | category: 'issue-tracking', 11 | route: { 12 | base: 'github', 13 | pattern: 14 | ':issueKind(issues|pulls)/detail/:variant(s|u)/:user/:repo/:number([0-9]+)', 15 | }, 16 | transformPath: ({ issueKind, variant, user, repo, number }) => 17 | `/github/${issueKind}/detail/${variantMap[variant]}/${user}/${repo}/${number}`, 18 | dateAdded: new Date('2019-04-04'), 19 | }), 20 | ] 21 | -------------------------------------------------------------------------------- /services/github/github-labels.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('labels').get('/badges/shields/bug.json').expectBadge({ 5 | message: 'bug', 6 | color: '#e11d21', 7 | }) 8 | 9 | t.create('labels (repo or label not found)') 10 | .get('/badges/shields/somenonexistentlabelthatwouldneverexist.json') 11 | .expectBadge({ 12 | message: 'repo or label not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/github/github-language-count.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('language count').get('/badges/shields.json').expectBadge({ 6 | label: 'languages', 7 | message: Joi.number().integer().positive(), 8 | }) 9 | 10 | t.create('language count (empty repo)') 11 | .get('/pyvesb/emptyrepo.json') 12 | .expectBadge({ label: 'languages', message: '0' }) 13 | 14 | t.create('language count (repo not found)') 15 | .get('/badges/helmets.json') 16 | .expectBadge({ label: 'languages', message: 'repo not found' }) 17 | -------------------------------------------------------------------------------- /services/github/github-license.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import GithubLicense from './github-license.service.js' 3 | 4 | test(GithubLicense.render, () => { 5 | given({ license: undefined }).expect({ message: 'not specified' }) 6 | given({ license: 'NOASSERTION' }).expect({ 7 | message: 'not identifiable by github', 8 | }) 9 | given({ license: 'MIT' }).expect({ message: 'MIT', color: 'green' }) 10 | }) 11 | -------------------------------------------------------------------------------- /services/github/github-release.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import { GithubRelease } from './github-release.service.js' 3 | 4 | describe('GithubRelease', function () { 5 | test(GithubRelease.transform, () => { 6 | given({ name: null, tag_name: '0.1.2', prerelease: true }, 'tag').expect({ 7 | version: '0.1.2', 8 | isPrerelease: true, 9 | }) 10 | given( 11 | { name: null, tag_name: '0.1.3', prerelease: true }, 12 | 'release', 13 | ).expect({ 14 | version: '0.1.3', 15 | isPrerelease: true, 16 | }) 17 | given( 18 | { name: 'fun name', tag_name: '1.0.0', prerelease: false }, 19 | 'release', 20 | ).expect({ 21 | version: 'fun name', 22 | isPrerelease: false, 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /services/github/github-repo-size.tester.js: -------------------------------------------------------------------------------- 1 | import { isIecFileSize } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('repository size').get('/badges/shields.json').expectBadge({ 6 | label: 'repo size', 7 | message: isIecFileSize, 8 | }) 9 | 10 | t.create('repository size (repo not found)') 11 | .get('/badges/helmets.json') 12 | .expectBadge({ 13 | label: 'repo size', 14 | message: 'repo not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/github/github-search.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('hit counter') 6 | .get('/search.json?query=async%20handle') 7 | .expectBadge({ label: 'async handle counter', message: isMetric }) 8 | 9 | t.create('hit counter, zero results') 10 | .get('/search.json?query=async%20handle%20repo%3Abadges%2Fpuppets') 11 | .expectBadge({ 12 | label: 'async handle repo:badges/puppets counter', 13 | message: '0', 14 | }) 15 | 16 | t.create('legacy redirect') 17 | .get('/search/badges/shields/async%20handle.svg') 18 | .expectRedirect( 19 | '/github/search.svg?query=async%20handle%20repo%3Abadges%2Fshields', 20 | ) 21 | -------------------------------------------------------------------------------- /services/github/github-sponsors.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { ServiceTester } from '../tester.js' 3 | 4 | export const t = new ServiceTester({ 5 | id: 'GithubSponsors', 6 | title: 'Github Sponsors', 7 | pathPrefix: '/github', 8 | }) 9 | 10 | t.create('Sponsors').get('/sponsors/Homebrew.json').expectBadge({ 11 | label: 'sponsors', 12 | message: isMetric, 13 | color: 'blue', 14 | }) 15 | 16 | t.create('Sponsors (user not found)') 17 | .get('/sponsors/badges-non-exist.json') 18 | .expectBadge({ 19 | label: 'sponsors', 20 | message: 'user/org not found', 21 | }) 22 | -------------------------------------------------------------------------------- /services/github/github-top-language.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isDecimalPercentage } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('top language').get('/badges/shields.json').expectBadge({ 7 | label: 'javascript', 8 | message: isDecimalPercentage, 9 | }) 10 | 11 | t.create('top language (empty repo)') 12 | .get('/pyvesb/emptyrepo.json') 13 | .expectBadge({ label: 'language', message: 'none' }) 14 | 15 | t.create('top language (repo not found)') 16 | .get('/not-a-real-user/not-a-real-repo.json') 17 | .expectBadge({ label: 'language', message: 'repo not found' }) 18 | -------------------------------------------------------------------------------- /services/github/github-watchers.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Watchers') 6 | .get('/badges/shields.json') 7 | .expectBadge({ 8 | label: 'watchers', 9 | message: Joi.number().integer().positive(), 10 | color: 'blue', 11 | link: [ 12 | 'https://github.com/badges/shields', 13 | 'https://github.com/badges/shields/watchers', 14 | ], 15 | }) 16 | 17 | t.create('Watchers (repo not found)').get('/badges/helmets.json').expectBadge({ 18 | label: 'watchers', 19 | message: 'repo not found', 20 | }) 21 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-contributors-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | // https://github.com/badges/shields/issues/8138 4 | export default redirector({ 5 | category: 'build', 6 | route: { 7 | base: 'gitlab/v/contributor', 8 | pattern: ':project+', 9 | }, 10 | transformPath: ({ project }) => `/gitlab/contributors/${project}`, 11 | dateAdded: new Date('2022-06-29'), 12 | }) 13 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-contributors-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('Contributors redirect') 5 | .get('/gitlab-org/gitlab', { 6 | followRedirect: false, 7 | }) 8 | .expectStatus(301) 9 | .expectHeader('Location', '/gitlab/contributors/gitlab-org/gitlab.svg') 10 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-coverage-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | category: 'coverage', 5 | route: { 6 | base: 'gitlab/coverage', 7 | pattern: ':user/:repo/:branch', 8 | }, 9 | transformPath: ({ user, repo }) => 10 | `/gitlab/pipeline-coverage/${user}/${repo}`, 11 | transformQueryParams: ({ branch }) => ({ branch }), 12 | dateAdded: new Date('2022-09-25'), 13 | }) 14 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-helper.js: -------------------------------------------------------------------------------- 1 | const description = ` 2 | You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g. 3 | [gitlab-org/gitlab](https://gitlab.com/gitlab-org/gitlab) ). 4 | Note that only internet-accessible GitLab instances are supported, for example 5 | [https://jihulab.com](https://jihulab.com), 6 | [https://gitlab.gnome.org](https://gitlab.gnome.org), or 7 | [https://gitlab.com](https://gitlab.com). 8 | ` 9 | 10 | function httpErrorsFor(notFoundMessage = 'project not found') { 11 | return { 12 | 401: notFoundMessage, 13 | 404: notFoundMessage, 14 | } 15 | } 16 | 17 | export { description, httpErrorsFor } 18 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-license-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | // https://github.com/badges/shields/issues/8138 4 | export default redirector({ 5 | category: 'build', 6 | route: { 7 | base: 'gitlab/v/license', 8 | pattern: ':project+', 9 | }, 10 | transformPath: ({ project }) => `/gitlab/license/${project}`, 11 | dateAdded: new Date('2022-06-29'), 12 | }) 13 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-license-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('License redirect') 5 | .get('/gitlab-org/gitlab', { 6 | followRedirect: false, 7 | }) 8 | .expectStatus(301) 9 | .expectHeader('Location', '/gitlab/license/gitlab-org/gitlab.svg') 10 | -------------------------------------------------------------------------------- /services/gitlab/gitlab-top-language.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isDecimalPercentage } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Valid Repository').get('/wireshark/wireshark.json').expectBadge({ 7 | label: 'c', 8 | message: isDecimalPercentage, 9 | }) 10 | 11 | t.create('Valid Blank Repo') 12 | .get('/KoruptTinker/gitlab-blank-repo.json') 13 | .expectBadge({ 14 | label: 'language', 15 | message: 'no languages found', 16 | }) 17 | 18 | t.create('Invalid Repository') 19 | .get('/wireshark/invalidexample.json') 20 | .expectBadge({ 21 | label: 'language', 22 | message: 'project not found', 23 | }) 24 | -------------------------------------------------------------------------------- /services/gitter/gitter.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('on gitter').get('/nwjs/nw.js.json').expectBadge({ 5 | label: 'chat', 6 | message: 'on gitter', 7 | }) 8 | -------------------------------------------------------------------------------- /services/greasyfork/greasyfork-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('License (valid)').get('/l/407466.json').expectBadge({ 5 | label: 'license', 6 | message: 'MIT', 7 | }) 8 | 9 | t.create('License (not found)') 10 | .get('/l/000000.json') 11 | .expectBadge({ label: 'license', message: 'not found' }) 12 | -------------------------------------------------------------------------------- /services/greasyfork/greasyfork-rating.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Rating Count') 6 | .get('/rating-count/407466.json') 7 | .expectBadge({ 8 | label: 'rating', 9 | message: Joi.string().regex(/^\d+ good, \d+ ok, \d+ bad$/), 10 | }) 11 | 12 | t.create('Rating Count (not found)') 13 | .get('/rating-count/000000.json') 14 | .expectBadge({ label: 'rating', message: 'not found' }) 15 | -------------------------------------------------------------------------------- /services/greasyfork/greasyfork-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Version').get('/v/407466.json').expectBadge({ 6 | label: 'greasy fork', 7 | message: isVPlusDottedVersionAtLeastOne, 8 | }) 9 | 10 | t.create('Version (not found)') 11 | .get('/v/000000.json') 12 | .expectBadge({ label: 'greasy fork', message: 'not found' }) 13 | -------------------------------------------------------------------------------- /services/hackage/hackage-deps.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const HackageDeps = deprecatedService({ 4 | category: 'dependencies', 5 | route: { 6 | base: 'hackage-deps/v', 7 | pattern: ':packageName', 8 | }, 9 | label: 'hackagedeps', 10 | dateAdded: new Date('2024-10-18'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/hackage/hackage-deps.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | export const t = new ServiceTester({ 3 | id: 'hackagedeps', 4 | title: 'Hackage Dependencies', 5 | pathPrefix: '/hackage-deps/v', 6 | }) 7 | 8 | t.create('hackage deps (deprecated)') 9 | .get('/package.json') 10 | .expectBadge({ label: 'hackagedeps', message: 'no longer available' }) 11 | -------------------------------------------------------------------------------- /services/hangar/hangar-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Essentials').get('/Essentials.json').expectBadge({ 6 | label: 'downloads', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Resource').get('/1.json').expectBadge({ 11 | label: 'downloads', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/hangar/hangar-stars.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Essentials').get('/Essentials.json').expectBadge({ 6 | label: 'stars', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Resource').get('/1.json').expectBadge({ 11 | label: 'stars', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/hangar/hangar-views.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Essentials').get('/Essentials.json').expectBadge({ 6 | label: 'views', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Resource').get('/1.json').expectBadge({ 11 | label: 'views', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/hangar/hangar-watchers.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Essentials').get('/Essentials.json').expectBadge({ 6 | label: 'watchers', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Resource').get('/1.json').expectBadge({ 11 | label: 'watchers', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/index.js: -------------------------------------------------------------------------------- 1 | export * from '../core/base-service/index.js' 2 | -------------------------------------------------------------------------------- /services/itunes/itunes.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('iTunes version (valid)').get('/324684580.json').expectBadge({ 6 | label: 'itunes app store', 7 | message: isVPlusDottedVersionAtLeastOne, 8 | }) 9 | 10 | t.create('iTunes version (not found)') 11 | .get('/9.json') 12 | .expectBadge({ label: 'itunes app store', message: 'not found' }) 13 | 14 | t.create('iTunes version (invalid)') 15 | .get('/x.json') 16 | .expectBadge({ label: 'itunes app store', message: 'invalid' }) 17 | -------------------------------------------------------------------------------- /services/jenkins/jenkins-base.js: -------------------------------------------------------------------------------- 1 | import { BaseJsonService } from '../index.js' 2 | 3 | export default class JenkinsBase extends BaseJsonService { 4 | static auth = { 5 | userKey: 'jenkins_user', 6 | passKey: 'jenkins_pass', 7 | serviceKey: 'jenkins', 8 | } 9 | 10 | async fetch({ 11 | url, 12 | schema, 13 | searchParams, 14 | httpErrors = { 404: 'instance or job not found' }, 15 | }) { 16 | return this._requestJson( 17 | this.authHelper.withBasicAuth({ 18 | url, 19 | options: { searchParams }, 20 | schema, 21 | httpErrors, 22 | }), 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /services/jira/jira-common.js: -------------------------------------------------------------------------------- 1 | const authConfig = { 2 | userKey: 'jira_user', 3 | passKey: 'jira_pass', 4 | serviceKey: 'jira', 5 | } 6 | 7 | export { authConfig } 8 | -------------------------------------------------------------------------------- /services/jira/jira-issue-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'issue-tracking', 6 | route: { 7 | base: 'jira/issue', 8 | pattern: ':protocol(http|https)/:hostAndPath(.+)/:issueKey', 9 | }, 10 | transformPath: ({ issueKey }) => `/jira/issue/${issueKey}`, 11 | transformQueryParams: ({ protocol, hostAndPath }) => ({ 12 | baseUrl: `${protocol}://${hostAndPath}`, 13 | }), 14 | dateAdded: new Date('2019-09-14'), 15 | }), 16 | ] 17 | -------------------------------------------------------------------------------- /services/jira/jira-issue-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'JiraIssueRedirect', 5 | title: 'JiraIssueRedirect', 6 | pathPrefix: '/jira/issue', 7 | }) 8 | 9 | t.create('jira issue') 10 | .get('/https/issues.apache.org/jira/kafka-2896.svg') 11 | .expectRedirect( 12 | `/jira/issue/kafka-2896.svg?baseUrl=${encodeURIComponent( 13 | 'https://issues.apache.org/jira', 14 | )}`, 15 | ) 16 | -------------------------------------------------------------------------------- /services/jira/jira-sprint-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'issue-tracking', 6 | route: { 7 | base: 'jira/sprint', 8 | pattern: ':protocol(http|https)/:hostAndPath(.+)/:sprintId', 9 | }, 10 | transformPath: ({ sprintId }) => `/jira/sprint/${sprintId}`, 11 | transformQueryParams: ({ protocol, hostAndPath }) => ({ 12 | baseUrl: `${protocol}://${hostAndPath}`, 13 | }), 14 | dateAdded: new Date('2019-09-14'), 15 | }), 16 | ] 17 | -------------------------------------------------------------------------------- /services/jira/jira-sprint-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'JiraSprintRedirect', 5 | title: 'JiraSprintRedirect', 6 | pathPrefix: '/jira/sprint', 7 | }) 8 | 9 | t.create('jira sprint') 10 | .get('/https/jira.spring.io/94.svg') 11 | .expectRedirect( 12 | `/jira/sprint/94.svg?baseUrl=${encodeURIComponent( 13 | 'https://jira.spring.io', 14 | )}`, 15 | ) 16 | -------------------------------------------------------------------------------- /services/jira/jira-test-helpers.js: -------------------------------------------------------------------------------- 1 | const sprintId = 8 2 | const sprintQueryString = { 3 | jql: `sprint=${sprintId} AND type IN (Bug,Improvement,Story,"Technical task")`, 4 | fields: 'resolution', 5 | maxResults: 500, 6 | } 7 | 8 | const user = 'admin' 9 | const pass = 'password' 10 | const host = 'myprivatejira.test' 11 | const config = { 12 | public: { 13 | services: { 14 | jira: { 15 | authorizedOrigins: [`https://${host}`], 16 | }, 17 | }, 18 | }, 19 | private: { jira_user: user, jira_pass: pass }, 20 | } 21 | 22 | export { sprintId, sprintQueryString, user, pass, host, config } 23 | -------------------------------------------------------------------------------- /services/jitpack/jitpack-version-redirector.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'JitpackVersionRedirect', 5 | title: 'JitpackVersionRedirect', 6 | pathPrefix: '/jitpack/v', 7 | }) 8 | 9 | t.create('jitpack version redirect (no vcs)') 10 | .get('/jitpack/maven-simple.svg') 11 | .expectRedirect('/jitpack/version/com.github.jitpack/maven-simple.svg') 12 | 13 | t.create('jitpack version redirect (github)') 14 | .get('/github/jitpack/maven-simple.svg') 15 | .expectRedirect('/jitpack/version/com.github.jitpack/maven-simple.svg') 16 | -------------------------------------------------------------------------------- /services/jitpack/jitpack-version.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | // Github allows versions with chars, etc. 6 | const isAnyV = Joi.string().regex(/^v.+$/) 7 | 8 | t.create('version (groupId)') 9 | .get('/com.github.erayerdin/kappdirs.json') 10 | .expectBadge({ label: 'jitpack', message: isAnyV }) 11 | 12 | t.create('unknown package') 13 | .get('/com.github.some-bogus-user/project.json') 14 | .expectBadge({ label: 'jitpack', message: 'project not found or private' }) 15 | -------------------------------------------------------------------------------- /services/jsdelivr/jsdelivr-base.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { renderDownloadsBadge } from '../downloads.js' 3 | import { BaseJsonService } from '../index.js' 4 | 5 | const schema = Joi.object({ 6 | total: Joi.number().required(), 7 | }).required() 8 | 9 | const periodMap = { 10 | hd: 'day', 11 | hw: 'week', 12 | hm: 'month', 13 | hy: 'year', 14 | } 15 | 16 | class BaseJsDelivrService extends BaseJsonService { 17 | static category = 'downloads' 18 | 19 | static defaultBadgeData = { 20 | label: 'jsdelivr', 21 | } 22 | 23 | static render({ period, hits: downloads }) { 24 | return renderDownloadsBadge({ downloads, interval: periodMap[period] }) 25 | } 26 | } 27 | 28 | export { schema, periodMap, BaseJsDelivrService } 29 | -------------------------------------------------------------------------------- /services/jsr/jsr-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('gets the version of @luca/flag') 6 | .get('/@luca/flag.json') 7 | .expectBadge({ label: 'jsr', message: isSemver }) 8 | 9 | t.create('gets the version of @std/assert') 10 | .get('/@std/assert.json') 11 | .expectBadge({ label: 'jsr', message: isSemver }) 12 | 13 | t.create('returns an error when getting a non-existent') 14 | .get('/@std/this-is-a-non-existent-package-name.json') 15 | .expectBadge({ label: 'jsr', message: 'package not found' }) 16 | -------------------------------------------------------------------------------- /services/keybase/keybase-pgp.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('existing key fingerprint') 6 | .get('/skyplabs.json') 7 | .expectBadge({ 8 | label: 'pgp', 9 | message: Joi.string().hex().length(16), 10 | }) 11 | 12 | t.create('unknown username').get('/skyplabsssssss.json').expectBadge({ 13 | label: 'pgp', 14 | message: 'profile not found', 15 | }) 16 | 17 | t.create('invalid username').get('/s.json').expectBadge({ 18 | label: 'pgp', 19 | message: 'invalid username', 20 | }) 21 | 22 | t.create('missing key fingerprint').get('/skyp.json').expectBadge({ 23 | label: 'pgp', 24 | message: 'no key fingerprint found', 25 | }) 26 | -------------------------------------------------------------------------------- /services/keybase/keybase-xlm.tester.js: -------------------------------------------------------------------------------- 1 | import { withRegex } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('existing stellar address') 6 | .get('/skyplabs.json') 7 | .expectBadge({ 8 | label: 'xlm', 9 | message: withRegex(/^(?!not found$)/), 10 | }) 11 | 12 | t.create('unknown username').get('/skyplabsssssss.json').expectBadge({ 13 | label: 'xlm', 14 | message: 'profile not found', 15 | }) 16 | 17 | t.create('invalid username').get('/s.json').expectBadge({ 18 | label: 'xlm', 19 | message: 'invalid username', 20 | }) 21 | 22 | t.create('missing stellar address').get('/test.json').expectBadge({ 23 | label: 'xlm', 24 | message: 'no stellar address found', 25 | }) 26 | -------------------------------------------------------------------------------- /services/keybase/keybase-zec.tester.js: -------------------------------------------------------------------------------- 1 | import { withRegex } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('existing zcash address') 6 | .get('/skyplabs.json') 7 | .expectBadge({ 8 | label: 'zec', 9 | message: withRegex(/^(?!not found$)/), 10 | }) 11 | 12 | t.create('unknown username').get('/skyplabsssssss.json').expectBadge({ 13 | label: 'zec', 14 | message: 'profile not found', 15 | }) 16 | 17 | t.create('invalid username').get('/s.json').expectBadge({ 18 | label: 'zec', 19 | message: 'invalid username', 20 | }) 21 | 22 | t.create('missing zcash address').get('/test.json').expectBadge({ 23 | label: 'zec', 24 | message: 'no zcash addresses found', 25 | }) 26 | -------------------------------------------------------------------------------- /services/liberapay/liberapay-patrons.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Patrons (valid)').get('/Liberapay.json').expectBadge({ 6 | label: 'patrons', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Patrons (not found)') 11 | .get('/does-not-exist.json') 12 | .expectBadge({ label: 'liberapay', message: 'not found' }) 13 | -------------------------------------------------------------------------------- /services/librariesio/librariesio-constellation.js: -------------------------------------------------------------------------------- 1 | import LibrariesIoApiProvider from './librariesio-api-provider.js' 2 | 3 | // Convenience class with all the stuff related to the Libraries.io API and its 4 | // authorization tokens, to simplify server initialization. 5 | export default class LibrariesIoConstellation { 6 | constructor({ private: { librariesio_tokens: tokens } }) { 7 | this.apiProvider = new LibrariesIoApiProvider({ 8 | baseUrl: 'https://libraries.io/api', 9 | tokens, 10 | defaultRateLimit: 60, 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /services/librariesio/librariesio-dependents.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('dependent count').timeout(10000).get('/npm/got.json').expectBadge({ 6 | label: 'dependents', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('dependent count (scoped npm package)') 11 | .timeout(10000) 12 | .get('/npm/@babel/core.json') 13 | .expectBadge({ 14 | label: 'dependents', 15 | message: isMetric, 16 | }) 17 | 18 | t.create('dependent count (nonexistent package)') 19 | .timeout(10000) 20 | .get('/npm/foobar-is-not-package.json') 21 | .expectBadge({ 22 | label: 'dependents', 23 | message: 'package not found', 24 | }) 25 | -------------------------------------------------------------------------------- /services/librariesio/librariesio-sourcerank.tester.js: -------------------------------------------------------------------------------- 1 | import { anyInteger } from '../validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('sourcerank').timeout(10000).get('/npm/got.json').expectBadge({ 6 | label: 'sourcerank', 7 | message: anyInteger, 8 | }) 9 | 10 | t.create('sourcerank (scoped npm package)') 11 | .timeout(10000) 12 | .get('/npm/@babel/core.json') 13 | .expectBadge({ 14 | label: 'sourcerank', 15 | message: anyInteger, 16 | }) 17 | 18 | t.create('sourcerank (not a package)') 19 | .timeout(10000) 20 | .get('/npm/foobar-is-not-package.json') 21 | .expectBadge({ 22 | label: 'sourcerank', 23 | message: 'package not found', 24 | }) 25 | -------------------------------------------------------------------------------- /services/maven-central/maven-central-base.js: -------------------------------------------------------------------------------- 1 | import { BaseXmlService } from '../index.js' 2 | 3 | export default class MavenCentralBase extends BaseXmlService { 4 | async fetch({ groupId, artifactId, schema }) { 5 | const group = encodeURIComponent(groupId).replace(/\./g, '/') 6 | const artifact = encodeURIComponent(artifactId) 7 | return this._requestXml({ 8 | schema, 9 | url: `https://repo1.maven.org/maven2/${group}/${artifact}/maven-metadata.xml`, 10 | httpErrors: { 404: 'artifact not found' }, 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /services/maven-central/maven-central-last-update.tester.js: -------------------------------------------------------------------------------- 1 | import { isFormattedDate } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('last update date').get('/com.google.guava/guava.json').expectBadge({ 6 | label: 'last updated', 7 | message: isFormattedDate, 8 | }) 9 | 10 | t.create('last update when artifact not found') 11 | .get('/com.fail.test/this-does-not-exist.json') 12 | .expectBadge({ 13 | label: 'last updated', 14 | message: 'artifact not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/maven-metadata/maven-metadata-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | category: 'version', 5 | route: { 6 | base: 'maven-metadata/v', 7 | pattern: ':protocol(http|https)/:hostAndPath+', 8 | }, 9 | transformPath: () => '/maven-metadata/v', 10 | transformQueryParams: ({ protocol, hostAndPath }) => ({ 11 | metadataUrl: `${protocol}://${hostAndPath}`, 12 | }), 13 | dateAdded: new Date('2019-09-16'), 14 | }) 15 | -------------------------------------------------------------------------------- /services/maven-metadata/maven-metadata.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // the file contains common constants for badges uses maven-metadata 4 | 5 | export const description = ` 6 | versionPrefix and versionSuffix allow narrowing down 7 | the range of versions the badge will take into account, 8 | but they are completely optional. 9 | ` 10 | -------------------------------------------------------------------------------- /services/modrinth/modrinth-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Downloads') 7 | .get('/AANobbMI.json') 8 | .expectBadge({ label: 'downloads', message: isMetric }) 9 | 10 | t.create('Downloads (not found)') 11 | .get('/not-existing.json') 12 | .expectBadge({ label: 'downloads', message: 'not found', color: 'red' }) 13 | -------------------------------------------------------------------------------- /services/modrinth/modrinth-followers.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Followers') 7 | .get('/AANobbMI.json') 8 | .expectBadge({ label: 'followers', message: isMetric }) 9 | 10 | t.create('Followers (not found)') 11 | .get('/not-existing.json') 12 | .expectBadge({ label: 'followers', message: 'not found', color: 'red' }) 13 | -------------------------------------------------------------------------------- /services/modrinth/modrinth-game-versions.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | import { withRegex } from '../test-validators.js' 4 | 5 | export const t = await createServiceTester() 6 | 7 | t.create('Game Versions') 8 | .get('/AANobbMI.json') 9 | .expectBadge({ 10 | label: 'game versions', 11 | message: Joi.alternatives().try( 12 | withRegex(/^(\d+\.\d+(\.\d+)?( \| )?)+$/), 13 | withRegex( 14 | /^\d+\.\d+(\.\d+)? \| \d+\.\d+(\.\d+)? \| \.\.\. \| \d+\.\d+(\.\d+)? \| \d+\.\d+(\.\d+)?$/, 15 | ), 16 | ), 17 | }) 18 | 19 | t.create('Game Versions (not found)') 20 | .get('/not-existing.json') 21 | .expectBadge({ label: 'game versions', message: 'not found', color: 'red' }) 22 | -------------------------------------------------------------------------------- /services/modrinth/modrinth-version.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { withRegex } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Version') 7 | .get('/AANobbMI.json') 8 | .expectBadge({ label: 'version', message: withRegex(/.*\d+\.\d+(\.d+)?.*/) }) 9 | 10 | t.create('Version (not found)') 11 | .get('/not-existing.json') 12 | .expectBadge({ label: 'version', message: 'not found', color: 'red' }) 13 | -------------------------------------------------------------------------------- /services/mozilla-observatory/mozilla-observatory.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | const isMessage = Joi.alternatives() 6 | .try(Joi.string().regex(/^[ABCDEF][+-]? \([0-9]{1,3}\/100\)$/)) 7 | .required() 8 | 9 | t.create('valid').get('/grade-score/observatory.mozilla.org.json').expectBadge({ 10 | label: 'observatory', 11 | message: isMessage, 12 | }) 13 | 14 | t.create('invalid') 15 | .get('/grade-score/invalidsubdomain.shields.io.json') 16 | .expectBadge({ 17 | label: 'observatory', 18 | message: 'invalid', 19 | }) 20 | -------------------------------------------------------------------------------- /services/netlify/netlify.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import Netlify from './netlify.service.js' 3 | 4 | const building = { message: 'building', label: undefined, color: 'yellow' } 5 | const notBuilt = { message: 'not built', label: undefined, color: undefined } 6 | 7 | describe('Netlify', function () { 8 | test(Netlify.render, () => { 9 | given({ status: 'building' }).expect(building) 10 | given({ status: 'stopped' }).expect(notBuilt) 11 | given({ status: 'infrastructure_failure' }).expect({ 12 | message: 'failing', 13 | color: 'red', 14 | label: undefined, 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /services/netlify/netlify.tester.js: -------------------------------------------------------------------------------- 1 | import { isBuildStatus } from '../build-status.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('netlify (valid, no branch)') 6 | .get('/e6d5a4e0-dee1-4261-833e-2f47f509c68f.json') 7 | .expectBadge({ 8 | label: 'netlify', 9 | message: isBuildStatus, 10 | }) 11 | 12 | t.create('netlify (repo not found)') 13 | .get('/not-a-repo.json') 14 | .expectBadge({ label: 'netlify', message: 'not found' }) 15 | -------------------------------------------------------------------------------- /services/nexus/nexus-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'version', 6 | route: { 7 | base: 'nexus', 8 | pattern: 9 | ':repo(r|s|[^/]+)/:scheme(http|https)/:hostAndPath+/:groupId/:artifactId([^/:]+?):queryOpt(:.+?)?', 10 | }, 11 | transformPath: ({ repo, groupId, artifactId }) => 12 | `/nexus/${repo}/${groupId}/${artifactId}`, 13 | transformQueryParams: ({ scheme, hostAndPath, queryOpt }) => ({ 14 | server: `${scheme}://${hostAndPath}`, 15 | queryOpt, 16 | }), 17 | dateAdded: new Date('2019-07-26'), 18 | }), 19 | ] 20 | -------------------------------------------------------------------------------- /services/nexus/nexus-version.js: -------------------------------------------------------------------------------- 1 | function isSnapshotVersion(version) { 2 | const pattern = /(\d+\.)*[0-9a-f]-SNAPSHOT/ 3 | return version && version.match(pattern) 4 | } 5 | 6 | export { isSnapshotVersion } 7 | -------------------------------------------------------------------------------- /services/node/testUtils/packageJsonTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">= 0.4.0" 4 | }, 5 | "maintainers": [ 6 | { 7 | "name": "jaredhanson", 8 | "email": "jaredhanson@gmail.com" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /services/node/testUtils/packageJsonVersionsTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist-tags": { 3 | "latest": "0.0.91" 4 | }, 5 | "versions": { 6 | "0.0.90": { 7 | "engines": { 8 | "node": ">= 0.4.0" 9 | }, 10 | "maintainers": [ 11 | { 12 | "name": "jaredhanson", 13 | "email": "jaredhanson@gmail.com" 14 | } 15 | ] 16 | }, 17 | "0.0.91": { 18 | "engines": { 19 | "node": ">= 0.4.0" 20 | }, 21 | "maintainers": [ 22 | { 23 | "name": "jaredhanson", 24 | "email": "jaredhanson@gmail.com" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/nostr-band/nostr-band-followers.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('fetch: valid npub') 6 | .get('/npub18c556t7n8xa3df2q82rwxejfglw5przds7sqvefylzjh8tjne28qld0we7.json') 7 | .expectBadge({ 8 | label: 'followers', 9 | message: isMetric, 10 | }) 11 | 12 | t.create('fetch: invalid npub').get('/invalidnpub.json').expectBadge({ 13 | label: 'followers', 14 | message: 'invalid pubkey', 15 | }) 16 | -------------------------------------------------------------------------------- /services/npm-stat/npm-stat-downloads.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import NpmStatDownloads from './npm-stat-downloads.service.js' 3 | 4 | describe('NpmStatDownloads helpers', function () { 5 | test(NpmStatDownloads.getTotalDownloads, () => { 6 | given({ 7 | 'hexo-theme-candelas': { 8 | '2022-12-01': 1, 9 | '2022-12-02': 2, 10 | '2022-12-03': 3, 11 | }, 12 | '@dukeluo/fanjs': { 13 | '2022-12-01': 10, 14 | '2022-12-02': 20, 15 | '2022-12-03': 30, 16 | }, 17 | 'eslint-plugin-check-file': { 18 | '2022-12-01': 100, 19 | '2022-12-02': 200, 20 | '2022-12-03': 300, 21 | }, 22 | }).expect(666) 23 | given({}).expect(0) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /services/npm/npm-downloads-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | category: 'downloads', 5 | route: { 6 | base: 'npm/dt', 7 | pattern: ':packageName+', 8 | }, 9 | transformPath: ({ packageName }) => `/npm/d18m/${packageName}`, 10 | dateAdded: new Date('2024-03-19'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/npm/npm-downloads.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import NpmDownloads from './npm-downloads.service.js' 3 | 4 | describe('NpmDownloads', function () { 5 | test(NpmDownloads._intervalMap.d18m.transform, () => { 6 | given({ 7 | downloads: [ 8 | { downloads: 2, day: '2018-01-01' }, 9 | { downloads: 3, day: '2018-01-02' }, 10 | ], 11 | }).expect(5) 12 | }) 13 | 14 | test(NpmDownloads.render, () => { 15 | given({ 16 | interval: 'd18m', 17 | downloadCount: 0, 18 | }).expect({ 19 | color: 'red', 20 | message: '0', 21 | label: undefined, 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /services/obs/obs.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import ObsService from './obs.service.js' 3 | 4 | describe('ObsService', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth( 8 | ObsService, 9 | 'BasicAuth', 10 | ` 11 | `, 12 | { contentType: 'application/xml' }, 13 | ) 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /services/offset-earth/offset-earth-carbon-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | // https://github.com/badges/shields/issues/5433 5 | redirector({ 6 | name: 'OffsetEarthCarbonRedirect', 7 | category: 'other', 8 | route: { 9 | base: 'offset-earth/carbon', 10 | pattern: ':username', 11 | }, 12 | transformPath: ({ username }) => `/ecologi/carbon/${username}`, 13 | dateAdded: new Date('2020-08-16'), 14 | }), 15 | ] 16 | -------------------------------------------------------------------------------- /services/offset-earth/offset-earth-carbon-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'OffsetEarthCarbonRedirect', 5 | title: 'Offset Earth (Carbon Offset) Redirector', 6 | pathPrefix: '/offset-earth', 7 | }) 8 | 9 | t.create('Offset Earth carbon alias') 10 | .get('/carbon/ecologi.svg') 11 | .expectRedirect('/ecologi/carbon/ecologi.svg') 12 | -------------------------------------------------------------------------------- /services/offset-earth/offset-earth-trees-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | // https://github.com/badges/shields/issues/5433 5 | redirector({ 6 | name: 'OffsetEarthTreesRedirect', 7 | category: 'other', 8 | route: { 9 | base: 'offset-earth/trees', 10 | pattern: ':username', 11 | }, 12 | transformPath: ({ username }) => `/ecologi/trees/${username}`, 13 | dateAdded: new Date('2020-08-16'), 14 | }), 15 | ] 16 | -------------------------------------------------------------------------------- /services/offset-earth/offset-earth-trees-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'OffsetEarthTreesRedirect', 5 | title: 'Offset Earth (Trees) Redirector', 6 | pathPrefix: '/offset-earth', 7 | }) 8 | 9 | t.create('Offset Earth trees alias') 10 | .get('/trees/ecologi.svg') 11 | .expectRedirect('/ecologi/trees/ecologi.svg') 12 | -------------------------------------------------------------------------------- /services/open-vsx/open-vsx-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { withRegex, isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | const isVersionLabel = withRegex(/^downloads@(\d+\.\d+\.\d+)(\.\d+)?$/) 6 | 7 | t.create('downloads invalid extension') 8 | .get('/dt/badges/shields.json') 9 | .expectBadge({ 10 | label: 'downloads', 11 | message: 'extension not found', 12 | }) 13 | 14 | t.create('downloads').get('/dt/redhat/java.json').expectBadge({ 15 | label: 'downloads', 16 | message: isMetric, 17 | }) 18 | 19 | t.create('downloads version').get('/dt/redhat/java/latest.json').expectBadge({ 20 | label: isVersionLabel, 21 | message: isMetric, 22 | }) 23 | -------------------------------------------------------------------------------- /services/open-vsx/open-vsx-rating.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import OpenVSXRating from './open-vsx-rating.service.js' 3 | 4 | describe('OpenVSXRating', function () { 5 | test(OpenVSXRating.render, () => { 6 | given({ ratingCount: 0 }).expect({ 7 | message: 'unrated', 8 | color: 'lightgrey', 9 | }) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /services/open-vsx/open-vsx-release-date.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isFormattedDate } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('release date invalid extension') 6 | .get('/release-date/badges/shields.json') 7 | .expectBadge({ 8 | label: 'release date', 9 | message: 'extension not found', 10 | }) 11 | 12 | t.create('release date').get('/release-date/redhat/java.json').expectBadge({ 13 | label: 'release date', 14 | message: isFormattedDate, 15 | }) 16 | -------------------------------------------------------------------------------- /services/open-vsx/open-vsx-version.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { withRegex } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | const isVersion = withRegex(/^v(\d+\.\d+\.\d+)(\.\d+)?$/) 6 | 7 | t.create('version invalid extension') 8 | .get('/v/badges/shields.json') 9 | .expectBadge({ 10 | label: 'open vsx', 11 | message: 'extension not found', 12 | }) 13 | 14 | t.create('version').get('/v/redhat/java.json').expectBadge({ 15 | label: 'open vsx', 16 | message: isVersion, 17 | }) 18 | -------------------------------------------------------------------------------- /services/opencollective/opencollective-all.tester.js: -------------------------------------------------------------------------------- 1 | import { nonNegativeInteger } from '../validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('gets amount of backers and sponsors') 6 | .get('/shields.json') 7 | .expectBadge({ 8 | label: 'backers and sponsors', 9 | message: nonNegativeInteger, 10 | }) 11 | 12 | t.create('handles not found correctly') 13 | .get('/nonexistent-collective.json') 14 | .expectBadge({ 15 | label: 'backers and sponsors', 16 | message: 'No collective found with slug nonexistent-collective', 17 | color: 'lightgrey', 18 | }) 19 | -------------------------------------------------------------------------------- /services/opencollective/opencollective-backers.tester.js: -------------------------------------------------------------------------------- 1 | import { nonNegativeInteger } from '../validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('gets amount of backers').get('/shields.json').expectBadge({ 6 | label: 'backers', 7 | message: nonNegativeInteger, 8 | }) 9 | 10 | t.create('handles not found correctly') 11 | .get('/nonexistent-collective.json') 12 | .expectBadge({ 13 | label: 'backers', 14 | message: 'No collective found with slug nonexistent-collective', 15 | color: 'lightgrey', 16 | }) 17 | -------------------------------------------------------------------------------- /services/opencollective/opencollective-sponsors.tester.js: -------------------------------------------------------------------------------- 1 | import { nonNegativeInteger } from '../validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('gets amount of sponsors').get('/shields.json').expectBadge({ 6 | label: 'sponsors', 7 | message: nonNegativeInteger, 8 | color: 'brightgreen', 9 | }) 10 | 11 | t.create('handles not found correctly') 12 | .get('/nonexistent-collective.json') 13 | .expectBadge({ 14 | label: 'sponsors', 15 | message: 'No collective found with slug nonexistent-collective', 16 | color: 'lightgrey', 17 | }) 18 | -------------------------------------------------------------------------------- /services/opm/opm-version.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | const isValidVersion = Joi.string() 6 | .regex(/^v[\d+.]+$/) 7 | .required() 8 | 9 | t.create('version').get('/openresty/lua-resty-lrucache.json').expectBadge({ 10 | label: 'opm', 11 | message: isValidVersion, 12 | }) 13 | 14 | t.create('unknown module') 15 | .get('/openresty/does-not-exist.json') 16 | .expectBadge({ label: 'opm', message: 'module not found' }) 17 | 18 | t.create('unknown user') 19 | .get('/nil/does-not-exist.json') 20 | .expectBadge({ label: 'opm', message: 'module not found' }) 21 | -------------------------------------------------------------------------------- /services/ore/ore-category.spec.js: -------------------------------------------------------------------------------- 1 | import { test, forCases, given } from 'sazerac' 2 | import OreCategory from './ore-category.service.js' 3 | 4 | describe('OreCategory', function () { 5 | test(OreCategory.prototype.transform, () => { 6 | forCases([ 7 | given({ data: { category: 'admin_tools' } }), 8 | given({ data: { category: 'admin tools' } }), 9 | ]).expect({ 10 | category: 'admin tools', 11 | }) 12 | }) 13 | 14 | test(OreCategory.render, () => { 15 | given({ category: 'admin tools' }).expect({ 16 | message: 'admin tools', 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /services/ore/ore-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Nucleus (pluginId nucleus)').get('/nucleus.json').expectBadge({ 6 | label: 'downloads', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Plugin (pluginId 1)').get('/1.json').expectBadge({ 11 | label: 'downloads', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/ore/ore-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('Nucleus (pluginId nucleus)').get('/nucleus.json').expectBadge({ 5 | label: 'license', 6 | message: 'MIT licence', 7 | }) 8 | 9 | t.create('Invalid Plugin (pluginId 1)').get('/1.json').expectBadge({ 10 | label: 'license', 11 | message: 'not found', 12 | }) 13 | -------------------------------------------------------------------------------- /services/ore/ore-stars.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Nucleus (pluginId nucleus)').get('/nucleus.json').expectBadge({ 6 | label: 'stars', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Plugin (pluginId 1)').get('/1.json').expectBadge({ 11 | label: 'stars', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/ore/ore-version.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import OreVersion from './ore-version.service.js' 3 | 4 | describe('OreVersion', function () { 5 | test(OreVersion.prototype.transform, () => { 6 | given({ 7 | data: { promoted_versions: [{ version: '2.3' }, { version: '4.5' }] }, 8 | }).expect({ version: '2.3' }) 9 | }) 10 | 11 | test(OreVersion.prototype.transform, () => { 12 | given({ 13 | data: { promoted_versions: [] }, 14 | }).expect({ version: undefined }) 15 | }) 16 | 17 | test(OreVersion.render, () => { 18 | given({ version: undefined }).expect({ 19 | message: 'none', 20 | color: 'inactive', 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /services/ore/ore-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionNClausesWithOptionalSuffix } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Nucleus (pluginId nucleus)').get('/nucleus.json').expectBadge({ 6 | label: 'version', 7 | message: isVPlusDottedVersionNClausesWithOptionalSuffix, 8 | }) 9 | 10 | t.create('Invalid Plugin (pluginId 1)').get('/1.json').expectBadge({ 11 | label: 'version', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/ossf-scorecard/ossf-scorecard.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('score valid') 6 | .get('/github.com/rohankh532/org-workflow-add.json') 7 | .expectBadge({ 8 | label: 'score', 9 | message: Joi.number().min(0), 10 | color: Joi.string().allow( 11 | 'red', 12 | 'yellow', 13 | 'yellowgreen', 14 | 'green', 15 | 'brightgreen', 16 | ), 17 | }) 18 | 19 | t.create('score ivalid') 20 | .get('/github.com/invalid-user/invalid-repo.json') 21 | .expectBadge({ 22 | label: 'score', 23 | message: 'invalid repo path', 24 | color: 'red', 25 | }) 26 | -------------------------------------------------------------------------------- /services/osslifecycle/osslifecycle-redirector.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | const commonProps = { 4 | category: 'other', 5 | dateAdded: new Date('2024-09-02'), 6 | } 7 | 8 | export default [ 9 | redirector({ 10 | route: { 11 | base: 'osslifecycle', 12 | pattern: ':user/:repo/:branch*', 13 | }, 14 | transformPath: () => '/osslifecycle', 15 | transformQueryParams: ({ user, repo, branch }) => ({ 16 | file_url: `https://raw.githubusercontent.com/${user}/${repo}/${branch || 'HEAD'}/OSSMETADATA`, 17 | }), 18 | ...commonProps, 19 | }), 20 | ] 21 | -------------------------------------------------------------------------------- /services/packagist/packagist-php-version.service.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { redirector } from '../index.js' 3 | import { optionalUrl } from '../validators.js' 4 | 5 | const queryParamSchema = Joi.object({ 6 | server: optionalUrl, 7 | }).required() 8 | 9 | export default redirector({ 10 | category: 'platform-support', 11 | route: { 12 | base: 'packagist/php-v', 13 | pattern: ':user/:repo/:version?', 14 | queryParamSchema, 15 | }, 16 | transformPath: ({ user, repo }) => 17 | `/packagist/dependency-v/${user}/${repo}/php`, 18 | transformQueryParams: ({ version, server }) => ({ version, server }), 19 | overrideTransformedQueryParams: true, 20 | dateAdded: new Date('2022-09-07'), 21 | }) 22 | -------------------------------------------------------------------------------- /services/pepy/pepy-downloads.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import PepyDownloads from './pepy-downloads.service.js' 3 | 4 | describe('PepyDownloads', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth(PepyDownloads, 'ApiKeyHeader', { total_downloads: 42 }) 8 | }) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /services/pepy/pepy-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('downloads (valid)') 6 | .get('/dt/django.json') 7 | .expectBadge({ label: 'downloads', message: isMetric }) 8 | 9 | t.create('downloads (not found)') 10 | .get('/dt/not-a-package.json') 11 | .expectBadge({ label: 'downloads', message: 'not found' }) 12 | -------------------------------------------------------------------------------- /services/pingpong/pingpong-uptime.tester.js: -------------------------------------------------------------------------------- 1 | import { isPercentage } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('PingPong: Uptime (valid)') 6 | .get('/sp_eb705b7c189f42e3b574dc790291c33f.json') 7 | .expectBadge({ label: 'uptime', message: isPercentage }) 8 | 9 | t.create('PingPong: Uptime (valid, incorrect format)') 10 | .get('/eb705b7c189f42e3b574dc790291c33f.json') 11 | .expectBadge({ label: 'uptime', message: 'invalid api key' }) 12 | -------------------------------------------------------------------------------- /services/piwheels/piwheels-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionNClauses } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('version (valid)').get('/flask.json').expectBadge({ 6 | label: 'piwheels', 7 | message: isVPlusDottedVersionNClauses, 8 | }) 9 | 10 | t.create('version (does not exist)').get('/doesn-not-exist.json').expectBadge({ 11 | label: 'piwheels', 12 | message: 'package not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/polymart/polymart-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Polymart Plugin (id 323)').get('/323.json').expectBadge({ 6 | label: 'downloads', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Resource (id 0)').get('/0.json').expectBadge({ 11 | label: 'downloads', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/polymart/polymart-latest-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionNClauses } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Polymart Plugin (id 323)').get('/323.json').expectBadge({ 6 | label: 'polymart', 7 | message: isVPlusDottedVersionNClauses, 8 | }) 9 | 10 | t.create('Invalid Resource (id 0)').get('/0.json').expectBadge({ 11 | label: 'polymart', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/pub/pub-common.js: -------------------------------------------------------------------------------- 1 | const baseDescription = 2 | '

Pub is a package registry for Dart and Flutter.

' 3 | 4 | export { baseDescription } 5 | -------------------------------------------------------------------------------- /services/pub/pub-likes.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('pub likes (valid)').get('/analysis_options.json').expectBadge({ 7 | label: 'likes', 8 | message: isMetric, 9 | color: 'blue', 10 | }) 11 | 12 | t.create('pub likes (not found)').get('/analysisoptions.json').expectBadge({ 13 | label: 'likes', 14 | message: 'not found', 15 | color: 'red', 16 | }) 17 | 18 | t.create('pub likes (invalid)').get('/analysis-options.json').expectBadge({ 19 | label: 'likes', 20 | message: 'invalid', 21 | color: 'lightgrey', 22 | }) 23 | -------------------------------------------------------------------------------- /services/pub/pub-points.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('pub points (valid)') 7 | .get('/analysis_options.json') 8 | .expectBadge({ 9 | label: 'points', 10 | message: Joi.string().regex(/^\d+\/\d+$/), 11 | }) 12 | 13 | t.create('pub points (not found)').get('/analysisoptions.json').expectBadge({ 14 | label: 'points', 15 | message: 'not found', 16 | color: 'red', 17 | }) 18 | 19 | t.create('pub points (invalid)').get('/analysis-options.json').expectBadge({ 20 | label: 'points', 21 | message: 'invalid', 22 | color: 'lightgrey', 23 | }) 24 | -------------------------------------------------------------------------------- /services/pub/pub-popularity.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const PubPopularity = deprecatedService({ 4 | category: 'rating', 5 | route: { 6 | base: 'pub/popularity', 7 | pattern: ':packageName', 8 | }, 9 | label: 'popularity', 10 | dateAdded: new Date('2025-05-11'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/pub/pub-popularity.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'PubPopularity', 5 | title: 'PubPopularity', 6 | pathPrefix: '/pub/popularity', 7 | }) 8 | 9 | t.create('pub popularity').get('/analysis_options.json').expectBadge({ 10 | label: 'popularity', 11 | message: 'no longer available', 12 | }) 13 | -------------------------------------------------------------------------------- /services/pub/pub-publisher.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('package publisher').get('/path.json').expectBadge({ 5 | label: 'publisher', 6 | message: 'dart.dev', 7 | }) 8 | 9 | t.create('package not verified publisher').get('/utf.json').expectBadge({ 10 | label: 'publisher', 11 | message: 'unverified', 12 | color: 'lightgrey', 13 | }) 14 | 15 | t.create('package not found').get('/doesnotexist.json').expectBadge({ 16 | label: 'publisher', 17 | message: 'not found', 18 | }) 19 | -------------------------------------------------------------------------------- /services/pulsar/pulsar-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | import { pulsarPurple } from './pulsar-helper.js' 4 | 5 | export const t = await createServiceTester() 6 | 7 | t.create('pulsar downloads (valid)') 8 | .get('/hey-pane.json') 9 | .expectBadge({ 10 | label: 'downloads', 11 | message: isMetric, 12 | color: `#${pulsarPurple}`, 13 | }) 14 | 15 | t.create('pulsar downloads (not found)').get('/test-package.json').expectBadge({ 16 | label: 'downloads', 17 | message: 'package not found', 18 | }) 19 | -------------------------------------------------------------------------------- /services/pulsar/pulsar-helper.js: -------------------------------------------------------------------------------- 1 | // This is based on the format the Docker badges have taken. 2 | // Seems Tests require `#` before colors, whereas the badges do not. 3 | // So a color variable can be exported for all modules to use as needed. 4 | 5 | const pulsarPurple = '662d91' 6 | 7 | export { pulsarPurple } 8 | -------------------------------------------------------------------------------- /services/pulsar/pulsar-stargazers.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | import { pulsarPurple } from './pulsar-helper.js' 4 | 5 | export const t = await createServiceTester() 6 | 7 | t.create('pulsar stargazers (valid)') 8 | .get('/hey-pane.json') 9 | .expectBadge({ 10 | label: 'stargazers', 11 | message: isMetric, 12 | color: `#${pulsarPurple}`, 13 | }) 14 | 15 | t.create('pulsar stargazers (not found)') 16 | .get('/test-package.json') 17 | .expectBadge({ 18 | label: 'stargazers', 19 | message: 'package not found', 20 | }) 21 | -------------------------------------------------------------------------------- /services/puppetforge/puppetforge-module-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('module downloads').get('/camptocamp/openssl.json').expectBadge({ 6 | label: 'downloads', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('module downloads (not found)') 11 | .get('/notarealuser/notarealpackage.json') 12 | .expectBadge({ 13 | label: 'downloads', 14 | message: 'not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/puppetforge/puppetforge-module-pdk-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('PDK version').get('/tragiccode/azure_key_vault.json').expectBadge({ 6 | label: 'pdk version', 7 | message: isSemver, 8 | }) 9 | 10 | t.create("PDK version (library doesn't use the PDK)") 11 | .get('/puppet/yum.json') 12 | .expectBadge({ 13 | label: 'pdk version', 14 | message: 'none', 15 | }) 16 | 17 | t.create('PDK version (not found)') 18 | .get('/notarealuser/notarealpackage.json') 19 | .expectBadge({ 20 | label: 'pdk version', 21 | message: 'not found', 22 | }) 23 | -------------------------------------------------------------------------------- /services/puppetforge/puppetforge-module-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('module version').get('/camptocamp/openssl.json').expectBadge({ 6 | label: 'puppetforge', 7 | message: isSemver, 8 | }) 9 | 10 | t.create('module version (not found)') 11 | .get('/notarealuser/notarealpackage.json') 12 | .expectBadge({ 13 | label: 'puppetforge', 14 | message: 'not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/puppetforge/puppetforge-user-module-count.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('modules by user').get('/camptocamp.json').expectBadge({ 6 | label: 'modules', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('modules by user').get('/not-a-real-user.json').expectBadge({ 11 | label: 'modules', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/puppetforge/puppetforge-user-release-count.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('releases by user').get('/camptocamp.json').expectBadge({ 6 | label: 'releases', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('releases by user').get('/not-a-real-user.json').expectBadge({ 11 | label: 'releases', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/pypi/pypi-django-versions.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | category: 'platform-support', 5 | route: { 6 | base: 'pypi/djversions', 7 | pattern: ':packageName*', 8 | }, 9 | transformPath: ({ packageName }) => 10 | `/pypi/frameworkversions/django/${packageName}`, 11 | dateAdded: new Date('2022-07-28'), 12 | }) 13 | -------------------------------------------------------------------------------- /services/pypi/pypi-types.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('types (yes)') 5 | .get('/pyre-check.json') 6 | .expectBadge({ label: 'types', message: 'typed' }) 7 | 8 | t.create('types (no)') 9 | .get('/z3-solver.json') 10 | .expectBadge({ label: 'types', message: 'untyped' }) 11 | 12 | t.create('types (stubs)') 13 | .get('/types-requests.json') 14 | .expectBadge({ label: 'types', message: 'stubs' }) 15 | 16 | t.create('types (invalid)') 17 | .get('/not-a-package.json') 18 | .expectBadge({ label: 'types', message: 'package or version not found' }) 19 | -------------------------------------------------------------------------------- /services/pypi/pypi-wheel.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('wheel (has wheel, package version in request)') 5 | .get('/requests/2.18.4.json') 6 | .expectBadge({ label: 'wheel', message: 'yes' }) 7 | 8 | t.create('wheel (has wheel, no package version specified)') 9 | .get('/requests.json') 10 | .expectBadge({ label: 'wheel', message: 'yes' }) 11 | 12 | t.create('wheel (no wheel)') 13 | .get('/chai/1.1.2.json') 14 | .expectBadge({ label: 'wheel', message: 'no' }) 15 | 16 | t.create('wheel (invalid)') 17 | .get('/not-a-package.json') 18 | .expectBadge({ label: 'wheel', message: 'package or version not found' }) 19 | -------------------------------------------------------------------------------- /services/redmine/redmine.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const RedminePluginRating = deprecatedService({ 4 | category: 'rating', 5 | route: { 6 | base: 'redmine/plugin/rating', 7 | pattern: ':various*', 8 | }, 9 | label: 'redmine', 10 | dateAdded: new Date('2023-09-14'), 11 | }) 12 | 13 | export const RedminePluginStars = deprecatedService({ 14 | category: 'rating', 15 | route: { 16 | base: 'redmine/plugin/stars', 17 | pattern: ':various*', 18 | }, 19 | label: 'redmine', 20 | dateAdded: new Date('2023-09-14'), 21 | }) 22 | -------------------------------------------------------------------------------- /services/redmine/redmine.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'redmine', 5 | title: 'Redmine', 6 | }) 7 | 8 | t.create('plugin rating') 9 | .get('/plugin/rating/redmine_xlsx_format_issue_exporter.json') 10 | .expectBadge({ 11 | label: 'redmine', 12 | message: 'no longer available', 13 | }) 14 | 15 | t.create('plugin stars') 16 | .get('/plugin/stars/redmine_xlsx_format_issue_exporter.json') 17 | .expectBadge({ 18 | label: 'redmine', 19 | message: 'no longer available', 20 | }) 21 | -------------------------------------------------------------------------------- /services/repology/repology-repositories.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { nonNegativeInteger } from '../validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Existing project').get('/starship.json').expectBadge({ 6 | label: 'repositories', 7 | message: nonNegativeInteger, 8 | }) 9 | 10 | t.create('Non-existent project') 11 | .get('/invalidprojectthatshouldnotexist.json') 12 | .expectBadge({ 13 | label: 'repositories', 14 | message: '0', 15 | }) 16 | -------------------------------------------------------------------------------- /services/resharper/resharper.service.js: -------------------------------------------------------------------------------- 1 | import { createServiceFamily } from '../nuget/nuget-v2-service-family.js' 2 | 3 | export default createServiceFamily({ 4 | name: 'ResharperPlugin', 5 | defaultLabel: 'resharper', 6 | serviceBaseUrl: 'resharper', 7 | apiBaseUrl: 'https://resharper-plugins.jetbrains.com/api/v2', 8 | title: 'JetBrains ReSharper plugins', 9 | examplePackageName: 'StyleCop.StyleCop', 10 | }) 11 | -------------------------------------------------------------------------------- /services/response-fixtures.js: -------------------------------------------------------------------------------- 1 | export const invalidJSONString = '{{{{{invalid json}}' 2 | 3 | export const invalidJSON = () => [ 4 | 200, 5 | invalidJSONString, 6 | { 'Content-Type': 'application/json' }, 7 | ] 8 | -------------------------------------------------------------------------------- /services/reuse/reuse-compliance-helper.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | 3 | const COLOR_MAP = { 4 | checking: 'brightgreen', 5 | compliant: 'green', 6 | 'non-compliant': 'red', 7 | unregistered: 'red', 8 | } 9 | 10 | const isReuseCompliance = Joi.string() 11 | .valid('compliant', 'non-compliant', 'checking', 'unregistered') 12 | .required() 13 | 14 | export { isReuseCompliance, COLOR_MAP } 15 | -------------------------------------------------------------------------------- /services/security-headers/security-headers.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('grade of https://shields.io') 5 | .timeout(15000) 6 | .get('/security-headers.json?url=https://shields.io') 7 | .expectBadge({ label: 'security headers', message: 'F', color: 'red' }) 8 | 9 | t.create('grade of https://httpstat.us/301 as redirect') 10 | .timeout(15000) 11 | .get('/security-headers.json?ignoreRedirects&url=https://httpstat.us/301') 12 | .expectBadge({ label: 'security headers', message: 'R', color: 'blue' }) 13 | -------------------------------------------------------------------------------- /services/snapcraft/snapcraft-base.js: -------------------------------------------------------------------------------- 1 | import { BaseJsonService, pathParam } from '../index.js' 2 | 3 | export const snapcraftPackageParam = pathParam({ 4 | name: 'package', 5 | example: 'redis', 6 | }) 7 | 8 | export const snapcraftBaseParams = [snapcraftPackageParam] 9 | 10 | const snapcraftBaseUrl = 'https://api.snapcraft.io/v2/snaps/info' 11 | 12 | export default class SnapcraftBase extends BaseJsonService { 13 | async fetch(schema, { packageName }) { 14 | return await this._requestJson({ 15 | schema, 16 | url: `${snapcraftBaseUrl}/${packageName}`, 17 | options: { 18 | headers: { 'Snap-Device-Series': 16 }, 19 | }, 20 | httpErrors: { 404: 'package not found' }, 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /services/snapcraft/snapcraft-licence.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import SnapcraftLicense from './snapcraft-licence.service.js' 3 | 4 | describe('SnapcraftLicense', function () { 5 | const testApiData = { 6 | snap: { 7 | license: 'BSD-3-Clause', 8 | }, 9 | } 10 | 11 | test(SnapcraftLicense.transform, () => { 12 | given(testApiData).expect('BSD-3-Clause') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /services/snapcraft/snapcraft-licence.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('Snapcraft license (valid)').get('/redis.json').expectBadge({ 5 | label: 'license', 6 | message: 'BSD-3-Clause', 7 | }) 8 | 9 | t.create('Snapcraft license(invalid)') 10 | .get('/this_package_doesnt_exist.json') 11 | .expectBadge({ 12 | label: 'license', 13 | message: 'package not found', 14 | }) 15 | -------------------------------------------------------------------------------- /services/snyk/snyk-vulnerability-github.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export default deprecatedService({ 4 | category: 'analysis', 5 | route: { 6 | base: 'snyk/vulnerabilities/github', 7 | pattern: ':various*', 8 | }, 9 | label: 'vulnerabilities', 10 | dateAdded: new Date('2023-07-03'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/snyk/snyk-vulnerability-github.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | export const t = new ServiceTester({ 3 | id: 'SnykVulnerabilityGitHub', 4 | title: 'SnykVulnerabilityGitHub', 5 | pathPrefix: '/snyk/vulnerabilities/github', 6 | }) 7 | 8 | t.create('repo').get('/snyk/snyk.json').expectBadge({ 9 | label: 'vulnerabilities', 10 | message: 'no longer available', 11 | }) 12 | 13 | t.create('manifest path') 14 | .get('/snyk/snyk/test/fixtures/demo-os/package.json.json') 15 | .expectBadge({ 16 | label: 'vulnerabilities', 17 | message: 'no longer available', 18 | }) 19 | -------------------------------------------------------------------------------- /services/snyk/snyk-vulnerability-npm.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export default deprecatedService({ 4 | category: 'analysis', 5 | route: { 6 | base: 'snyk/vulnerabilities/npm', 7 | pattern: ':various*', 8 | }, 9 | label: 'vulnerabilities', 10 | dateAdded: new Date('2023-07-03'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/snyk/snyk-vulnerability-npm.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | export const t = new ServiceTester({ 3 | id: 'SnykVulnerabilityNpm', 4 | title: 'SnykVulnerabilityNpm', 5 | pathPrefix: '/snyk/vulnerabilities/npm', 6 | }) 7 | t.create('latest version').get('/commander.json').expectBadge({ 8 | label: 'vulnerabilities', 9 | message: 'no longer available', 10 | }) 11 | 12 | t.create('scoped package latest version').get('/@babel/core.json').expectBadge({ 13 | label: 'vulnerabilities', 14 | message: 'no longer available', 15 | }) 16 | 17 | t.create('package specific version').get('/commander@2.20.0.json').expectBadge({ 18 | label: 'vulnerabilities', 19 | message: 'no longer available', 20 | }) 21 | -------------------------------------------------------------------------------- /services/sonar/sonar-generic.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Security Rating') 6 | .timeout(10000) 7 | .get('/security_rating/WebExtensions.Net.json?server=https://sonarcloud.io') 8 | .expectBadge({ 9 | label: 'security rating', 10 | message: isMetric, 11 | color: 'blue', 12 | }) 13 | 14 | t.create('Security Rating (branch)') 15 | .timeout(10000) 16 | .get( 17 | '/security_rating/WebExtensions.Net/main.json?server=https://sonarcloud.io', 18 | ) 19 | .expectBadge({ 20 | label: 'security rating', 21 | message: isMetric, 22 | color: 'blue', 23 | }) 24 | -------------------------------------------------------------------------------- /services/sonar/sonar-quality-gate.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import SonarQualityGate from './sonar-quality-gate.service.js' 3 | 4 | describe('SonarQualityGate', function () { 5 | test(SonarQualityGate.render, () => { 6 | given({ qualityState: 'OK' }).expect({ 7 | message: 'passed', 8 | color: 'success', 9 | }) 10 | given({ qualityState: 'ERROR' }).expect({ 11 | message: 'failed', 12 | color: 'critical', 13 | }) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-base.js: -------------------------------------------------------------------------------- 1 | import { BaseJsonService } from '../index.js' 2 | 3 | export default class BaseSourceForgeService extends BaseJsonService { 4 | async fetch({ project, schema }) { 5 | return this._requestJson({ 6 | url: `https://sourceforge.net/rest/p/${project}/`, 7 | schema, 8 | httpErrors: { 9 | 404: 'project not found', 10 | }, 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-commit-count-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | // SourceForge commit count service used to only have project name as a parameter 5 | // and the repository name was always `git`. 6 | // The service was later updated to have the repository name as a parameter. 7 | // This redirector is used to keep the old URLs working. 8 | category: 'activity', 9 | route: { 10 | base: 'sourceforge/commit-count', 11 | pattern: ':project', 12 | }, 13 | transformPath: ({ project }) => `/sourceforge/commit-count/${project}/git`, 14 | dateAdded: new Date('2025-03-15'), 15 | }) 16 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-commit-count-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('commit count (redirect)') 5 | .get('/guitarix.json') 6 | .expectRedirect('/sourceforge/commit-count/guitarix/git.json') 7 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-contributors.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('contributors') 6 | .get('/guitarix.json') 7 | .expectBadge({ label: 'contributors', message: isMetric }) 8 | 9 | t.create('contributors (project not found)') 10 | .get('/that-doesnt-exist.json') 11 | .expectBadge({ label: 'contributors', message: 'project not found' }) 12 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-languages.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('languages') 6 | .get('/guitarix.json') 7 | .expectBadge({ label: 'languages', message: isMetric }) 8 | 9 | t.create('languages (project not found)') 10 | .get('/that-doesnt-exist.json') 11 | .expectBadge({ label: 'languages', message: 'project not found' }) 12 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-last-commit-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | // SourceForge last commit service used to only have project name as a parameter 5 | // and the repository name was always `git`. 6 | // The service was later updated to have the repository name as a parameter. 7 | // This redirector is used to keep the old URLs working. 8 | category: 'activity', 9 | route: { 10 | base: 'sourceforge/last-commit', 11 | pattern: ':project', 12 | }, 13 | transformPath: ({ project }) => `/sourceforge/last-commit/${project}/git`, 14 | dateAdded: new Date('2025-03-08'), 15 | }) 16 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-last-commit-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('last commit (redirect)') 5 | .get('/guitarix.json') 6 | .expectRedirect('/sourceforge/last-commit/guitarix/git.json') 7 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-open-tickets.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('bugs') 6 | .get('/sevenzip/bugs.json') 7 | .expectBadge({ 8 | label: 'open tickets', 9 | message: isMetric, 10 | }) 11 | .timeout(10000) 12 | 13 | t.create('feature requests') 14 | .get('/sevenzip/feature-requests.json') 15 | .expectBadge({ 16 | label: 'open tickets', 17 | message: isMetric, 18 | }) 19 | .timeout(10000) 20 | 21 | t.create('invalid project').get('/invalid/bugs.json').expectBadge({ 22 | label: 'open tickets', 23 | message: 'project not found', 24 | }) 25 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-platform.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('platform') 6 | .get('/guitarix.json') 7 | .expectBadge({ label: 'platform', message: Joi.string().required() }) 8 | 9 | t.create('platform (project not found)') 10 | .get('/that-doesnt-exist.json') 11 | .expectBadge({ label: 'platform', message: 'project not found' }) 12 | -------------------------------------------------------------------------------- /services/sourceforge/sourceforge-translations.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('translations') 6 | .get('/guitarix.json') 7 | .expectBadge({ label: 'translations', message: isMetric }) 8 | 9 | t.create('translations (project not found)') 10 | .get('/that-doesnt-exist.json') 11 | .expectBadge({ label: 'translations', message: 'project not found' }) 12 | -------------------------------------------------------------------------------- /services/spack/spack.tester.js: -------------------------------------------------------------------------------- 1 | import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('version (valid)').get('/adios2.json').expectBadge({ 6 | label: 'spack', 7 | message: isVPlusDottedVersionAtLeastOne, 8 | }) 9 | 10 | t.create('version (not found)') 11 | .get('/not-a-package.json') 12 | .expectBadge({ label: 'spack', message: 'package not found' }) 13 | -------------------------------------------------------------------------------- /services/spiget/spiget-download-size.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetricFileSize } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('EssentialsX (hosted resource)') 6 | .get('/771.json') 7 | .expectBadge({ label: 'size', message: isMetricFileSize }) 8 | 9 | t.create('external resource').get('/9089.json').expectBadge({ 10 | label: 'size', 11 | message: 'resource hosted externally', 12 | }) 13 | 14 | t.create('Invalid Resource').get('/1.json').expectBadge({ 15 | label: 'size', 16 | message: 'not found', 17 | }) 18 | -------------------------------------------------------------------------------- /services/spiget/spiget-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('EssentialsX (id 9089)').get('/9089.json').expectBadge({ 6 | label: 'downloads', 7 | message: isMetric, 8 | }) 9 | 10 | t.create('Invalid Resource (id 1)').get('/1.json').expectBadge({ 11 | label: 'downloads', 12 | message: 'not found', 13 | }) 14 | -------------------------------------------------------------------------------- /services/spiget/spiget-latest-version.tester.js: -------------------------------------------------------------------------------- 1 | import { withRegex } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | // Note that Spigot versions can be anything (including just a string), so we'll make sure it's not returning 'not found' 6 | 7 | t.create('EssentialsX (id 9089)') 8 | .get('/9089.json') 9 | .expectBadge({ 10 | label: 'spiget', 11 | message: withRegex(/^(?!not found$)/), 12 | }) 13 | 14 | t.create('Invalid Resource (id 1)').get('/1.json').expectBadge({ 15 | label: 'spiget', 16 | message: 'not found', 17 | }) 18 | -------------------------------------------------------------------------------- /services/stackexchange/stackexchange-monthlyquestions.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import StackExchangeMonthlyQuestions from './stackexchange-monthlyquestions.service.js' 3 | 4 | describe('StackExchangeMonthlyQuestions', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth( 8 | StackExchangeMonthlyQuestions, 9 | 'QueryStringAuth', 10 | { 11 | total: 8, 12 | }, 13 | { queryPassKey: 'key' }, 14 | ) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /services/stackexchange/stackexchange-monthlyquestions.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetricOverTimePeriod } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Monthly Questions for StackOverflow dayjs') 6 | .get('/stackoverflow/qm/dayjs.json') 7 | .expectBadge({ 8 | label: 'stackoverflow dayjs questions', 9 | message: isMetricOverTimePeriod, 10 | }) 11 | 12 | t.create('Monthly Questions for Tex Spacing') 13 | .get('/tex/qm/spacing.json') 14 | .expectBadge({ 15 | label: 'tex spacing questions', 16 | message: isMetricOverTimePeriod, 17 | }) 18 | -------------------------------------------------------------------------------- /services/stackexchange/stackexchange-reputation.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import StackExchangeReputation from './stackexchange-reputation.service.js' 3 | 4 | describe('StackExchangeReputation', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth( 8 | StackExchangeReputation, 9 | 'QueryStringAuth', 10 | { 11 | items: [{ reputation: 8 }], 12 | }, 13 | { queryPassKey: 'key' }, 14 | ) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /services/stackexchange/stackexchange-reputation.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Invalid parameters') 6 | .get('/stackoverflow/r/invalidimage.json') 7 | .expectBadge({ label: 'stackoverflow', message: 'invalid parameters' }) 8 | 9 | t.create('Reputation for StackOverflow user 22656') 10 | .get('/stackoverflow/r/22656.json') 11 | .expectBadge({ 12 | label: 'stackoverflow reputation', 13 | message: isMetric, 14 | }) 15 | 16 | t.create('Reputation for Tex user 22656').get('/tex/r/226.json').expectBadge({ 17 | label: 'tex reputation', 18 | message: isMetric, 19 | }) 20 | -------------------------------------------------------------------------------- /services/stackexchange/stackexchange-taginfo.spec.js: -------------------------------------------------------------------------------- 1 | import { testAuth } from '../test-helpers.js' 2 | import StackExchangeQuestions from './stackexchange-taginfo.service.js' 3 | 4 | describe('StackExchangeQuestions', function () { 5 | describe('auth', function () { 6 | it('sends the auth information as configured', async function () { 7 | return testAuth( 8 | StackExchangeQuestions, 9 | 'QueryStringAuth', 10 | { 11 | items: [{ count: 8 }], 12 | }, 13 | { queryPassKey: 'key' }, 14 | ) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /services/stackexchange/stackexchange-taginfo.tester.js: -------------------------------------------------------------------------------- 1 | import { isMetric } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('JavaScript Questions') 6 | .get('/stackoverflow/t/javascript.json') 7 | .expectBadge({ 8 | label: 'stackoverflow javascript questions', 9 | message: isMetric, 10 | }) 11 | 12 | t.create('Tex Programming Questions') 13 | .get('/tex/t/programming.json') 14 | .expectBadge({ 15 | label: 'tex programming questions', 16 | message: isMetric, 17 | }) 18 | -------------------------------------------------------------------------------- /services/static-badge/query-string-static.service.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { BaseStaticService } from '../index.js' 3 | 4 | const queryParamSchema = Joi.object({ 5 | message: Joi.string().required(), 6 | }).required() 7 | 8 | export default class QueryStringStaticBadge extends BaseStaticService { 9 | static category = 'static' 10 | 11 | static route = { 12 | base: '', 13 | pattern: 'static/:schemaVersion(v1)', 14 | // All but one of the parameters are parsed via coalesceBadge. This 15 | // reuses what is the override behaviour for other badges. 16 | queryParamSchema, 17 | } 18 | 19 | handle(namedParams, queryParams) { 20 | return { message: queryParams.message } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services/swagger/swagger-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'other', 6 | name: 'SwaggerRedirect', 7 | route: { 8 | base: 'swagger/valid/2.0', 9 | pattern: ':scheme(http|https)/:url*', 10 | }, 11 | transformPath: () => '/swagger/valid/3.0', 12 | transformQueryParams: ({ scheme, url }) => { 13 | const suffix = /(yaml|yml|json)$/.test(url) ? '' : '.json' 14 | return { specUrl: `${scheme}://${url}${suffix}` } 15 | }, 16 | dateAdded: new Date('2019-11-03'), 17 | }), 18 | ] 19 | -------------------------------------------------------------------------------- /services/symfony/sensiolabs-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | // The SymfonyInsight service was previously branded as SensioLabs, and 5 | // accordingly the badge path used to be /sensiolabs/i/projectUuid'. 6 | redirector({ 7 | category: 'analysis', 8 | route: { 9 | base: 'sensiolabs/i', 10 | pattern: ':projectUuid', 11 | }, 12 | transformPath: ({ projectUuid }) => `/symfony/i/grade/${projectUuid}`, 13 | dateAdded: new Date('2019-02-08'), 14 | }), 15 | ] 16 | -------------------------------------------------------------------------------- /services/symfony/sensiolabs-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'sensiolabs', 5 | title: 'SensioLabs', 6 | }) 7 | 8 | t.create('sensiolabs insight') 9 | .get('/i/825be328-29f8-44f7-a750-f82818ae9111.svg') 10 | .expectRedirect('/symfony/i/grade/825be328-29f8-44f7-a750-f82818ae9111.svg') 11 | -------------------------------------------------------------------------------- /services/symfony/symfony-insight-base.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { NotFound } from '../index.js' 3 | import { SymfonyInsightBase } from './symfony-insight-base.js' 4 | 5 | describe('SymfonyInsightBase', function () { 6 | context('transform()', function () { 7 | it('throws NotFound error when there is no coverage data', function () { 8 | try { 9 | SymfonyInsightBase.prototype.transform({ 10 | data: { project: {} }, 11 | }) 12 | expect.fail('Expected to throw') 13 | } catch (e) { 14 | expect(e).to.be.an.instanceof(NotFound) 15 | expect(e.prettyMessage).to.equal('no analyses found') 16 | } 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /services/tas/tas-tests.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | const TasBuildStatus = deprecatedService({ 4 | category: 'test-results', 5 | route: { 6 | base: 'tas/tests', 7 | pattern: ':provider/:org/:repo', 8 | }, 9 | label: 'tests', 10 | dateAdded: new Date('2024-01-29'), 11 | }) 12 | 13 | export default TasBuildStatus 14 | -------------------------------------------------------------------------------- /services/tas/tas-tests.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'tas', 5 | title: 'TasBuildStatus', 6 | pathPrefix: '/tas/tests', 7 | }) 8 | 9 | t.create('Test status') 10 | .get('/github/tasdemo/axios.json') 11 | .expectBadge({ label: 'tests', message: 'no longer available' }) 12 | -------------------------------------------------------------------------------- /services/teamcity/teamcity-test-helpers.js: -------------------------------------------------------------------------------- 1 | const user = 'admin' 2 | const pass = 'password' 3 | const host = 'mycompany.teamcity.com' 4 | const config = { 5 | public: { 6 | services: { 7 | teamcity: { 8 | authorizedOrigins: [`https://${host}`], 9 | }, 10 | }, 11 | }, 12 | private: { 13 | teamcity_user: user, 14 | teamcity_pass: pass, 15 | }, 16 | } 17 | 18 | export { user, pass, host, config } 19 | -------------------------------------------------------------------------------- /services/tester.js: -------------------------------------------------------------------------------- 1 | import createServiceTester from '../core/service-test-runner/create-service-tester.js' 2 | import ServiceTester from '../core/service-test-runner/service-tester.js' 3 | 4 | export { createServiceTester, ServiceTester } 5 | -------------------------------------------------------------------------------- /services/testspace/testspace-base.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { NotFound } from '../index.js' 3 | import TestspaceBase from './testspace-base.js' 4 | 5 | describe('TestspaceBase', function () { 6 | it('throws NotFound when response is missing space results', function () { 7 | expect(() => TestspaceBase.prototype.transformCaseCounts([])) 8 | .to.throw(NotFound) 9 | .with.property('prettyMessage', 'space not found or results purged') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /services/testspace/testspace-test-pass-ratio.spec.js: -------------------------------------------------------------------------------- 1 | import { test, given } from 'sazerac' 2 | import TestspacePassRatio from './testspace-test-pass-ratio.service.js' 3 | 4 | describe('TestspacePassRatio', function () { 5 | test(TestspacePassRatio.render, () => { 6 | given({ passed: 3, total: 5 }).expect({ 7 | message: '60%', 8 | color: 'critical', 9 | }) 10 | 11 | given({ passed: 4, total: 4 }).expect({ 12 | message: '100%', 13 | color: 'success', 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /services/thunderstore/thunderstore-downloads.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Downloads') 7 | .get('/ebkr/r2modman.json') 8 | .expectBadge({ label: 'downloads', message: isMetric }) 9 | 10 | t.create('Downloads (not found)') 11 | .get('/not-a-namespace/not-a-package-name.json') 12 | .expectBadge({ label: 'downloads', message: 'not found', color: 'red' }) 13 | -------------------------------------------------------------------------------- /services/thunderstore/thunderstore-likes.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Likes') 7 | .get('/ebkr/r2modman.json') 8 | .expectBadge({ label: 'likes', message: isMetric }) 9 | 10 | t.create('Likes (not found)') 11 | .get('/not-a-namespace/not-a-package-name.json') 12 | .expectBadge({ label: 'likes', message: 'not found', color: 'red' }) 13 | -------------------------------------------------------------------------------- /services/thunderstore/thunderstore-version.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isSemver } from '../test-validators.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('Version') 7 | .get('/ebkr/r2modman.json') 8 | .expectBadge({ label: 'thunderstore', message: isSemver }) 9 | 10 | t.create('Version (not found)') 11 | .get('/not-a-namespace/not-a-package-name.json') 12 | .expectBadge({ label: 'thunderstore', message: 'not found', color: 'red' }) 13 | -------------------------------------------------------------------------------- /services/tokei/tokei.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const Tokei = deprecatedService({ 4 | category: 'size', 5 | route: { 6 | base: 'tokei/lines', 7 | pattern: ':various*', 8 | }, 9 | label: 'tokei', 10 | dateAdded: new Date('2023-09-17'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/tokei/tokei.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ id: 'tokei', title: 'Tokei LOC Tests' }) 4 | 5 | t.create('GitHub LOC') 6 | .get('/lines/github/badges/shields.json') 7 | .expectBadge({ label: 'tokei', message: 'no longer available' }) 8 | 9 | t.create('BitBucket LOC') 10 | .get('/lines/bitbucket.org/MonliH/tokei-shields-test.json') 11 | .expectBadge({ label: 'tokei', message: 'no longer available' }) 12 | -------------------------------------------------------------------------------- /services/twitch/twitch-extension-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | import { noToken } from '../test-helpers.js' 4 | import _noTwitchToken from './twitch.service.js' 5 | const noTwitchToken = noToken(_noTwitchToken) 6 | export const t = await createServiceTester() 7 | 8 | t.create('gets the released version of Schedule with Google Calendar') 9 | .skipWhen(noTwitchToken) 10 | .get('/2nq5cu1nc9f4p75b791w8d3yo9d195.json') 11 | .expectBadge({ label: 'twitch extension', message: isSemver }) 12 | 13 | t.create('invalid extension id') 14 | .skipWhen(noTwitchToken) 15 | .get('/will-never-exist.json') 16 | .expectBadge({ label: 'twitch extension', message: 'not found' }) 17 | -------------------------------------------------------------------------------- /services/twitter/twitter-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default [ 4 | redirector({ 5 | category: 'social', 6 | name: 'TwitterUrlRedirect', 7 | route: { 8 | base: 'twitter/url', 9 | pattern: ':protocol(https|http)/:hostAndPath+', 10 | }, 11 | transformPath: () => '/twitter/url', 12 | transformQueryParams: ({ protocol, hostAndPath }) => ({ 13 | url: `${protocol}://${hostAndPath}`, 14 | }), 15 | dateAdded: new Date('2019-09-17'), 16 | }), 17 | ] 18 | -------------------------------------------------------------------------------- /services/twitter/twitter-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'TwitterUrlRedirect', 5 | title: 'TwitterUrlRedirect', 6 | pathPrefix: '/twitter/url', 7 | }) 8 | 9 | t.create('twitter') 10 | .get('/https/shields.io.svg') 11 | .expectRedirect( 12 | `/twitter/url.svg?url=${encodeURIComponent('https://shields.io')}`, 13 | ) 14 | -------------------------------------------------------------------------------- /services/twitter/twitter.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | 3 | export const t = new ServiceTester({ 4 | id: 'twitter', 5 | title: 'Twitter', 6 | }) 7 | 8 | t.create('Followers') 9 | .get('/follow/shields_io.json') 10 | .expectBadge({ 11 | label: 'follow @shields_io', 12 | message: '', 13 | link: ['https://twitter.com/intent/follow?screen_name=shields_io'], 14 | }) 15 | 16 | t.create('URL') 17 | .get('/url.json?url=https://shields.io') 18 | .expectBadge({ 19 | label: 'tweet', 20 | message: '', 21 | link: [ 22 | 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fshields.io', 23 | 'https://twitter.com/search?q=https%3A%2F%2Fshields.io', 24 | ], 25 | }) 26 | -------------------------------------------------------------------------------- /services/vaadin-directory/vaadin-directory-rating-count.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('rating count of component') 6 | .get('/rating-count/vaadinvaadin-grid.json') 7 | .expectBadge({ 8 | label: 'rating count', 9 | message: Joi.string().regex(/^\d+?\stotal$/), 10 | }) 11 | 12 | t.create('rating count of component') 13 | .get('/rc/vaadinvaadin-grid.json') 14 | .expectBadge({ 15 | label: 'rating count', 16 | message: Joi.string().regex(/^\d+?\stotal$/), 17 | }) 18 | 19 | t.create('not found').get('/rating-count/does-not-exist.json').expectBadge({ 20 | label: 'rating count', 21 | message: 'not found', 22 | }) 23 | -------------------------------------------------------------------------------- /services/vaadin-directory/vaadin-directory-status.tester.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { createServiceTester } from '../tester.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('publish status of the component') 6 | .get('/vaadinvaadin-grid.json') 7 | .expectBadge({ 8 | label: 'vaadin directory', 9 | message: Joi.equal('published', 'unpublished'), 10 | }) 11 | 12 | t.create('not found').get('/does-not-exist.json').expectBadge({ 13 | label: 'vaadin directory', 14 | message: 'not found', 15 | }) 16 | -------------------------------------------------------------------------------- /services/vcpkg/vcpkg-version-helpers.js: -------------------------------------------------------------------------------- 1 | import { InvalidResponse } from '../index.js' 2 | 3 | export function parseVersionFromVcpkgManifest(manifest) { 4 | if (manifest['version-date']) { 5 | return manifest['version-date'] 6 | } 7 | if (manifest['version-semver']) { 8 | return manifest['version-semver'] 9 | } 10 | if (manifest['version-string']) { 11 | return manifest['version-string'] 12 | } 13 | if (manifest.version) { 14 | return manifest.version 15 | } 16 | throw new InvalidResponse({ prettyMessage: 'missing version' }) 17 | } 18 | -------------------------------------------------------------------------------- /services/vcpkg/vcpkg-version.tester.js: -------------------------------------------------------------------------------- 1 | import { isSemver } from '../test-validators.js' 2 | import { createServiceTester } from '../tester.js' 3 | 4 | export const t = await createServiceTester() 5 | 6 | t.create('gets nlohmann-json port version') 7 | .get('/nlohmann-json.json') 8 | .expectBadge({ label: 'vcpkg', color: 'blue', message: isSemver }) 9 | 10 | t.create('gets not found error for invalid port') 11 | .get('/this-port-does-not-exist.json') 12 | .expectBadge({ 13 | label: 'vcpkg', 14 | color: 'red', 15 | message: 'port not found', 16 | }) 17 | -------------------------------------------------------------------------------- /services/weblate/weblate-component-license.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('License') 5 | .get('/godot-engine/godot.json') 6 | .expectBadge({ label: 'license', message: 'MIT' }) 7 | 8 | t.create("Component Doesn't Exist") 9 | .get('/fake-project/fake-component.json?server=https://hosted.weblate.org') 10 | .expectBadge({ label: 'license', message: 'component not found' }) 11 | -------------------------------------------------------------------------------- /services/weblate/weblate-entities.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Components') 6 | .get('/components.json') 7 | .expectBadge({ label: 'components', message: isMetric }) 8 | 9 | t.create('Projects') 10 | .get('/projects.json') 11 | .expectBadge({ label: 'projects', message: isMetric }) 12 | 13 | t.create('Users') 14 | .get('/users.json?server=https://hosted.weblate.org') 15 | .expectBadge({ label: 'users', message: isMetric }) 16 | 17 | t.create('Components') 18 | .get('/components.json?server=https://translate.mattermost.com') 19 | .expectBadge({ label: 'components', message: isMetric }) 20 | -------------------------------------------------------------------------------- /services/weblate/weblate-project-translated-percentage.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isPercentage } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('License') 6 | .get('/godot-engine.json') 7 | .expectBadge({ label: 'translated', message: isPercentage }) 8 | 9 | t.create('Not Valid') 10 | .get('/fake-project.json?server=https://hosted.weblate.org') 11 | .expectBadge({ label: 'translated', message: 'project not found' }) 12 | -------------------------------------------------------------------------------- /services/weblate/weblate-user-statistic.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | import { isMetric } from '../test-validators.js' 3 | export const t = await createServiceTester() 4 | 5 | t.create('Translations') 6 | .get('/translations/nijel.json') 7 | .expectBadge({ label: 'translations', message: isMetric }) 8 | 9 | t.create('Suggestions') 10 | .get('/suggestions/nijel.json') 11 | .expectBadge({ label: 'suggestions', message: isMetric }) 12 | 13 | t.create('Languages') 14 | .get('/languages/nijel.json?server=https://hosted.weblate.org') 15 | .expectBadge({ label: 'languages', message: isMetric }) 16 | -------------------------------------------------------------------------------- /services/wheelmap/wheelmap.service.js: -------------------------------------------------------------------------------- 1 | import { deprecatedService } from '../index.js' 2 | 3 | export const Wheelmap = deprecatedService({ 4 | category: 'other', 5 | route: { 6 | base: 'wheelmap/a', 7 | pattern: ':nodeId', 8 | }, 9 | label: 'wheelmap', 10 | dateAdded: new Date('2024-09-14'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/wheelmap/wheelmap.tester.js: -------------------------------------------------------------------------------- 1 | import { ServiceTester } from '../tester.js' 2 | export const t = new ServiceTester({ 3 | id: 'Wheelmap', 4 | title: 'Wheelmap', 5 | pathPrefix: '/wheelmap/a', 6 | }) 7 | 8 | t.create('wheelmap (deprecated)') 9 | .get('/26699541.json') 10 | .expectBadge({ label: 'wheelmap', message: 'no longer available' }) 11 | -------------------------------------------------------------------------------- /services/wordpress/wordpress-platform-redirect.service.js: -------------------------------------------------------------------------------- 1 | import { redirector } from '../index.js' 2 | 3 | export default redirector({ 4 | category: 'platform-support', 5 | route: { 6 | base: 'wordpress/v', 7 | pattern: ':slug', 8 | }, 9 | transformPath: ({ slug }) => `/wordpress/plugin/tested/${slug}`, 10 | dateAdded: new Date('2019-04-17'), 11 | }) 12 | -------------------------------------------------------------------------------- /services/wordpress/wordpress-platform-redirect.tester.js: -------------------------------------------------------------------------------- 1 | import { createServiceTester } from '../tester.js' 2 | export const t = await createServiceTester() 3 | 4 | t.create('Plugin Tested WP Version (Alias)') 5 | .get('/akismet.svg') 6 | .expectRedirect('/wordpress/plugin/tested/akismet.svg') 7 | -------------------------------------------------------------------------------- /services/wordpress/wordpress-version-color.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { toSemver } from './wordpress-version-color.js' 3 | 4 | describe('toSemver() function', function () { 5 | it('coerces versions', function () { 6 | expect(toSemver('1.1.1')).to.equal('1.1.1') 7 | expect(toSemver('4.2')).to.equal('4.2.0') 8 | expect(toSemver('1.0.0-beta')).to.equal('1.0.0-beta') 9 | expect(toSemver('1.0-beta')).to.equal('1.0.0-beta') 10 | expect(toSemver('foobar')).to.equal('foobar') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /services/youtube/youtube-comments.service.js: -------------------------------------------------------------------------------- 1 | import { pathParams } from '../index.js' 2 | import { description, YouTubeVideoBase } from './youtube-base.js' 3 | 4 | export default class YouTubeComments extends YouTubeVideoBase { 5 | static route = { 6 | base: 'youtube/comments', 7 | pattern: ':videoId', 8 | } 9 | 10 | static openApi = { 11 | '/youtube/comments/{videoId}': { 12 | get: { 13 | summary: 'YouTube Video Comments', 14 | description, 15 | parameters: pathParams({ 16 | name: 'videoId', 17 | example: 'wGJHwc5ksMA', 18 | }), 19 | }, 20 | }, 21 | } 22 | 23 | static render({ statistics, id }) { 24 | return super.renderSingleStat({ statistics, statisticName: 'comment', id }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /services/youtube/youtube-views.service.js: -------------------------------------------------------------------------------- 1 | import { pathParams } from '../index.js' 2 | import { description, YouTubeVideoBase } from './youtube-base.js' 3 | 4 | export default class YouTubeViews extends YouTubeVideoBase { 5 | static route = { 6 | base: 'youtube/views', 7 | pattern: ':videoId', 8 | } 9 | 10 | static openApi = { 11 | '/youtube/views/{videoId}': { 12 | get: { 13 | summary: 'YouTube Video Views', 14 | description, 15 | parameters: pathParams({ 16 | name: 'videoId', 17 | example: 'abBdk8bSPKU', 18 | }), 19 | }, 20 | }, 21 | } 22 | 23 | static render({ statistics, id }) { 24 | return super.renderSingleStat({ statistics, statisticName: 'view', id }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec/proportions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badges/shields/21411e1a01704640598833dc95ce853a50a67af8/spec/proportions.png --------------------------------------------------------------------------------