Skip to main content

hyperlane_cli/new/
fn.rs

1use crate::*;
2
3/// Validate project name
4///
5/// # Arguments
6///
7/// - `&str`: Project name to validate
8///
9/// # Returns
10///
11/// - `Result<(), NewError>`: Ok if valid, error otherwise
12fn validate_project_name(name: &str) -> Result<(), NewError> {
13    if name.is_empty() {
14        return Err(NewError::InvalidName(
15            "Project name cannot be empty".to_string(),
16        ));
17    }
18    if name.contains('/') || name.contains('\\') || name.contains(':') {
19        return Err(NewError::InvalidName(
20            "Project name contains invalid characters".to_string(),
21        ));
22    }
23    if name.starts_with('.') || name.starts_with('-') {
24        return Err(NewError::InvalidName(
25            "Project name cannot start with '.' or '-'".to_string(),
26        ));
27    }
28    Ok(())
29}
30
31/// Check if git is available in the system
32///
33/// # Returns
34///
35/// - `Result<(), NewError>`: Ok if git is available, error otherwise
36async fn check_git_available() -> Result<(), NewError> {
37    let output: std::process::Output = Command::new("git")
38        .arg("--version")
39        .stdout(Stdio::null())
40        .stderr(Stdio::null())
41        .output()
42        .await
43        .map_err(|_| NewError::GitNotFound)?;
44    if output.status.success() {
45        Ok(())
46    } else {
47        Err(NewError::GitNotFound)
48    }
49}
50
51/// Execute git clone command
52///
53/// # Arguments
54///
55/// - `&NewProjectConfig`: Project configuration containing template URL and project name
56///
57/// # Returns
58///
59/// - `Result<(), NewError>`: Success or error
60async fn git_clone(config: &NewProjectConfig) -> Result<(), NewError> {
61    let project_path: PathBuf = PathBuf::from(&config.project_name);
62    if project_path.exists() {
63        return Err(NewError::ProjectExists(config.project_name.clone()));
64    }
65    let output: std::process::Output = Command::new("git")
66        .arg("clone")
67        .arg(&config.template_url)
68        .arg(&config.project_name)
69        .stdout(Stdio::piped())
70        .stderr(Stdio::piped())
71        .output()
72        .await
73        .map_err(NewError::IoError)?;
74    if output.status.success() {
75        Ok(())
76    } else {
77        let stderr: String = String::from_utf8_lossy(&output.stderr).to_string();
78        Err(NewError::CloneFailed(stderr))
79    }
80}
81
82/// Execute new command to create a project from template
83///
84/// # Arguments
85///
86/// - `&str`: Name of the project to create
87///
88/// # Returns
89///
90/// - `Result<(), NewError>`: Success or error
91pub async fn execute_new(project_name: &str) -> Result<(), NewError> {
92    validate_project_name(project_name)?;
93    check_git_available().await?;
94    let config: NewProjectConfig = NewProjectConfig::new(project_name.to_string());
95    log::info!(
96        "Creating new project '{}' from template...",
97        config.project_name
98    );
99    git_clone(&config).await?;
100    log::info!("Successfully created project '{}'", config.project_name);
101    log::info!("  cd {}", config.project_name);
102    log::info!("  cargo build");
103    Ok(())
104}