| Crates.io | prox |
| lib.rs | prox |
| version | 0.1.1 |
| created_at | 2025-09-08 20:11:26.167663+00 |
| updated_at | 2025-09-08 21:01:02.130831+00 |
| description | Rusty development process manager like foreman, but better! |
| homepage | https://github.com/dra11y/prox |
| repository | https://github.com/dra11y/prox.git |
| max_upload_size | |
| id | 1829817 |
| size | 121,386 |
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.
I wanted something to run multiple processes in my project like Ruby's foreman (back in the day) but
without the drawbacks of:
docker-compose.yamlcargo bacon has tedious config and is meant for one-off cargo check/cargo test, not running multiple procs in parallel like docker or foremancargo watch is abandonware (why not transfer the name to someone else?)just is great, but requires manual watchexec, etc.foreman but a bit smarter for restarts/crash handling, almost like dockerawk + control-C are a pain!cargo install prox
(Or use as a library in your dev-dependencies and create your own wrapper -- see examples/basic_usage.rs)
Create a prox.toml (or .yaml or .json) file in your workspace/crate root:
[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!
Add prox to your [dev-dependencies] and create your own bin wrapper, e.g. dev.
See examples/basic_usage.rs for an example.
Events you can receive (via setup_event_rx()):
AllStarted when every proc is readyStarted, Restarted per processExited, StartFailed on failureIdle after no output for the debounce periodSigIntReceived on Ctrl-C (if enabled)Runtime control (optional): send ProxSignal::Start, Restart, Shutdown through the signal_rx channel you provide.
Environment:
env (clears first if env_clear = true).Colors:
config.colors unless a proc sets color.Readiness:
readiness_pattern set, logs are scanned case-insensitively.readiness_fallback_timeout the proc is assumed running.IMPORTANT! Not a production supervisor! For local development only.
examples/basic_usage.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(())
}