blumeops/mise-tasks/docs-check-frontmatter
Erich Blume b0bac91ca9 Fix frontmatter field name for Quartz date display (#158)
## Summary

- Rename `date-modified` -> `modified` in all 80 docs and the `docs-check-frontmatter` task

Quartz's `CreatedModifiedDate` plugin recognizes `modified`, `lastmod`, `updated`, and `last-modified` — but not `date-modified`. The wrong field name caused Quartz to ignore frontmatter dates entirely and fall through to filesystem timestamps (UTC inside Dagger), showing Feb 12 on pages built late on Feb 11 PST.

## Test plan

- [x] `mise run docs-check-frontmatter` passes
- [ ] Kick off docs release after merge — verify rendered dates match frontmatter values

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/158
2026-02-11 16:45:12 -08:00

86 lines
2.5 KiB
Text
Executable file

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["rich>=13.0.0"]
# ///
#MISE description="Check that all docs have required frontmatter fields"
"""Validate that all documentation files have required YAML frontmatter.
Required fields: title, tags, modified
Scans all markdown files in docs/ (excluding changelog.d/) and checks
that each file has YAML frontmatter containing the required fields.
Usage: mise run docs-check-frontmatter
"""
import re
import sys
from pathlib import Path
from rich.console import Console
from rich.table import Table
DOCS_DIR = Path(__file__).parent.parent / "docs"
REQUIRED_FIELDS = {"title", "tags", "modified"}
# Match YAML frontmatter block
FRONTMATTER_PATTERN = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)
# Match top-level YAML keys (not indented)
KEY_PATTERN = re.compile(r"^([a-zA-Z][a-zA-Z0-9_-]*):", re.MULTILINE)
def extract_frontmatter_keys(file_path: Path) -> set[str] | None:
"""Extract top-level keys from YAML frontmatter. Returns None if no frontmatter."""
content = file_path.read_text()
match = FRONTMATTER_PATTERN.match(content)
if not match:
return None
frontmatter = match.group(1)
return set(KEY_PATTERN.findall(frontmatter))
def main() -> int:
console = Console()
console.print("[bold]Frontmatter Validation[/bold]")
console.print(f"Required fields: {', '.join(sorted(REQUIRED_FIELDS))}")
console.print()
issues: list[tuple[str, set[str]]] = []
for md_file in sorted(DOCS_DIR.rglob("*.md")):
if "changelog.d" in md_file.parts:
continue
rel_path = str(md_file.relative_to(DOCS_DIR))
keys = extract_frontmatter_keys(md_file)
if keys is None:
issues.append((rel_path, REQUIRED_FIELDS))
continue
missing = REQUIRED_FIELDS - keys
if missing:
issues.append((rel_path, missing))
if issues:
console.print("[bold red]Missing Required Frontmatter[/bold red]")
console.print()
table = Table(show_header=True, header_style="bold")
table.add_column("File")
table.add_column("Missing Fields")
for rel_path, missing in issues:
table.add_row(rel_path, ", ".join(sorted(missing)))
console.print(table)
console.print()
return 1
console.print("[bold green]All docs have required frontmatter![/bold green]")
return 0
if __name__ == "__main__":
sys.exit(main())