#![allow(dead_code)]
use crate::paths;
use crate::services::{
ComponentCatalogService, ConfigService, DeploymentService, DevService, DiscoveryService, DockerService,
InitService, PackageService, ProcessService, ProjectService, ProjectTemplateService, RedisService,
SimulationService, TemplateDownloadService, TemplateService, TopologyService,
};
use crate::types::ProjectConfig;
use std::path::PathBuf;
use tracing::Level;
pub struct CliContext {
pub config_path: PathBuf,
pub log_level: Level,
pub working_dir: PathBuf,
redis_url: String,
postgres_url: Option<String>,
verbose: bool,
dev_mode: bool,
redis_service: Option<RedisService>,
project_service: Option<ProjectService>,
template_service: Option<TemplateService>,
simulation_service: Option<SimulationService>,
process_service: Option<ProcessService>,
shared_process_service: Option<std::sync::Arc<std::sync::Mutex<ProcessService>>>,
docker_service: Option<DockerService>,
package_service: Option<PackageService>,
deployment_service: Option<DeploymentService>,
component_catalog_service: Option<ComponentCatalogService>,
discovery_service: Option<DiscoveryService>,
init_service: Option<InitService>,
project_template_service: Option<ProjectTemplateService>,
dev_service: Option<DevService>,
topology_service: Option<TopologyService>,
template_download_service: Option<TemplateDownloadService>,
_phantom: std::marker::PhantomData<ProjectConfig>,
}
impl CliContext {
pub async fn load_project_config(&self) -> anyhow::Result<ProjectConfig> {
ConfigService::load_from(&self.config_path).await
}
pub fn is_project_initialized(&self) -> bool {
ConfigService::is_initialized(&self.working_dir)
}
pub fn project_path(&self, path: &str) -> PathBuf {
self.working_dir.join(path)
}
pub fn is_verbose(&self) -> bool {
self.verbose
}
pub fn is_dev_mode(&self) -> bool {
self.dev_mode
}
pub fn redis_url(&self) -> &str {
&self.redis_url
}
pub fn postgres_url(&self) -> Option<&str> {
self.postgres_url.as_deref()
}
pub fn redis(&mut self) -> anyhow::Result<&RedisService> {
if self.redis_service.is_none() {
self.redis_service = Some(RedisService::new(&self.redis_url)?);
}
Ok(self.redis_service.as_ref().unwrap())
}
pub fn project(&mut self) -> anyhow::Result<&ProjectService> {
if self.project_service.is_none() {
self.project_service = Some(ProjectService::detect(&self.working_dir)?);
}
Ok(self.project_service.as_ref().unwrap())
}
pub fn template(&mut self) -> &TemplateService {
if self.template_service.is_none() {
self.template_service = Some(TemplateService::new());
}
self.template_service.as_ref().unwrap()
}
pub fn simulation(&mut self) -> &SimulationService {
if self.simulation_service.is_none() {
self.simulation_service = Some(SimulationService::new());
}
self.simulation_service.as_ref().unwrap()
}
pub fn process(&mut self) -> &mut ProcessService {
if self.process_service.is_none() {
self.process_service = Some(ProcessService::new());
}
self.process_service.as_mut().unwrap()
}
pub fn shared_process(&mut self) -> std::sync::Arc<std::sync::Mutex<ProcessService>> {
if self.shared_process_service.is_none() {
self.shared_process_service = Some(std::sync::Arc::new(std::sync::Mutex::new(ProcessService::new())));
}
self.shared_process_service.as_ref().unwrap().clone()
}
pub fn docker(&mut self) -> &DockerService {
if self.docker_service.is_none() {
self.docker_service = Some(DockerService::new());
}
self.docker_service.as_ref().unwrap()
}
pub fn package(&mut self) -> anyhow::Result<&PackageService> {
if self.package_service.is_none() {
let project = self.project()?;
let name = project.name()?;
let version = project.version()?;
self.package_service = Some(PackageService::new(name, version, self.working_dir.clone())?);
}
Ok(self.package_service.as_ref().unwrap())
}
pub fn deployment(&mut self) -> &DeploymentService {
if self.deployment_service.is_none() {
self.deployment_service = Some(DeploymentService::new());
}
self.deployment_service.as_ref().unwrap()
}
pub fn component_catalog(&mut self) -> &ComponentCatalogService {
if self.component_catalog_service.is_none() {
self.component_catalog_service = Some(ComponentCatalogService::new());
}
self.component_catalog_service.as_ref().unwrap()
}
pub fn discovery(&mut self) -> &mut DiscoveryService {
if self.discovery_service.is_none() {
self.discovery_service = Some(DiscoveryService::new());
}
self.discovery_service.as_mut().unwrap()
}
pub fn init_service(&mut self) -> &InitService {
if self.init_service.is_none() {
self.init_service = Some(InitService::new());
}
self.init_service.as_ref().unwrap()
}
pub fn project_template_service(&mut self) -> &ProjectTemplateService {
if self.project_template_service.is_none() {
self.project_template_service = Some(ProjectTemplateService::new());
}
self.project_template_service.as_ref().unwrap()
}
pub fn dev(&mut self) -> &DevService {
if self.dev_service.is_none() {
self.dev_service = Some(DevService::new(self.redis_url.clone()));
}
self.dev_service.as_ref().unwrap()
}
pub fn topology(&mut self) -> &TopologyService {
if self.topology_service.is_none() {
self.topology_service = Some(TopologyService::new(self.working_dir.clone()));
}
self.topology_service.as_ref().unwrap()
}
pub fn template_download(&mut self) -> &TemplateDownloadService {
if self.template_download_service.is_none() {
self.template_download_service = Some(TemplateDownloadService::new());
}
self.template_download_service.as_ref().unwrap()
}
pub fn validate_docker(&mut self) -> anyhow::Result<()> {
let docker = self.docker();
docker.check_installation()?;
docker.check_daemon()?;
Ok(())
}
pub async fn validate_redis(&mut self) -> anyhow::Result<()> {
let redis = self.redis()?;
let _ = redis;
Ok(())
}
pub fn validate_project_structure(&self) -> anyhow::Result<()> {
if !self.is_project_initialized() {
return Err(anyhow::anyhow!("Project not initialized. Run 'mecha10 init' first."));
}
let required_dirs = vec!["nodes", "drivers"];
for dir in required_dirs {
let path = self.project_path(dir);
if !path.exists() {
return Err(anyhow::anyhow!(
"Required directory missing: {}. Expected at: {}",
dir,
path.display()
));
}
}
Ok(())
}
pub fn validate_godot(&mut self) -> anyhow::Result<()> {
let sim = self.simulation();
sim.validate_godot()?;
Ok(())
}
pub fn is_ci(&self) -> bool {
std::env::var("CI").is_ok()
|| std::env::var("GITHUB_ACTIONS").is_ok()
|| std::env::var("GITLAB_CI").is_ok()
|| std::env::var("CIRCLECI").is_ok()
|| std::env::var("JENKINS_HOME").is_ok()
}
pub fn is_interactive(&self) -> bool {
atty::is(atty::Stream::Stdout) && !self.is_ci()
}
pub fn logs_dir(&self) -> PathBuf {
self.project_path("logs")
}
pub fn data_dir(&self) -> PathBuf {
self.project_path("data")
}
pub fn recordings_dir(&self) -> PathBuf {
self.project_path("data/recordings")
}
pub fn maps_dir(&self) -> PathBuf {
self.project_path("data/maps")
}
pub fn telemetry_dir(&self) -> PathBuf {
self.project_path("data/telemetry")
}
pub fn simulation_dir(&self) -> PathBuf {
self.project_path("simulation")
}
pub fn target_debug_dir(&self) -> PathBuf {
self.project_path("target/debug")
}
pub fn target_release_dir(&self) -> PathBuf {
self.project_path("target/release")
}
pub fn packages_dir(&self) -> PathBuf {
self.project_path("target/packages")
}
pub fn ensure_dir(&self, path: &PathBuf) -> anyhow::Result<()> {
if !path.exists() {
std::fs::create_dir_all(path)?;
}
Ok(())
}
}
impl Default for CliContext {
fn default() -> Self {
CliContextBuilder::new()
.log_level(Level::INFO)
.build()
.expect("Failed to build default CliContext")
}
}
pub struct CliContextBuilder {
config_path: Option<PathBuf>,
log_level: Option<Level>,
working_dir: Option<PathBuf>,
redis_url: Option<String>,
postgres_url: Option<String>,
verbose: Option<bool>,
dev_mode: Option<bool>,
}
impl CliContextBuilder {
pub fn new() -> Self {
Self {
config_path: None,
log_level: None,
working_dir: None,
redis_url: None,
postgres_url: None,
verbose: None,
dev_mode: None,
}
}
pub fn config_path(mut self, path: Option<PathBuf>) -> Self {
self.config_path = path;
self
}
pub fn log_level(mut self, level: Level) -> Self {
self.log_level = Some(level);
self
}
pub fn working_dir(mut self, dir: PathBuf) -> Self {
self.working_dir = Some(dir);
self
}
pub fn redis_url(mut self, url: String) -> Self {
self.redis_url = Some(url);
self
}
pub fn postgres_url(mut self, url: Option<String>) -> Self {
self.postgres_url = url;
self
}
pub fn verbose(mut self, verbose: bool) -> Self {
self.verbose = Some(verbose);
self
}
pub fn dev_mode(mut self, dev: bool) -> Self {
self.dev_mode = Some(dev);
self
}
pub fn build(self) -> anyhow::Result<CliContext> {
let working_dir = self
.working_dir
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
let config_path = self
.config_path
.unwrap_or_else(|| working_dir.join(paths::PROJECT_CONFIG));
let config_redis_url = if config_path.exists() {
std::fs::read_to_string(&config_path)
.ok()
.and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
.and_then(|json| json.get("redis")?.get("url")?.as_str().map(String::from))
} else {
None
};
let redis_url = self
.redis_url
.or_else(|| std::env::var("MECHA10_REDIS_URL").ok())
.or_else(|| std::env::var("REDIS_URL").ok())
.or(config_redis_url)
.unwrap_or_else(|| "redis://localhost:6380".to_string());
let postgres_url = self
.postgres_url
.or_else(|| std::env::var("DATABASE_URL").ok())
.or_else(|| std::env::var("POSTGRES_URL").ok());
let log_level = self.log_level.unwrap_or(Level::INFO);
let verbose = self.verbose.unwrap_or(false);
let dev_mode = self.dev_mode.unwrap_or_else(|| {
std::env::var("MECHA10_DEV_MODE")
.or_else(|_| std::env::var("DEV_MODE"))
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false)
});
Ok(CliContext {
config_path,
log_level,
working_dir,
redis_url,
postgres_url,
verbose,
dev_mode,
redis_service: None,
project_service: None,
template_service: None,
simulation_service: None,
process_service: None,
shared_process_service: None,
docker_service: None,
package_service: None,
deployment_service: None,
component_catalog_service: None,
discovery_service: None,
init_service: None,
project_template_service: None,
dev_service: None,
topology_service: None,
template_download_service: None,
_phantom: std::marker::PhantomData,
})
}
}
impl Default for CliContextBuilder {
fn default() -> Self {
Self::new()
}
}