stellar_scaffold_cli/commands/
init.rs1use 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/theahaco/scaffold-stellar-frontend";
11
12#[derive(Parser, Debug, Clone)]
14pub struct Cmd {
15 pub project_path: PathBuf,
17}
18
19#[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 pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
42 let printer: Print = Print::new(global_args.quiet);
43
44 let absolute_project_path = self.project_path.canonicalize().unwrap_or_else(|_| {
46 if self.project_path.is_absolute() {
48 self.project_path.clone()
49 } else {
50 env::current_dir()
51 .unwrap_or_default()
52 .join(&self.project_path)
53 }
54 });
55
56 printer.infoln(format!(
57 "Creating new Stellar project in {}",
58 absolute_project_path.display()
59 ));
60
61 let project_str = absolute_project_path
62 .to_str()
63 .ok_or(Error::InvalidProjectPathEncoding)?;
64
65 degit(FRONTEND_TEMPLATE, project_str);
66
67 if metadata(&absolute_project_path).is_err()
68 || read_dir(&absolute_project_path)?.next().is_none()
69 {
70 return Err(Error::DegitError(format!(
71 "Failed to clone template into {project_str}: directory is empty or missing",
72 )));
73 }
74
75 self.update_oz_example(
77 &absolute_project_path,
78 "fungible-token-interface",
79 global_args,
80 )
81 .await?;
82 self.update_oz_example(&absolute_project_path, "nft-enumerable", global_args)
83 .await?;
84
85 printer.checkln(format!("Project successfully created at {project_str}"));
86 Ok(())
87 }
88
89 async fn update_oz_example(
90 &self,
91 absolute_project_path: &PathBuf,
92 contract_path: &'static str,
93 global_args: &global::Args,
94 ) -> Result<(), Error> {
95 let original_dir = env::current_dir()?;
96 env::set_current_dir(absolute_project_path)?;
97
98 let contracts_path = absolute_project_path.join("contracts");
99 let existing_contract_path = contracts_path.join(contract_path);
100
101 if existing_contract_path.exists() {
102 remove_dir_all(&existing_contract_path)?;
103 }
104
105 let mut quiet_global_args = global_args.clone();
106 quiet_global_args.quiet = true;
107
108 generate::contract::Cmd {
109 from: Some(contract_path.to_owned()),
110 ls: false,
111 from_wizard: false,
112 output: Some(
113 contracts_path
114 .join(contract_path)
115 .to_string_lossy()
116 .into_owned(),
117 ),
118 }
119 .run(&quiet_global_args)
120 .await?;
121 env::set_current_dir(original_dir)?;
122 Ok(())
123 }
124}