# devforge
Config-driven dev environment orchestrator for Rust workspaces. Define your Docker services, health checks, hooks, and process manager in a single TOML file.
## Quick Start
### 1. Add the xtask crate to your workspace
```toml
# xtask/Cargo.toml
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
devforge = "0.3"
```
```rust
// xtask/src/main.rs
fn main() {
devforge::run();
}
```
### 2. Add the cargo alias
```toml
# .cargo/config.toml
[alias]
xtask = "run --package xtask --"
```
### 3. Create `devforge.toml` at your workspace root
```toml
env_files = [".env"]
required_tools = ["docker", "cargo", "mprocs"]
[docker]
compose_file = "docker-compose.yml"
[[docker.health_checks]]
name = "postgres"
cmd = ["docker", "compose", "exec", "-T", "postgres", "pg_isready", "-U", "myuser"]
timeout = 30
[[docker.health_checks]]
name = "redis"
tcp = "localhost:6379"
timeout = 15
[dev]
mprocs_config = "mprocs.yaml"
[[dev.hooks]]
cmd = "npm install"
cwd = "web"
[dev.hooks.condition]
missing = "web/node_modules"
```
### 4. Run it
```bash
cargo xtask dev # Start everything
cargo xtask infra # Docker only (Ctrl+C to stop)
```
## Commands
| `dev` | Preflight checks, docker compose up, health checks, pre-launch hooks, process runner. Docker tears down automatically on exit. |
| `infra` | Docker compose up + health checks. Blocks until Ctrl+C, then tears down. |
| Custom | Any `[[commands]]` entry defined in your TOML. |
## Configuration Reference
### Top-level
```toml
# Files that must exist before starting (checked in order)
env_files = [".env", "web/.env.local"]
# Commands that must be in PATH
required_tools = ["docker", "cargo", "node", "npm", "mprocs"]
```
Files listed in `env_files` are loaded into the process environment if they have no extension or a `.env` extension (e.g. `.env`, `.env.local`).
Entries can also use the object form with a `template` field. If the target file doesn't exist, devforge copies the template before running preflight checks:
```toml
[[env_files]]
path = "safe-route/.env"
template = "safe-route/.env.example"
```
### `[docker]`
```toml
[docker]
compose_file = "docker-compose.yml" # default
```
### `[[docker.health_checks]]`
Three types of health checks (exactly one of `cmd`, `url`, or `tcp` must be set):
**Command-based** -- runs a command, success = exit code 0:
```toml
[[docker.health_checks]]
name = "postgres"
cmd = ["docker", "compose", "exec", "-T", "postgres", "pg_isready", "-U", "myuser"]
timeout = 30 # seconds, default 30
```
**URL-based** -- HTTP GET, success = 2xx response (must use `http://` or `https://`):
```toml
[[docker.health_checks]]
name = "minio"
url = "http://localhost:9000/minio/health/live"
timeout = 30
```
**TCP-based** -- connects to a TCP socket, success = connection established:
```toml
[[docker.health_checks]]
name = "redis"
tcp = "localhost:6379"
timeout = 30
```
Health checks poll every 1 second until success or timeout. On failure, the error includes the service name, check type, and specific reason:
```
[devforge] ✗ health check failed: postgres (cmd: docker compose exec ...) — exit code 1: could not connect
```
### `[dev]`
```toml
[dev]
mprocs_config = "mprocs.yaml" # default
```
### `[dev.runner]`
Controls which process manager the `dev` command uses. Defaults to mprocs if omitted.
```toml
# Explicit mprocs (same as omitting the block entirely)
[dev.runner]
type = "mprocs"
# Run an arbitrary shell command instead of mprocs
[dev.runner]
type = "shell"
cmd = "npm run dev"
# No process manager — just run hooks, then block until Ctrl+C
[dev.runner]
type = "none"
```
### `[[dev.hooks]]`
Hooks run before the process runner launches. They execute via `sh -c`.
```toml
[[dev.hooks]]
cmd = "npm install"
cwd = "web" # optional, relative to workspace root
[dev.hooks.condition]
missing = "web/node_modules" # only run if this path doesn't exist
```
### `[[commands]]`
Define custom commands accessible via `cargo xtask <name>`:
```toml
[[commands]]
name = "migrate"
cmd = ["cargo", "run", "--release", "--package", "my-api", "--", "migrate"]
description = "Run database migrations"
docker = true # start docker infra first (default: false)
```
## How It Works
```
cargo xtask dev
|
v
Preflight: check env files, load .env, verify tools
|
v
docker compose up -d
|
v
Health checks (poll until ready or timeout)
|
v
Pre-launch hooks (conditional)
|
v
Process runner (mprocs TUI / shell cmd / Ctrl+C wait)
|
v (on exit)
docker compose down
```
## License
MIT