-
Notifications
You must be signed in to change notification settings - Fork 1
Add GitHub Actions workflows for automated broken link detection #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
5b24a6a
1bb6149
a5248c0
5bf030a
7a2d412
5fcf12e
0f5eba7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,211 @@ | ||||||||||||||
| name: Broken Links Check - Nightly | ||||||||||||||
|
|
||||||||||||||
| on: | ||||||||||||||
| schedule: | ||||||||||||||
| # Run every day at 2:00 AM UTC | ||||||||||||||
| - cron: '0 2 * * *' | ||||||||||||||
| workflow_dispatch: # Allow manual trigger | ||||||||||||||
|
|
||||||||||||||
| permissions: | ||||||||||||||
| contents: read | ||||||||||||||
|
|
||||||||||||||
| jobs: | ||||||||||||||
| check-broken-links: | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
|
|
||||||||||||||
| steps: | ||||||||||||||
| - name: Checkout code | ||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||
|
|
||||||||||||||
| - name: Setup Node.js | ||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||
| with: | ||||||||||||||
| node-version: '18' | ||||||||||||||
| cache: 'npm' | ||||||||||||||
|
|
||||||||||||||
| - name: Install dependencies | ||||||||||||||
| run: | | ||||||||||||||
| PUPPETEER_SKIP_DOWNLOAD=true npm install | ||||||||||||||
| env: | ||||||||||||||
| NODE_ENV: production | ||||||||||||||
|
|
||||||||||||||
| - name: Run broken links check | ||||||||||||||
| id: broken_links | ||||||||||||||
| run: | | ||||||||||||||
| echo "Running broken links check..." | ||||||||||||||
| OUTPUT=$(./node_modules/.bin/mint broken-links 2>&1 || true) | ||||||||||||||
|
||||||||||||||
| echo "$OUTPUT" | ||||||||||||||
|
|
||||||||||||||
| # Save output to file | ||||||||||||||
| echo "$OUTPUT" > broken-links-output.txt | ||||||||||||||
|
|
||||||||||||||
| # Extract the summary line | ||||||||||||||
| SUMMARY=$(echo "$OUTPUT" | grep -E "found [0-9]+ broken links" || echo "No broken links found") | ||||||||||||||
| echo "summary=$SUMMARY" >> $GITHUB_OUTPUT | ||||||||||||||
|
|
||||||||||||||
| # Check if there are any broken links (non-zero count) | ||||||||||||||
| if echo "$SUMMARY" | grep -qE "found [1-9][0-9]* broken links"; then | ||||||||||||||
| echo "has_broken_links=true" >> $GITHUB_OUTPUT | ||||||||||||||
| # Count total broken links | ||||||||||||||
| TOTAL_LINKS=$(echo "$SUMMARY" | grep -oE "[0-9]+" | head -1) | ||||||||||||||
| echo "total_links=$TOTAL_LINKS" >> $GITHUB_OUTPUT | ||||||||||||||
| # Count files with broken links | ||||||||||||||
| TOTAL_FILES=$(echo "$SUMMARY" | grep -oE "[0-9]+" | tail -1) | ||||||||||||||
| echo "total_files=$TOTAL_FILES" >> $GITHUB_OUTPUT | ||||||||||||||
| else | ||||||||||||||
| echo "has_broken_links=false" >> $GITHUB_OUTPUT | ||||||||||||||
| echo "total_links=0" >> $GITHUB_OUTPUT | ||||||||||||||
| echo "total_files=0" >> $GITHUB_OUTPUT | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| - name: Prepare Slack message | ||||||||||||||
| id: slack_message | ||||||||||||||
| run: | | ||||||||||||||
| OUTPUT_FILE="broken-links-output.txt" | ||||||||||||||
| HAS_BROKEN_LINKS="${{ steps.broken_links.outputs.has_broken_links }}" | ||||||||||||||
| SUMMARY="${{ steps.broken_links.outputs.summary }}" | ||||||||||||||
| TOTAL_LINKS="${{ steps.broken_links.outputs.total_links }}" | ||||||||||||||
| TOTAL_FILES="${{ steps.broken_links.outputs.total_files }}" | ||||||||||||||
| REPO_URL="https://github.com/${{ github.repository }}" | ||||||||||||||
|
|
||||||||||||||
| if [ "$HAS_BROKEN_LINKS" = "true" ]; then | ||||||||||||||
| # Create a truncated version of the output for Slack (first 100 lines) | ||||||||||||||
| TRUNCATED_OUTPUT=$(head -100 "$OUTPUT_FILE") | ||||||||||||||
|
Comment on lines
+80
to
+81
|
||||||||||||||
| # Create a truncated version of the output for Slack (first 100 lines) | |
| TRUNCATED_OUTPUT=$(head -100 "$OUTPUT_FILE") | |
| # Create a truncated version of the output for Slack (first 100 lines) and add a truncation note | |
| TRUNCATED_OUTPUT="$(head -100 "$OUTPUT_FILE") | |
| [Output truncated to first 100 lines. See full report in workflow artifacts.]" |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using exit 0 when the webhook URL is not set means the workflow will show as successful even when notifications couldn't be sent. While the echo messages inform about the skip, consider whether this should be a warning or if the workflow should continue to upload artifacts even without Slack notification. The current behavior is acceptable but could be clearer with a warning annotation using echo "::warning::SLACK_WEBHOOK_URL not configured".
| if [ -z "$SLACK_WEBHOOK_URL" ]; then | |
| if [ -z "$SLACK_WEBHOOK_URL" ]; then | |
| echo "::warning::SLACK_WEBHOOK_URL not configured" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,187 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Broken Links Check - PR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| types: [opened, synchronize, reopened] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| paths: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - '**.mdx' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - '**.md' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - 'docs.json' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull-requests: write | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check-broken-links: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Checkout code | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Setup Node.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node-version: '18' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node-version: '18' | |
| node-version: '22' |
Outdated
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow always runs the broken links check with || true to prevent failures, but this means the step will never fail even if the mint command encounters an error (e.g., invalid configuration, missing dependencies). Consider checking the exit code separately and failing the workflow if the error is not related to finding broken links, to ensure genuine errors are not silently ignored.
Outdated
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script uses mapfile -t CHANGED < "$CHANGED_FILES" to read changed files into an array, but if the changed-files.txt is empty (no changed files), this will create an array with one empty string element. The subsequent comparison in the loop (line 91) will then try to match files against an empty string, which could cause issues. Consider checking if the file is empty first or handling the empty array case explicitly.
Outdated
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filter-links.sh script reads the output file line by line and performs string comparisons in nested loops (lines 90-95), resulting in O(n*m) complexity where n is the number of lines and m is the number of changed files. For repositories with many broken links and many changed files, this could be slow. Consider optimizing by using associative arrays or grep with pattern matching to improve performance.
Outdated
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name CAPTURE is used as a boolean flag to track whether to include output for the current file. This name could be more descriptive. Consider renaming to SHOULD_INCLUDE_FILE or IS_CHANGED_FILE to better convey its purpose and improve code readability.
| CAPTURE=false | |
| while IFS= read -r line; do | |
| # Check if line is a file path (file paths end with .mdx, .md, or .json) | |
| if [[ $line =~ ^[a-zA-Z0-9] ]] && ([[ $line == *.mdx ]] || [[ $line == *.md ]] || [[ $line == *.json ]]); then | |
| CURRENT_FILE="$line" | |
| # Check if this file is in changed files | |
| CAPTURE=false | |
| for changed in "${CHANGED[@]}"; do | |
| if [[ "$CURRENT_FILE" == "$changed" ]]; then | |
| CAPTURE=true | |
| break | |
| fi | |
| done | |
| if $CAPTURE; then | |
| FILTERED_OUTPUT+="${CURRENT_FILE}"$'\n' | |
| fi | |
| elif $CAPTURE && [[ $line =~ ^[[:space:]]*⎿ ]]; then | |
| SHOULD_INCLUDE_FILE=false | |
| while IFS= read -r line; do | |
| # Check if line is a file path (file paths end with .mdx, .md, or .json) | |
| if [[ $line =~ ^[a-zA-Z0-9] ]] && ([[ $line == *.mdx ]] || [[ $line == *.md ]] || [[ $line == *.json ]]); then | |
| CURRENT_FILE="$line" | |
| # Check if this file is in changed files | |
| SHOULD_INCLUDE_FILE=false | |
| for changed in "${CHANGED[@]}"; do | |
| if [[ "$CURRENT_FILE" == "$changed" ]]; then | |
| SHOULD_INCLUDE_FILE=true | |
| break | |
| fi | |
| done | |
| if $SHOULD_INCLUDE_FILE; then | |
| FILTERED_OUTPUT+="${CURRENT_FILE}"$'\n' | |
| fi | |
| elif $SHOULD_INCLUDE_FILE && [[ $line =~ ^[[:space:]]*⎿ ]]; then |
Outdated
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The multiline output delimiter uses 'EOF' as the marker, but if the actual filtered output contains the literal string 'EOF' on its own line, this will prematurely terminate the heredoc and cause parsing errors. Consider using a more unique delimiter like 'FILTERED_LINKS_EOF' or 'END_OF_FILTERED_OUTPUT' to avoid potential conflicts with actual output content.
| echo 'filtered<<EOF' | |
| echo "$FILTERED" | |
| echo 'EOF' | |
| echo 'filtered<<FILTERED_LINKS_EOF' | |
| echo "$FILTERED" | |
| echo 'FILTERED_LINKS_EOF' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow uses Node.js version 18, but this version will reach End-of-Life on April 30, 2025, which has already passed as of January 2026. Consider updating to Node.js 20 or 22 (LTS versions) to ensure continued support and security updates. This applies to both the PR and nightly workflows.