name: Docs link check on: push: branches: - main pull_request: permissions: contents: read jobs: lychee: runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 # Cache lychee results (e.g. to avoid hitting rate limits) # https://lychee.cli.rs/github_action_recipes/caching/ - name: Restore lychee cache uses: actions/cache@v4 with: path: .lycheecache key: cache-lychee-${{ github.sha }} restore-keys: cache-lychee- - name: Check documentation links with lychee uses: lycheeverse/lychee-action@v2 with: # TODO: once DYN-2259 is fixed remove exclusion of ./docs/components/profiler/profiler_guide.md args: >- --cache --no-progress --max-retries 4 --retry-wait-time 3 --timeout 30 --root-dir ${{ github.workspace }} --exclude-path ".*ATTRIBUTIONS.*" --exclude-path "./lib/llm/tests/data/.*" --exclude-path "./docs/components/profiler/profiler_guide.md" --accept "200..=299, 403, 429" --exclude-all-private --exclude 0.0.0.0 ${{ github.event_name == 'pull_request' && '--offline' || '' }} . fail: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} broken-links-check: name: Check for broken markdown links runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install Python dependencies run: | python -m pip install --upgrade pip # No additional dependencies needed for the basic script - name: Run broken links check id: check-links run: | echo "Running broken links check on documentation files..." # Run the broken links detection script and capture exit code set +e # Don't exit immediately on error python3 .github/workflows/detect_broken_links.py \ --verbose \ --format json \ --check-symlinks \ --output broken-links-report.json \ . exit_code=$? set -e # Re-enable exit on error # Check if the script found any broken links (exit code 1 means broken links found) if [ $exit_code -eq 1 ]; then echo "::error::Broken links found in documentation files" echo "broken_links_found=true" >> $GITHUB_OUTPUT elif [ $exit_code -eq 0 ]; then echo "::notice::No broken links found" echo "broken_links_found=false" >> $GITHUB_OUTPUT else echo "::error::Script failed with exit code $exit_code" echo "broken_links_found=error" >> $GITHUB_OUTPUT exit $exit_code fi - name: Display broken links and create annotations if: steps.check-links.outputs.broken_links_found == 'true' run: | echo "::group::šŸ”— Broken Links Found" # Parse and display the JSON report in a readable format, plus create GitHub annotations python3 -c " import json import sys import os try: with open('broken-links-report.json', 'r') as f: report = json.load(f) summary = report['summary'] broken_links = report['broken_links'] problematic_symlinks = report.get('problematic_symlinks', {}) has_broken_links = bool(broken_links) has_problematic_symlinks = bool(problematic_symlinks) if has_broken_links: print('āŒ BROKEN LINKS DETECTED') print('=' * 50) print(f'šŸ“Š Summary:') print(f' • Total files processed: {summary[\"total_files_processed\"]}') print(f' • Files with broken links: {summary[\"files_with_broken_links\"]}') print(f' • Total broken links: {summary[\"total_broken_links\"]}') print() if broken_links: print('šŸ” Detailed Broken Links Report:') print('-' * 40) for file_path, links in broken_links.items(): print(f'\\nšŸ“„ File: {file_path}') print(f' {len(links)} broken link(s) found') print() for i, link in enumerate(links, 1): line = link['line'] link_text = link['link_text'] link_url = link['link_url'] error_reason = link.get('error_reason', 'Link target not found') github_url = link.get('github_url', '') # Create GitHub annotation for each broken link annotation_msg = f'Broken link: [{link_text}]({link_url}) - {error_reason}' if github_url: annotation_msg += f' - View: {github_url}' print(f'::error file={file_path},line={line}::{annotation_msg}') # Display in workflow output print(f' {i}. Line {line}: [{link_text}]({link_url})') print(f' āŒ {error_reason}') if github_url: print(f' šŸ”— View on GitHub: {github_url}') else: print(f' šŸ“ Target: {link_url}') print() if has_problematic_symlinks: if has_broken_links: print('\\n' + '=' * 50) print('šŸ”— PROBLEMATIC SYMBOLIC LINKS DETECTED') print('=' * 50) print(f'šŸ“Š Summary:') print(f' • Total problematic symlinks: {summary.get(\"total_problematic_symlinks\", 0)}') print() for category, symlinks in problematic_symlinks.items(): if symlinks: category_icons = { 'broken': 'šŸ’”', 'circular': 'šŸ”„', 'external': '🌐', 'suspicious': 'āš ļø' } icon = category_icons.get(category, 'ā“') print(f'{icon} {category.upper()} SYMLINKS ({len(symlinks)}):') print('-' * 40) for i, symlink in enumerate(symlinks, 1): symlink_path = symlink['symlink_path'] target_path = symlink['target_path'] issue = symlink['issue'] # Create GitHub annotation for each problematic symlink annotation_msg = f'Problematic symlink: {issue}' print(f'::error file={symlink_path}::{annotation_msg}') # Display in workflow output print(f' {i}. {symlink_path}') print(f' → {target_path}') print(f' āŒ {issue}') print() print('=' * 50) print('āœ… Next Steps:') if has_broken_links: print('1. Check the annotations above in the Files Changed tab (for PRs)') print('2. Click the GitHub links to jump directly to each broken link') print('3. Fix all broken links before merging') if has_problematic_symlinks: step_num = 4 if has_broken_links else 1 print(f'{step_num}. Review and fix problematic symbolic links') print(f'{step_num + 1}. Consider replacing broken symlinks with actual files or fixing targets') print(f'{step_num + 2}. Evaluate if suspicious symlinks with many traversals are necessary') final_step = (7 if has_broken_links and has_problematic_symlinks else 4 if has_broken_links else 4 if has_problematic_symlinks else 1) print(f'{final_step}. Re-run this workflow to verify fixes') except Exception as e: print(f'āŒ Error reading broken links report: {e}') sys.exit(1) " echo "::endgroup::" - name: Alert for broken links if: steps.check-links.outputs.broken_links_found == 'true' || steps.check-links.outputs.broken_links_found == 'error' run: | if [ "${{ steps.check-links.outputs.broken_links_found }}" = "error" ]; then echo "::error::Workflow failed due to script error" echo "Please check the workflow logs for details." exit 1 else echo "::error::āŒ WORKFLOW FAILED: Broken links found in documentation files" echo "" echo "šŸ” What to do next:" echo "1. Check the 'Display broken links and create annotations' step above for details" echo "2. Look for ::error annotations in the Files Changed tab (for PRs)" echo "3. Click the GitHub URLs in the workflow output to jump directly to each broken link" echo "4. Fix all broken links before merging" echo "" echo "šŸ’” Each broken link shows:" echo " - File name and line number where the broken link is located" echo " - The broken link text and target URL" echo " - A clickable GitHub URL to jump directly to the problem line" exit 1 fi