Skip to content

Commit 777bddc

Browse files
committed
Enhance documentation workflows with cross-reference checking
- Adds cross-reference validation to detect broken links when files or headings change - Centralizes file processing for better efficiency - Creates a reusable docs-setup action to reduce redundancy - Updates PR comment with cross-reference validation results - Improves documentation with updated features - Optimizes code for better maintainability
1 parent 4fcb322 commit 777bddc

File tree

5 files changed

+231
-26
lines changed

5 files changed

+231
-26
lines changed

.github/docs/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
lint-markdown: true
2828
check-format: true
2929
check-links: true
30+
check-cross-references: true
3031
lint-vale: true
3132
generate-preview: true
3233
post-comment: true
@@ -47,11 +48,12 @@ The `docs-link-check.yaml` workflow runs after merges to main and on a weekly sc
4748
1. **Documentation Preview**: Generates preview links for documentation changes
4849
2. **Vale Style Checking**: Enforces consistent terminology and style
4950
3. **Link Validation**: Checks for broken links in documentation
50-
4. **Markdown Linting**: Ensures proper markdown formatting with markdownlint-cli2
51-
5. **Markdown Table Format Checking**: Checks (but doesn't apply) markdown table formatting
52-
6. **PR Comments**: Creates or updates PR comments with preview links and validation results
53-
7. **Post-Merge Validation**: Ensures documentation quality after merges to main
54-
8. **Issue Creation**: Automatically creates GitHub issues for broken links
51+
4. **Cross-Reference Validation**: Detects broken references when files or headings are changed/removed
52+
5. **Markdown Linting**: Ensures proper markdown formatting with markdownlint-cli2
53+
6. **Markdown Table Format Checking**: Checks (but doesn't apply) markdown table formatting
54+
7. **PR Comments**: Creates or updates PR comments with preview links and validation results
55+
8. **Post-Merge Validation**: Ensures documentation quality after merges to main
56+
9. **Issue Creation**: Automatically creates GitHub issues for broken links
5557

5658
## Formatting Local Workflow
5759

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: 'Docs Setup'
2+
description: 'Sets up the environment for docs-related workflows'
3+
author: 'Coder'
4+
5+
inputs:
6+
node-version:
7+
description: 'Node.js version'
8+
required: false
9+
default: '20'
10+
fetch-depth:
11+
description: 'Git fetch depth'
12+
required: false
13+
default: '0'
14+
15+
runs:
16+
using: 'composite'
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: ${{ inputs.fetch-depth }}
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: ${{ inputs.node-version }}
27+
cache: 'pnpm'
28+
29+
- name: Install PNPM
30+
uses: pnpm/action-setup@v3
31+
with:
32+
run_install: false
33+
34+
- name: Install dependencies
35+
shell: bash
36+
run: ./scripts/pnpm_install.sh

.github/docs/actions/docs-shared/action.yaml

Lines changed: 170 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ inputs:
3030
description: 'Whether to run Vale style checks on documentation'
3131
required: false
3232
default: 'true'
33+
check-cross-references:
34+
description: 'Whether to check for broken cross-references when files or headings change'
35+
required: false
36+
default: 'true'
3337
generate-preview:
3438
description: 'Whether to generate preview links'
3539
required: false
@@ -84,6 +88,9 @@ outputs:
8488
vale_results:
8589
description: 'Results from Vale style checks'
8690
value: ${{ steps.lint-vale.outputs.result || '' }}
91+
cross_ref_results:
92+
description: 'Results from cross-reference checking'
93+
value: ${{ steps.cross-references.outputs.cross_ref_results || '' }}
8794

8895
runs:
8996
using: 'composite'
@@ -110,6 +117,31 @@ runs:
110117
${{ inputs.include-md-files == 'true' && '**.md' || '' }}
111118
separator: ','
112119
json: true
120+
121+
- name: Process file lists
122+
id: process-files
123+
shell: bash
124+
run: |
125+
# Set up environment
126+
CHANGED_FILES='${{ steps.changed-files.outputs.all_changed_files_json }}'
127+
DELETED_FILES='${{ steps.changed-files.outputs.deleted_files_json || '[]' }}'
128+
129+
# Process files into different formats once
130+
echo "md_files_comma<<EOF" >> $GITHUB_OUTPUT
131+
echo "${{ steps.changed-files.outputs.all_changed_files }}" >> $GITHUB_OUTPUT
132+
echo "EOF" >> $GITHUB_OUTPUT
133+
134+
echo "md_files_line<<EOF" >> $GITHUB_OUTPUT
135+
echo "$CHANGED_FILES" | jq -r '.[] | select(endswith(".md"))' >> $GITHUB_OUTPUT
136+
echo "EOF" >> $GITHUB_OUTPUT
137+
138+
echo "docs_files_line<<EOF" >> $GITHUB_OUTPUT
139+
echo "$CHANGED_FILES" | jq -r '.[] | select(endswith(".md")) | select(startswith("${{ inputs.docs-dir }}/"))' >> $GITHUB_OUTPUT
140+
echo "EOF" >> $GITHUB_OUTPUT
141+
142+
echo "deleted_md_files_line<<EOF" >> $GITHUB_OUTPUT
143+
echo "$DELETED_FILES" | jq -r '.[] | select(endswith(".md"))' >> $GITHUB_OUTPUT
144+
echo "EOF" >> $GITHUB_OUTPUT
113145
114146
- name: Check if manifest changed
115147
id: manifest-check
@@ -239,7 +271,7 @@ runs:
239271
id: lint-docs
240272
shell: bash
241273
run: |
242-
lint_output=$(pnpm exec markdownlint-cli2 ${{ steps.changed-files.outputs.all_changed_files }} 2>&1) || true
274+
lint_output=$(pnpm exec markdownlint-cli2 ${{ steps.process-files.outputs.md_files_comma }} 2>&1) || true
243275
echo "result<<EOF" >> $GITHUB_OUTPUT
244276
echo "$lint_output" >> $GITHUB_OUTPUT
245277
echo "EOF" >> $GITHUB_OUTPUT
@@ -256,7 +288,7 @@ runs:
256288
shell: bash
257289
run: |
258290
# markdown-table-formatter requires a space separated list of files
259-
format_output=$(echo ${{ steps.changed-files.outputs.all_changed_files }} | tr ',' '\n' | pnpm exec markdown-table-formatter --check 2>&1) || true
291+
format_output=$(echo "${{ steps.process-files.outputs.md_files_line }}" | pnpm exec markdown-table-formatter --check 2>&1) || true
260292
echo "result<<EOF" >> $GITHUB_OUTPUT
261293
echo "$format_output" >> $GITHUB_OUTPUT
262294
echo "EOF" >> $GITHUB_OUTPUT
@@ -289,7 +321,7 @@ runs:
289321
shell: bash
290322
run: |
291323
# Run Vale on changed files and capture output
292-
vale_output=$(echo ${{ steps.changed-files.outputs.all_changed_files }} | tr ',' '\n' | grep '\.md$' | xargs -r vale --config=.github/docs/vale/.vale.ini --output=line 2>&1) || true
324+
vale_output=$(echo "${{ steps.process-files.outputs.md_files_line }}" | xargs -r vale --config=.github/docs/vale/.vale.ini --output=line 2>&1) || true
293325
294326
echo "result<<EOF" >> $GITHUB_OUTPUT
295327
echo "$vale_output" >> $GITHUB_OUTPUT
@@ -300,6 +332,137 @@ runs:
300332
echo "$vale_output"
301333
exit 1
302334
fi
335+
336+
- name: Check for broken cross-references
337+
if: inputs.check-cross-references == 'true' && steps.docs-analysis.outputs.has_changes == 'true'
338+
id: cross-references
339+
shell: bash
340+
run: |
341+
# Get the base branch (usually main)
342+
BASE_SHA=$(git merge-base HEAD origin/main)
343+
344+
echo "Checking for broken cross-references..."
345+
346+
# Initialize results
347+
BROKEN_REFS=""
348+
349+
# Process deleted files
350+
if [ -n "${{ steps.process-files.outputs.deleted_md_files_line }}" ]; then
351+
echo "Processing deleted files"
352+
353+
# Loop through deleted markdown files
354+
while IFS= read -r file; do
355+
[ -z "$file" ] && continue
356+
357+
echo "File $file was deleted, checking for references..."
358+
359+
# Convert file path to potential link formats (removing .md extension)
360+
OLD_PATH=$(echo "$file" | sed 's/\.md$//')
361+
362+
# Search in docs directory
363+
DOC_REFS=$(grep -r --include="*.md" -l -E "\[$OLD_PATH\]|\($OLD_PATH\)" ${{ inputs.docs-dir }} || echo "")
364+
365+
# Search in codebase (excluding specific directories)
366+
CODE_REFS=$(grep -r --include="*.{go,ts,js,py,java,cs,php}" -l "$OLD_PATH" . --exclude-dir={node_modules,.git,build,dist} || echo "")
367+
368+
if [ -n "$DOC_REFS" ] || [ -n "$CODE_REFS" ]; then
369+
BROKEN_REFS="${BROKEN_REFS}## References to deleted file: $file\n\n"
370+
371+
if [ -n "$DOC_REFS" ]; then
372+
BROKEN_REFS="${BROKEN_REFS}### In documentation:\n"
373+
BROKEN_REFS="${BROKEN_REFS}$(echo "$DOC_REFS" | sed 's/^/- /')\n\n"
374+
fi
375+
376+
if [ -n "$CODE_REFS" ]; then
377+
BROKEN_REFS="${BROKEN_REFS}### In codebase:\n"
378+
BROKEN_REFS="${BROKEN_REFS}$(echo "$CODE_REFS" | sed 's/^/- /')\n\n"
379+
fi
380+
fi
381+
done <<< "${{ steps.process-files.outputs.deleted_md_files_line }}"
382+
fi
383+
384+
# Process modified files for heading changes
385+
while IFS= read -r file; do
386+
[ -z "$file" ] && continue
387+
388+
if [ -f "$file" ]; then
389+
echo "Checking for changed headings in $file..."
390+
391+
# Extract headings before the change
392+
OLD_HEADINGS=$(git show "$BASE_SHA:$file" 2>/dev/null | grep -E "^#{1,6} " | sed 's/^#\{1,6\} \(.*\)$/\1/' || echo "")
393+
394+
# Extract current headings
395+
NEW_HEADINGS=$(cat "$file" | grep -E "^#{1,6} " | sed 's/^#\{1,6\} \(.*\)$/\1/')
396+
397+
# Find removed headings
398+
REMOVED_HEADINGS=$(comm -23 <(echo "$OLD_HEADINGS" | sort) <(echo "$NEW_HEADINGS" | sort))
399+
400+
if [ -n "$REMOVED_HEADINGS" ]; then
401+
while IFS= read -r heading; do
402+
[ -z "$heading" ] && continue
403+
404+
# Convert heading to anchor format (lowercase, spaces to hyphens)
405+
ANCHOR=$(echo "$heading" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:] -' | tr ' ' '-')
406+
407+
# Search for references to this anchor in documentation
408+
HEAD_REFS=$(grep -r --include="*.md" -l "#$ANCHOR" ${{ inputs.docs-dir }} || echo "")
409+
410+
if [ -n "$HEAD_REFS" ]; then
411+
BROKEN_REFS="${BROKEN_REFS}## References to removed heading: '$heading' in $file\n\n"
412+
BROKEN_REFS="${BROKEN_REFS}$(echo "$HEAD_REFS" | sed 's/^/- /')\n\n"
413+
fi
414+
done <<< "$REMOVED_HEADINGS"
415+
fi
416+
fi
417+
done <<< "${{ steps.process-files.outputs.docs_files_line }}"
418+
419+
# Check for renamed files by comparing paths
420+
while IFS= read -r file; do
421+
[ -z "$file" ] && continue
422+
423+
# Use git to check if this is a renamed file
424+
PREV_PATH=$(git diff --name-status "$BASE_SHA" | grep "^R" | grep "$file$" | cut -f2)
425+
426+
if [ -n "$PREV_PATH" ] && [ "$PREV_PATH" != "$file" ]; then
427+
echo "File renamed from $PREV_PATH to $file, checking for references..."
428+
429+
# Convert old file path to potential link formats
430+
OLD_PATH=$(echo "$PREV_PATH" | sed 's/\.md$//')
431+
432+
# Search in docs directory
433+
DOC_REFS=$(grep -r --include="*.md" -l -E "\[$OLD_PATH\]|\($OLD_PATH\)" ${{ inputs.docs-dir }} || echo "")
434+
435+
# Search in codebase (excluding specific directories)
436+
CODE_REFS=$(grep -r --include="*.{go,ts,js,py,java,cs,php}" -l "$OLD_PATH" . --exclude-dir={node_modules,.git,build,dist} || echo "")
437+
438+
if [ -n "$DOC_REFS" ] || [ -n "$CODE_REFS" ]; then
439+
BROKEN_REFS="${BROKEN_REFS}## References to renamed file: $PREV_PATH → $file\n\n"
440+
441+
if [ -n "$DOC_REFS" ]; then
442+
BROKEN_REFS="${BROKEN_REFS}### In documentation:\n"
443+
BROKEN_REFS="${BROKEN_REFS}$(echo "$DOC_REFS" | sed 's/^/- /')\n\n"
444+
fi
445+
446+
if [ -n "$CODE_REFS" ]; then
447+
BROKEN_REFS="${BROKEN_REFS}### In codebase:\n"
448+
BROKEN_REFS="${BROKEN_REFS}$(echo "$CODE_REFS" | sed 's/^/- /')\n\n"
449+
fi
450+
fi
451+
fi
452+
done <<< "${{ steps.process-files.outputs.md_files_line }}"
453+
454+
if [ -n "$BROKEN_REFS" ]; then
455+
echo "cross_ref_results<<EOF" >> $GITHUB_OUTPUT
456+
echo -e "$BROKEN_REFS" >> $GITHUB_OUTPUT
457+
echo "EOF" >> $GITHUB_OUTPUT
458+
459+
if [ "${{ inputs.fail-on-error }}" == "true" ]; then
460+
echo "::error::Broken cross-references found. See output for details."
461+
exit 1
462+
fi
463+
else
464+
echo "No broken cross-references found"
465+
fi
303466
304467
- name: Generate Preview URL
305468
if: inputs.generate-preview == 'true' && steps.docs-analysis.outputs.has_changes == 'true'
@@ -368,6 +531,10 @@ runs:
368531
${{ steps.lint-vale.outputs.result != '' && steps.lint-vale.outputs.result || '' }}
369532
${{ steps.lint-vale.outputs.result != '' && '```' || '' }}
370533
534+
${{ steps.cross-references.outputs.cross_ref_results != '' && '### Broken Cross-References' || '' }}
535+
${{ steps.cross-references.outputs.cross_ref_results != '' && 'The following cross-references may be broken due to file or heading changes:' || '' }}
536+
${{ steps.cross-references.outputs.cross_ref_results != '' && steps.cross-references.outputs.cross_ref_results || '' }}
537+
371538
---
372539
<sub>🤖 This comment is automatically generated and updated when documentation changes.</sub>
373540
edit-mode: replace

.github/workflows/docs-reusable-example.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
lint-markdown: true
2020
check-format: true
2121
check-links: true
22+
check-cross-references: true
2223
lint-vale: true
2324
generate-preview: true
2425
post-comment: true
@@ -77,6 +78,7 @@ jobs:
7778
check-links: "true"
7879
lint-markdown: "true"
7980
check-format: "true"
81+
check-cross-references: "true"
8082
lint-vale: "true"
8183
generate-preview: "true"
8284
post-comment: "true"
@@ -92,4 +94,8 @@ jobs:
9294
9395
if [ "${{ steps.docs-shared.outputs.format_results }}" != "" ]; then
9496
echo "Formatting issues found, please run 'make fmt/markdown' locally"
97+
fi
98+
99+
if [ "${{ steps.docs-shared.outputs.cross_ref_results }}" != "" ]; then
100+
echo "Broken cross-references found"
95101
fi

.github/workflows/docs-unified.yaml

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ on:
1717
required: false
1818
type: boolean
1919
default: true
20+
check-cross-references:
21+
description: 'Whether to check for broken cross-references when files or headings change'
22+
required: false
23+
type: boolean
24+
default: true
2025
lint-vale:
2126
description: 'Whether to run Vale style checks on documentation'
2227
required: false
@@ -51,24 +56,8 @@ jobs:
5156
with:
5257
egress-policy: audit
5358

54-
- name: Checkout
55-
uses: actions/checkout@v4
56-
with:
57-
fetch-depth: 0
58-
59-
- name: Setup Node
60-
uses: actions/setup-node@v4
61-
with:
62-
node-version: 20
63-
cache: 'pnpm'
64-
65-
- name: Install pnpm
66-
uses: pnpm/action-setup@v3
67-
with:
68-
run_install: false
69-
70-
- name: Install dependencies
71-
run: ./scripts/pnpm_install.sh
59+
- name: Setup Environment
60+
uses: ./.github/docs/actions/docs-setup
7261

7362
- name: Get PR info
7463
id: pr_info
@@ -90,6 +79,7 @@ jobs:
9079
check-links: ${{ inputs.check-links }}
9180
lint-markdown: ${{ inputs.lint-markdown }}
9281
check-format: ${{ inputs.check-format }}
82+
check-cross-references: ${{ inputs.check-cross-references }}
9383
lint-vale: ${{ inputs.lint-vale }}
9484
generate-preview: ${{ inputs.generate-preview }}
9585
post-comment: ${{ inputs.post-comment }}
@@ -115,4 +105,8 @@ jobs:
115105
116106
if [ "${{ steps.docs-shared.outputs.vale_results }}" != "" ]; then
117107
echo "Vale style issues found"
108+
fi
109+
110+
if [ "${{ steps.docs-shared.outputs.cross_ref_results }}" != "" ]; then
111+
echo "Broken cross-references found"
118112
fi

0 commit comments

Comments
 (0)