use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::fs;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct XbpConfig {
pub project_name: String,
pub port: u16,
pub build_dir: String,
pub app_type: Option<String>,
pub build_command: Option<String>,
pub start_command: Option<String>,
pub install_command: Option<String>,
pub environment: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone)]
pub struct DeploymentConfig {
pub app_name: String,
pub port: u16,
pub app_dir: PathBuf,
pub build_command: Option<String>,
pub start_command: Option<String>,
pub install_command: Option<String>,
pub environment: HashMap<String, String>,
}
impl DeploymentConfig {
pub async fn from_args_or_config(
app_name: Option<String>,
port: Option<u16>,
app_dir: Option<PathBuf>,
config_path: Option<PathBuf>,
) -> Result<Self, String> {
let xbp_config = if app_name.is_none() || port.is_none() || app_dir.is_none() {
match Self::load_xbp_config(config_path).await {
Ok(config) => Some(config),
Err(_) => None, }
} else {
None
};
let app_name = app_name
.or_else(|| xbp_config.as_ref().map(|c| c.project_name.clone()))
.ok_or("Missing app name")?;
let port = port
.or_else(|| xbp_config.as_ref().map(|c| c.port))
.ok_or("Missing port")?;
let app_dir = app_dir
.or_else(|| xbp_config.as_ref().map(|c| PathBuf::from(&c.build_dir)))
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let app_dir = app_dir.canonicalize()
.map_err(|e| format!("Failed to resolve app directory: {}", e))?;
let build_command = xbp_config.as_ref().and_then(|c| c.build_command.clone());
let start_command = xbp_config.as_ref().and_then(|c| c.start_command.clone());
let install_command = xbp_config.as_ref().and_then(|c| c.install_command.clone());
let environment = xbp_config.as_ref()
.and_then(|c| c.environment.clone())
.unwrap_or_default();
Ok(DeploymentConfig {
app_name,
port,
app_dir,
build_command,
start_command,
install_command,
environment,
})
}
async fn load_xbp_config(config_path: Option<PathBuf>) -> Result<XbpConfig, String> {
let config_path = config_path.unwrap_or_else(|| {
std::env::current_dir()
.unwrap_or_default()
.join(".xbp")
.join("xbp.json")
});
if !config_path.exists() {
return Err(format!("Configuration file not found: {}", config_path.display()));
}
let content = fs::read_to_string(&config_path)
.map_err(|e| format!("Failed to read config file: {}", e))?;
let config: XbpConfig = serde_json::from_str(&content)
.map_err(|e| format!("Failed to parse config file: {}", e))?;
Ok(config)
}
pub async fn save_xbp_config(&self, config_path: Option<PathBuf>) -> Result<(), String> {
let config_path = config_path.unwrap_or_else(|| {
self.app_dir.join(".xbp").join("xbp.json")
});
if let Some(parent) = config_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create config directory: {}", e))?;
}
let xbp_config = XbpConfig {
project_name: self.app_name.clone(),
port: self.port,
build_dir: self.app_dir.to_string_lossy().to_string(),
app_type: None, build_command: self.build_command.clone(),
start_command: self.start_command.clone(),
install_command: self.install_command.clone(),
environment: if self.environment.is_empty() {
None
} else {
Some(self.environment.clone())
},
};
let content = serde_json::to_string_pretty(&xbp_config)
.map_err(|e| format!("Failed to serialize config: {}", e))?;
fs::write(&config_path, content)
.map_err(|e| format!("Failed to write config file: {}", e))?;
Ok(())
}
pub fn update_port(&mut self, new_port: u16) {
self.port = new_port;
}
pub fn merge_with_recommendations(
&mut self,
recommendations: &super::project_detector::DeploymentRecommendations,
) {
if self.build_command.is_none() {
self.build_command = recommendations.build_command.clone();
}
if self.start_command.is_none() {
self.start_command = recommendations.start_command.clone();
}
if self.install_command.is_none() {
self.install_command = recommendations.install_command.clone();
}
if let Some(recommended_name) = &recommendations.process_name {
if self.app_name == "app" || self.app_name == "unknown" {
self.app_name = recommended_name.clone();
}
}
}
}