From dce1e2d115ff5474c12079cfca344fea16540934 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 4 Jun 2026 20:47:33 +0000 Subject: [PATCH 1/2] Add lychee link checker config and CI workflow Adds a lychee configuration and a Links GitHub Actions workflow so that stale or redirecting links are caught automatically going forward. The checker runs on push, pull request, and weekly to catch external link rot. max_redirects is 0 so links that have moved are surfaced and can be updated to their canonical destination. The config is tuned for this Node/TypeScript repo: it scans Markdown, TypeScript sources, and package.json, and excludes generated output (node_modules, dist, build, coverage, docs) and the changelog. lychee is pinned via mise (0.23.0), which is the single source of version truth for both local runs and CI. Part of STF-557. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/links.yml | 32 +++++++++++++++++++ .gitignore | 1 + lychee.toml | 63 +++++++++++++++++++++++++++++++++++++ mise.lock | 29 +++++++++++++++++ mise.toml | 9 ++++++ 5 files changed, 134 insertions(+) create mode 100644 .github/workflows/links.yml create mode 100644 lychee.toml create mode 100644 mise.lock create mode 100644 mise.toml diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 00000000..5e3255ba --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,32 @@ +name: Links + +on: + push: + pull_request: + schedule: + - cron: "0 13 * * 1" # weekly, to catch external link rot without a commit + workflow_dispatch: + +permissions: + contents: read + +jobs: + linkChecker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup mise + uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + with: + install: false + + # Install only lychee (not the repo's full toolchain) and run the check. + - name: Check links + env: + MISE_AUTO_INSTALL: "false" + run: | + mise install lychee + mise run check-links diff --git a/.gitignore b/.gitignore index 6e56af17..c66925af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ dist docs +.lycheecache # Logs logs diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 00000000..f35a3d36 --- /dev/null +++ b/lychee.toml @@ -0,0 +1,63 @@ +# Lychee link checker configuration +# https://lychee.cli.rs/#/usage/config +# +# Run locally with: +# lychee './**/*.md' './src/**/*.ts' './package.json' + +# Include URL fragments in checks +include_fragments = true + +# Don't allow any redirects, so links that have moved are surfaced and updated +# to their canonical destination. +max_redirects = 0 + +# Accept these HTTP status codes +# 100-103: Informational responses +# 200-299: Success responses +# 403: Forbidden (some sites use this for rate limiting) +# 429: Too Many Requests +# 500-599: Server errors (temporary issues shouldn't fail CI) +# 999: LinkedIn's custom status code +accept = ["100..=103", "200..=299", "403", "429", "500..=599", "999"] + +# Exclude URL patterns from checking (treated as regular expressions) +exclude = [ + '^file://', + # Live / auth-gated endpoints that appear as string literals or require login + '^https://geoip\.maxmind\.com', + '^https://geolite\.info', + '^https://minfraud\.maxmind\.com', + '^https://sandbox\.maxmind\.com', + '^https://updates\.maxmind\.com', + '^https://www\.maxmind\.com/en/accounts/', + 'https://www\.maxmind\.com/en/account/login', + # package.json repository.url uses the canonical npm git clone URL + # (https://github.com/maxmind/minfraud-api-node.git); the ".git" form 301s to + # the web UI but is the correct packaging metadata, so it is not a "link" to + # fix. + '^https://github\.com/maxmind/minfraud-api-node\.git$', + # Placeholders / local + '^https?://example\.(com|org|net)', + '^http://localhost', + '127\.0\.0\.1', +] + +# Exclude file paths from getting checked (treated as regular expressions) +exclude_path = [ + '(^|/)node_modules/', + '(^|/)dist/', + '(^|/)build/', + '(^|/)coverage/', + # docs/ contains generated TypeDoc HTML (gitignored) + '(^|/)docs/', + '(^|/)\.git/', + # Changelog: historical entries are preserved as-is, not rewritten + '(^|/)CHANGELOG\.md$', +] + +# Cache results for 1 day to speed up repeated checks +cache = true +max_cache_age = "1d" + +# Skip missing input files instead of erroring +skip_missing = true diff --git a/mise.lock b/mise.lock new file mode 100644 index 00000000..156e9128 --- /dev/null +++ b/mise.lock @@ -0,0 +1,29 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + +[[tools.lychee]] +version = "0.23.0" +backend = "aqua:lycheeverse/lychee" + +[tools.lychee."platforms.linux-arm64"] +checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz" + +[tools.lychee."platforms.linux-arm64-musl"] +checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz" + +[tools.lychee."platforms.linux-x64"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.linux-x64-musl"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.macos-arm64"] +checksum = "sha256:4c8034900e11083b68ac6f6582c377ff1f704e268991999e09d717973e493e7f" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-arm64-macos.dmg" + +[tools.lychee."platforms.windows-x64"] +checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe" diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..cdc12d5a --- /dev/null +++ b/mise.toml @@ -0,0 +1,9 @@ +[settings] +lockfile = true + +[tools] +lychee = "latest" + +[tasks.check-links] +description = "Check links with lychee" +run = "lychee --no-progress './**/*.md' './src/**/*.ts' './package.json'" From 63a65b36630e32468465fa7129fbe85206c3d8df Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 4 Jun 2026 20:49:37 +0000 Subject: [PATCH 2/2] Update stale and redirecting links Validated all links with lychee and updated those that redirected elsewhere to their canonical destinations: - README.md: dev.maxmind.com/minfraud/api-documentation/responses?lang=en#errors -> .../responses/?lang=en#errors (trailing slash before query; was a 308) - README.md: www.maxmind.com/en/support -> support.maxmind.com/knowledge-base (was a 302) - README.dev.md: npmjs.com/package/@maxmind/minfraud-api-node -> www.npmjs.com/package/@maxmind/minfraud-api-node (canonical www form) Also excluded a few false positives in lychee.toml: - dev.maxmind.com/.../responses/#schema-- fragment links: the docs page is client-side rendered, so schema anchors are absent from the static HTML lychee fetches even though the page resolves 200 and the anchor exists in the browser. - http://google.com and www.foobar.com: placeholder/example URLs used as referrer values in test fixtures, not real links. The package.json repository.url (github.com/...minfraud-api-node.git) is the canonical npm git clone URL and is left unchanged. Historical CHANGELOG.md entries are intentionally left unchanged. Part of STF-557. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.dev.md | 2 +- README.md | 4 ++-- lychee.toml | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.dev.md b/README.dev.md index 57ea8989..512c778b 100644 --- a/README.dev.md +++ b/README.dev.md @@ -15,7 +15,7 @@ - Commit changes and push - Create a GitHub release (which triggers the npm publish workflow) 6. Merge the release PR after the workflow succeeds. -7. Verify the release on [npm](https://npmjs.com/package/@maxmind/minfraud-api-node). +7. Verify the release on [npm](https://www.npmjs.com/package/@maxmind/minfraud-api-node). Note: Publishing is done via GitHub Actions using npm Trusted Publishing (OIDC). Manual `npm publish` is not supported. diff --git a/README.md b/README.md index fa049006..d0178bdd 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Thrown by the request and transaction models: * `ArgumentError` - Thrown when invalid data is passed to the Transaction and Transaction property constructors. -In addition to the [response errors](https://dev.maxmind.com/minfraud/api-documentation/responses?lang=en#errors) +In addition to the [response errors](https://dev.maxmind.com/minfraud/api-documentation/responses/?lang=en#errors) returned by the web API, we also return: ```js @@ -286,7 +286,7 @@ Please report all issues with this code using the If you are having an issue with the minFraud service that is not specific to the client API, please see -[our support page](https://www.maxmind.com/en/support). +[our support page](https://support.maxmind.com/knowledge-base). ## Contributing diff --git a/lychee.toml b/lychee.toml index f35a3d36..8b71aa93 100644 --- a/lychee.toml +++ b/lychee.toml @@ -36,8 +36,15 @@ exclude = [ # the web UI but is the correct packaging metadata, so it is not a "link" to # fix. '^https://github\.com/maxmind/minfraud-api-node\.git$', - # Placeholders / local + # dev.maxmind.com API docs are client-side rendered, so schema anchors + # (#schema--...) are generated in the browser and are not present in the + # static HTML lychee fetches. The page itself resolves 200; only the + # fragment cannot be verified, so exclude these schema-fragment URLs. + '^https://dev\.maxmind\.com/minfraud/api-documentation/responses/#schema--', + # Placeholders / example URLs used in tests and docstrings '^https?://example\.(com|org|net)', + '^https?://(www\.)?foobar\.com', + '^http://google\.com', '^http://localhost', '127\.0\.0\.1', ]