Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ markdown-include
pymdown-extensions
pillow
cairosvg
# selectolax 1.0.1 makes mkdocs-glightbox drop every image from the built site (Oct 2026).
selectolax<1.0.1
2 changes: 1 addition & 1 deletion tools/docs-checks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ changed lines in the PR. Fix them and push again; the checks re-run.
| `check_markdown.py` | Missing front matter `title:` or `description:`, heading levels that skip (`##` to `####`), more than one `#` H1, images without alt text, snippet includes that point at a missing file or start inside the included page's front matter | Add the field, use the next heading level, describe the image inside `![...]`, or start the include after the included page's closing `---` |
| `check_secrets.py` | ESPHome API encryption keys, generated OTA passwords, passwords typed into YAML, and access tokens | Use `!secret` (or a placeholder like `your-wifi-password`). If the value was real, change it on the device or account too |
| `check_images.py` | Photos with GPS location data (phones add it by default) | `exiftool -gps:all= -overwrite_original docs/assets/<file>` |
| `check_links.py` | Broken links, links to a missing `#anchor`, filename case mismatches (GitHub Pages is case-sensitive), and images without alt text, checked on the built site | Fix the link target or anchor; match the file name's case exactly |
| `check_links.py` | Broken links, links to a missing `#anchor`, filename case mismatches (GitHub Pages is case-sensitive), images without alt text, and a built site with far fewer images than the Markdown, checked on the built site | Fix the link target or anchor; match the file name's case exactly. If images went missing, look for a recently updated build package and pin it in `requirements.txt` |

Warnings (shown, but they don't fail the PR): em dashes and the filler words
the style guide bans, and images or recordings over 1.5 MB that the PR adds
Expand Down
19 changes: 15 additions & 4 deletions tools/docs-checks/check_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
links (../page/, https://wiki.apolloautomation.com/...), so this checks every
link, #anchor, image, and video in page content against the built site.
Filename case must match exactly, because GitHub Pages is case-sensitive.
Fails on any broken link, missing anchor, or image without alt text.
Fails on any broken link, missing anchor, or image without alt text, and
when the built site has far fewer images than the Markdown (a plugin
dropping them, as selectolax 1.0.1 did to mkdocs-glightbox in October 2026).
"""
import os, sys, posixpath
import os, re, sys, posixpath
from html.parser import HTMLParser
from urllib.parse import urlsplit, unquote

Expand Down Expand Up @@ -136,8 +138,17 @@ def src_md(url):
print(f"::error file={a['page']}::Link to a missing #anchor: {a['ref']}")
for m in missing_alt:
print(f"::error file={m['page']}::Image without alt text: {m['src']}")
md_imgs = 0
for r, _, fs in os.walk(DOCS):
for f in fs:
if f.endswith(".md"):
md_imgs += len(re.findall(r"!\[[^\]]*\]\(|<img\s", open(os.path.join(r, f), encoding="utf-8", errors="replace").read()))
built_imgs = sum(1 for pr in pages.values() for kind, _, _ in pr.refs if kind == "img")
images_dropped = md_imgs > 0 and built_imgs < md_imgs // 2
if images_dropped:
print(f"::error::The built site has {built_imgs} images, but the Markdown has about {md_imgs}. A build plugin is probably removing images; check recently updated packages.")
for wrong, right in sorted(set(case_mismatch)):
print(f"::error::Link case doesn't match the file (GitHub Pages is case-sensitive): {wrong} should be {right}")
total = len(broken) + len(bad_anchor) + len(missing_alt) + len(set(case_mismatch))
print(f"check_links: {len(pages)} pages, {len(broken)} broken, {len(bad_anchor)} bad anchors, {len(missing_alt)} missing alt, {len(set(case_mismatch))} case mismatches")
total = len(broken) + len(bad_anchor) + len(missing_alt) + len(set(case_mismatch)) + images_dropped
print(f"check_links: {len(pages)} pages, {built_imgs} images, {len(broken)} broken, {len(bad_anchor)} bad anchors, {len(missing_alt)} missing alt, {len(set(case_mismatch))} case mismatches")
sys.exit(1 if total else 0)
Loading