prox 0.1.0

Rusty development process manager like foreman, but better!
Documentation

prox

A development process manager library/CLI.

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:

[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

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(())
}