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 miette::ensure;
use std::sync::Arc;
#[derive(Debug, clap::Args)]
#[clap(
visible_alias = "s",
verbatim_doc_comment,
long_about = "\
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"
)]
pub struct Start {
#[clap(
conflicts_with = "local",
conflicts_with = "global",
conflicts_with = "all"
)]
id: Vec<String>,
#[clap(
long,
value_name = "GROUP",
conflicts_with = "local",
conflicts_with = "global",
conflicts_with = "all"
)]
group: Option<String>,
#[clap(
long,
short = 'l',
visible_alias = "all-local",
conflicts_with = "all",
conflicts_with = "global"
)]
local: bool,
#[clap(
long,
short = 'g',
visible_alias = "all-global",
conflicts_with = "local",
conflicts_with = "all"
)]
global: bool,
#[clap(long, short = 'a', conflicts_with = "local", conflicts_with = "global")]
all: bool,
#[clap(long, hide = true)]
shell_pid: Option<u32>,
#[clap(short, long)]
force: bool,
#[clap(long)]
delay: Option<u64>,
#[clap(long)]
output: Option<String>,
#[clap(long)]
http: Option<String>,
#[clap(long)]
port: Option<u16>,
#[clap(long)]
cmd: Option<String>,
#[clap(long, value_delimiter = ',')]
expected_port: Vec<u16>,
#[clap(long, num_args = 0..=1, value_name = "[BUMP]")]
bump: Option<Option<u32>>,
#[clap(short, long)]
quiet: bool,
}
impl Start {
pub async fn run(&self) -> Result<()> {
ensure!(
self.local || self.global || self.all || !self.id.is_empty() || self.group.is_some(),
"At least one daemon ID, --group, or one of --all / --local / --global must be provided"
);
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 {
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(),
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_display_name(None::<std::iter::Empty<&DaemonId>>);
println!(
" {} {} {}",
ndim("↳"),
display_name,
ncyan(&proxy_url).underlined()
);
}
}
}
}
super::drain_notifications(&ipc).await;
if result.any_failed {
std::process::exit(1);
}
Ok(())
}
}