use std::sync::Arc;
use anyhow::Result;
use clap::{CommandFactory, Parser, Subcommand};
use owo_colors::OwoColorize;
use url::Url;
use crate::commands::{
BuildCommand, CheckCommand, CompletionCommand, ConfigCommand, DepsArgs, DlqArgs, DocCommand,
GenCommand, InitCommand, LogsCommand, PkgArgs, PsCommand, RegistryArgs, RestartCommand,
RmCommand, RunCommand, StartCommand, StopCommand, VersionCommand,
};
use crate::core::{
ActrCliError, Command, CommandContext, CommandResult, ConfigManager, ConsoleUI,
ContainerBuilder, DefaultCacheManager, DefaultDependencyResolver, DefaultFingerprintValidator,
DefaultNetworkValidator, DefaultProtoProcessor, DiscoveryContext, ErrorReporter,
NetworkServiceDiscovery, ServiceContainer, TomlConfigManager,
};
#[derive(Parser)]
#[command(name = "actr")]
#[command(
about = "Actor-RTC Command Line Tool",
long_about = "Actor-RTC Command Line Tool.\n\n\
Commands are grouped by audience:\n \
development: init / gen / build / check / doc\n \
runtime: run / ps / logs / start / stop / restart / rm\n \
resources: deps / pkg / registry / dlq\n \
meta: config / version / completion",
version,
disable_version_flag = true
)]
pub struct Cli {
#[arg(short, action = clap::ArgAction::Count, hide = true)]
pub verbose: u8,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand)]
pub enum Commands {
Init(InitCommand),
Gen(GenCommand),
Build(BuildCommand),
Check(CheckCommand),
Doc(DocCommand),
Run(RunCommand),
Ps(PsCommand),
Logs(LogsCommand),
Start(StartCommand),
Stop(StopCommand),
Restart(RestartCommand),
Rm(RmCommand),
Deps(DepsArgs),
Pkg(PkgArgs),
Registry(RegistryArgs),
Dlq(DlqArgs),
Config(ConfigCommand),
Version(VersionCommand),
Completion(CompletionCommand),
}
impl Commands {
pub fn as_command(&self) -> &dyn Command {
match self {
Commands::Init(c) => c,
Commands::Gen(c) => c,
Commands::Build(c) => c,
Commands::Check(c) => c,
Commands::Doc(c) => c,
Commands::Run(c) => c,
Commands::Ps(c) => c,
Commands::Logs(c) => c,
Commands::Start(c) => c,
Commands::Stop(c) => c,
Commands::Restart(c) => c,
Commands::Rm(c) => c,
Commands::Deps(c) => c,
Commands::Pkg(c) => c,
Commands::Registry(c) => c,
Commands::Dlq(c) => c,
Commands::Config(c) => c,
Commands::Version(c) => c,
Commands::Completion(c) => c,
}
}
}
pub fn build_cli() -> clap::Command {
Cli::command()
}
pub async fn run() -> Result<()> {
let cli = Cli::parse();
let Some(cmd) = cli.command else {
Cli::command().print_help()?;
return Ok(());
};
let command = cmd.as_command();
let needs_container = !command.required_components().is_empty();
let container = if needs_container {
build_container().await?
} else {
ContainerBuilder::new().build()?
};
let ctx = CommandContext {
container: Arc::new(std::sync::Mutex::new(container)),
args: crate::core::CommandArgs {
command: String::new(),
subcommand: None,
flags: std::collections::HashMap::new(),
positional: Vec::new(),
},
working_dir: std::env::current_dir()?,
};
match command.execute(&ctx).await {
Ok(result) => {
render_result(result);
Ok(())
}
Err(e) => {
if let Some(cli_error) = e.downcast_ref::<ActrCliError>() {
if matches!(cli_error, ActrCliError::OperationCancelled) {
std::process::exit(0);
}
eprintln!("{}", ErrorReporter::format_error(cli_error));
} else {
eprintln!("{} {e:?}", "Error:".red());
}
std::process::exit(1);
}
}
}
fn render_result(result: CommandResult) {
match result {
CommandResult::Success(msg) => {
if !msg.is_empty() && msg != "Help displayed" {
println!("{msg}");
}
}
CommandResult::Install(install_result) => {
println!("Installation complete: {}", install_result.summary());
}
CommandResult::Validation(report) => {
let formatted = ErrorReporter::format_validation_report(&report);
println!("{formatted}");
}
CommandResult::Generation(gen_result) => {
println!("Generated {} files", gen_result.generated_files.len());
}
CommandResult::Error(error) => {
eprintln!("{} {error}", "Error:".red());
std::process::exit(1);
}
}
}
async fn build_container() -> Result<ServiceContainer> {
let config_path = std::path::Path::new("manifest.toml");
let mut builder = ContainerBuilder::new();
let mut config_manager = None;
if config_path.exists() {
builder = builder.config_path(config_path);
}
let mut container = builder.build()?;
container = container.register_user_interface(Arc::new(ConsoleUI::new()));
if config_path.exists() {
let manager = Arc::new(TomlConfigManager::new(config_path));
container = container.register_config_manager(manager.clone());
config_manager = Some(manager);
}
let mut container =
container.register_dependency_resolver(Arc::new(DefaultDependencyResolver::new()));
container = container.register_network_validator(Arc::new(DefaultNetworkValidator::new()));
container =
container.register_fingerprint_validator(Arc::new(DefaultFingerprintValidator::new()));
container = container.register_proto_processor(Arc::new(DefaultProtoProcessor::new()));
container = container.register_cache_manager(Arc::new(DefaultCacheManager::new()));
if let Some(manager) = config_manager {
let config = manager.load_config(config_path).await?;
let effective_cli =
crate::config::resolver::resolve_effective_cli_config().unwrap_or_default();
let signaling_url = Url::parse(&effective_cli.network.signaling_url).map_err(|e| {
anyhow::anyhow!(
"Invalid network.signaling_url '{}': {}",
effective_cli.network.signaling_url,
e
)
})?;
let ais_endpoint = effective_cli.network.ais_endpoint.clone();
let realm_id = effective_cli.network.realm_id.unwrap_or(1);
let realm_secret = effective_cli.network.realm_secret.clone();
let discovery_context = DiscoveryContext {
package_actr_type: config.package.actr_type.clone(),
signaling_url,
ais_endpoint,
realm: actr_protocol::Realm { realm_id },
realm_secret,
};
container = container
.register_service_discovery(Arc::new(NetworkServiceDiscovery::new(discovery_context)));
}
Ok(container)
}