Development
This guide covers development workflows for contributing to Taxus.
Prerequisites
- Rust — Install Rust
- mdbook — Documentation:
cargo install mdbook --locked(the docs workflow in CI pins 0.4.40; the book builds with 0.4 and 0.5)
Setup
git clone https://github.com/crustyrustacean/taxus.git
cd taxus
cargo build
Running Tests
# Run all unit, integration and doc tests
cargo test
# Run tests for a specific crate
cargo test -p taxus
# Run unit tests only
cargo test --lib
# Run integration tests only
cargo test --test config_loading
Building
# Build the site (islands and the WASM client are compiled and embedded automatically)
cargo run -- build --dir my-site
# Build release binary
cargo build --release
Development Server
# Start server with auto-reload
cargo run -- serve --dir my-site --open
Documentation
cd docs
mdbook serve
Open http://localhost:3000 to view.
Code Commands
| Command | Description |
|---|---|
cargo build | Build all crates |
cargo test | Run all tests |
cargo run -- build | Build the static site |
cargo run -- serve | Start dev server |
cargo doc --workspace --no-deps | Generate API docs (taxus-domain warns on any undocumented public item) |
cargo clippy | Run linter |
cargo fmt | Format code |
xtask Task Runner
The workspace includes an xtask crate (aliased as cargo xtask via
.cargo/config.toml) that wraps common developer workflows:
| Command | Description |
|---|---|
cargo xtask build [--release] [--features ...] | Build the project |
cargo xtask test [--release] [--nextest] [--features ...] | Run unit and integration tests |
cargo xtask check [--features ...] | Fast compile check (no codegen) |
cargo xtask lint [--features ...] [--fix] | Lint with Clippy |
cargo xtask fmt [--check] | Check formatting with rustfmt |
cargo xtask doc [--open] | Build Rust documentation |
cargo xtask book [--serve] | Build the mdBook documentation in docs/ |
cargo xtask audit | Run cargo audit security scan (requires cargo-audit) |
cargo xtask wasm [--release] | Build WASM artifacts |
cargo xtask clean | Clean build artifacts |
cargo xtask ci | Run the full local CI pipeline (fmt, lint, build, test, WASM check, build get-taxus-org/ into target/ci-site) |
cargo xtask release --bump <major|minor|patch> [--dry-run] | Changelog only: promotes [Unreleased] in CHANGELOG.md to the workspace version bumped by the level. See Releasing — versioning and tagging go through cargo release |
cargo xtask changelog --version <x.y.z> [--dry-run] | The cargo release pre-release hook: promotes [Unreleased] to that version; a no-op once done |
cargo xtask deploy [--project <name>] [--branch <name>] [--prod-branch <name>] [--no-build] | Build get-taxus-org/ and deploy to Cloudflare Pages via wrangler (workspace tool; requires Cloudflare credentials) |
Logging
Control log output with CLI flags or RUST_LOG:
# Default: info level
cargo run -- build
# Verbose: debug level
cargo run -- build --verbose
# Quiet: errors only
cargo run -- build --quiet
# Custom via RUST_LOG
RUST_LOG=debug cargo run -- build
RUST_LOG=taxus_lib=trace cargo run -- build
Add logging to code:
#![allow(unused)] fn main() { use tracing::{info, debug, warn, error}; fn build_site() { info!("Building site"); debug!("Processing content"); // Structured fields info!(pages = 5, sections = 2, "Build complete"); } }
Releasing
The changelog is written by hand: every change adds an entry under
## [Unreleased] in CHANGELOG.md as it lands (Keep a Changelog headings —
Added, Changed, Removed, Fixed). Releasing renames that section; nothing is
generated from commit messages.
Releasing involves two machines, each triggered once:
cargo release(local) — bumps the workspace version, promotes the changelog, commits, tags. Configured inrelease.tomlwithpush = false/publish = false: nothing leaves the machine on its own.cargo-dist(CI,.github/workflows/release.yml) — watches for version-tag pushes. On one, it builds thetaxusbinary on six platforms (macOS/Linux/Windows × ARM64/x86-64), packages archives, installers and checksums, and creates the GitHub release with generated notes.
The tag is the starting gun for the second machine. Four steps, one decision — the bump level:
# 1. Read the [Unreleased] section — that is the release note
cargo xtask release --bump <level> --dry-run
# 2. Cut the release (bumps all crates, writes the changelog, commits, tags)
cargo release <level> --execute --no-confirm
# 3. Push the branch; CI runs on the commit as always
git push origin trunk
# 4. Push the tag — dist takes it from here: six-platform build, then
# the GitHub release is created with binaries and generated notes
git push origin vX.Y.Z
After the release exists, replace dist's generated notes with the hand-written ones (the same voice as every previous release):
gh release edit vX.Y.Z --title vX.Y.Z --notes-file <file>
The division of labour matters: dist owns release creation (running
gh release create by hand races it and loses), and we own the
words. Editing is safe at any time after creation.
Choosing the bump level
| Level | When |
|---|---|
patch | Bugfixes only — no feat commits in the range |
minor | Any feat commit (new feature or behavior change) |
major | Breaking changes |
Check quickly:
git log v<last-tag>..HEAD --format="%s" | grep -c "^feat"
Notes:
--no-confirmskips the interactive prompt (required for non-interactive terminals).cargo-releaserunscargo xtask changelog --version <x.y.z>as its pre-release hook, which turns## [Unreleased]into## [x.y.z] - <date>and leaves an empty## [Unreleased]above it. It fails if[Unreleased]is empty, and is a no-op if the version's section already exists, so re-running is safe.cargo release --dry-runskips the hook entirely; usecargo xtask release --bump <level> --dry-runto check the changelog step, orcargo release hookto run the hook alone. A plaincargo release <level>(no--execute) still modifiesCargo.tomlandCHANGELOG.mdbefore stopping (the hook runs even without--execute—git checkout -- .to undo).push = falseandpublish = falseinrelease.toml: nothing leaves the machine until steps 3–4.- The dist workflow also runs in
planmode on pull requests — a free check that the dist configuration still resolves; the expensive build jobs skip PRs. - If
cargo build/testfails withAccess is denied (os error 5)on Windows, a runningtaxus.exe(usually a leftoverserve) is holding the binary:taskkill /F /IM taxus.exeand retry. - CI runs on the push: build/test/clippy, security audit, docs, and the get-taxus.org deploy. Check with
gh run list. The audit fails on yanked crates too, not just vulnerabilities — those appear unpredictably (they're other people's unpublish decisions, e.g. chacha20 0.10.1). If only the audit is red, runcargo update -p <crate>and push a lockfile-only follow-up.
Workspace Structure
taxus/
├── taxus-domain/ # Site Tree, identity types, frontmatter, derivations (no I/O)
├── taxus-generator/ # SSG library and CLI
├── taxus-common/ # Shared Yew components and the search index
├── taxus-client/ # WASM hydration client
├── xtask/ # Workspace task runner (`cargo xtask`)
└── docs/ # mdBook documentation
See Architecture for what each crate does and Theory for the model behind the build.
Documentation conventions
- The vocabulary is fixed by the Glossary. A page that needs a new domain term adds it there first.
taxus-domainhas#![warn(missing_docs)]: every public item says what it is in glossary terms and why it exists.- Every public module in
taxus-generatorstates which phase it belongs to (parse, analyse, emit) and links the theory page that explains it. CHANGELOG.mdgets an entry under[Unreleased]with every change.
Contributing
- Fork the repository
- Create a feature branch
- Make changes
- Run tests:
cargo test - Run linter:
cargo clippy - Format:
cargo fmt - Submit a pull request