Skip to content
Open
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
9 changes: 9 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(gh run list:*)",
"Bash(gh run view:*)"
],
"deny": []
}
}
47 changes: 47 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# GitHub Actions Workflows

This directory contains GitHub Actions workflows for automated testing and quality assurance.

## link-check.yml

**Purpose:** Validates all links in documentation previews on ReadTheDocs.

**Triggers:**
- Pull requests opened, synchronized, or reopened against `main` branch

**What it does:**
1. **Waits for ReadTheDocs Preview:** Monitors the ReadTheDocs build process and waits for the PR preview to become available
2. **Runs Link Checker:** Uses [muffet](https://github.com/raviqqe/muffet) to crawl all pages and validate both internal and external links
3. **Reports Results:** Comments on the PR with detailed results and uploads full logs as artifacts
4. **Fails PR if broken links found:** Prevents merging documentation with broken links

**Configuration:**
- **Timeout:** 15 minutes total (10 minutes to wait for ReadTheDocs + 5 minutes for link checking)
- **Excluded domains:** Social media sites (LinkedIn, Twitter/X, Facebook, Discord) that often block crawlers
- **Rate limiting:** 10 requests/second to be respectful to external sites
- **User agent:** Identifies as a link checker to external sites

**ReadTheDocs URL Pattern:**
The workflow constructs preview URLs using the pattern:
```
https://wafer-space--{PR_NUMBER}.org.readthedocs.build/en/{PR_NUMBER}/
```

**Artifacts:**
- Full muffet output is saved as workflow artifacts for 30 days
- Includes detailed information about any broken links found

**Benefits:**
- **Prevents broken links:** Catches broken links before they reach main branch
- **Validates external references:** Ensures external documentation links remain valid
- **Improves user experience:** Users won't encounter 404s in documentation
- **Automated quality assurance:** No manual link checking required

**Example Results:**
- ✅ **PASSED:** All links are valid, PR can be merged
- ❌ **FAILED:** Broken links found, detailed report provided in PR comment

**Troubleshooting:**
- If ReadTheDocs preview doesn't load within 10 minutes, check ReadTheDocs build status
- If external links fail intermittently, they may be temporarily unavailable or blocking crawlers
- Check the full muffet output in artifacts for detailed error information
215 changes: 215 additions & 0 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
name: Link Check on ReadTheDocs Preview

on:
pull_request:
types: [opened, synchronize, reopened]
branches: [main]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
wait-for-readthedocs:
name: Wait for ReadTheDocs Preview and Check Links
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Get PR number
id: pr
run: echo "number=${{ github.event.number }}" >> $GITHUB_OUTPUT

- name: Wait for ReadTheDocs build to complete
id: wait-rtd
run: |
PR_NUMBER="${{ steps.pr.outputs.number }}"
PROJECT_SLUG="wafer-space"
PREVIEW_URL="https://wafer-space--${PR_NUMBER}.org.readthedocs.build/en/${PR_NUMBER}/"

echo "Waiting for ReadTheDocs preview at: $PREVIEW_URL"
echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT

# Function to check if URL returns 200
check_url() {
local url=$1
local status_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" || echo "000")
echo "$status_code"
}

# Wait up to 10 minutes for the preview to be available
MAX_ATTEMPTS=60
SLEEP_INTERVAL=10
ATTEMPT=1

echo "Checking ReadTheDocs preview availability..."
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
STATUS_CODE=$(check_url "$PREVIEW_URL")

if [ "$STATUS_CODE" = "200" ]; then
echo "✅ ReadTheDocs preview is available!"
echo "Preview URL: $PREVIEW_URL"
exit 0
else
echo "⏳ Attempt $ATTEMPT/$MAX_ATTEMPTS: Preview not ready (HTTP $STATUS_CODE)"
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "❌ ReadTheDocs preview did not become available within timeout"
echo "Final status code: $STATUS_CODE"
exit 1
fi
sleep $SLEEP_INTERVAL
ATTEMPT=$((ATTEMPT + 1))
fi
done

- name: Install muffet
run: |
# Install muffet link checker
wget -q https://github.com/raviqqe/muffet/releases/latest/download/muffet_linux_amd64.tar.gz
tar -xzf muffet_linux_amd64.tar.gz
sudo mv muffet /usr/local/bin/
muffet --version

- name: Run link checker with muffet
id: link-check
run: |
PREVIEW_URL="${{ steps.wait-rtd.outputs.preview_url }}"

echo "🔍 Running link checker on: $PREVIEW_URL"

# Create results directory
mkdir -p link-check-results

# Run muffet with comprehensive options
set +e # Don't exit on muffet errors, we want to capture them

muffet \
--verbose \
--color=never \
--buffer-size=8192 \
--max-connections=10 \
--max-connections-per-host=2 \
--rate-limit=10 \
--timeout=30s \
--user-agent="Mozilla/5.0 (compatible; link-checker/1.0)" \
--exclude=".*linkedin\.com.*" \
--exclude=".*twitter\.com.*" \
--exclude=".*x\.com.*" \
--exclude=".*facebook\.com.*" \
--exclude=".*discord\.gg.*" \
--header="Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
--one-page-only=false \
"$PREVIEW_URL" \
> link-check-results/muffet-output.txt 2>&1

MUFFET_EXIT_CODE=$?

echo "exit_code=$MUFFET_EXIT_CODE" >> $GITHUB_OUTPUT

# Display results
echo "📊 Link Check Results:"
echo "Exit code: $MUFFET_EXIT_CODE"
echo ""

if [ $MUFFET_EXIT_CODE -eq 0 ]; then
echo "✅ All links are valid!"
else
echo "❌ Some links failed validation"
echo ""
echo "Detailed output:"
cat link-check-results/muffet-output.txt
fi

- name: Upload link check results
uses: actions/upload-artifact@v4
if: always()
with:
name: link-check-results
path: link-check-results/
retention-days: 30

- name: Comment PR with results
uses: actions/github-script@v7
if: always()
with:
script: |
const fs = require('fs');
const path = './link-check-results/muffet-output.txt';
const exitCode = '${{ steps.link-check.outputs.exit_code }}';
const previewUrl = '${{ steps.wait-rtd.outputs.preview_url }}';

let output = '';
if (fs.existsSync(path)) {
output = fs.readFileSync(path, 'utf8');
}

const success = exitCode === '0';
const status = success ? '✅ PASSED' : '❌ FAILED';
const emoji = success ? '🎉' : '🔍';

let comment = `## ${emoji} Link Check Results - ${status}\n\n`;
comment += `**ReadTheDocs Preview:** ${previewUrl}\n\n`;

if (success) {
comment += `🎉 **All links are valid!**\n\n`;
comment += `The link checker found no broken links in your documentation.\n`;
} else {
comment += `⚠️ **Some links failed validation**\n\n`;

if (output) {
// Truncate output if too long for GitHub comment
const maxLength = 30000;
let truncatedOutput = output;
if (output.length > maxLength) {
truncatedOutput = output.substring(0, maxLength) + '\n\n... (output truncated, see full results in artifacts)';
}

comment += `<details><summary>🔍 Click to view detailed results</summary>\n\n`;
comment += '```\n' + truncatedOutput + '\n```\n\n';
comment += `</details>\n\n`;
}

comment += `📊 Full results are available in the [workflow artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).\n`;
}

comment += `\n---\n`;
comment += `*Link check performed by [muffet](https://github.com/raviqqe/muffet) on commit ${{ github.sha }}*`;

// Find existing comment to update or create new one
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});

const existingComment = comments.find(comment =>
comment.body.includes('Link Check Results')
);

if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}

- name: Fail workflow if links are broken
if: steps.link-check.outputs.exit_code != '0'
run: |
echo "❌ Link check failed with exit code ${{ steps.link-check.outputs.exit_code }}"
echo "Some links in the documentation are broken."
echo "Please check the detailed output above and fix the broken links."
exit 1
11 changes: 10 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,13 @@ make clean-venv
- When making changes, start a new git branch and then make sure to commit to the git repository frequently as you work
- Once the work has been completed ask the user if you should merge the created branch into the main branch
- Don't delete the branches after merging
- When merging the branches to main, make sure to not fast forward
- When merging the branches to main, make sure to not fast forward

## Automated Testing

### Link Checking
- All pull requests automatically trigger link checking via GitHub Actions
- The workflow waits for ReadTheDocs preview deployment, then uses muffet to validate all links
- Both internal navigation and external references are tested
- PR comments show results and detailed reports are available as workflow artifacts
- Broken links will fail the PR status checks and prevent merging
1 change: 1 addition & 0 deletions temp-wafer-space-jekyll-theme
Loading