use crate::Result;
use crate::cli::list::build_proxy_url;
use crate::daemon_id::DaemonId;
use crate::ipc::batch::{StartOptions, update_job_with_result};
use crate::ipc::client::IpcClient;
use crate::pitchfork_toml::PitchforkToml;
use crate::settings::settings;
use crate::ui::style::{ncyan, ndim};
use std::sync::Arc;
pub(crate) const LONG_ABOUT: &str = "\
Starts a daemon from a pitchfork.toml file
Daemons are defined in pitchfork.toml with a `[daemons.<name>]` section.
The command waits for the daemon to be ready before returning.
Examples:
pitchfork start api Start a single daemon
pitchfork start api worker Start multiple daemons
pitchfork start --group backend Start all daemons in the 'backend' group
pitchfork start -l Start all local daemons in pitchfork.toml
pitchfork start -g Start all global daemons in config.toml
pitchfork start -a Start all daemons (local and global)
pitchfork start api -f Restart daemon if already running
pitchfork start api --delay 5 Wait 5 seconds for daemon to be ready
pitchfork start api --output 'Listening on'
Wait for output pattern before ready
pitchfork start api --http http://localhost:8080/health
Wait for HTTP endpoint to return 2xx
pitchfork start api --port 8080
Wait for TCP port to be listening";
#[derive(Debug, usage_rs::Args)]
#[usage(
verbatim_doc_comment,
long_about = LONG_ABOUT
)]
pub struct Start {
#[usage(conflicts = "local", conflicts = "global", conflicts = "all")]
id: Vec<String>,
#[usage(
long,
value_name = "GROUP",
conflicts = "local",
conflicts = "global",
conflicts = "all"
)]
group: Option<String>,
#[usage(
long,
short = 'l',
visible_alias = "all-local",
conflicts = "all",
conflicts = "global"
)]
local: bool,
#[usage(
long,
short = 'g',
visible_alias = "all-global",
conflicts = "local",
conflicts = "all"
)]
global: bool,
#[usage(long, short = 'a', conflicts = "local", conflicts = "global")]
all: bool,
#[usage(long, hide = true)]
shell_pid: Option<u32>,
#[usage(short, long)]
force: bool,
#[usage(long)]
delay: Option<u64>,
#[usage(long)]
output: Option<String>,
#[usage(long)]
http: Option<String>,
#[usage(long)]
port: Option<u16>,
#[usage(long)]
cmd: Option<String>,
#[usage(long)]
health_cmd: Option<String>,
#[usage(long)]
health_http: Option<String>,
#[usage(long)]
health_port: Option<u16>,
#[usage(long, delimiter = ',')]
expected_port: Vec<u16>,
#[usage(long, value_name = "BUMP")]
bump: Option<Option<u32>>,
#[usage(short, long)]
quiet: bool,
}
impl Start {
pub async fn run(&self) -> Result<()> {
let no_target =
self.id.is_empty() && self.group.is_none() && !self.local && !self.global && !self.all;
if no_target {
super::interactive::require_interactive_terminal()?;
}
let ipc = Arc::new(IpcClient::connect(true).await?);
let ids: Vec<DaemonId> = if self.all {
IpcClient::get_all_configured_daemons()?
} else if self.global {
IpcClient::get_global_configured_daemons()?
} else if self.local {
IpcClient::get_local_configured_daemons()?
} else if no_target {
let all = IpcClient::get_all_configured_daemons()?;
let candidates = if self.force {
all
} else {
let running: std::collections::HashSet<DaemonId> =
ipc.get_running_daemons().await?.into_iter().collect();
all.into_iter().filter(|id| !running.contains(id)).collect()
};
super::interactive::select_daemons_interactively(&candidates, "start")?
} else {
PitchforkToml::resolve_ids_and_group(&self.id, self.group.as_deref())?
};
if ids.is_empty() {
warn!("No daemons to start");
return Ok(());
}
let opts = StartOptions {
force: self.force,
shell_pid: self.shell_pid,
delay: self.delay,
output: self.output.clone(),
http: self.http.clone(),
port: self.port,
cmd: self.cmd.clone(),
health_cmd: self.health_cmd.clone(),
health_http: self.health_http.clone(),
health_port: self.health_port,
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(
crate::settings::settings().default_port_bump_attempts(),
)),
Some(Some(n)) => Some(crate::config_types::PortBump(n)),
},
quiet: self.quiet,
..Default::default()
};
let result = ipc.start_daemons(&ids, opts).await?;
for update in &result.pending_job_updates {
update_job_with_result(update.job.as_deref(), &update.id, &update.run_result);
}
clx::progress::stop();
clx::progress::clear_jobs();
if !self.quiet {
let global_slugs = settings()
.proxy
.enable
.then(PitchforkToml::read_global_slugs)
.unwrap_or_default();
for (id, _start_time, resolved_ports) in &result.started {
let s = settings();
if s.proxy.enable && !resolved_ports.is_empty() {
let slug_name =
PitchforkToml::find_slug_for_daemon_in_registry(id, &global_slugs);
if let Some(proxy_url) = build_proxy_url(slug_name.as_deref(), &s) {
let display_name = id.styled_qualified();
println!(
" {} {} {}",
ndim("↳"),
display_name,
ncyan(&proxy_url).underlined()
);
}
}
}
}
super::drain_notifications(&ipc).await;
if result.any_failed {
std::process::exit(1);
}
Ok(())
}
}