use clap::{CommandFactory, FromArgMatches};
use decdn_cli::commands;
use decdn_common::cli::{self, Cli, Command, ConfigCommand};
#[tokio::main]
#[expect(
clippy::print_stderr,
reason = "process exit boundary; the final user-facing error line, not a log event"
)]
async fn main() -> std::process::ExitCode {
decdn_common::tls::install_crypto_provider();
match Box::pin(run()).await {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(e) => {
if e.downcast_ref::<decdn_cli::commands::doctor::DoctorFailed>()
.is_some()
{
return std::process::ExitCode::FAILURE;
}
if let Some(line) = decdn_cli::commands::interrupt::stop_message(&e) {
eprintln!("{line}");
} else {
eprintln!("Error: {}", decdn_common::redact::sanitize_err_chain(&e));
}
exit_code_for(&e)
}
}
}
fn exit_code_for(err: &anyhow::Error) -> std::process::ExitCode {
decdn_cli::commands::interrupt::stop_exit(err).map_or(
std::process::ExitCode::FAILURE,
std::process::ExitCode::from,
)
}
async fn run() -> anyhow::Result<()> {
let matches = Cli::command().get_matches();
let cli = Cli::from_arg_matches(&matches).unwrap_or_else(|e| e.exit());
let nudge = commands::self_update::NudgePolicy {
json: cli::json_requested(&matches),
excluded: matches!(cli.command, Command::SelfUpdate(_)),
};
decdn_cli::logging::init(decdn_cli::logging::requested_level(
cli.log_level,
cli.verbose,
));
let config_path = cli.config.map(|p| cli::common::expand_tilde(&p));
let pending = commands::self_update::start_nudge(nudge, config_path.as_deref());
let result = match cli.command {
Command::KeyGen(args) => commands::key_gen::key_gen(&args),
Command::Whoami(args) => commands::whoami::whoami(&args, config_path.as_deref()),
Command::Config(args) => match args.command {
ConfigCommand::Init(init) => commands::config::config_init(&init),
ConfigCommand::Validate(validate) => {
commands::config::config_validate(config_path.as_deref(), &validate)
}
},
Command::Probe(args) => commands::probe::probe(&args, config_path.as_deref()).await,
Command::Gateway(args) => commands::gateway::gateway(&args, config_path.as_deref()).await,
Command::Get(args) => commands::get::get(&args, config_path.as_deref()).await,
Command::Setup(args) => commands::setup::run(&args, config_path.as_deref()).await,
Command::Node(args) => commands::node::node_dispatch(&args, config_path.as_deref()).await,
Command::Origin(args) => commands::origin::origin_dispatch(&args).await,
Command::Pool(args) => commands::pool::pool_dispatch(&args, config_path.as_deref()).await,
Command::Publish(args) => {
commands::publish::publish_dispatch(&args, config_path.as_deref()).await
}
Command::Appeal(args) => match args.command {
cli::AppealCommand::Slash(slash) => {
commands::appeal::run(&slash, config_path.as_deref()).await
}
},
Command::SelfUpdate(args) => commands::self_update::self_update(&args).await,
};
if let Some(pending) = pending {
if result.is_ok() {
pending.finish().await;
} else {
pending.cancel();
}
}
result
}
#[cfg(test)]
mod tests;