use crate::cli::logs::print_startup_logs;
use crate::ipc::batch::StartOptions;
use crate::ipc::client::IpcClient;
use crate::pitchfork_toml::PitchforkToml;
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)]
auto_bump_port: bool,
#[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: self.auto_bump_port,
port_bump_attempts: None,
retry: Some(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
&& let Err(e) = print_startup_logs(&daemon_id, result.start_time)
{
debug!("Failed to print startup logs: {e}");
}
Ok(())
}
}