use crate::cli::logs::{collect_startup_logs, print_startup_logs_block};
use crate::ipc::batch::StartOptions;
use crate::ipc::client::IpcClient;
use crate::pitchfork_toml::PitchforkToml;
use crate::settings::settings;
use crate::{Result, env};
use miette::bail;
#[derive(Debug, clap::Args)]
#[clap(
visible_alias = "r",
verbatim_doc_comment,
long_about = "\
Runs a one-off daemon
Runs a command as a managed daemon without needing a pitchfork.toml.
The daemon is tracked by pitchfork and can be monitored with 'pitchfork status'.
Examples:
pitchfork run api -- npm run dev
Run npm as daemon named 'api'
pitchfork run api -f -- npm run dev
Force restart if 'api' is running
pitchfork run api --retry 3 -- ./server
Restart up to 3 times on failure
pitchfork run api -d 5 -- ./server
Wait 5 seconds for ready check
pitchfork run api -o 'Listening' -- ./server
Wait for output pattern before ready
pitchfork run api --http http://localhost:8080/health -- ./server
Wait for HTTP endpoint to return 2xx
pitchfork run api --port 8080 -- ./server
Wait for TCP port to be listening"
)]
pub struct Run {
id: String,
#[clap(last = true)]
run: Vec<String>,
#[clap(short, long)]
force: bool,
#[clap(long, default_value = "0")]
retry: u32,
#[clap(short, long)]
delay: Option<u64>,
#[clap(short, long)]
output: Option<String>,
#[clap(long)]
http: Option<String>,
#[clap(long)]
port: Option<u16>,
#[clap(long = "expected-port", value_delimiter = ',')]
expected_port: Vec<u16>,
#[clap(long, num_args = 0..=1, value_name = "[BUMP]")]
bump: Option<Option<u32>>,
#[clap(long)]
cmd: Option<String>,
#[clap(short, long)]
quiet: bool,
}
impl Run {
pub async fn run(&self) -> Result<()> {
if self.run.is_empty() {
bail!("No command provided");
}
let ipc = IpcClient::connect(true).await?;
let opts = StartOptions {
force: self.force,
shell_pid: None,
delay: self.delay,
output: self.output.clone(),
http: self.http.clone(),
port: self.port,
cmd: self.cmd.clone(),
expected_port: (!self.expected_port.is_empty()).then_some(self.expected_port.clone()),
auto_bump_port: match self.bump {
None => None,
Some(None) => Some(crate::config_types::PortBump(
settings().default_port_bump_attempts(),
)),
Some(Some(n)) => Some(crate::config_types::PortBump(n)),
},
retry: Some(crate::config_types::Retry(self.retry)),
};
let daemon_id = PitchforkToml::resolve_id_allow_adhoc(&self.id)?;
let result = ipc
.run_adhoc(daemon_id.clone(), self.run.clone(), env::CWD.clone(), opts)
.await?;
if result.exit_code.is_some() {
std::process::exit(1);
}
if !self.quiet && result.started {
match collect_startup_logs(&daemon_id, result.start_time) {
Ok(lines) => print_startup_logs_block(&lines),
Err(e) => debug!("Failed to collect startup logs for {daemon_id}: {e}"),
}
}
Ok(())
}
}