88 type : boolean
99 default : false
1010
11+ env :
12+ ALLOW_FAILURE : ${{ github.event_name == 'workflow_dispatch' && inputs.allow_failure }}
13+
1114jobs :
1215 spelling :
1316 runs-on : ubuntu-latest
@@ -19,21 +22,37 @@ jobs:
1922 - uses : actions/cache@v4
2023 with :
2124 path : ~/.npm
22- key : ${{ runner.os }}-npm-cspell-${{ hashFiles('package-lock.json') }}
25+ key : ${{ runner.os }}-npm-cspell
2326 restore-keys : ${{ runner.os }}-npm-cspell-
24- - run : npm ci
2527 - run : |
26- npx cspell lint \
28+ npx --yes cspell@9 lint \
2729 "docs/**/*.{md,mdx}" \
2830 "src/pages/**/*.{md,mdx}" \
2931 --config cspell.json \
3032 --no-progress \
3133 --reporter json \
3234 > cspell-report.json || true
33- - uses : actions/upload-artifact@v4
34- with :
35- name : cspell-report
36- path : cspell-report.json
35+ - run : |
36+ python - <<'PY'
37+ import json
38+ import os
39+ import sys
40+
41+ with open("cspell-report.json", encoding="utf-8") as f:
42+ data = json.load(f)
43+
44+ issues = data.get("issues", [])
45+ for issue in issues:
46+ file = issue.get("uri")
47+ line = issue.get("row")
48+ message = issue.get("message") or f"Unknown word: {issue.get('text')!r}"
49+ print(f"::error file={file},line={line}::{message}")
50+
51+ print(f"{len(issues)} spelling issue(s) found.")
52+
53+ allow_failure = os.environ.get("ALLOW_FAILURE") == "true"
54+ sys.exit(1 if issues and not allow_failure else 0)
55+ PY
3756
3857 grammar :
3958 runs-on : ubuntu-latest
@@ -50,162 +69,47 @@ jobs:
5069 - run : |
5170 command -v harper-cli >/dev/null 2>&1 || \
5271 cargo install --git https://github.com/Automattic/harper \
53- --rev <PIN_THIS_TO_A_COMMIT_SHA> \
72+ --tag v2.1.0 \
5473 harper-cli --locked
5574 - run : |
5675 find docs src/pages -type f \( -name "*.md" -o -name "*.mdx" \) -print0 | \
5776 xargs -0 harper-cli lint --format json > harper-report.json || true
58- - uses : actions/upload-artifact@v4
59- with :
60- name : harper-report
61- path : harper-report.json
62-
63- heading-case :
64- runs-on : ubuntu-latest
65- steps :
66- - uses : actions/checkout@v6
67- - id : check
68- continue-on-error : true
69- run : python check_heading_case.py docs src/pages
70- - run : echo "${{ steps.check.outcome }}" > heading-case-result.txt
71- - uses : actions/upload-artifact@v4
72- with :
73- name : heading-case-result
74- path : heading-case-result.txt
75-
76- aggregate :
77- needs : [spelling, grammar, heading-case]
78- runs-on : ubuntu-latest
79- steps :
80- - uses : actions/download-artifact@v4
81- - env :
82- ALLOW_FAILURE : ${{ github.event_name == 'workflow_dispatch' && inputs.allow_failure }}
83- run : |
77+ - run : |
8478 python - <<'PY'
8579 import json
8680 import os
8781 import sys
8882
89- def load_json(path):
90- try:
91- with open(path, encoding="utf-8") as f:
92- return json.load(f)
93- except FileNotFoundError:
94- return None
83+ with open("harper-report.json", encoding="utf-8") as f:
84+ reports = json.load(f)
9585
86+ ALLOWED_KINDS = {"Grammar", "Repetition"}
9687 issues = []
9788
98- cspell_data = load_json("cspell-report/cspell-report.json")
99- for issue in (cspell_data or {}).get("issues", []):
100- issues.append(
101- f"[Spelling] {issue.get('uri')}:{issue.get('row')}: "
102- f"{issue.get('text')!r} - {issue.get('message', 'unknown word')}"
103- )
104-
105- ALLOWED_KINDS = {"Grammar", "Repetition"}
106- for report in load_json("harper-report/harper-report.json") or []:
89+ for report in reports:
10790 file = report.get("file")
10891 for lint in report.get("lints", []):
109- kind = lint.get("kind")
110- if kind not in ALLOWED_KINDS:
92+ if lint.get("kind") not in ALLOWED_KINDS:
11193 continue
112- issues.append(
113- f"[{kind}::{lint.get('rule')}] {file}:{lint.get('line')}: "
114- f"{lint.get('matched_text')!r} - {lint.get('message')}"
115- )
116-
117- with open("heading-case-result/heading-case-result.txt", encoding="utf-8") as f:
118- if f.read().strip() == "failure":
119- issues.append("[HeadingCase] Title case violations found, see heading-case job log.")
94+ line = lint.get("line")
95+ message = lint.get("message")
96+ print(f"::error file={file},line={line}::{message}")
97+ issues.append(lint)
12098
121- for item in issues:
122- print(item)
123- print(f"\n{len(issues)} issue(s) found.")
99+ print(f"{len(issues)} grammar/repetition issue(s) found.")
124100
125101 allow_failure = os.environ.get("ALLOW_FAILURE") == "true"
126102 sys.exit(1 if issues and not allow_failure else 0)
127- PY print("Suggested dictionary candidates:")
128- for word in sorted(words, key=str.lower):
129- print(word)
130103 PY
131104
132- - name : Check Selected Harper Rules
133- run : |
134- python - <<'PY'
135- import json
136- import re
137- import sys
138-
139- with open("harper-report.json", encoding="utf-8") as f:
140- reports = json.load(f)
141-
142- selected = []
143- total_lints = 0
144-
145- IGNORED_RULES = {
146- ("Spelling", "OkToOkay"),
147- ("Spelling", "DisjointPrefixes"),
105+ heading-case :
106+ runs-on : ubuntu-latest
107+ steps :
108+ - uses : actions/checkout@v6
109+ - run : |
110+ python check_heading_case.py docs src/pages || {
111+ if [ "$ALLOW_FAILURE" = "true" ]; then
112+ exit 0
113+ fi
114+ exit 1
148115 }
149-
150- def should_ignore_spelling_text(text):
151- if not text:
152- return False
153-
154- stripped = text.strip()
155-
156- # URLs and URL fragments
157- if stripped.startswith(("http://", "https://", "www.")):
158- return True
159-
160- # File paths / command paths
161- if "/" in stripped:
162- return True
163-
164- # Long hashes, IDs, tokens
165- if len(stripped) >= 16 and any(c.isdigit() for c in stripped):
166- return True
167-
168- # Hex-like strings, e.g. d5442a5dc4baadd48b32
169- if len(stripped) >= 8 and re.fullmatch(r"[a-fA-F0-9]+", stripped):
170- return True
171-
172- # Mostly random-looking alphanumeric tokens
173- if len(stripped) >= 12 and re.fullmatch(r"[A-Za-z0-9_-]+", stripped):
174- return True
175-
176- return False
177-
178- for report in reports:
179- file = report.get("file")
180-
181- for lint in report.get("lints", []):
182- total_lints += 1
183-
184- kind = lint.get("kind")
185- rule = lint.get("rule")
186- line_no = lint.get("line")
187- message = lint.get("message")
188- text = lint.get("matched_text")
189-
190- if (kind, rule) in IGNORED_RULES:
191- continue
192-
193- if kind == "Spelling" and should_ignore_spelling_text(text):
194- continue
195-
196- if kind in {"Spelling", "Grammar", "Repetition"}:
197- selected.append(
198- f"{file}:{line_no}: [{kind}::{rule}] {text!r} - {message}"
199- )
200-
201- print(f"Total Harper lint count: {total_lints}")
202- print(f"Selected lint count: {len(selected)}")
203-
204- if selected:
205- print("Selected Harper lints found:")
206- for item in selected:
207- print(item)
208- sys.exit(1)
209-
210- print("No selected Harper lints found.")
211- PY
0 commit comments