use crate::Result;
use crate::cli::list::build_proxy_url;
use crate::cli::logs::print_startup_logs;
use crate::daemon_id::DaemonId;
use crate::ipc::batch::StartOptions;
use crate::ipc::client::IpcClient;
use crate::pitchfork_toml::PitchforkToml;
use crate::settings::settings;
use crate::ui::style::{nbold, ncyan, ndim, nstyle};
use itertools::Itertools;
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 -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,
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)]
auto_bump_port: bool,
#[clap(short, long)]
quiet: bool,
}
impl Start {
pub async fn run(&self) -> Result<()> {
ensure!(
self.local || self.global || self.all || !self.id.is_empty(),
"At least one daemon ID 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(&self.id)?
};
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: self.auto_bump_port,
..Default::default()
};
let result = ipc.start_daemons(&ids, opts).await?;
if !self.quiet {
for (id, start_time, resolved_ports) in &result.started {
if let Err(e) = print_startup_logs(id, *start_time) {
debug!("Failed to print startup logs for {id}: {e}");
}
if !resolved_ports.is_empty() {
let port_str = resolved_ports.iter().map(ToString::to_string).join(", ");
let port_label = if resolved_ports.len() == 1 {
"port"
} else {
"ports"
};
println!(
" {} {} started on {} {}",
nstyle("✔").green(),
nbold(id),
port_label,
ncyan(&port_str),
);
} else {
println!(" {} {} started", nstyle("✔").green(), nbold(id));
}
let s = settings();
if s.proxy.enable && !resolved_ports.is_empty() {
let global_slugs = PitchforkToml::read_global_slugs();
let slug_name: Option<&str> = global_slugs
.iter()
.find(|(slug, entry)| {
let daemon_name = entry.daemon.as_deref().unwrap_or(slug);
id.name() == daemon_name
})
.map(|(slug, _)| slug.as_str());
if let Some(proxy_url) = build_proxy_url(slug_name, s) {
println!(" {} {}", ndim("↳"), ncyan(&proxy_url).underlined(),);
}
}
}
}
super::drain_notifications(&ipc).await;
if result.any_failed {
std::process::exit(1);
}
Ok(())
}
}