use crate::config::HylixConfig;
use crate::env_builder::EnvBuilder;
use crate::error::{HylixError, HylixResult};
use crate::logging::{log_error, log_info, log_success};
use crate::validation::{validate_project, ValidationLevel};
use std::process::Command;
pub async fn execute(testnet: bool, watch: bool, extra_args: Vec<String>) -> HylixResult<()> {
if testnet {
log_info("Starting backend in testnet mode...");
} else {
log_info("Starting backend in local dev mode...");
}
if watch {
log_info("Watch mode enabled (auto-rebuild on file changes)");
}
let config = HylixConfig::load()?;
validate_project(ValidationLevel::Run)?;
if !testnet && !crate::commands::devnet::is_devnet_responding(&config.clone().into()).await? {
return Err(HylixError::devnet(format!(
"Devnet is not running. Please start the devnet first with '{}'.",
console::style("hy devnet up").green()
)));
}
if watch {
run_with_watch(testnet, &config).await?;
} else {
let backend = run_backend(testnet, &config, false, &extra_args).await?;
wait_backend(backend).await?;
}
Ok(())
}
pub async fn run_backend(
testnet: bool,
config: &crate::config::HylixConfig,
for_testing: bool,
extra_args: &[String],
) -> HylixResult<tokio::process::Child> {
let server_port = config.run.server_port.to_string();
let mut args = vec![
"run",
"--bin",
"server",
"-F",
crate::constants::features::NONREPRODUCIBLE,
];
args.extend(config.build.extra_flags.iter().map(String::as_str));
args.extend(["--", "--server-port", &server_port]);
if config.build.release {
args.insert(1, "--release");
}
if testnet {
args.push("--testnet");
}
if for_testing && config.test.clean_server_data {
args.push("--clean-data-directory");
}
if !for_testing && config.run.clean_server_data {
args.push("--clean-data-directory");
}
args.extend(extra_args.iter().map(String::as_str));
let print_logs = !for_testing || config.test.print_server_logs;
log_info(&format!(
"{}",
console::style(&format!("$ cargo {}", args.join(" "))).green()
));
let backend = EnvBuilder::for_devnet(config)
.into_tokio_command("cargo")
.stdout(if print_logs {
std::process::Stdio::inherit()
} else {
std::process::Stdio::piped()
})
.stderr(if print_logs {
std::process::Stdio::inherit()
} else {
std::process::Stdio::piped()
})
.args(&args)
.spawn()
.map_err(|e| HylixError::backend(format!("Failed to start backend: {e}")))?;
log_success("Backend started successfully!");
log_info("Backend is running. Press Ctrl+C to stop.");
if !print_logs {
log_info("Backend logs will not be printed to console. They will be saved to a file in the working directory.");
log_info(&format!(
"You can change this with `{}`.",
console::style("hy config edit test.print_server_logs true").green()
));
} else {
log_info("Backend logs will be printed to console.");
log_info(&format!(
"You can change this with `{}`.",
console::style("hy config edit test.print_server_logs false").green()
));
}
Ok(backend)
}
async fn wait_backend(mut backend: tokio::process::Child) -> HylixResult<()> {
match backend.wait().await {
Ok(status) => {
if status.success() {
log_info("Backend stopped gracefully");
} else {
log_error(&format!("Backend exited with error: {status}"));
}
}
Err(e) => {
return Err(HylixError::backend(format!(
"Error waiting for backend: {e}"
)));
}
}
Ok(())
}
async fn run_with_watch(testnet: bool, config: &crate::config::HylixConfig) -> HylixResult<()> {
log_info("Starting backend with watch mode...");
if which::which("cargo-watch").is_err() {
log_info("Installing cargo-watch for watch mode...");
install_cargo_watch().await?;
}
let mut args = vec!["watch", "-x", "run -p server"];
if testnet {
args.push("--");
args.push("--testnet");
}
let mut backend = EnvBuilder::for_devnet(config)
.hyli_database_url(&config.devnet.postgres_port)
.into_std_command("cargo")
.args(&args)
.spawn()
.map_err(|e| HylixError::backend(format!("Failed to start backend with watch: {e}")))?;
log_success("Backend started with watch mode!");
log_info("Backend will automatically rebuild and restart on file changes.");
log_info("Press Ctrl+C to stop.");
match backend.wait() {
Ok(status) => {
if status.success() {
log_info("Backend stopped gracefully");
} else {
log_error(&format!("Backend exited with error: {status}"));
}
}
Err(e) => {
return Err(HylixError::backend(format!(
"Error waiting for backend: {e}"
)));
}
}
Ok(())
}
async fn install_cargo_watch() -> HylixResult<()> {
let output = Command::new("cargo")
.args(["install", "cargo-watch"])
.output()
.map_err(|e| HylixError::backend(format!("Failed to install cargo-watch: {e}")))?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
return Err(HylixError::backend(format!(
"Failed to install cargo-watch: {error_msg}"
)));
}
Ok(())
}