feat(docs): vitepress site for docs/, mermaid rendering, ci build workflow #154

Merged
julien merged 1 commits from feat/docs-site-vitepress into main 2026-05-15 19:14:16 +02:00
Owner

Summary

Implementation of ADR-0022. Stands up the static documentation site that renders docs/**/*.md (architecture diagrams, daily-dev guide, ADRs, onboarding) via VitePress + vitepress-plugin-mermaid, behind a Gitea Actions build gate.

Local dev: pnpm docs:dev. Full build: pnpm docs:build (~9 s, output in docs/.vitepress/dist/).

What lands

Dependencies

vitepress 1.6.4, vitepress-plugin-mermaid 2.0.17, mermaid 11.15.0 — workspace devDependencies. No runtime impact on portal-shell / portal-admin / portal-bff.

docs/.vitepress/config.mts

The single source of truth for the site. Highlights:

  • srcExclude drops docs/README.md (git/IDE-only index per ADR-0022's option A) and docs/decisions/template.md (authoring scaffold).
  • rewrites maps decisions/README.mddecisions/index.md so /decisions/ resolves to the curated tag-grouped landing while the source filename stays git-conventional.
  • ignoreDeadLinks skips:
    • localhost:* URLs (Jaeger, OTLP — only resolve in a live dev session),
    • cross-repo references (../CLAUDE, ../../apps/**, ../../infra/**, ../../notes/**) — intentional from git/IDE consumers; not the site's job to render them,
    • excluded targets (./template, ./README) — file exists in the repo, just not in the site.
  • Auto-sidebar for /decisions/adrSidebarItems() walks docs/decisions/00*-*.md and emits sorted ADR-NNNN — title entries. Adding an ADR is a single-file change, no config.mts edit.
  • Hand-curated top-level nav (Development, Architecture, Decisions, Onboarding).
  • Mermaid via withMermaid() with securityLevel: 'strict' so diagrams can't inject arbitrary HTML.

docs/index.md

VitePress Hero landing with four feature cards (Architecture, Decisions, Development, Onboarding).

docs/development.md — two surgical fixes

  • Line ~5: [setup/](setup/)[setup/01-wsl-terminal-setup.md](setup/01-wsl-terminal-setup.md). Folder-style links don't resolve cleanly under cleanUrls: true; pointing at the first onboarding page is both correct and useful.
  • Line 330: wrap ${{ github.* }} in <code v-pre>…</code>. VitePress runs every Markdown file through the Vue template compiler, which sees the inline {{ … }} as an interpolation. v-pre keeps the literal text intact. The rest of the source is unaffected.

package.json

Three new scripts:

docs:dev      → vitepress dev docs
docs:build    → vitepress build docs
docs:preview  → vitepress preview docs

Pure pnpm scripts, no Nx project — the site has no cross-project dependency graph to track.

.gitea/workflows/docs-site.yml

Triggers on push to main and on PR, scoped by paths: to docs/**, package.json, pnpm-lock.yaml, and the workflow itself. Three steps:

  1. pnpm install --frozen-lockfile
  2. pnpm docs:build
  3. Regression fence: grep ADR-0009's rendered HTML for class="mermaid" or <svg> so a silent Mermaid-plugin breakage on a major upgrade fails the workflow rather than ship a site with raw code blocks where diagrams should be.
  4. On push only: upload docs/.vitepress/dist/ as a docs-site artifact (30-day retention). The actual rsync to the static host lands when the future infrastructure ADR locks the deployment target.

.gitignore

Excludes docs/.vitepress/{cache,dist}/ so local builds don't leak into commits.

Notes for the reviewer

  • Why config.mts and not config.ts? VitePress is ESM-only, and vitepress-plugin-mermaid follows. Vite loads .ts config files via its CJS bundler in this workspace's setup and chokes on the ESM imports. .mts flips the loader to ESM and the build succeeds. Same pattern is used elsewhere in the workspace (jest.config.cts, app vite.config.mts).
  • Why no Nx project (docs/project.json)? The doc site has no Nx-trackable dependencies (it consumes .md files, not TypeScript projects). Putting it in the Nx graph adds ceremony with no caching benefit — VitePress's incremental rebuilds are sub-second already, and the site never has cross-project affected semantics. Pure pnpm scripts keep the surface small.
  • Why the regression fence on Mermaid? ADR-0022 §"Confirmation" promises it. The plugin is a community dep (sub-1.0 wrapper around the official Mermaid renderer); a major upgrade or a Mermaid runtime change could leave fenced ```mermaid blocks rendered as raw code without anyone noticing — until an RSSI clicks ADR-0009 and sees no diagram. Cheap grep gate, real signal.
  • Why upload as artifact, not deploy? Per ADR-0022 §"Deployment & CI": the host (docs.portal.apf.fr or a sub-path) is provisional. Locking an rsync target now would couple this PR to a not-yet-made infra decision. Artifact upload is the staging mechanism — manual drop on the host until the infrastructure ADR formalises the target.
  • Why ignoreDeadLinks rather than fixing every cross-repo reference? The cross-repo links are genuinely useful from a git/IDE perspective (where the docs/ markdown is browsed alongside the rest of the codebase). Rewriting them to https://git.unespace.com/julien/apf_portal/src/branch/main/… would make them work on the site but lose the IDE quick-jump. Skipping at site-build time is the right trade-off — the site reader gets a graceful "link doesn't exist here" if they click, the IDE reader gets a working jump.

Test plan

  • pnpm docs:build succeeds in ~9 s. Output at docs/.vitepress/dist/ contains an index.html, every ADR, the development guide, the architecture diagrams, and the three setup pages.
  • Mermaid renders: grep 'class="mermaid"' docs/.vitepress/dist/decisions/0009-…html returns a match.
  • pnpm exec nx run-many -t format:check lint test build for the 6 main projects — 18/18 tasks green, no Nx regression from the new top-level config.
  • Manual smoke: pnpm docs:dev, open http://localhost:5173, walk through:
    • Landing renders Hero + 4 feature cards.
    • Search box returns hits for "audit", "MFA", "OBO".
    • /decisions/0009-… renders the OIDC sequence diagram (Mermaid SVG, not raw text).
    • /decisions/0010-… ERD or /architecture C4 diagrams likewise.
    • Dark-mode toggle flips diagrams to dark theme without page reload.
    • Sidebar shows the 22 ADRs auto-listed under /decisions/.
    • The "Decisions" curated index at /decisions/ lists ADRs by tag (no regression on the source markdown).

What's next

Once the deployment target is fixed (future infra ADR), wire the rsync step into the workflow — that lands as a small follow-up PR. Until then the artifact carries the bundle.

## Summary Implementation of [ADR-0022](docs/decisions/0022-docs-site-vitepress.md). Stands up the static documentation site that renders `docs/**/*.md` (architecture diagrams, daily-dev guide, ADRs, onboarding) via **VitePress + `vitepress-plugin-mermaid`**, behind a Gitea Actions build gate. Local dev: `pnpm docs:dev`. Full build: `pnpm docs:build` (~9 s, output in `docs/.vitepress/dist/`). ## What lands ### Dependencies `vitepress 1.6.4`, `vitepress-plugin-mermaid 2.0.17`, `mermaid 11.15.0` — workspace devDependencies. No runtime impact on `portal-shell` / `portal-admin` / `portal-bff`. ### [`docs/.vitepress/config.mts`](docs/.vitepress/config.mts) The single source of truth for the site. Highlights: - **`srcExclude`** drops `docs/README.md` (git/IDE-only index per ADR-0022's option A) and `docs/decisions/template.md` (authoring scaffold). - **`rewrites`** maps `decisions/README.md` → `decisions/index.md` so `/decisions/` resolves to the curated tag-grouped landing while the source filename stays git-conventional. - **`ignoreDeadLinks`** skips: - `localhost:*` URLs (Jaeger, OTLP — only resolve in a live dev session), - cross-repo references (`../CLAUDE`, `../../apps/**`, `../../infra/**`, `../../notes/**`) — intentional from git/IDE consumers; not the site's job to render them, - excluded targets (`./template`, `./README`) — file exists in the repo, just not in the site. - **Auto-sidebar for `/decisions/`** — `adrSidebarItems()` walks `docs/decisions/00*-*.md` and emits sorted `ADR-NNNN — title` entries. Adding an ADR is a single-file change, no `config.mts` edit. - **Hand-curated top-level nav** (Development, Architecture, Decisions, Onboarding). - **Mermaid via `withMermaid()`** with `securityLevel: 'strict'` so diagrams can't inject arbitrary HTML. ### [`docs/index.md`](docs/index.md) VitePress Hero landing with four feature cards (Architecture, Decisions, Development, Onboarding). ### [`docs/development.md`](docs/development.md) — two surgical fixes - Line ~5: `[setup/](setup/)` → `[setup/01-wsl-terminal-setup.md](setup/01-wsl-terminal-setup.md)`. Folder-style links don't resolve cleanly under `cleanUrls: true`; pointing at the first onboarding page is both correct and useful. - Line 330: wrap `${{ github.* }}` in `<code v-pre>…</code>`. VitePress runs every Markdown file through the Vue template compiler, which sees the inline `{{ … }}` as an interpolation. `v-pre` keeps the literal text intact. The rest of the source is unaffected. ### [`package.json`](package.json) Three new scripts: ``` docs:dev → vitepress dev docs docs:build → vitepress build docs docs:preview → vitepress preview docs ``` Pure pnpm scripts, no Nx project — the site has no cross-project dependency graph to track. ### [`.gitea/workflows/docs-site.yml`](.gitea/workflows/docs-site.yml) Triggers on push to `main` and on PR, scoped by `paths:` to `docs/**`, `package.json`, `pnpm-lock.yaml`, and the workflow itself. Three steps: 1. `pnpm install --frozen-lockfile` 2. `pnpm docs:build` 3. Regression fence: `grep` ADR-0009's rendered HTML for `class="mermaid"` or `<svg>` so a silent Mermaid-plugin breakage on a major upgrade fails the workflow rather than ship a site with raw code blocks where diagrams should be. 4. On push only: upload `docs/.vitepress/dist/` as a `docs-site` artifact (30-day retention). The actual rsync to the static host lands when the future infrastructure ADR locks the deployment target. ### [`.gitignore`](.gitignore) Excludes `docs/.vitepress/{cache,dist}/` so local builds don't leak into commits. ## Notes for the reviewer - **Why `config.mts` and not `config.ts`?** VitePress is ESM-only, and `vitepress-plugin-mermaid` follows. Vite loads `.ts` config files via its CJS bundler in this workspace's setup and chokes on the ESM imports. `.mts` flips the loader to ESM and the build succeeds. Same pattern is used elsewhere in the workspace (`jest.config.cts`, app `vite.config.mts`). - **Why no Nx project (`docs/project.json`)?** The doc site has no Nx-trackable dependencies (it consumes `.md` files, not TypeScript projects). Putting it in the Nx graph adds ceremony with no caching benefit — VitePress's incremental rebuilds are sub-second already, and the site never has cross-project `affected` semantics. Pure pnpm scripts keep the surface small. - **Why the regression fence on Mermaid?** ADR-0022 §"Confirmation" promises it. The plugin is a community dep (sub-1.0 wrapper around the official Mermaid renderer); a major upgrade or a Mermaid runtime change could leave fenced ` ```mermaid ` blocks rendered as raw code without anyone noticing — until an RSSI clicks ADR-0009 and sees no diagram. Cheap grep gate, real signal. - **Why upload as artifact, not deploy?** Per [ADR-0022](docs/decisions/0022-docs-site-vitepress.md) §"Deployment & CI": the host (`docs.portal.apf.fr` or a sub-path) is provisional. Locking an rsync target now would couple this PR to a not-yet-made infra decision. Artifact upload is the staging mechanism — manual drop on the host until the infrastructure ADR formalises the target. - **Why `ignoreDeadLinks` rather than fixing every cross-repo reference?** The cross-repo links are genuinely useful from a git/IDE perspective (where the docs/ markdown is browsed alongside the rest of the codebase). Rewriting them to `https://git.unespace.com/julien/apf_portal/src/branch/main/…` would make them work on the site but lose the IDE quick-jump. Skipping at site-build time is the right trade-off — the site reader gets a graceful "link doesn't exist here" if they click, the IDE reader gets a working jump. ## Test plan - [x] `pnpm docs:build` succeeds in ~9 s. Output at `docs/.vitepress/dist/` contains an `index.html`, every ADR, the development guide, the architecture diagrams, and the three setup pages. - [x] Mermaid renders: `grep 'class="mermaid"' docs/.vitepress/dist/decisions/0009-…html` returns a match. - [x] `pnpm exec nx run-many -t format:check lint test build` for the 6 main projects — 18/18 tasks green, no Nx regression from the new top-level config. - [ ] **Manual smoke**: `pnpm docs:dev`, open `http://localhost:5173`, walk through: - Landing renders Hero + 4 feature cards. - Search box returns hits for "audit", "MFA", "OBO". - `/decisions/0009-…` renders the OIDC sequence diagram (Mermaid SVG, not raw text). - `/decisions/0010-…` ERD or `/architecture` C4 diagrams likewise. - Dark-mode toggle flips diagrams to dark theme without page reload. - Sidebar shows the 22 ADRs auto-listed under `/decisions/`. - The "Decisions" curated index at `/decisions/` lists ADRs by tag (no regression on the source markdown). ## What's next Once the deployment target is fixed (future infra ADR), wire the rsync step into the workflow — that lands as a small follow-up PR. Until then the artifact carries the bundle.
julien added 1 commit 2026-05-15 19:14:03 +02:00
feat(docs): vitepress site for docs/, mermaid rendering, ci build workflow
CI / scan (pull_request) Failing after 2m27s
CI / commits (pull_request) Successful in 2m38s
CI / a11y (pull_request) Successful in 1m45s
CI / check (pull_request) Successful in 4m27s
Docs site / build (pull_request) Successful in 2m2s
CI / perf (pull_request) Successful in 4m24s
e0e090f024
Implementation of ADR-0022.

* `vitepress`, `vitepress-plugin-mermaid` and `mermaid` added to
  workspace devDependencies. Build settled at ~9 s for the full
  site, sub-second incremental in dev.
* `docs/.vitepress/config.mts`:
  * `srcExclude` drops `docs/README.md` (git/IDE-only index) and
    `docs/decisions/template.md` (authoring scaffold).
  * `rewrites` maps `decisions/README.md` → `decisions/index.md`
    so `/decisions/` resolves to the curated landing while the
    source stays git-friendly.
  * `ignoreDeadLinks` skips cross-repo refs (`../CLAUDE`,
    `../../apps/**`, `../../notes/**`), `localhost:*` dev URLs,
    and the deliberately-excluded `README`/`template` references —
    the source stays usable from a git/IDE reader without
    breaking the site build.
  * Auto-generated sub-sidebar for `/decisions` walks
    `00NN-*.md` so adding an ADR is one file.
  * Mermaid via `withMermaid` with `securityLevel: 'strict'`.
* `docs/index.md` — VitePress Hero landing with four feature
  cards (Architecture, Decisions, Development, Onboarding).
* `package.json` exposes `docs:dev`, `docs:build`, `docs:preview`.
* `docs/development.md` (line ~5): `[setup/](setup/)` → first
  page, since folder-style links don't resolve cleanly under
  `cleanUrls`. Line 330: wrap `${{ github.* }}` in
  `<code v-pre>…</code>` so VitePress's Vue template parser
  doesn't trip on the curly-brace interpolation.
* `.gitea/workflows/docs-site.yml` — builds on every PR (gate
  on parse errors + genuine dead links), uploads the dist as an
  artifact on push to main. The Mermaid plugin regression fence
  greps for `class="mermaid"` / `<svg>` in ADR-0009's rendered
  HTML.
* `.gitignore` excludes `docs/.vitepress/{cache,dist}/`.

Deployment target stays provisional per ADR-0022; the workflow
publishes a `docs-site` artifact for now. The rsync step lands
once the future infrastructure ADR locks the host.
julien merged commit 7579b25dfe into main 2026-05-15 19:14:16 +02:00
julien deleted branch feat/docs-site-vitepress 2026-05-15 19:14:19 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#154