pub mod command_listener;
pub mod ops;
pub mod session;
use crate::commands::dev::DevArgs;
use crate::config_validation::{validate_configs, ConfigKind};
use crate::context::CliContext;
use anyhow::{Context, Result};
use std::path::Path;
use session::DevSession;
pub async fn handle_dev(ctx: &mut CliContext, args: &DevArgs) -> Result<()> {
let kinds =
dev_config_kinds(&ctx.working_dir).context("failed to determine config validation scope for `mecha10 dev`")?;
let assets_dir = if crate::config_validation::needs_environment_schema(&ctx.working_dir) {
Some(
ctx.assets_download()
.fetch_and_extract()
.await
.context("Failed to download mecha10-assets for config validation")?,
)
} else {
None
};
let schemas_dir = assets_dir
.as_ref()
.map(|dir| dir.path().join("configs/simulation/schemas"));
validate_configs(&ctx.working_dir, &kinds, schemas_dir.as_deref())
.context("pre-flight config validation failed for `mecha10 dev`")?;
let session = DevSession::initialize(ctx, args).await?;
session.execute(ctx).await?;
Ok(())
}
fn dev_config_kinds(project_root: &Path) -> Result<Vec<ConfigKind>> {
let mut kinds = vec![ConfigKind::ProjectAndNodes, ConfigKind::SimulationEnvironments];
let project_config_path = project_root.join(mecha10_cli_core::paths::PROJECT_CONFIG);
if project_config_path.exists() {
let content = std::fs::read_to_string(&project_config_path)
.with_context(|| format!("failed to read {}", project_config_path.display()))?;
let value: serde_json::Value = serde_json::from_str(&content)
.with_context(|| format!("failed to parse JSON from {}", project_config_path.display()))?;
let has_active_behavior = value
.get("behaviors")
.and_then(|b| b.get("active"))
.and_then(|a| a.as_str())
.is_some();
if has_active_behavior {
kinds.push(ConfigKind::ActiveBehaviorTree);
}
}
Ok(kinds)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn dev_config_kinds_is_project_and_nodes_plus_simulation_environments_for_a_minimal_project() {
let dir = tempdir().unwrap();
fs::write(
dir.path().join("mecha10.json"),
serde_json::json!({"name": "proj", "version": "1.0.0"}).to_string(),
)
.unwrap();
assert_eq!(
dev_config_kinds(dir.path()).unwrap(),
vec![ConfigKind::ProjectAndNodes, ConfigKind::SimulationEnvironments]
);
}
#[test]
fn dev_config_kinds_is_project_and_nodes_plus_simulation_environments_when_no_mecha10_json_exists() {
let dir = tempdir().unwrap();
assert_eq!(
dev_config_kinds(dir.path()).unwrap(),
vec![ConfigKind::ProjectAndNodes, ConfigKind::SimulationEnvironments]
);
}
#[test]
fn dev_config_kinds_adds_active_behavior_tree_when_behaviors_active_set() {
let dir = tempdir().unwrap();
fs::write(
dir.path().join("mecha10.json"),
serde_json::json!({"name": "proj", "version": "1.0.0", "behaviors": {"active": "patrol"}}).to_string(),
)
.unwrap();
assert_eq!(
dev_config_kinds(dir.path()).unwrap(),
vec![
ConfigKind::ProjectAndNodes,
ConfigKind::SimulationEnvironments,
ConfigKind::ActiveBehaviorTree
]
);
}
}