mecha10_cli/dev/
node_selection.rs

1//! Node selection logic for development mode
2
3use crate::services::ProcessService;
4use crate::types::{NodeSpec, ProjectConfig};
5
6/// Information about a node to run
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct NodeToRun {
9    /// Node name
10    pub name: String,
11    /// Path to the node package (e.g., "mecha10-nodes-motor-fake" or "nodes/custom")
12    pub path: String,
13    /// Whether this is a framework node (@mecha10/*)
14    pub is_framework_node: bool,
15}
16
17impl NodeToRun {
18    /// Create a new NodeToRun from NodeSpec
19    pub fn from_spec(spec: &NodeSpec) -> Self {
20        Self {
21            name: spec.name.clone(),
22            path: spec.package_path(),
23            is_framework_node: spec.is_framework(),
24        }
25    }
26
27    /// Get the binary path for this node
28    ///
29    /// Uses smart resolution to find the best binary location:
30    /// - Framework dev mode: local build in target/
31    /// - Production mode: globally installed binary (if available)
32    /// - Fallback: local build
33    ///
34    /// Note: This method is no longer used by CLI (replaced by node-runner in Phase 2).
35    /// Kept for testing purposes.
36    #[allow(dead_code)]
37    pub fn binary_path(&self, project_name: &str) -> String {
38        ProcessService::resolve_node_binary(&self.name, self.is_framework_node, project_name)
39    }
40
41    /// Get command args for running this node
42    ///
43    /// Returns args based on whether we're using:
44    /// - Global binary: no args (binary IS the node)
45    /// - Local project build: ["node", "node_name"] subcommand
46    ///
47    /// Note: This method is no longer used by CLI (replaced by node-runner in Phase 2).
48    /// Kept for testing purposes.
49    #[allow(dead_code)]
50    pub fn args(&self, project_name: &str) -> Vec<String> {
51        if self.is_framework_node {
52            // Check if we're using a global binary
53            let binary_path = self.binary_path(project_name);
54
55            // If the binary path doesn't start with "target/", it's a global binary
56            // Global binaries don't need the "node" subcommand
57            if !binary_path.starts_with("target/") {
58                vec![]
59            } else {
60                // Local build uses "node" subcommand
61                vec!["node".to_string(), self.name.clone()]
62            }
63        } else {
64            vec![]
65        }
66    }
67}
68
69/// Get list of nodes to run based on configuration
70///
71/// # Arguments
72///
73/// * `requested_nodes` - Specific nodes requested by user (empty = all nodes)
74/// * `project_config` - Project configuration
75///
76/// # Returns
77///
78/// List of nodes to run
79pub fn get_nodes_to_run(requested_nodes: &[String], project_config: &ProjectConfig) -> Vec<NodeToRun> {
80    // Get all node specs from config (works with both old and new formats)
81    let all_specs = project_config.nodes.get_node_specs();
82
83    // If specific nodes requested, filter to those
84    let specs_to_run: Vec<NodeSpec> = if requested_nodes.is_empty() {
85        // Use all nodes
86        all_specs
87    } else {
88        // Use only requested nodes
89        all_specs
90            .into_iter()
91            .filter(|s| requested_nodes.contains(&s.name))
92            .collect()
93    };
94
95    // Convert to NodeToRun
96    specs_to_run.iter().map(NodeToRun::from_spec).collect()
97}
98
99/// Get list of all available node names
100pub fn get_available_node_names(project_config: &ProjectConfig) -> Vec<String> {
101    project_config.nodes.get_node_names()
102}
103
104/// Get list of enabled node names
105///
106/// Note: In the new format, all nodes in the list are considered "enabled".
107/// Use lifecycle modes to control which nodes run in different contexts.
108#[allow(dead_code)] // Tested, planned for future use
109pub fn get_enabled_node_names(project_config: &ProjectConfig) -> Vec<String> {
110    // In the new format, all nodes are enabled by default
111    // Lifecycle modes control which nodes run in which context
112    project_config.nodes.get_node_names()
113}
114
115/// Find a node spec by name
116#[allow(dead_code)] // API for future use
117pub fn find_node_by_name(project_config: &ProjectConfig, name: &str) -> Option<NodeSpec> {
118    project_config.nodes.find_by_name(name)
119}
120
121/// Check if a node is a framework node by name
122#[allow(dead_code)] // API for future use
123pub fn is_framework_node(project_config: &ProjectConfig, name: &str) -> bool {
124    project_config
125        .nodes
126        .find_by_name(name)
127        .map(|s| s.is_framework())
128        .unwrap_or(false)
129}
130
131/// Check if a node is a project node by name
132#[allow(dead_code)] // API for future use
133pub fn is_project_node(project_config: &ProjectConfig, name: &str) -> bool {
134    project_config
135        .nodes
136        .find_by_name(name)
137        .map(|s| s.is_project())
138        .unwrap_or(false)
139}