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}