# Development
## Building
```bash
# Fast feedback build
cargo build
# Optimized build
cargo build --release
# Run locally
cargo run -- --help
```
## Linting
```bash
cargo fmt
cargo clippy
```
## Testing
Standard test suite:
```bash
cargo test
```
Verbose end-to-end output:
```bash
DSC_TEST_VERBOSE=1 cargo test -- --nocapture
```
End-to-end tests hit a real Discourse. Provide credentials in `testdsc.toml` (or point `TEST_DSC_CONFIG` to a file) using the shape shown below; otherwise e2e tests auto-skip.
```toml
[[discourse]]
name = "myforum"
baseurl = "https://forum.example.com"
apikey = "<admin api key>"
api_username = "system"
changelog_topic_id = 123 # optional unless testing update changelog posting
test_topic_id = 456 # topic used by e2e topic tests
test_category_id = 789 # category used by e2e category tests
test_color_scheme_id = 321 # palette used by e2e palette tests
emoji_path = "./smile.png" # optional; enables emoji add test
emoji_name = "smile"
test_plugin_url = "https://github.com/discourse/discourse-reactions"
test_plugin_name = "discourse-reactions"
test_theme_url = "https://github.com/discourse/discourse-brand-header"
test_theme_name = "discourse-brand-header"
```
## Shell completions
Completion scripts are generated on demand by the binary itself — they are not committed to the repo. Regenerate them for any supported shell with:
```bash
cargo run -- completions zsh --dir completions/
cargo run -- completions bash --dir completions/
cargo run -- completions fish --dir completions/
```
The `completions/` directory is gitignored. See [docs/completions.md](completions.md) for user-facing installation instructions.
## Documentation site
The docs you're reading are built with [Zensical](https://zensical.org) and deployed to GitHub Pages by `.github/workflows/deploy-docs-to-ghpages.yml` on every push to `main` that touches `docs/`, `mkdocs.yml`, or `requirements.txt`.
To preview locally:
```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
s/docs # serves at http://localhost:8000 with hot reload
```
`s/docs` is a thin wrapper around `zensical serve`.
### Gotcha: inotify instances on Linux
Zensical's file watcher consumes inotify *instances*. The kernel default on most Linux distros is `fs.inotify.max_user_instances=128`, which runs out fast once you've got editors, file syncers and the like open. Symptoms:
```text
thread 'zrx/monitor' panicked … Too many open files
Build started
```
Fix (one-liner to try the current session, then the persistent form):
```bash
sudo sysctl fs.inotify.max_user_instances=512
```
The `s/docs` script warns if the limit is at its stock 128 value.
## Release
Releasing is **one action**: `s/version++`.
1. Commit your feature work first, with a conventional-commit message (`feat(...)`, `fix:`, …) - git-cliff builds the changelog from committed history.
2. Run `s/version++ [patch|minor|major]` (default `patch`). It bumps the version in `Cargo.toml`, regenerates `CHANGELOG.md` (git-cliff), makes the `chore(release): vX.Y.Z` commit, tags it, and pushes `main` + the tag. It refuses to run off `main` or on a dirty tree.
3. The pushed `v*` tag triggers the `Release` workflow (cargo-dist: prebuilt binaries + GitHub Release) and the `crates.io` publish (requires `CARGO_REGISTRY_TOKEN`).
There is no separate `s/release` step - `s/version++` does everything, in the correct order (commit before tag, so the tag contains the bump).
## Project layout
- CLI entrypoint and commands: [src/main.rs](../src/main.rs)
- API client and forum interactions: [src/discourse.rs](../src/discourse.rs)
- Config structures and helpers: [src/config.rs](../src/config.rs)
- Utility helpers (slugify, I/O): [src/utils.rs](../src/utils.rs)
- Example configuration: [dsc.example.toml](../dsc.example.toml)
- CLI design standards: [spec/cli-design.md](../spec/cli-design.md); internals/release: [spec/spec.md](../spec/spec.md)