syncable_cli/wizard/
target_selection.rs

1//! Target selection step for deployment wizard
2
3use crate::platform::api::types::{DeploymentTarget, ProviderDeploymentStatus};
4use crate::wizard::render::{display_step_header, wizard_render_config};
5use colored::Colorize;
6use inquire::{InquireError, Select};
7
8/// Result of target selection step
9#[derive(Debug, Clone)]
10pub enum TargetSelectionResult {
11    /// User selected a deployment target
12    Selected(DeploymentTarget),
13    /// User wants to go back to provider selection
14    Back,
15    /// User cancelled the wizard
16    Cancelled,
17}
18
19/// Display target selection based on provider capabilities
20pub fn select_target(provider_status: &ProviderDeploymentStatus) -> TargetSelectionResult {
21    display_step_header(
22        2,
23        "Select Target",
24        "Choose how to deploy your service. Cloud Runner is fully managed. Kubernetes gives you more control.",
25    );
26
27    let available_targets = provider_status.available_targets();
28
29    if available_targets.is_empty() {
30        println!(
31            "\n{}",
32            "No deployment targets available for this provider.".red()
33        );
34        return TargetSelectionResult::Cancelled;
35    }
36
37    // Build options with descriptions
38    let mut options: Vec<String> = available_targets
39        .iter()
40        .map(|t| {
41            match t {
42                DeploymentTarget::CloudRunner => {
43                    format!(
44                        "{}  {}",
45                        "Cloud Runner".cyan(),
46                        "Fully managed, auto-scaling containers".dimmed()
47                    )
48                }
49                DeploymentTarget::Kubernetes => {
50                    let cluster_count = provider_status.clusters.iter().filter(|c| c.is_healthy).count();
51                    format!(
52                        "{}  {} cluster{} available",
53                        "Kubernetes".cyan(),
54                        cluster_count,
55                        if cluster_count == 1 { "" } else { "s" }
56                    )
57                }
58            }
59        })
60        .collect();
61
62    // Add back option
63    options.push("← Back to provider selection".dimmed().to_string());
64
65    let selection = Select::new("Select deployment target:", options.clone())
66        .with_render_config(wizard_render_config())
67        .with_help_message("↑↓ to move, Enter to select, Esc to cancel")
68        .with_page_size(4)
69        .prompt();
70
71    match selection {
72        Ok(answer) => {
73            if answer.contains("Back") {
74                return TargetSelectionResult::Back;
75            }
76
77            let target = if answer.contains("Cloud Runner") {
78                DeploymentTarget::CloudRunner
79            } else {
80                DeploymentTarget::Kubernetes
81            };
82
83            println!("\n{} Selected: {}", "✓".green(), target.display_name());
84            TargetSelectionResult::Selected(target)
85        }
86        Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
87            println!("\n{}", "Wizard cancelled.".dimmed());
88            TargetSelectionResult::Cancelled
89        }
90        Err(_) => TargetSelectionResult::Cancelled,
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_target_selection_result_variants() {
100        let _ = TargetSelectionResult::Selected(DeploymentTarget::CloudRunner);
101        let _ = TargetSelectionResult::Selected(DeploymentTarget::Kubernetes);
102        let _ = TargetSelectionResult::Back;
103        let _ = TargetSelectionResult::Cancelled;
104    }
105}