dojo_cli/commands/
init.rs

1use crate::config::Config;
2use dialoguer::{Confirm, Input, Password};
3use std::path::Path;
4use std::process::Command;
5
6pub async fn handle_init_command(
7    sub_matches: &clap::ArgMatches,
8) -> Result<(), Box<dyn std::error::Error>> {
9    println!("__________.__  __               .__         ________             __        ");
10    println!("\\______   \\__|/  |_  ____  ____ |__| ____   \\______ \\   ____    |__| ____  ");
11    println!(" |    |  _/  \\   __\\/ ___\\/  _ \\|  |/    \\   |    |  \\ /  _ \\   |  |/  _ \\ ");
12    println!(" |    |   \\  ||  | \\  \\__(  <_> )  |   |  \\  |    `   (  <_> )  |  (  <_> )");
13    println!(" |______  /__||__|  \\___  >____/|__|___|  / /_______  /\\____/\\__|  |\\____/ ");
14    println!("        \\/              \\/              \\/          \\/      \\______|       \n");
15    println!("šŸš€ Welcome to Bitcoin Dojo CLI Setup!");
16    println!("This will help you configure your development environment.\n");
17
18    // Check if config already exists
19    if Config::exists() {
20        let overwrite = Confirm::new()
21            .with_prompt("Configuration already exists. Do you want to overwrite it?")
22            .default(false)
23            .interact()?;
24
25        if !overwrite {
26            println!("Setup cancelled.");
27            return Ok(());
28        }
29    }
30
31    // Prompt for workspace directory
32    let default_workspace = dirs::home_dir()
33        .map(|home| {
34            home.join("bitcoin_dojo_workspace")
35                .to_string_lossy()
36                .to_string()
37        })
38        .unwrap_or_else(|| "./bitcoin_dojo_workspace".to_string());
39
40    let workspace_directory: String = Input::new()
41        .with_prompt("Enter your workspace directory (or press enter to use the default directory)")
42        .default(default_workspace)
43        .interact_text()?;
44
45    // Validate and create workspace directory if it doesn't exist
46    let workspace_path = Path::new(&workspace_directory);
47    if !workspace_path.exists() {
48        let create_dir = Confirm::new()
49            .with_prompt(format!(
50                "Directory '{}' doesn't exist. Create it?",
51                workspace_directory
52            ))
53            .default(true)
54            .interact()?;
55
56        if create_dir {
57            std::fs::create_dir_all(workspace_path)?;
58            println!("āœ… Created workspace directory: {}", workspace_directory);
59        }
60    }
61
62    // Get API token from command line argument or prompt user
63    let api_token = if let Some(token) = sub_matches.get_one::<String>("token") {
64        token.to_string()
65    } else {
66        println!("\nšŸ”‘ API Token Setup");
67        println!("You can get your API token from the Bitcoin Dojo web dashboard.");
68
69        Password::new()
70            .with_prompt("Enter your API token")
71            .interact()?
72    };
73
74    // Validate API token (basic check)
75    if api_token.trim().is_empty() {
76        return Err("API token cannot be empty".into());
77    }
78
79    // Initialize curriculum version from backend (required)
80    println!("\nšŸ”§ Initializing curriculum version...");
81    let curriculum_version = Config::init_curriculum_version(&api_token).await?;
82    println!("āœ… Curriculum version initialized: {}", curriculum_version);
83
84    // Create and save config
85    let config = Config::new(
86        workspace_directory.clone(),
87        api_token,
88        curriculum_version.clone(),
89    );
90    config.save()?;
91
92    let config_path = Config::get_config_path()?;
93
94    println!("\nāœ… Configuration saved successfully!");
95    println!("šŸ“ Config file: {}", config_path.display());
96    println!("šŸ“‚ Workspace: {}", workspace_directory);
97    println!("šŸ“š Curriculum version: {}", curriculum_version);
98
99    // Check if cargo is available before attempting to create Rust project
100    let cargo_available = is_cargo_available();
101
102    if !cargo_available {
103        println!("\nāš ļø  Cargo is not available on your system.");
104        println!("This means you won't be able to create a Rust project automatically.");
105        println!("You can still use the CLI to download exercises, but you'll need to create the Rust project manually.");
106
107        let continue_without_cargo = Confirm::new()
108            .with_prompt("Continue without creating a Rust project?")
109            .default(true)
110            .interact()?;
111
112        if !continue_without_cargo {
113            println!("Setup cancelled. Please install Rust and Cargo first, then try again.");
114            return Ok(());
115        }
116
117        println!("āœ… Configuration saved successfully!");
118        println!("šŸ“ Config file: {}", config_path.display());
119        println!("šŸ“‚ Workspace: {}", workspace_directory);
120        println!("šŸ“š Curriculum version: {}", curriculum_version);
121        println!("\nšŸŽÆ You can now use 'dojo-cli download --exercise <exercise-name>' to download exercises.");
122        println!("Remember to create a Rust project manually in your workspace when you're ready.");
123        return Ok(());
124    }
125
126    // Create new Rust project in workspace
127    println!("\nšŸ¦€ Creating Rust project...");
128    let project_path = workspace_path.join("bitcoin_dojo");
129
130    if project_path.exists() {
131        let overwrite_project = Confirm::new()
132            .with_prompt("Rust project 'bitcoin_dojo' already exists in workspace. Overwrite it?")
133            .default(false)
134            .interact()?;
135
136        if !overwrite_project {
137            println!("Skipping Rust project creation.");
138        } else {
139            std::fs::remove_dir_all(&project_path)?;
140            create_rust_project(&workspace_directory)?;
141        }
142    } else {
143        create_rust_project(&workspace_directory)?;
144    }
145
146    println!("\nšŸŽÆ You're all set! Open your editor to {}/bitcoin_dojo and run 'dojo-cli download --exercise 1.1-hashing-randomness' to download the first exercise.", &workspace_directory);
147
148    Ok(())
149}
150
151fn create_rust_project(workspace_directory: &str) -> Result<(), Box<dyn std::error::Error>> {
152    // Check if cargo is available
153    if !is_cargo_available() {
154        return Err(
155            "āŒ Cargo is not available. Please install Rust and Cargo first.\n\n\
156                   To install Rust, visit https://rustup.rs/ or run:\n\
157                   curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n\
158                   After installation, restart your terminal and try again."
159                .into(),
160        );
161    }
162
163    let output = Command::new("cargo")
164        .args(["new", "--lib", "bitcoin_dojo"])
165        .current_dir(workspace_directory)
166        .output()?;
167
168    if output.status.success() {
169        println!("āœ… Created Rust project: bitcoin_dojo");
170        println!("šŸ“ Project location: {}/bitcoin_dojo", workspace_directory);
171    } else {
172        let error_msg = String::from_utf8_lossy(&output.stderr);
173        return Err(format!("Failed to create Rust project: {}", error_msg).into());
174    }
175
176    Ok(())
177}
178
179/// Check if cargo is available in the system PATH
180fn is_cargo_available() -> bool {
181    Command::new("cargo")
182        .arg("--version")
183        .output()
184        .map(|output| output.status.success())
185        .unwrap_or(false)
186}