Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ cypress-a11y-report.json
/dynamic-demo-plugin/**/dist
**/.claude/settings.local.json
**/chartstore-*/
link-markdown-check.csv
121 changes: 121 additions & 0 deletions contrib/check-md-links.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/bin/bash
#
# check-md-links.sh — Check markdown files for broken or redirected URLs.
#
# Extracts all http(s) URLs from the given markdown files, tests each one
# with curl, and writes problems (404s, errors, redirects) to a CSV report.
#
# Usage:
# ./contrib/check-md-links.sh README.md
# ./contrib/check-md-links.sh CONTRIBUTING.md
# ./contrib/check-md-links.sh README.md TESTING.md STYLEGUIDE.md
# ./contrib/check-md-links.sh -o report.csv README.md
#
# Output:
# link-markdown-check.csv (or custom path via -o) in the current directory.
# Only problematic links (404, redirects, errors) are written.
#
# Idempotent: overwrites the output CSV on every run.

set -uo pipefail

OUTPUT="link-markdown-check.csv"
FILES=()

usage() {
cat <<'EOF'
check-md-links.sh — Check markdown files for broken or redirected URLs.

Usage:
./contrib/check-md-links.sh README.md
./contrib/check-md-links.sh CONTRIBUTING.md
./contrib/check-md-links.sh README.md TESTING.md STYLEGUIDE.md
./contrib/check-md-links.sh -o report.csv README.md

Output:
link-markdown-check.csv (or custom path via -o) in the current directory.
Only problematic links (404, redirects, errors) are written.
EOF
}

# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
if [ $# -lt 2 ] || [[ "$2" == -* ]]; then
echo "ERROR: -o/--output requires a value" >&2
exit 1
fi
OUTPUT="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
FILES+=("$1")
shift
;;
esac
done

# Require at least one markdown file argument
if [ ${#FILES[@]} -eq 0 ]; then
echo "ERROR: please provide at least one markdown file to check." >&2
echo "Usage: ./contrib/check-md-links.sh <file.md> [more.md ...]" >&2
echo ""
usage
exit 1
fi

# Validate inputs
for f in "${FILES[@]}"; do
if [ ! -f "$f" ]; then
echo "ERROR: $f not found" >&2
exit 1
fi
done

# Start fresh
echo "file,line,status,http_code,url,redirect_url" > "$OUTPUT"

for file in "${FILES[@]}"; do
grep -noE 'https?://[^)> "]+' "$file" | sed 's/[).,]*$//' | while IFS=: read -r line url; do
# Skip localhost / local-only URLs
case "$url" in
*localhost*|*127.0.0.1*|*api.crc.testing*) continue ;;
esac

result=$(curl -sL -o /dev/null -w "%{http_code}|%{url_effective}" --max-time 15 "$url" 2>/dev/null || echo "000|")
http_code="${result%%|*}"
effective_url="${result#*|}"

if [ "$http_code" = "000" ]; then
status="TIMEOUT/ERROR"
redirect=""
echo "$status at line $line - url: $url"
elif [ "$http_code" = "404" ]; then
status="NOT_FOUND"
redirect=""
echo "Not found at line $line - url: $url"
elif [ "$http_code" -ge 400 ] 2>/dev/null; then
status="ERROR_${http_code}"
redirect=""
echo "$status at line $line - url: $url"
elif [ "$effective_url" != "$url" ] && [ "$effective_url" != "${url}/" ] && [ "${url}/" != "$effective_url" ]; then
status="REDIRECT"
redirect="$effective_url"
echo "$status at line $line - url: $url ==> $effective_url"
else
# Link is fine, skip
continue
fi

echo "$file,$line,$status,$http_code,$url,$redirect" >> "$OUTPUT"
done
done

lines=$(tail -n +2 "$OUTPUT" | wc -l | tr -d ' ')
echo ""
echo "Done. Found $lines problem(s). Results in $OUTPUT"