theater_cli/commands/
create.rs

1use anyhow::Result;
2use clap::Parser;
3use std::path::PathBuf;
4use tracing::debug;
5
6use crate::{error::CliError, output::formatters::ProjectCreated, templates, CommandContext};
7
8#[derive(Debug, Parser)]
9pub struct CreateArgs {
10    /// Name of the new actor project
11    #[arg(required = true)]
12    pub name: String,
13
14    /// Template to use for the new actor
15    #[arg(short, long, default_value = "basic")]
16    pub template: String,
17
18    /// Output directory to create the project in
19    #[arg(short, long)]
20    pub output_dir: Option<PathBuf>,
21}
22
23/// Execute the create command asynchronously (modernized)
24pub async fn execute_async(args: &CreateArgs, ctx: &CommandContext) -> Result<(), CliError> {
25    debug!("Creating new actor project: {}", args.name);
26    debug!("Using template: {}", args.template);
27
28    return Err(CliError::not_implemented(
29        "create",
30        "Theater CLI \n\
31        The create command is not implemented yet \n\
32        Please create a new component with cargo component new <name> --lib \n\
33        Then, import theater:simple from wa.dev and add the necessary dependencies in your Cargo.toml \n\
34        I do apologize for this, working on documenting / implementing this command soon! \n\
35        In the meantime, please feel free to reach out to me at colinrozzi@gmail.com and I will absolutely walk you through how I create actors!"
36    ));
37
38    // Check if the name is valid
39    if !is_valid_project_name(&args.name) {
40        return Err(CliError::invalid_input(
41            "project_name",
42            &args.name,
43            "Project names must only contain alphanumeric characters, hyphens, and underscores",
44        ));
45    }
46
47    // Get the output directory
48    let output_dir = match &args.output_dir {
49        Some(dir) => dir.clone(),
50        None => std::env::current_dir()
51            .map_err(|e| CliError::file_operation_failed("get current directory", ".", e))?,
52    };
53
54    debug!("Output directory: {}", output_dir.display());
55
56    // Get available templates
57    let templates_list = templates::available_templates();
58
59    // Check if the template exists
60    if !templates_list.contains_key(&args.template) {
61        let available_templates: Vec<String> = templates_list.keys().cloned().collect();
62        return Err(CliError::template_not_found(
63            &args.template,
64            available_templates,
65        ));
66    }
67
68    // Create the project
69    let project_path = output_dir.join(&args.name);
70    templates::create_project(&args.template, &args.name, &output_dir).map_err(|e| {
71        CliError::file_operation_failed(
72            "create project",
73            project_path.display().to_string(),
74            std::io::Error::new(std::io::ErrorKind::Other, e),
75        )
76    })?;
77
78    // Create success result and output
79    let result = ProjectCreated {
80        name: args.name.clone(),
81        template: args.template.clone(),
82        path: project_path,
83        build_instructions: vec![
84            format!("cd {}", args.name),
85            "cargo build --target wasm32-unknown-unknown --release".to_string(),
86            "theater start manifest.toml".to_string(),
87        ],
88    };
89
90    ctx.output.output(&result, None)?;
91    Ok(())
92}
93
94fn is_valid_project_name(name: &str) -> bool {
95    // Check that the name only contains alphanumeric characters, hyphens, and underscores
96    !name.is_empty()
97        && name
98            .chars()
99            .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
100}