|
| 1 | +# ============================================================ |
| 2 | +# .github/workflows/links.yml (Lychee Link Checker) |
| 3 | +# ============================================================ |
| 4 | +# Updated: 2026-04-13 |
| 5 | + |
1 | 6 | # WHY-FILE: Automated link checking. |
2 | 7 | # OBS: Behavior is configured in lychee.toml in this repository. |
3 | 8 | # OBS: Runs on pull requests and monthly on schedule; manual trigger always available. |
4 | 9 |
|
5 | 10 | name: Check Links |
6 | 11 |
|
7 | 12 | on: |
| 13 | + workflow_call: # WHY: Allow this workflow to be called by other workflows if needed |
8 | 14 | workflow_dispatch: # WHY: Manual trigger - always available |
9 | | - |
10 | 15 | pull_request: # WHY: Validates PR links before merge |
11 | | - |
12 | 16 | schedule: |
13 | 17 | - cron: "0 6 1 * *" # WHY: Runs monthly (1st of month) |
14 | 18 |
|
15 | 19 | concurrency: |
16 | 20 | # WHY: Prevent multiple simultaneous link checks on same ref |
17 | | - group: link-check-${{ github.ref }} |
| 21 | + group: link-check-${{ github.ref || github.run_id }} |
18 | 22 | cancel-in-progress: true |
19 | 23 |
|
| 24 | +permissions: |
| 25 | + contents: read # WHY: Needed to checkout code. |
| 26 | + |
| 27 | +env: |
| 28 | + PYTHONUNBUFFERED: "1" # WHY: Real-time logging. |
| 29 | + PYTHONIOENCODING: "utf-8" # WHY: Ensure UTF-8 encoding for international characters. |
| 30 | + REPORT_PATH: "./lychee/out.md" # WHY: Predictable markdown report path for summary generation. |
| 31 | + |
20 | 32 | jobs: |
21 | 33 | lychee: |
22 | | - runs-on: ubuntu-latest |
23 | | - |
24 | | - permissions: |
25 | | - contents: read |
26 | | - issues: write |
27 | | - pull-requests: write |
| 34 | + name: Link checks |
| 35 | + runs-on: ubuntu-latest # WHY: Linux environment matches most production deployments |
| 36 | + timeout-minutes: 20 # WHY: Prevent hanging jobs. If over time, likely stuck. |
28 | 37 |
|
29 | 38 | steps: |
30 | | - - name: A1) Checkout repository code |
| 39 | + - name: 1) Checkout repository code |
31 | 40 | uses: actions/checkout@v6 |
| 41 | + # WHY: Required so Lychee can inspect repository files. |
32 | 42 |
|
33 | | - - name: 2) Check links with Lychee |
34 | | - uses: lycheeverse/lychee-action@v2 |
| 43 | + - name: 2) Run Lychee |
| 44 | + uses: lycheeverse/lychee-action@v2.8.0 |
35 | 45 | with: |
36 | 46 | args: > |
37 | | - --config lychee.toml |
38 | | - --user-agent "${{ github.repository }}/lychee" |
39 | | - './**/*.bib' |
| 47 | + --config .github/lychee.toml |
| 48 | + --verbose |
| 49 | + --no-progress |
40 | 50 | './**/*.md' |
41 | 51 | './**/*.html' |
42 | | - './**/*.tex' |
43 | 52 | './**/*.yml' |
44 | 53 | './**/*.yaml' |
45 | | - lycheeVersion: latest # OBS: Always use latest lychee release |
46 | | - env: |
47 | | - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
48 | | - |
49 | | - - name: 3) Comment on PR if links broken |
50 | | - if: failure() && github.event_name == 'pull_request' |
51 | | - uses: actions/github-script@v9 |
52 | | - with: |
53 | | - script: | |
54 | | - const runUrl = `${context.payload.repository.html_url}/actions/runs/${context.runId}`; |
55 | | - const comment = [ |
56 | | - "## Link Check Results", |
57 | | - "", |
58 | | - `Some links appear broken. Check the workflow logs: ${runUrl}`, |
59 | | - ].join("\n"); |
60 | | -
|
61 | | - await github.rest.issues.createComment({ |
62 | | - issue_number: context.issue.number, |
63 | | - owner: context.repo.owner, |
64 | | - repo: context.repo.repo, |
65 | | - body: comment, |
66 | | - }); |
67 | | -
|
68 | | - - name: 4) Create issue for scheduled failures # WHY: Track broken links found during scheduled checks |
69 | | - # OBS: Only creates issue if none already open with 'broken-links' label |
70 | | - if: failure() && github.event_name == 'schedule' |
71 | | - uses: actions/github-script@v9 |
72 | | - with: |
73 | | - script: | |
74 | | - const date = new Date().toISOString().split("T")[0]; |
75 | | - const title = `Link Check Failed - ${date}`; |
76 | | - const runUrl = `${context.payload.repository.html_url}/actions/runs/${context.runId}`; |
77 | | - const body = `Monthly link check found broken links.\n\nWorkflow logs: ${runUrl}`; |
78 | | -
|
79 | | - const existing = await github.rest.issues.listForRepo({ |
80 | | - owner: context.repo.owner, |
81 | | - repo: context.repo.repo, |
82 | | - labels: "broken-links", |
83 | | - state: "open", |
84 | | - }); |
85 | | -
|
86 | | - if (existing.data.length === 0) { |
87 | | - await github.rest.issues.create({ |
88 | | - owner: context.repo.owner, |
89 | | - repo: context.repo.repo, |
90 | | - title, |
91 | | - body, |
92 | | - labels: ["maintenance", "broken-links"], |
93 | | - }); |
94 | | - } |
0 commit comments