mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Node selection logic for development mode

use crate::services::ProcessService;
use crate::types::{NodeSpec, ProjectConfig};

/// Information about a node to run
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeToRun {
    /// Node name (short name, e.g., "speaker")
    pub name: String,
    /// Full node identifier (e.g., "@mecha10/speaker")
    pub identifier: String,
    /// Path to the node package (e.g., "mecha10-nodes-motor-fake" or "nodes/custom")
    pub path: String,
    /// Whether this is a framework node (@mecha10/*)
    pub is_framework_node: bool,
}

impl NodeToRun {
    /// Create a new NodeToRun from NodeSpec
    pub fn from_spec(spec: &NodeSpec) -> Self {
        Self {
            name: spec.name.clone(),
            identifier: spec.identifier.clone(),
            path: spec.package_path(),
            is_framework_node: spec.is_framework(),
        }
    }

    /// Get the binary path for this node
    ///
    /// Uses smart resolution to find the best binary location:
    /// - Framework dev mode: local build in target/
    /// - Production mode: globally installed binary (if available)
    /// - Fallback: local build
    ///
    /// Note: This method is no longer used by CLI (replaced by node-runner in Phase 2).
    /// Kept for testing purposes.
    #[allow(dead_code)]
    pub fn binary_path(&self, project_name: &str) -> String {
        ProcessService::resolve_node_binary(&self.name, self.is_framework_node, project_name)
    }

    /// Get command args for running this node
    ///
    /// Returns args based on whether we're using:
    /// - Global binary: no args (binary IS the node)
    /// - Local project build: ["node", "node_name"] subcommand
    ///
    /// Note: This method is no longer used by CLI (replaced by node-runner in Phase 2).
    /// Kept for testing purposes.
    #[allow(dead_code)]
    pub fn args(&self, project_name: &str) -> Vec<String> {
        if self.is_framework_node {
            // Check if we're using a global binary
            let binary_path = self.binary_path(project_name);

            // If the binary path doesn't start with "target/", it's a global binary
            // Global binaries don't need the "node" subcommand
            if !binary_path.starts_with("target/") {
                vec![]
            } else {
                // Local build uses "node" subcommand
                vec!["node".to_string(), self.name.clone()]
            }
        } else {
            vec![]
        }
    }
}

/// Get list of nodes to run based on configuration
///
/// # Arguments
///
/// * `requested_nodes` - Specific nodes requested by user (empty = all nodes)
/// * `project_config` - Project configuration
///
/// # Returns
///
/// List of nodes to run
pub fn get_nodes_to_run(requested_nodes: &[String], project_config: &ProjectConfig) -> Vec<NodeToRun> {
    // Get all node specs from config (works with both old and new formats)
    let all_specs = project_config.nodes.get_node_specs();

    // If specific nodes requested, filter to those
    let specs_to_run: Vec<NodeSpec> = if requested_nodes.is_empty() {
        // Use all nodes
        all_specs
    } else {
        // Use only requested nodes
        all_specs
            .into_iter()
            .filter(|s| requested_nodes.contains(&s.name))
            .collect()
    };

    // Convert to NodeToRun
    specs_to_run.iter().map(NodeToRun::from_spec).collect()
}

/// Get list of all available node names
pub fn get_available_node_names(project_config: &ProjectConfig) -> Vec<String> {
    project_config.nodes.get_node_names()
}

/// Get list of enabled node names
///
/// Note: In the new format, all nodes in the list are considered "enabled".
/// Use lifecycle modes to control which nodes run in different contexts.
#[allow(dead_code)] // Tested, planned for future use
pub fn get_enabled_node_names(project_config: &ProjectConfig) -> Vec<String> {
    // In the new format, all nodes are enabled by default
    // Lifecycle modes control which nodes run in which context
    project_config.nodes.get_node_names()
}

/// Find a node spec by name
#[allow(dead_code)] // API for future use
pub fn find_node_by_name(project_config: &ProjectConfig, name: &str) -> Option<NodeSpec> {
    project_config.nodes.find_by_name(name)
}

/// Check if a node is a framework node by name
#[allow(dead_code)] // API for future use
pub fn is_framework_node(project_config: &ProjectConfig, name: &str) -> bool {
    project_config
        .nodes
        .find_by_name(name)
        .map(|s| s.is_framework())
        .unwrap_or(false)
}

/// Check if a node is a project node by name
#[allow(dead_code)] // API for future use
pub fn is_project_node(project_config: &ProjectConfig, name: &str) -> bool {
    project_config
        .nodes
        .find_by_name(name)
        .map(|s| s.is_project())
        .unwrap_or(false)
}