Skip to content

Commit a870b91

Browse files
committed
chore: add weekly link-rot GitHub Action
- Saturday cron plus manual dispatch; gh-api liveness check with archived and 12-month-stale flags - opens or updates a `link-rot` issue only when a dead link exists (archived and stale are review hints, not auto-removed) - reusable .github/scripts/link-check.sh runs locally too; ignore the report.md artifact
1 parent af7849a commit a870b91

3 files changed

Lines changed: 89 additions & 0 deletions

File tree

.github/scripts/link-check.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
# Link-rot + staleness check for the entry list.
3+
# Verifies every entry repo is reachable; flags archived and >12-month-stale repos.
4+
# Runs locally (macOS/Linux) and in CI. Uses only `gh` (no jq/curl dependency).
5+
# Exits nonzero only when a dead link exists — archived/stale are review hints.
6+
set -uo pipefail
7+
8+
README="${1:-README.md}"
9+
today=$(date -u +%F)
10+
11+
# 12-month cutoff as YYYY-MM-DD (ISO dates sort lexically == chronologically).
12+
if date -u -d '12 months ago' +%F >/dev/null 2>&1; then
13+
cutoff=$(date -u -d '12 months ago' +%F) # GNU date (CI)
14+
else
15+
cutoff=$(date -u -v-12m +%F) # BSD date (macOS)
16+
fi
17+
18+
# Extract entry repos (owner/repo) from list-item GitHub links.
19+
repos=$(grep -oE '^- \[[^]]+\]\(https://github.com/[^)]+\)' "$README" \
20+
| grep -oE 'github\.com/[^/)#]+/[^/)#]+' | sed 's#github\.com/##' | sort -u)
21+
total=$(printf '%s\n' "$repos" | grep -c .)
22+
23+
dead=""; n_dead=0
24+
arch=""; n_arch=0
25+
stale=""; n_stale=0
26+
27+
for r in $repos; do
28+
if ! out=$(gh api "repos/$r" --jq '(.archived|tostring)+" "+(.pushed_at|.[0:10])' 2>/dev/null); then
29+
dead="${dead}- \`${r}\`"$'\n'; n_dead=$((n_dead + 1)); continue
30+
fi
31+
a=${out%% *}; p=${out##* }
32+
[ "$a" = "true" ] && { arch="${arch}- \`${r}\` (pushed ${p})"$'\n'; n_arch=$((n_arch + 1)); }
33+
[[ "$p" < "$cutoff" ]] && { stale="${stale}- \`${r}\` (pushed ${p})"$'\n'; n_stale=$((n_stale + 1)); }
34+
done
35+
36+
{
37+
echo "## Link-rot report — ${today}"
38+
echo
39+
echo "- Checked: ${total} entry links"
40+
echo "- Dead: ${n_dead}"
41+
echo "- Archived: ${n_arch}"
42+
echo "- Stale (>12 months): ${n_stale}"
43+
[ "$n_dead" -gt 0 ] && { echo; echo "### ❌ Dead links (fix or remove)"; printf '%s' "$dead"; }
44+
[ "$n_arch" -gt 0 ] && { echo; echo "### 📦 Archived (review — a frozen model repo is often fine)"; printf '%s' "$arch"; }
45+
[ "$n_stale" -gt 0 ] && { echo; echo "### 🕸 Stale >12 months (review)"; printf '%s' "$stale"; }
46+
} | tee report.md
47+
48+
[ "$n_dead" -eq 0 ]

.github/workflows/link-rot.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: link-rot
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * 6' # Saturday 00:00 UTC (Sat 09:00 KST)
6+
workflow_dispatch: {}
7+
8+
permissions:
9+
contents: read
10+
issues: write
11+
12+
jobs:
13+
check:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Check entry links
19+
id: check
20+
env:
21+
GH_TOKEN: ${{ github.token }}
22+
run: |
23+
if bash .github/scripts/link-check.sh README.md; then
24+
echo "dead=0" >> "$GITHUB_OUTPUT"
25+
else
26+
echo "dead=1" >> "$GITHUB_OUTPUT"
27+
fi
28+
29+
- name: Report dead links to an issue
30+
if: steps.check.outputs.dead == '1'
31+
env:
32+
GH_TOKEN: ${{ github.token }}
33+
run: |
34+
gh label create link-rot --color B60205 --description "Dead or rotten entry link" --force
35+
existing=$(gh issue list --label link-rot --state open --json number --jq '.[0].number // empty')
36+
if [ -n "$existing" ]; then
37+
gh issue comment "$existing" --body-file report.md
38+
else
39+
gh issue create --title "Link rot detected" --label link-rot --body-file report.md
40+
fi

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.DS_Store
22
.gstack/
3+
report.md

0 commit comments

Comments
 (0)