AIG #751
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Check Links | |
| on: | |
| pull_request: | |
| jobs: | |
| check-links: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.14.0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Check for broken links | |
| id: check-links | |
| run: | | |
| set +e | |
| pnpm test-links > links-output.txt 2>&1 | |
| set -e | |
| if grep -q "broken links" links-output.txt; then | |
| echo "⚠️ Found broken links:" | |
| echo "has_broken_links=true" >> $GITHUB_OUTPUT | |
| cat links-output.txt | |
| else | |
| echo "✅ No broken links found" | |
| echo "has_broken_links=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check for redirect conflicts | |
| id: check-redirects | |
| run: | | |
| set +e | |
| pnpm check-redirect-conflicts > redirects-output.txt 2>&1 | |
| REDIRECTS_EXIT=$? | |
| set -e | |
| if [ $REDIRECTS_EXIT -ne 0 ]; then | |
| echo "⚠️ Found redirects that conflict with each other:" | |
| echo "has_conflicts=true" >> $GITHUB_OUTPUT | |
| cat redirects-output.txt | |
| else | |
| echo "✅ No redirect conflicts found" | |
| echo "has_conflicts=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Filter exceptions and determine result | |
| if: steps.check-links.outputs.has_broken_links == 'true' || steps.check-redirects.outputs.has_conflicts == 'true' | |
| run: | | |
| node << 'EOF' | |
| const fs = require('fs'); | |
| // Load exceptions | |
| const exceptions = JSON.parse(fs.readFileSync('custom/link-exceptions.json', 'utf8')); | |
| // Read outputs | |
| const linksOutput = fs.existsSync('links-output.txt') | |
| ? fs.readFileSync('links-output.txt', 'utf8') | |
| : ''; | |
| const redirectsOutput = fs.existsSync('redirects-output.txt') | |
| ? fs.readFileSync('redirects-output.txt', 'utf8') | |
| : ''; | |
| // Extract broken links from output | |
| const brokenLinks = []; | |
| const redirectConflicts = []; | |
| // Parse links output - mint broken-links outputs like " ⎿ /bad-path/thing" | |
| linksOutput.split('\n').forEach(line => { | |
| const trimmed = line.trim(); | |
| // Look for patterns like "⎿ /path" or direct "/path" or "https://url" | |
| if (trimmed && (trimmed.startsWith('/') || trimmed.startsWith('http') || trimmed.includes('⎿'))) { | |
| // Extract the actual path after the ⎿ symbol if present | |
| const match = trimmed.match(/⎿\s+(.+)/) || [null, trimmed]; | |
| const link = match[1]; | |
| if (link && (link.startsWith('/') || link.startsWith('http'))) { | |
| brokenLinks.push(link); | |
| } | |
| } | |
| }); | |
| // Parse redirects output - format is "source -> destination" | |
| redirectsOutput.split('\n').forEach(line => { | |
| const trimmed = line.trim(); | |
| // Skip pnpm metadata lines (starting with > or empty) | |
| if (trimmed && !trimmed.startsWith('>') && trimmed.includes('->')) { | |
| redirectConflicts.push(trimmed); | |
| } | |
| }); | |
| // Check if a link matches an exception pattern | |
| function matchesException(link, pattern) { | |
| // No wildcards - exact match with trailing slash normalization | |
| if (!pattern.includes('*')) { | |
| // Normalize both by removing trailing slashes | |
| const normalizedLink = link.replace(/\/$/, ''); | |
| const normalizedPattern = pattern.replace(/\/$/, ''); | |
| return normalizedLink === normalizedPattern; | |
| } | |
| // Convert pattern to regex | |
| const regexPattern = pattern | |
| .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except * | |
| .replace(/\*/g, '.*'); // Replace * with .* | |
| const regex = new RegExp('^' + regexPattern + '$'); | |
| return regex.test(link); | |
| } | |
| // Filter exceptions only for links (not redirects) | |
| const actualLinkErrors = brokenLinks.filter(link => { | |
| return !exceptions.some(exception => matchesException(link, exception)); | |
| }); | |
| const hasErrors = actualLinkErrors.length > 0 || redirectConflicts.length > 0; | |
| if (hasErrors) { | |
| console.log('❌ Found issues:'); | |
| if (actualLinkErrors.length > 0) { | |
| console.log('\nBroken links not in exceptions:'); | |
| actualLinkErrors.forEach(link => console.log(` - ${link}`)); | |
| } | |
| if (redirectConflicts.length > 0) { | |
| console.log('\nRedirect conflicts:'); | |
| redirectConflicts.forEach(conflict => console.log(` - ${conflict}`)); | |
| } | |
| process.exit(1); | |
| } else if (brokenLinks.length > 0) { | |
| console.log('✅ All broken links are in exceptions list'); | |
| process.exit(0); | |
| } else { | |
| console.log('✅ No broken links or redirect conflicts found'); | |
| process.exit(0); | |
| } | |
| EOF |