Verify documentation in CI
Run FsLiveDocs on every change so a renamed symbol, a broken example, or a stale transcript fails the build instead of reaching readers. The same commands publish the site and its release history.
What to run
The documentation build has three stages, in order:
dotnet build— the projects must compile before their documentation can be checked against them.dotnet livedocs test— audits every F# block and runs each executable example and transcript.dotnet livedocs build— renders the current site tooutput/.
test and build both perform the full audit, so a pull request that only needs
verification can stop after test.
CI-friendly output
Pass these on every livedocs invocation in an automated environment:
dotnet livedocs test --interactive false --banner false
--interactive false— line-oriented logs with no animated progress. FsLiveDocs also detects a non-terminal stdout, but wrapper scripts and background jobs can defeat that detection, so set it explicitly.--banner false— suppresses the start-up banner.--verbosity info(optional) — adds progress messages;debugexpands every audited block and its compiler message.--warn-as-error(optional) — makes API documentation quality warnings fail the run.
Generic pipeline
Any CI system that provides the .NET SDK and Node.js can run:
dotnet tool restore
dotnet build --nologo
dotnet livedocs test --interactive false --banner false
dotnet livedocs build --interactive false --banner false
livedocs build shells out to npx pagefind to build the search index, so Node.js
must be on PATH. Publish the output/ directory as the site artifact.
If you commit a generated snapshot test project (see Author and test examples), run it as an ordinary test project and add a check that it is up to date:
dotnet test tests/FsLiveDocs.SnapshotTests/FsLiveDocs.SnapshotTests.fsproj
dotnet livedocs generate-tests --interactive false --banner false
git diff --exit-code tests/FsLiveDocs.SnapshotTests
Releasing: what the tool does and what CI does
FsLiveDocs owns the capsule, the history index, and verification. It never uploads an asset or moves a Git ref — your CI does that, in steps you can read. A release is:
# tool
dotnet livedocs capture --version "$V" --output "artifacts/$NAME-$V-livedocs.zip"
dotnet livedocs history-check --capsule "artifacts/$NAME-$V-livedocs.zip" --version "$V"
# CI — upload the capsule + report + .sha256 to a durable HTTPS location
# tool
dotnet livedocs history-add --version "$V" \
--url "$CAPSULE_URL" --sha256-file "artifacts/$NAME-$V-livedocs.zip.sha256"
dotnet livedocs history-check
# CI — commit .livedocs/history.json to the default branch
The committed .livedocs/history.json is the source of truth. The site job then runs
build-history + verify-output from it.
GitHub Actions
dotnet livedocs generate-ci
writes .github/workflows/livedocs.yml with the provider steps (gh release create,
git commit/push) spelled out and interleaved with the livedocs calls. It:
| Trigger | Action |
|---|---|
| every pull request | dotnet build, livedocs test |
push to main |
the above, then livedocs build + build-history + verify-output, then deploy to GitHub Pages |
a v* tag |
capture, history-check, gh release create, history-add, history-check, commit history.json to main |
Assumptions:
- GitHub repository with Pages set to "GitHub Actions", default branch
main; - releases tagged
v<semver>; - .NET SDK
10.0.x, Node.js22— edit the workflow for other versions; GITHUB_TOKENmay create releases and push tomain(permissions: contents: write, pages: write, id-token: write).
generate-ci refuses to overwrite an existing livedocs.yml; delete it to regenerate.
GitLab CI (or any other host)
Replace the two provider steps. Publish however your host does it, then register and commit:
release:
script:
- dotnet livedocs capture --version "$V" --output "artifacts/$NAME-$V-livedocs.zip" --interactive false --banner false
- dotnet livedocs history-check --capsule "artifacts/$NAME-$V-livedocs.zip" --version "$V" --interactive false --banner false
- |
glab release create "v$V" \
"artifacts/$NAME-$V-livedocs.zip" "artifacts/$NAME-$V-livedocs.zip.report.json"
- |
url="$CI_PROJECT_URL/-/releases/v$V/downloads/$NAME-$V-livedocs.zip"
dotnet livedocs history-add --version "$V" --url "$url" \
--sha256-file "artifacts/$NAME-$V-livedocs.zip.sha256" --interactive false --banner false
dotnet livedocs history-check --interactive false --banner false
- |
git add .livedocs/history.json
git commit -m "Record $V in the release history"
git push "https://oauth2:$RELEASE_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git" HEAD:$CI_DEFAULT_BRANCH
Set history.urlPattern in .livedocs/config.json to drop the explicit --url. For a host
without a predictable download URL, upload wherever you like and pass that URL. To keep
history-sync working for backfills on a non-GitHub host, set history.discover to a command
that prints version url sha256 lines.
First-time migration
If your history has so far been rebuilt in CI by history-sync rather than committed, run it
once locally and commit the result so the index is complete before switching the site job to
build straight from it:
dotnet livedocs history-sync <owner/repo> --output .livedocs/history.json
git add .livedocs/history.json && git commit -m "Backfill release history"
See Capture and publish releases for each command and the command reference for every flag.