stellar_scaffold_cli/commands/
init.rs

1use clap::Parser;
2use degit::degit;
3use std::fs::{metadata, read_dir, remove_dir_all};
4use std::path::PathBuf;
5use std::{env, io};
6
7use super::generate;
8use stellar_cli::{commands::global, print::Print};
9
10const FRONTEND_TEMPLATE: &str = "https://github.com/AhaLabs/scaffold-stellar-frontend";
11
12/// A command to initialize a new project
13#[derive(Parser, Debug, Clone)]
14pub struct Cmd {
15    /// The path to the project must be provided
16    pub project_path: PathBuf,
17}
18
19/// Errors that can occur during initialization
20#[derive(thiserror::Error, Debug)]
21pub enum Error {
22    #[error("Failed to clone template: {0}")]
23    DegitError(String),
24    #[error("Project path contains invalid UTF-8 characters and cannot be converted to a string")]
25    InvalidProjectPathEncoding,
26    #[error("IO error: {0}")]
27    IoError(#[from] io::Error),
28    #[error(transparent)]
29    GenerateError(#[from] generate::contract::Error),
30}
31
32impl Cmd {
33    /// Run the initialization command
34    ///
35    /// # Example:
36    ///
37    /// ```
38    /// /// From the command line
39    /// stellar scaffold init /path/to/project
40    /// ```
41    pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
42        let printer = Print::new(global_args.quiet);
43
44        printer.infoln(format!(
45            "Creating new Stellar project in {:?}",
46            self.project_path
47        ));
48
49        let project_str = self
50            .project_path
51            .to_str()
52            .ok_or(Error::InvalidProjectPathEncoding)?;
53        degit(FRONTEND_TEMPLATE, project_str);
54
55        if metadata(&self.project_path).is_err() || read_dir(&self.project_path)?.next().is_none() {
56            return Err(Error::DegitError(format!(
57                "Failed to clone template into {project_str}: directory is empty or missing",
58            )));
59        }
60
61        self.update_fungible_token_example(global_args).await?;
62
63        printer.checkln(format!("Project successfully created at {project_str}"));
64        Ok(())
65    }
66
67    async fn update_fungible_token_example(&self, global_args: &global::Args) -> Result<(), Error> {
68        let original_dir = env::current_dir()?;
69        env::set_current_dir(&self.project_path)?;
70
71        let contracts_path = self.project_path.join("contracts");
72        let fungible_token_path = contracts_path.join("fungible-token-interface");
73
74        if fungible_token_path.exists() {
75            remove_dir_all(&fungible_token_path)?;
76        }
77
78        let mut quiet_global_args = global_args.clone();
79        quiet_global_args.quiet = true;
80
81        generate::contract::Cmd {
82            from: Some("fungible-token-interface".to_owned()),
83            ls: false,
84            from_wizard: false,
85            output: Some(
86                contracts_path
87                    .join("fungible-token-interface")
88                    .to_string_lossy()
89                    .into_owned(),
90            ),
91        }
92        .run(&quiet_global_args)
93        .await?;
94        env::set_current_dir(original_dir)?;
95        Ok(())
96    }
97}