DR-0018: Versioned docs site from a pure-Go static generator
Status: accepted
Context
The docs of record are the Markdown under docs/ (architecture, specs, design
records). A hand-authored GitHub Pages landing page (site/index.html) already
ships, but its "Docs" links pointed at github.com/.../blob/main/docs/... —
always main, never the release a consumer has pinned. M4 calls for a
versioned docs site: a reader on v0.3.0 should see the v0.3.0 docs, with a
selector to move between releases.
The obvious tools for this (mkdocs, Docusaurus, Antora) are Python or Node.
DR-0017 deliberately made VendKit a single Go implementation and the roadmap
records that "no Python runtime residue remains"; reintroducing an interpreter
or an npm tree purely to render docs would undo that and add a setup-python/
setup-node step and a lockfile to maintain. The consumer-facing promise —
"one static binary, needs nothing" — should extend to how the project builds
its own site.
Decision
- A small static-site generator, written in Go, renders the versioned
docs. It lives at
internal/docsgenas a standalonepackage mainand is run at build time withgo run ./internal/docsgen --docs docs --out dist --version vX.Y.Z. It is never imported bycmd/vendkit, so the shippedvendkitbinary's dependency set is unchanged. - Markdown→HTML uses
github.com/yuin/goldmark(with the GFM extension) — pure Go, no cgo, compiled only into the docsgen tool. It is a build-time dependency of the module, invisible to consumers who install the CLI. A hand-rolled renderer was rejected: goldmark handles the docs' tables, fenced code, autolinks and heading IDs correctly with far less risk than re-implementing CommonMark + GFM. - Output is versioned and accumulating. Each run renders one version into
dist/<version>/(one page per doc plus anindex.html), merges the version into adist/versions.jsonmanifest (newest first, SemVer order markinglatest), and mirrors the newest version intodist/latest/. Every page carries a left nav and a version-selector dropdown that readsversions.jsonat runtime, so pages published for an older release surface releases added later. Internal*.mddoc-to-doc links are rewritten to the rendered*.htmlpaths within the same version. - Output is deterministic — stable ordering, no embedded timestamps — so the generated tree is diff-friendly and testable, and so re-running a version is idempotent.
- Publishing accumulates on a
gh-pagesbranch. The Pages workflow seeds from the currently published branch, overlays the change (landing page and/or a newly rendered version), and republishes the whole tree. This replaces the single-artifactupload-pages-artifact/deploy-pagesmodel, which cannot accumulate versions.peaceiris/actions-gh-pagesis a self-contained action run on the runner — it adds no Node/Python toolchain to the repository.
Alternatives considered
- mkdocs / mkdocs-material — the natural fit for Markdown docs, but pulls in
Python + pip + a
requirements.txt, reintroducing the interpreter DR-0017 retired. Rejected. - Docusaurus / Antora — Node/npm toolchains with a
package.jsonand lockfile to maintain and audit; heavier than the job needs. Rejected. - Hand-rolled Markdown renderer, zero new dependencies — avoids a dependency but must correctly cover headings, paragraphs, fenced/inline code, links, emphasis, ordered+unordered lists and GFM tables across the real docs; a well-maintained pure-Go library is lower risk for the same output. Rejected in favour of goldmark.
- Keep linking to
blob/main/docs/...— no build, but the docs never match a pinned release, which is the entire point of M4. Rejected. - Single-artifact GitHub Pages deploy — simplest CI, but each release overwrites the last; versions cannot coexist. Rejected for the gh-pages accumulation pattern.
Consequences
go build ./cmd/vendkitis unaffected: docsgen is a separate main package and goldmark never enters the binary's dependency graph (verifiable withgo list -deps ./cmd/vendkit).go.modgains one build-time require,github.com/yuin/goldmark;go mod tidykeeps it andgo.sumhonest.- The generator has its own tests (
internal/docsgen), and rendering is exercised against the realdocs/tree, so the site build is covered bygo test ./...rather than only proven in CI. - The GitHub Pages source must be switched once to the
gh-pagesbranch (a repo setting, documented in.github/workflows/pages.yml). - Adding docs is free: any new
*.mdunderdocs/is picked up automatically; no per-page registration.