ADR 0014: Docs-as-Code — DocFX, Mermaid CLI, and DocsGen
Status
Accepted
Context and Problem Statement
A reference implementation needs living documentation: architecture diagrams, ADR records, and a project-structure overview. If any of these are maintained manually in a separate tool or wiki, they will drift from the codebase over time. The question is how to keep documentation in the repository, rendered consistently, and automatically up to date for sections that are a direct reflection of repository state.
Decision Drivers
- Architecture diagrams must be version-controlled as source files, not as committed image binaries. Diffs must be readable in pull requests.
- Sections that are purely derived from the repository (ADR index, project structure tree) must never require manual editing — they should be regenerated on every build.
- The documentation site must be deployable from CI with no manual steps.
- Build tooling must be pinned and reproducible (
package-lock.json, specific CLI versions). - No runtime dependency on an external CDN — all assets must be bundled into the generated site.
Considered Options
Option A — DocFX + Mermaid CLI + custom DocsGen tool (Selected)
Markdown authored by hand is processed by DocFX into a static site.
Architecture diagrams are authored as .mmd Mermaid source files and
rendered to SVG at build time by @mermaid-js/mermaid-cli. A small
custom .NET tool (DocsGen) generates the ADR index table and refreshes
the project-structure section between marker comments. The Swagger UI
is bundled as a static copy from swagger-ui-dist. The finished site is
deployed to GitHub Pages on every push to main.
Option B — Plain Markdown only, no generated site
Keep all documentation as Markdown files in the repository, readable on GitHub. No build step, no deployment. Diagrams would be Mermaid inline blocks (rendered by GitHub) or committed SVGs. No programmatic generation of derived content. Simpler, but loses the navigable site, cross-references, and the guarantee that derived sections are correct.
Option C — External documentation platform
Host documentation in Confluence, Notion, or ReadTheDocs. Decouples docs from the repository — but makes docs a second-class citizen, breaks the PR review flow for documentation changes, and introduces an external dependency that could become unavailable or require licensing.
Decision Outcome
Chosen option: Option A — DocFX + Mermaid CLI + custom DocsGen tool.
Why this trade-off makes sense for this project
- Mermaid source files are the diagrams. Fifteen
.mmdfiles indocs/diagrams/cover the full architecture (dependency rules, domain model, request lifecycle, UoW boundary, etc.). They are plain text, diff cleanly in PRs, and are rendered to SVG once per build. There is no committed SVG or PNG to keep in sync manually. - DocsGen eliminates two categories of stale documentation. The ADR
index (
docs/adr/index.md) is generated by scanningdocs/adr/*.mdand extracting file names and first headings — adding a new ADR file automatically updates the index. The project-structure section indocs/structure/project-structure.mdis rebuilt by walking the repository tree between<!-- AUTOGENERATED:BEGIN -->and<!-- AUTOGENERATED:END -->markers. Both are marked as auto-generated in the file header; manual edits would be silently overwritten on the next build. npm ciwith a committedpackage-lock.jsonpins tooling versions.@mermaid-js/mermaid-cli 11.4.2andswagger-ui-dist ^5.0.0are installed from the lock file, not from the latest available version. This ensures diagrams render identically on every CI run.- Swagger UI is bundled, not CDN-linked.
copy-swagger-ui.mjscopies the Swagger UI distribution into the docs output folder during the build. The published site has no runtime dependency onunpkg.comor any other external CDN — it works in air-gapped or restricted environments.
Documentation build pipeline
docs workflow (ubuntu-latest, push to main)
├── npm ci install Mermaid CLI + swagger-ui-dist
├── npm run generate .mmd → SVG (via mermaid-cli)
├── dotnet run DocsGen ADR index + project-structure section
├── npm run swagger-ui copy swagger-ui-dist assets to docs/
├── docfx ./docs/docfx.json Markdown + SVG → static site (_site/)
└── deploy to GitHub Pages
Consequences
Positive Consequences
- Diagram changes are reviewable as text diffs in pull requests.
- The ADR index and project-structure section are always accurate — they cannot drift because they are regenerated on every deploy.
- The full documentation site is available at the GitHub Pages URL with no manual publishing step.
- All build inputs (Mermaid version, swagger-ui version) are pinned and auditable.
Negative Consequences
- The documentation pipeline has a Node.js dependency (
npm ci,mermaid-cli) in addition to the .NET toolchain. Contributors who only run the .NET build locally will not see rendered diagrams. - DocFX is a global dotnet tool installed at runtime in CI
(
dotnet tool update -g docfx). This is not version-pinned via adotnet-tools.jsonmanifest, making the DocFX version non-deterministic across runs. - DocsGen overwrites auto-generated sections silently. A contributor who manually edits the ADR index or the project-structure section will have their changes discarded on the next CI run without warning.
Re-evaluation Triggers
Revisit when:
- DocFX is replaced or upgraded to a version with breaking template
changes — the
docfx.jsonand custom templates need to be updated. - The
dotnet-tools.jsonmanifest is introduced — move the DocFX installation there to pin its version alongside the rest of the toolchain.
Related
- ADR 0013 – OpenAPI Snapshot (Swagger UI is co-hosted on the same site)
.build/docs/package.json— Node tooling.build/docs/generate-diagrams.mjs— Mermaid render script.build/docs/copy-swagger-ui.mjs— Swagger UI bundlingtools/ServiceDeskLite.DocsGen/Program.cs.github/workflows/docs.ymldocs/diagrams/— 15 Mermaid source files