Files
FreshRSS/.github/workflows/link-checker.yml
InverleandAlexandre Alapetite 456b9fbff5 Add a link checker workflow using lychee (#9209)
* Add a link checker workflow using lychee

Depends on #9207 (merged)

https://github.com/lycheeverse/lychee-action

* Adjust cron to run once every month instead

* Apply suggestions

* Pass zizmor's pedantic audit

* Set `failIfEmpty: false` for lychee action

* Add offline Lychee check to catch broken links after renames

* Add workaround for `--files-from` option not respecting `extensions`

* Escape `?` correctly in `.lycheeignore`

* Don't accept timeouts, retry instead

* Bump link checker workflow to `ubuntu-26.04`

* Always scope to host correctly in `.lycheeignore`

* Set `hidden = true` in config to ensure that dotfiles are scanned

* Exclude issue template from the scan

* with open

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-24 09:51:08 +02:00

114 lines
3.8 KiB
YAML

name: Link Checker
on:
push:
branches: [ edge ]
pull_request:
branches: [ edge ]
schedule:
- cron: '30 12 1 * *'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: link-checker-${{ github.ref }}
cancel-in-progress: false
jobs:
linkChecker:
name: linkChecker
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
# Check local links across the repository first to catch broken links in
# files that are not part of the PR's changed files.
- name: Check local links (offline run)
if: github.event_name == 'pull_request'
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
with:
fail: true
failIfEmpty: false
args: >
--offline
.
- name: Get changed files
if: github.event_name == 'pull_request'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
git diff --name-only --diff-filter=ACMR \
"$BASE_SHA" \
"$HEAD_SHA" \
> changed-files.txt
# Lychee does not apply `extensions` from the config to files passed via `--files-from`.
# Filter the changed files so only allowed file types are checked.
python3 <<'PY'
import pathlib
import tomllib
with open("lychee.toml", "rb") as fp:
config = tomllib.load(fp)
extensions = {e.lstrip(".").lower() for e in config["extensions"]}
files = pathlib.Path("changed-files.txt").read_text().splitlines()
files = [
f for f in files
if (
pathlib.PurePath(f).suffix.lstrip(".").lower() in extensions
or (
not pathlib.PurePath(f).suffix
and pathlib.PurePath(f).name.startswith(".")
and pathlib.PurePath(f).name.lstrip(".").lower() in extensions
)
or (
not pathlib.PurePath(f).suffix
and not pathlib.PurePath(f).name.startswith(".")
and config.get("default_extension") in extensions
)
)
]
pathlib.Path("changed-files.txt").write_text(
"\n".join(files) + ("\n" if files else "")
)
PY
- name: Restore lychee cache
id: restore-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-
- name: Run lychee
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
with:
# PAT with read-only permissions used to avoid GitHub rate limits (allows 5,000 requests per hour)
# Won't be available to forks in pull requests due to restrictions, so falls back to default
# GITHUB_TOKEN with 1,000 requests per hour instead.
token: ${{ secrets.LYCHEE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
fail: true
failIfEmpty: false # Avoid failing in PRs with no links in changed files (`true` is the default)
# Config is loaded from lychee.toml
args: >
${{ github.event_name == 'pull_request' && '--files-from changed-files.txt' || '.' }}
- name: Save lychee cache
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: always()
with:
path: .lycheecache
key: ${{ steps.restore-cache.outputs.cache-primary-key }}