cargo_tangle/command/create/
mod.rs

1pub use crate::command::create::error::Error;
2pub use crate::command::create::source::Source;
3pub use crate::command::create::types::BlueprintType;
4use crate::foundry::FoundryToolchain;
5use types::{BlueprintVariant, EigenlayerVariant};
6
7pub mod error;
8pub mod source;
9pub mod types;
10
11/// Generate a new blueprint from a template
12///
13/// # Errors
14///
15/// See [`cargo_generate::generate()`]
16///
17/// # Parameters
18///
19/// * `name` - The name of the blueprint
20/// * `source` - Optional source information (repo, branch, path)
21/// * `blueprint_type` - Optional blueprint type (Tangle or Eigenlayer)
22/// * `define` - Template variable definitions (key=value pairs)
23/// * `template_values_file` - Optional path to a file containing template values
24/// * `skip_prompts` - Whether to skip all interactive prompts, using defaults for unspecified values
25pub fn new_blueprint(
26    name: &str,
27    source: Option<Source>,
28    blueprint_type: Option<BlueprintType>,
29    mut define: Vec<String>,
30    template_values_file: &Option<String>,
31    skip_prompts: bool,
32) -> Result<(), Error> {
33    println!("Generating blueprint with name: {}", name);
34
35    let source = source.unwrap_or_default();
36    let blueprint_variant = blueprint_type.map(|t| t.get_type()).unwrap_or_default();
37    let template_path_opt: Option<cargo_generate::TemplatePath> = source.into();
38
39    let template_path = template_path_opt.unwrap_or_else(|| {
40        // TODO: Interactive selection (#352)
41        let template_repo: String = match blueprint_variant {
42            Some(BlueprintVariant::Tangle) | None => {
43                "https://github.com/tangle-network/blueprint-template".into()
44            }
45            Some(BlueprintVariant::Eigenlayer(EigenlayerVariant::BLS)) => {
46                "https://github.com/tangle-network/eigenlayer-bls-template".into()
47            }
48            Some(BlueprintVariant::Eigenlayer(EigenlayerVariant::ECDSA)) => {
49                "https://github.com/tangle-network/eigenlayer-ecdsa-template".into()
50            }
51        };
52
53        cargo_generate::TemplatePath {
54            git: Some(template_repo),
55            branch: Some(String::from("main")),
56            ..Default::default()
57        }
58    });
59
60    if skip_prompts {
61        println!("Skipping prompts and using default values for unspecified template variables");
62
63        // Create a map of existing variable definitions
64        let mut defined_vars = std::collections::HashMap::new();
65        for def in &define {
66            if let Some((key, value)) = def.split_once('=') {
67                defined_vars.insert(key.to_string(), value.to_string());
68            }
69        }
70
71        // Define default values for common template variables
72        let defaults = [
73            ("gh-username", ""),
74            ("gh-repo", ""),
75            ("gh-organization", ""),
76            ("project-description", ""),
77            ("project-homepage", ""),
78            ("flakes", "false"),
79            ("container", "true"),
80            ("base-image", "rustlang/rust:nightly"),
81            ("container-registry", "docker.io"),
82            ("ci", "true"),
83            ("rust-ci", "true"),
84            ("release-ci", "true"),
85        ];
86
87        // Add default values for any variables that aren't already defined
88        for (key, value) in defaults {
89            if !defined_vars.contains_key(key) {
90                define.push(format!("{key}={value}"));
91                println!("  Using default value for {key}: {value}");
92            }
93        }
94    } else {
95        println!("Running in interactive mode - will prompt for template variables as needed");
96    }
97
98    if !define.is_empty() {
99        println!("Using template variables: {:?}", define);
100    }
101    let (silent, template_values_file) = if let Some(file) = &template_values_file {
102        println!("Using template values file: {}", file);
103        (true, Some(file.clone()))
104    } else {
105        (false, None)
106    };
107
108    let path = cargo_generate::generate(cargo_generate::GenerateArgs {
109        template_path,
110        list_favorites: false,
111        name: Some(name.to_string()),
112        force: false,
113        verbose: false,
114        template_values_file,
115        silent,
116        config: None,
117        vcs: Some(cargo_generate::Vcs::Git),
118        lib: false,
119        bin: true,
120        ssh_identity: None,
121        gitconfig: None,
122        define,
123        init: false,
124        destination: None,
125        force_git_init: false,
126        allow_commands: false,
127        overwrite: false,
128        skip_submodules: false,
129        other_args: Option::default(),
130        continue_on_error: false,
131        quiet: false,
132    })
133    .map_err(Error::GenerationFailed)?;
134
135    println!("Blueprint generated at: {}", path.display());
136
137    let foundry = FoundryToolchain::new();
138    if !foundry.forge.is_installed() {
139        blueprint_core::warn!("Forge not installed, skipping dependencies");
140        blueprint_core::warn!("NOTE: See <https://getfoundry.sh>");
141        blueprint_core::warn!(
142            "NOTE: After installing Forge, you can run `forge soldeer update -d` to install dependencies"
143        );
144        return Ok(());
145    }
146
147    std::env::set_current_dir(path)?;
148    if let Err(e) = foundry.forge.install_dependencies() {
149        blueprint_core::error!("{e}");
150    }
151
152    Ok(())
153}