From 57e52c67b9e7a24a9851028447a1dc18e8fa57b2 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Mon, 22 Jun 2026 15:59:14 +0200 Subject: [PATCH] ci: add external link check (lychee, hybrid non-blocking) (#339) * ci: add external link check (lychee, scheduled + on-demand) Adds a non-blocking external-link checker mirroring wickra-docs: an authoritative weekly + on-demand lychee run (this workflow), kept off the PR/release path so a flaky third-party site can never block a merge or an irreversible publish. Fixes the one redirecting link it would flag (docs.rs/wickra -> docs.rs/wickra/latest/wickra/ in SUPPORT.md). * ci: run lychee as a non-blocking PR job (continue-on-error) Hybrid link-check: the authoritative run is the scheduled links.yml; this adds an early heads-up on PRs that never gates the merge. release.yml stays ungated so a flaky third-party site can never block an irreversible publish. --- .github/workflows/ci.yml | 19 +++++++++++++++++++ .github/workflows/links.yml | 28 ++++++++++++++++++++++++++++ SUPPORT.md | 2 +- lychee.toml | 23 +++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/links.yml create mode 100644 lychee.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af6fd9d2..199848e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1146,3 +1146,22 @@ jobs: # (.github/workflows/bench.yml) — see audit finding R10. It runs nightly # at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on # the every-push / every-PR critical path. + + # Non-blocking external-link heads-up on PRs. The authoritative check is the + # scheduled links.yml; here it only surfaces link rot early without ever + # gating a PR — external sites flake (rate limits, transient outages, moves), + # so `continue-on-error` keeps a third-party hiccup from blocking the merge. + # Redirects are reported as warnings (lychee only fails on hard errors). + links: + name: External links (non-blocking) + runs-on: ubuntu-latest + continue-on-error: true + timeout-minutes: 10 + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 + with: + args: --config lychee.toml --no-progress --root-dir "${{ github.workspace }}" "*.md" "bindings/*/README.md" + fail: true diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 00000000..425f1c89 --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,28 @@ +name: Link check + +# External link rot is non-deterministic (third-party sites go down, rate-limit, +# or move), so the authoritative check runs on a weekly schedule and on demand +# rather than gating PRs or releases. ci.yml additionally runs a non-blocking +# lychee job on PRs for an early heads-up (continue-on-error), and release.yml is +# deliberately NOT gated on it — a transient external outage must never block an +# irreversible publish. +on: + schedule: + - cron: "0 6 * * 1" # Mondays 06:00 UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + links: + name: External links (lychee) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 + with: + args: --config lychee.toml --no-progress --root-dir "${{ github.workspace }}" "*.md" "bindings/*/README.md" + fail: true diff --git a/SUPPORT.md b/SUPPORT.md index f9053bd2..6e2ede9f 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -11,7 +11,7 @@ Most questions are answered in the documentation: data layer, and an FAQ. - **README:** — installation and a quick overview. -- **API docs (Rust):** . +- **API docs (Rust):** . ## Questions and help diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 00000000..5a8324a1 --- /dev/null +++ b/lychee.toml @@ -0,0 +1,23 @@ +# lychee external-link checker config (see .github/workflows/links.yml). +# Only http(s) URLs are checked; relative/internal links are skipped via the +# scheme filter and --root-dir resolution. Loopback and private addresses are +# skipped too. + +max_retries = 3 +retry_wait_time = 2 +timeout = 20 + +# Treat these as success: OK, partial content, and rate-limited (transient). +accept = [200, 206, 429] + +scheme = ["https", "http"] +exclude_all_private = true + +# Known false positives (the URLs are valid; lychee just can't verify them as a +# non-browser client): +# - crates.io and npmjs.com return 403/404 to non-browser requests +# (anti-scraping); the package pages are live in a browser. +exclude = [ + 'crates\.io/crates/', + 'www\.npmjs\.com/package/', +]