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

CommandDescription
cargo buildBuild all crates
cargo testRun all tests
cargo run -- buildBuild the static site
cargo run -- serveStart dev server
cargo doc --workspace --no-depsGenerate API docs (taxus-domain warns on any undocumented public item)
cargo clippyRun linter
cargo fmtFormat code

xtask Task Runner

The workspace includes an xtask crate (aliased as cargo xtask via .cargo/config.toml) that wraps common developer workflows:

CommandDescription
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 auditRun cargo audit security scan (requires cargo-audit)
cargo xtask wasm [--release]Build WASM artifacts
cargo xtask cleanClean build artifacts
cargo xtask ciRun 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 in release.toml with push = 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 the taxus binary 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

LevelWhen
patchBugfixes only — no feat commits in the range
minorAny feat commit (new feature or behavior change)
majorBreaking changes

Check quickly:

git log v<last-tag>..HEAD --format="%s" | grep -c "^feat"

Notes:

  • --no-confirm skips the interactive prompt (required for non-interactive terminals).
  • cargo-release runs cargo 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-run skips the hook entirely; use cargo xtask release --bump <level> --dry-run to check the changelog step, or cargo release hook to run the hook alone. A plain cargo release <level> (no --execute) still modifies Cargo.toml and CHANGELOG.md before stopping (the hook runs even without --execute — git checkout -- . to undo).
  • push = false and publish = false in release.toml: nothing leaves the machine until steps 3–4.
  • The dist workflow also runs in plan mode on pull requests — a free check that the dist configuration still resolves; the expensive build jobs skip PRs.
  • If cargo build/test fails with Access is denied (os error 5) on Windows, a running taxus.exe (usually a leftover serve) is holding the binary: taskkill /F /IM taxus.exe and 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, run cargo 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-domain has #![warn(missing_docs)]: every public item says what it is in glossary terms and why it exists.
  • Every public module in taxus-generator states which phase it belongs to (parse, analyse, emit) and links the theory page that explains it.
  • CHANGELOG.md gets an entry under [Unreleased] with every change.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make changes
  4. Run tests: cargo test
  5. Run linter: cargo clippy
  6. Format: cargo fmt
  7. Submit a pull request