Skip to main content

mecha10_cli/dev/
lifecycle_adapter.rs

1//! Lifecycle adapter for CLI process management
2//!
3//! Adapts the runtime lifecycle system for the CLI's process-based node management.
4
5use crate::types::project::{NodeRef, ProjectConfig, TargetsConfig};
6use anyhow::Result;
7use mecha10_runtime::lifecycle::diff_nodes;
8use std::collections::{HashMap, HashSet};
9
10pub struct CliLifecycleManager {
11    current_mode: String,
12
13    running_nodes: HashSet<String>,
14
15    mode_config: HashMap<String, Vec<String>>,
16}
17
18#[derive(Debug)]
19#[allow(dead_code)]
20pub struct ModeTransitionDiff {
21    pub start: Vec<String>,
22
23    pub stop: Vec<String>,
24}
25
26impl CliLifecycleManager {
27    /// Create a new CLI lifecycle manager from project config
28    ///
29    /// Returns None if project doesn't have any `targets` configured
30    pub fn from_project_config(config: &ProjectConfig) -> Option<Self> {
31        let mode_config: HashMap<String, Vec<String>> = config
32            .targets
33            .present_entries()
34            .into_iter()
35            .map(|(name, nodes)| (name.to_string(), nodes.iter().map(NodeRef::wire_identifier).collect()))
36            .collect();
37
38        if mode_config.is_empty() {
39            return None;
40        }
41
42        Some(Self {
43            current_mode: "dev".to_string(),
44            running_nodes: HashSet::new(),
45            mode_config,
46        })
47    }
48
49    /// Get the current mode
50    pub fn current_mode(&self) -> &str {
51        &self.current_mode
52    }
53
54    /// Check if a mode exists in the configuration
55    pub fn has_mode(&self, mode: &str) -> bool {
56        self.mode_config.contains_key(mode)
57    }
58
59    /// Get list of nodes that should run in the current mode
60    pub fn nodes_for_current_mode(&self) -> Vec<String> {
61        self.mode_config.get(&self.current_mode).cloned().unwrap_or_default()
62    }
63
64    #[allow(dead_code)]
65    pub fn calculate_mode_diff(&self, target_mode: &str) -> Result<ModeTransitionDiff> {
66        let target_nodes = self
67            .mode_config
68            .get(target_mode)
69            .ok_or_else(|| anyhow::anyhow!("Mode '{}' not found", target_mode))?;
70
71        let (start, stop) = diff_nodes(target_nodes, &self.running_nodes);
72
73        Ok(ModeTransitionDiff { start, stop })
74    }
75
76    /// Mark nodes as running (after spawning them)
77    pub fn mark_nodes_running(&mut self, nodes: &[String]) {
78        for node in nodes {
79            self.running_nodes.insert(node.clone());
80        }
81    }
82
83    /// Mark nodes as stopped (after killing them)
84    #[allow(dead_code)]
85    pub fn mark_nodes_stopped(&mut self, nodes: &[String]) {
86        for node in nodes {
87            self.running_nodes.remove(node);
88        }
89    }
90
91    /// Change to a new mode (updates internal state only)
92    ///
93    /// Returns the diff of what needs to change. Caller is responsible
94    /// for actually spawning/killing processes.
95    #[allow(dead_code)]
96    pub fn change_mode(&mut self, target_mode: &str) -> Result<ModeTransitionDiff> {
97        let diff = self.calculate_mode_diff(target_mode)?;
98        self.current_mode = target_mode.to_string();
99        Ok(diff)
100    }
101
102    /// Get available modes
103    #[allow(dead_code)]
104    pub fn available_modes(&self) -> Vec<&str> {
105        self.mode_config.keys().map(|s| s.as_str()).collect()
106    }
107
108    /// Validate target configuration
109    ///
110    /// Checks that:
111    /// - All node references exist in project config
112    #[allow(dead_code)]
113    pub fn validate(targets: &TargetsConfig, available_nodes: &[String]) -> Result<()> {
114        let node_set: HashSet<_> = available_nodes.iter().map(|s| s.as_str()).collect();
115
116        for (mode_name, nodes) in targets.present_entries() {
117            for node in nodes {
118                if !node_set.contains(node.name()) {
119                    return Err(anyhow::anyhow!(
120                        "Mode '{}' references unknown node '{}'",
121                        mode_name,
122                        node.name()
123                    ));
124                }
125            }
126        }
127
128        Ok(())
129    }
130}