Skip to main content

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| match t {
41            DeploymentTarget::CloudRunner => {
42                format!(
43                    "{}  {}",
44                    "Cloud Runner".cyan(),
45                    "Fully managed, auto-scaling containers".dimmed()
46                )
47            }
48            DeploymentTarget::Kubernetes => {
49                let cluster_count = provider_status
50                    .clusters
51                    .iter()
52                    .filter(|c| c.is_healthy)
53                    .count();
54                format!(
55                    "{}  {} cluster{} available",
56                    "Kubernetes".cyan(),
57                    cluster_count,
58                    if cluster_count == 1 { "" } else { "s" }
59                )
60            }
61        })
62        .collect();
63
64    // Add back option
65    options.push("← Back to provider selection".dimmed().to_string());
66
67    let selection = Select::new("Select deployment target:", options.clone())
68        .with_render_config(wizard_render_config())
69        .with_help_message("↑↓ to move, Enter to select, Esc to cancel")
70        .with_page_size(4)
71        .prompt();
72
73    match selection {
74        Ok(answer) => {
75            if answer.contains("Back") {
76                return TargetSelectionResult::Back;
77            }
78
79            let target = if answer.contains("Cloud Runner") {
80                DeploymentTarget::CloudRunner
81            } else {
82                DeploymentTarget::Kubernetes
83            };
84
85            println!("\n{} Selected: {}", "✓".green(), target.display_name());
86            TargetSelectionResult::Selected(target)
87        }
88        Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
89            println!("\n{}", "Wizard cancelled.".dimmed());
90            TargetSelectionResult::Cancelled
91        }
92        Err(_) => TargetSelectionResult::Cancelled,
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_target_selection_result_variants() {
102        let _ = TargetSelectionResult::Selected(DeploymentTarget::CloudRunner);
103        let _ = TargetSelectionResult::Selected(DeploymentTarget::Kubernetes);
104        let _ = TargetSelectionResult::Back;
105        let _ = TargetSelectionResult::Cancelled;
106    }
107}