|
1 | 1 | name: Docs Quality |
2 | | - |
3 | 2 | on: |
4 | 3 | pull_request: |
5 | | - paths: |
6 | | - - "docs/**/*.md" |
7 | | - - "docs/**/*.mdx" |
8 | | - - "_typos.toml" |
9 | | - - ".github/workflows/docs-quality.yml" |
10 | | - push: |
11 | | - branches: [main] |
12 | | - workflow_dispatch: {} |
13 | | - |
14 | | -permissions: |
15 | | - contents: read |
16 | | - |
| 4 | + workflow_dispatch: |
17 | 5 | jobs: |
18 | 6 | typos: |
19 | 7 | name: Spelling (typos-cli) |
20 | 8 | runs-on: ubuntu-latest |
21 | 9 | steps: |
22 | | - - uses: actions/checkout@v4 |
| 10 | + - name: Checkout |
| 11 | + uses: actions/checkout@v6 |
23 | 12 | - uses: crate-ci/typos@master |
24 | 13 |
|
25 | 14 | harper: |
26 | | - name: Grammar & headings (Harper) |
| 15 | + name: Grammar and Style Check |
27 | 16 | runs-on: ubuntu-latest |
28 | 17 | continue-on-error: true |
29 | 18 | steps: |
30 | | - - uses: actions/checkout@v4 |
31 | | - |
32 | | - - name: Cache harper-cli binary |
| 19 | + - name: Checkout |
| 20 | + uses: actions/checkout@v6 |
| 21 | + - name: Cache Harper CLI |
33 | 22 | id: cache-harper |
34 | 23 | uses: actions/cache@v4 |
35 | 24 | with: |
36 | | - path: ~/.cargo/bin/harper-cli |
37 | | - key: harper-cli-${{ runner.os }}-v1 |
| 25 | + path: | |
| 26 | + ~/.cargo/bin/harper-cli |
| 27 | + ~/.cargo/registry |
| 28 | + ~/.cargo/git |
| 29 | + key: ${{ runner.os }}-harper-cli |
38 | 30 | restore-keys: | |
39 | | - harper-cli-${{ runner.os }}- |
| 31 | + ${{ runner.os }}-harper-cli |
40 | 32 | - name: Report cache status |
41 | 33 | run: | |
42 | 34 | echo "cache-hit output: '${{ steps.cache-harper.outputs.cache-hit }}'" |
43 | | - if [ -f ~/.cargo/bin/harper-cli ]; then |
44 | | - echo "harper-cli binary present at ~/.cargo/bin/harper-cli" |
| 35 | + ls -la ~/.cargo/bin/ 2>&1 || echo "~/.cargo/bin does not exist" |
| 36 | + if [ -x ~/.cargo/bin/harper-cli ]; then |
| 37 | + echo "RESULT: harper-cli binary present and executable" |
| 38 | + ~/.cargo/bin/harper-cli --version || true |
45 | 39 | else |
46 | | - echo "harper-cli binary NOT present -- will build from source" |
| 40 | + echo "RESULT: harper-cli binary NOT present -- full reinstall will run" |
47 | 41 | fi |
48 | | - - name: Install harper-cli |
| 42 | + - name: Install Harper CLI |
49 | 43 | if: steps.cache-harper.outputs.cache-hit != 'true' |
50 | 44 | run: cargo install --locked --git https://github.com/Automattic/harper.git harper-cli |
51 | | - |
52 | 45 | - name: Lint docs with Harper |
53 | 46 | run: | |
54 | 47 | set -uo pipefail |
55 | | - IGNORED_RULES=( |
56 | | - SpellCheck |
57 | | - OrthographicConsistency |
58 | | - ProperNouns |
59 | | - CompaniesProductsAndTrademarks |
60 | | - GoogleNames MicrosoftNames AmazonNames AzureNames MetaNames |
61 | | - LongSentences Hedging FillerWords |
62 | | - ExpandStandardInputAndOutput ExpandTimeShorthands ExpandMemoryShorthands |
63 | | - ExpandDirectory ExpandArgument ExpandParameter ExpandPointer |
64 | | - ExpandDependencies ExpandControl ExpandAlgorithm ExpandAlloc |
65 | | - ExpandDecl ExpandVulnerability |
| 48 | + ONLY_RULES=( |
| 49 | + UseTitleCase |
| 50 | + RepeatedWords |
| 51 | + MergeWords |
| 52 | + ItsContraction |
66 | 53 | ) |
67 | | - IGNORE_ARGS=() |
68 | | - for r in "${IGNORED_RULES[@]}"; do IGNORE_ARGS+=(--ignore "$r"); done |
| 54 | + ONLY_ARG="--only $(IFS=,; echo "${ONLY_RULES[*]}")" |
69 | 55 | out="$(mktemp)" |
70 | 56 | total=0 |
71 | 57 | bad=0 |
72 | 58 | while IFS= read -r -d '' file; do |
73 | 59 | total=$((total+1)) |
74 | 60 | echo "::group::$file" |
75 | | - if harper-cli lint "${IGNORE_ARGS[@]}" "$file" 2>&1 | tee -a "$out"; then |
| 61 | + if harper-cli lint $ONLY_ARG "$file" 2>&1 | tee -a "$out"; then |
76 | 62 | : |
77 | 63 | else |
78 | 64 | bad=$((bad+1)) |
79 | 65 | fi |
80 | 66 | echo "::endgroup::" |
81 | | - done < <(find docs \( -name '*.md' -o -name '*.mdx' \) -print0) |
| 67 | + done < <( |
| 68 | + find docs src/pages \ |
| 69 | + -type f \( -name "*.md" -o -name "*.mdx" \) \ |
| 70 | + -print0 2>/dev/null |
| 71 | + ) |
82 | 72 | titlecase=$(grep -c 'Capitalization::UseTitleCase' "$out" || true) |
83 | 73 | { |
84 | 74 | echo "### Harper summary" |
85 | 75 | echo "- Files scanned: $total" |
86 | 76 | echo "- Files with issues: $bad" |
87 | | - echo "- Heading (UseTitleCase) violations: $titlecase" |
| 77 | + echo "- Heading title case violations: $titlecase" |
88 | 78 | echo "" |
89 | 79 | echo "### Violations by rule" |
90 | 80 | echo '```' |
91 | | - grep -oE '\[[A-Za-z]+::[A-Za-z]+\]' "$out" | sort | uniq -c | sort -rn |
| 81 | + grep -oE '\[[A-Za-z]+::[A-Za-z]+\]' "$out" | sort | uniq -c | sort -rn || true |
92 | 82 | echo '```' |
93 | 83 | } >> "$GITHUB_STEP_SUMMARY" |
94 | 84 | echo "== Harper: $bad/$total files with issues; $titlecase UseTitleCase violations ==" |
95 | 85 | [ "$bad" -eq 0 ] |
| 86 | + - name: Annotate PR with Harper warnings |
| 87 | + if: always() |
| 88 | + run: | |
| 89 | + set -uo pipefail |
| 90 | + ONLY_RULES=( |
| 91 | + UseTitleCase |
| 92 | + RepeatedWords |
| 93 | + MergeWords |
| 94 | + ItsContraction |
| 95 | + ) |
| 96 | + ONLY_ARG="--only $(IFS=,; echo "${ONLY_RULES[*]}")" |
| 97 | + while IFS= read -r -d '' file; do |
| 98 | + if json_out="$(harper-cli lint $ONLY_ARG --format json "$file" 2>/dev/null)"; then |
| 99 | + : |
| 100 | + else |
| 101 | + printf '%s' "$json_out" | python3 -c "import json,sys;d=json.load(sys.stdin);esc=lambda s: s.replace('%','%25').replace('\r','%0D').replace('\n','%0A');[print('::warning file='+esc(f.get('file',''))+',line='+str(l.get('line',1))+',col='+str(l.get('column',1))+',title='+esc(l.get('rule','?'))+'::'+esc(l.get('message',''))) for f in d for l in f.get('lints',[])]" || true |
| 102 | + fi |
| 103 | + done < <( |
| 104 | + find docs src/pages \ |
| 105 | + -type f \( -name "*.md" -o -name "*.mdx" \) \ |
| 106 | + -print0 2>/dev/null |
| 107 | + ) |
0 commit comments