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
34 changes: 34 additions & 0 deletions .github/workflows/validate-docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Validate Docs

"on":
pull_request:
branches:
- main
paths:
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
- "modules/**"
- "antora.yml"
- "bin/validate-docs.sh"
- "Makefile"

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
disable-telemetry: true

- name: Checkout
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install asciidoctor
run: sudo gem install asciidoctor -v 2.0.26 --no-document

- name: Validate documentation
run: make validate-docs
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ This is the Conforma User Guide repository, which contains documentation for Con

## Common Commands

### Documentation Validation
```bash
make validate-docs
```
Lightweight validation that works without external repositories or network access. Checks:
- AsciiDoc syntax errors (requires `asciidoctor`; skips gracefully if not installed)
- Broken `xref:` references to non-existent pages within this component
- Broken `include::` references to non-existent partials
- Navigation references to non-existent pages

Run this after editing any `.adoc` file to catch errors before pushing.

### Documentation Preview
```bash
make ec-docs-preview
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ HACBS_DOCS_REPO=git@github.com:conforma/conforma.github.io.git
$(HACBS_DOCS_DIR):
mkdir $(HACBS_DOCS_DIR) && cd $(HACBS_DOCS_DIR) && git clone $(HACBS_DOCS_REPO) .

.PHONY: validate-docs
validate-docs: ## Validate AsciiDoc syntax, xrefs, and includes (no external deps required)
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
@bash bin/validate-docs.sh

CURRENT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
ec-docs-preview: $(HACBS_DOCS_DIR) ## Build a preview of the documentation
cd $(HACBS_DOCS_DIR) && \
Expand Down
159 changes: 159 additions & 0 deletions bin/validate-docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MODULES_DIR="${REPO_ROOT}/modules"
PAGES_DIR="${MODULES_DIR}/ROOT/pages"
PARTIALS_DIR="${MODULES_DIR}/ROOT/partials"
NAV_FILE="${MODULES_DIR}/ROOT/nav.adoc"

ERROR_LOG=$(mktemp)
WORK_DIR=$(mktemp -d)
trap 'rm -f "${ERROR_LOG}"; rm -rf "${WORK_DIR}"' EXIT

error() {
echo "ERROR: $1" >&2
echo "x" >> "${ERROR_LOG}"
}

warn() {
echo "WARN: $1" >&2
}

# --- AsciiDoc syntax validation ---
validate_syntax() {
echo "==> Validating AsciiDoc syntax..."

if ! command -v asciidoctor &>/dev/null; then
warn "asciidoctor not found, skipping syntax validation."
warn "Install with: gem install asciidoctor"
return
fi

# Antora's partial$ prefix isn't understood by standalone asciidoctor.
# Resolve it to a relative path via a temp copy with a symlinked partials dir.
cp -a "${PAGES_DIR}" "${WORK_DIR}/pages"
ln -s "${PARTIALS_DIR}" "${WORK_DIR}/partials"
find "${WORK_DIR}/pages" -name '*.adoc' -exec \
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
perl -pi -e 's|include::partial\$|include::../partials/|g' {} +

local syntax_errors=0
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
while IFS= read -r -d '' file; do
local output
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
output=$(asciidoctor \
--failure-level=WARN \
--backend=html5 \
--safe-mode=unsafe \
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
--out-file=/dev/null \
"$file" 2>&1) || true
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved

if [[ -n "$output" ]]; then
echo "$output" | while IFS= read -r line; do
echo " $line"
done
syntax_errors=$((syntax_errors + 1))
fi
done < <(find "${WORK_DIR}/pages" -name '*.adoc' -print0)

if [[ ${syntax_errors} -gt 0 ]]; then
error "${syntax_errors} file(s) produced asciidoctor warnings or errors"
fi
}

# --- xref validation ---
validate_xrefs() {
echo "==> Validating xref targets..."

while IFS= read -r -d '' file; do
local relpath="${file#"${REPO_ROOT}/"}"
local matches
matches=$(grep -noE 'xref:[^[]+\[' "$file" 2>/dev/null) || continue

while IFS=: read -r lineno match; do
local target="${match#xref:}"
target="${target%\[}"
target="${target%%#*}"

# Skip cross-component xrefs, attribute-substituted targets, and same-page anchors
if [[ "$target" == *:* || "$target" == "{"* || -z "$target" ]]; then
continue
fi

if [[ ! -f "${PAGES_DIR}/${target}" ]]; then
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
error "${relpath}:${lineno}: broken xref to '${target}' (file not found in pages/)"
fi
done <<< "$matches"
done < <(find "${MODULES_DIR}" -name '*.adoc' -print0)
Comment thread
cuipinghuo marked this conversation as resolved.
Resolved
}

# --- include validation ---
validate_includes() {
echo "==> Validating include targets..."

while IFS= read -r -d '' file; do
local relpath="${file#"${REPO_ROOT}/"}"
local matches
matches=$(grep -noE 'include::[^[]+\[' "$file" 2>/dev/null) || continue

while IFS=: read -r lineno match; do
local target="${match#include::}"
target="${target%\[}"

if [[ "$target" == partial\$* ]]; then
local partial_name="${target#partial\$}"
if [[ ! -f "${PARTIALS_DIR}/${partial_name}" ]]; then
error "${relpath}:${lineno}: broken include, partial '${partial_name}' not found in partials/"
fi
fi
done <<< "$matches"
done < <(find "${MODULES_DIR}" -name '*.adoc' -print0)
}

# --- nav validation ---
validate_nav() {
echo "==> Validating navigation references..."

if [[ ! -f "${NAV_FILE}" ]]; then
error "Navigation file not found: ${NAV_FILE}"
return
fi

for navfile in "${NAV_FILE}" "${PARTIALS_DIR}/contents.adoc"; do
[[ -f "$navfile" ]] || continue
local relpath="${navfile#"${REPO_ROOT}/"}"
local matches
matches=$(grep -noE 'xref:[^[]+\[' "$navfile" 2>/dev/null) || continue

while IFS=: read -r lineno match; do
local target="${match#xref:}"
target="${target%\[}"
target="${target%%#*}"

if [[ "$target" == *:* || "$target" == "{"* || -z "$target" ]]; then
continue
fi

if [[ ! -f "${PAGES_DIR}/${target}" ]]; then
error "${relpath}:${lineno}: nav references non-existent page '${target}'"
fi
done <<< "$matches"
done
}

echo "Validating documentation in ${REPO_ROOT}..."
echo

validate_syntax
validate_xrefs
validate_includes
validate_nav

echo
error_count=$(wc -l < "${ERROR_LOG}" | tr -d ' ')
if [[ ${error_count} -gt 0 ]]; then
echo "FAILED: ${error_count} error(s) found."
exit 1
else
echo "PASSED: All validation checks passed."
exit 0
fi
Loading