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