name: Docs Quality Gates # Restores the enforcing half of the old docs-governance.yml, which was removed in #6000 # ("prune dead workflows") along with four genuinely dead workflows. Its two advisory jobs — # docs staleness and preview staleness — only ever posted PR comments and are not restored; # this is the job that actually failed a build. # # Path-filtered so it runs only when something it checks can break: the docs themselves, the # in-app index that must list them, or the scripts doing the checking. on: pull_request: branches: [main] paths: - "docs/en/**" - "feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt" - "scripts/check-doc-aliases.js" - "scripts/check-doc-coverage.js" - "scripts/check-doc-freshness.js" - "scripts/validate-doc-links.js" - "scripts/lib/frontmatter.js" - ".github/workflows/docs-quality.yml" workflow_dispatch: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: validate: name: Docs quality gates # Lightweight, but it fetches a Node toolchain, so it takes the ARM tier rather than # ubuntu-slim. No workflow currently runs Node; ubuntu-slim's suitability for setup-node # is untested and this is not the place to find out. runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: # `pull_request`, never `pull_request_target`: this checks out and executes # fork-supplied code (node scripts/*.js), so it must run with the read-only token and # no secrets. The original carried this same warning; keep it. - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 1 - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "24" - name: Validate internal links run: node scripts/validate-doc-links.js docs/en - name: Check doc coverage run: node scripts/check-doc-coverage.js . - name: Validate DocBundleLoader registry # Both directions matter, and they fail differently: # page with no entry -> the page ships in the bundle but is unreachable in-app # entry with no page -> the in-app index offers a link that resolves to nothing run: | loader="feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt" status=0 # Match the page's own resourcePath, not the bare slug: every page also carries an # alias list, so a loose slug search passes when some *other* page happens to alias # this one's name (docs/en/user/measurement.md would match units-and-locale's alias). for f in docs/en/user/*.md docs/en/developer/*.md; do [ -e "$f" ] || continue resource="en/${f#docs/en/}" resource="${resource%.md}.html" if ! grep -qF "\"$resource\"" "$loader"; then echo "::error file=$f::no page definition in DocBundleLoader.kt references \"$resource\", so this page is unreachable in the in-app docs browser." status=1 fi done grep -oE '"en/(user|developer)/[a-z0-9-]+\.html"' "$loader" \ | tr -d '"' \ | while read -r page; do md="docs/${page%.html}.md" if [ ! -f "$md" ]; then echo "::error file=$loader::DocBundleLoader references '$page' but $md does not exist." exit 1 fi done || status=1 if [ "$status" -ne 0 ]; then echo "FAILED: the in-app doc index and docs/en/ are out of sync." exit 1 fi echo "DocBundleLoader and docs/en/ agree in both directions." - name: Check alias registration # The registry check above proves a page is reachable; this proves it is findable. # Only DocBundleLoader's alias list reaches the app — the frontmatter one is stripped # before render and stripped again by sync-android-docs.js — so an alias authored in # frontmatter and never registered is a search term no consumer ever sees. run: node scripts/check-doc-aliases.js . - name: Check doc freshness # Advisory, as it always was: a page being old is a prompt to look, not a defect. continue-on-error: true run: node scripts/check-doc-freshness.js docs/en --max-age-days=180