# prox
Rusty development process manager like foreman, but better!
**WIP:** This project is a work-in-progress but it's basically working. **The API is subject to change.** I plan to add improvements as I use it/as needed, including a possible TUI interface.
## Motivation
I wanted something to run multiple processes in my project like Ruby's `foreman` (back in the day) but
without the drawbacks of:
- the complexity/sluggishness of wrapping every binary in a Docker container and maintaining a `docker-compose.yaml`
- `cargo bacon` has tedious config and is meant for one-off `cargo check`/`cargo test`, not running multiple procs in parallel like `docker` or `foreman`
- `cargo watch` is abandonware (why not transfer the name to someone else?)
- `just` is great, but requires manual `watchexec`, etc.
- something like `foreman` but a bit smarter for restarts/crash handling, almost like `docker`
- shell scripts to combine logs and manage the procs + `awk` + control-C are a pain!
## Installation
`cargo install prox`
(Or use as a library in your dev-dependencies and create your own wrapper -- see `examples/basic_usage.rs`)
## Usage
### Binary Usage:
Create a prox.toml (or .yaml or .json) file in your workspace/crate root:
```toml
[config]
readiness_fallback_timeout = 15
[[procs]]
name = "api"
command = "cargo"
args = ["run", "--bin", "api"]
working_dir = "api"
readiness_pattern = "Server listening"
# In a workspace, you can watch from the workspace root:
watch = ["Cargo.toml"]
# ... and/or relative to `working_dir`:
watch_rel = ["src"]
env_clear = true
env = { PORT = "3000", RUST_LOG = "debug" }
[[procs]]
name = "worker"
command = "cargo"
args = ["run", "--bin", "worker"]
working_dir = "tests/fixtures"
env_clear = true
```
Then just run `prox`!
### Library Usage:
Add `prox` to your `[dev-dependencies]` and create your own bin wrapper, e.g. `dev`.
See `examples/basic_usage.rs` for an example.
## Features:
- Run several commands in parallel
- Colored prefixes per process
- File watching with automatic restart (global + per-proc watch paths)
- Readiness pattern or fallback timeout
- Debounced output idle detection
- Clean shutdown & optional cleanup commands
- Config via Rust builders or TOML / YAML / JSON files
Events you can receive (via `setup_event_rx()`):
- `AllStarted` when every proc is ready
- `Started`, `Restarted` per process
- `Exited`, `StartFailed` on failure
- `Idle` after no output for the debounce period
- `SigIntReceived` on Ctrl-C (if enabled)
Runtime control (optional): send `ProxSignal::Start`, `Restart`, `Shutdown` through the `signal_rx` channel you provide.
Environment:
- Per process vars: `env` (clears first if `env_clear = true`).
Colors:
- Auto-assigned from `config.colors` unless a proc sets `color`.
Readiness:
- If `readiness_pattern` set, logs are scanned case-insensitively.
- Otherwise, after `readiness_fallback_timeout` the proc is assumed running.
**IMPORTANT!** Not a production supervisor! For local development only.
## Example:
examples/basic_usage.rs
```rs
License: MIT
use owo_colors::AnsiColors;
use prox::{Config, Proc, Prox};
use std::time::Duration;
use std::{collections::HashMap, path::PathBuf};
fn main() -> anyhow::Result<()> {
// Example: Simple process manager with multiple services
let mut manager = Prox::builder()
.config(
Config::builder()
.readiness_fallback_timeout(Duration::from_secs(15))
.build(),
)
.procs(
[
Proc::builder()
.name("api".into())
.command("cargo".into())
.args(vec!["run".into(), "--bin".into(), "api".into()])
.working_dir(PathBuf::from("./api"))
.readiness_pattern("Server listening".into())
.watch(vec![
PathBuf::from("./api/src"),
PathBuf::from("./shared/src"),
])
.env(HashMap::from_iter([("LOG_LEVEL".into(), "debug".into())]))
.color(AnsiColors::BrightGreen)
.build(),
Proc::builder()
.name("database".into())
.command("docker".into())
.args(["run".into()].into())
.env(
[
("POSTGRES_DB".into(), "myapp".into()),
("POSTGRES_PASSWORD".into(), "password".into()),
]
.into(),
)
.readiness_pattern("database system is ready to accept connections".into())
.build(),
Proc::builder()
.name("frontend".into())
.command("npm".into())
.args(vec!["run".into(), "dev".into()])
.working_dir(PathBuf::from("./frontend"))
.readiness_pattern("Local:".into())
.watch(vec![PathBuf::from("./frontend/src")])
.color(AnsiColors::BrightYellow)
.build(),
]
.into(),
)
.build();
println!("Starting development environment...");
// Start all processes - this will block until Ctrl+C or a process exits
manager.start()?;
Ok(())
}
```