From 6ce3af588829bcd0f790f8d86246336dc4cb23be Mon Sep 17 00:00:00 2001 From: Hugo C <911307+hugocaillard@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:37:03 +0200 Subject: [PATCH] fix: better new project name handling (#1481) * fix: better new project name handling * fix: make project creation more robust if directory exists * fix: ignore sub dependency audit for now --- .github/workflows/ci.yaml | 2 +- components/clarinet-cli/src/frontend/cli.rs | 181 ++++++++++---- components/clarinet-cli/src/generate/mod.rs | 8 +- .../clarinet-cli/src/generate/project.rs | 230 +++++------------- 4 files changed, 199 insertions(+), 222 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f9f6cf9e6..f8a319be3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -85,7 +85,7 @@ jobs: run: cargo install cargo-audit - name: Run audit - run: cargo audit --ignore RUSTSEC-2022-0093 --ignore RUSTSEC-2021-0076 --ignore RUSTSEC-2022-0090 --ignore RUSTSEC-2022-002 --ignore RUSTSEC-2022-0028 + run: cargo audit --ignore RUSTSEC-2022-0093 --ignore RUSTSEC-2021-0076 --ignore RUSTSEC-2022-0090 --ignore RUSTSEC-2022-002 --ignore RUSTSEC-2022-0028 --ignore RUSTSEC-2024-0344 - name: Run rustfmt run: cargo fmt --all -- --check diff --git a/components/clarinet-cli/src/frontend/cli.rs b/components/clarinet-cli/src/frontend/cli.rs index 40ed13b11..1b89ad7e6 100644 --- a/components/clarinet-cli/src/frontend/cli.rs +++ b/components/clarinet-cli/src/frontend/cli.rs @@ -463,7 +463,7 @@ pub fn main() { let mut file = match File::create(file_name.clone()) { Ok(file) => file, Err(e) => { - println!( + eprintln!( "{} Unable to create file {}: {}", red!("error:"), file_name, @@ -477,15 +477,43 @@ pub fn main() { println!("Check your shell's documentation for details about using this file to enable completions for clarinet"); } Command::New(project_opts) => { - let current_path = { - let current_dir = match env::current_dir() { - Ok(dir) => dir, - Err(e) => { - println!("{}{}", format_err!("unable to get current directory"), e); - std::process::exit(1); + let current_path = std::env::current_dir().unwrap_or_else(|e| { + eprintln!("{}{}", format_err!("unable to get current directory"), e); + std::process::exit(1); + }); + let current_dir_name = current_path.file_name().map(|s| s.to_string_lossy()); + let current_path = current_path.to_str().expect("Invalid path").to_owned(); + let use_current_dir = project_opts.name == "."; + + let (relative_dir, project_id) = if use_current_dir { + if let Ok(entries) = std::fs::read_dir(¤t_path) { + let is_empty = entries.count() == 0; + if !is_empty { + println!("{}", yellow!("Current directory is not empty")); + prompt_user_to_continue(); } }; - current_dir.to_str().unwrap().to_owned() + ( + ".", + current_dir_name + .unwrap_or(std::borrow::Cow::Borrowed("project")) + .to_string(), + ) + } else { + if std::fs::read_dir(&project_opts.name).is_ok() { + println!("{}", yellow!("Directory already exists")); + prompt_user_to_continue(); + } + let mut name_and_dir = project_opts.name.rsplitn(2, '/'); + let project_id = name_and_dir.next().unwrap(); + let relative_dir = name_and_dir.next().unwrap_or("."); + (relative_dir, sanitize_project_name(project_id)) + }; + + let project_path = if relative_dir == "." { + current_path + } else { + format!("{}/{}", current_path, relative_dir) }; let telemetry_enabled = if cfg!(feature = "telemetry") { @@ -533,15 +561,16 @@ pub fn main() { ) ); } - let project_id = project_opts.name.clone(); + let changes = match generate::get_changes_for_new_project( - current_path, + project_path, project_id, + use_current_dir, telemetry_enabled, ) { Ok(changes) => changes, Err(message) => { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); std::process::exit(1); } }; @@ -567,7 +596,7 @@ pub fn main() { println!("Checking deployments"); let res = check_deployments(&manifest); if let Err(message) = res { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); process::exit(1); } } @@ -590,7 +619,7 @@ pub fn main() { match generate_default_deployment(&manifest, &network, cmd.no_batch) { Ok(deployment) => deployment, Err(message) => { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); std::process::exit(1); } }; @@ -601,14 +630,14 @@ pub fn main() { (_, true, _) => 1, (true, _, _) => 0, (false, false, false) => { - println!("{}", format_err!("cost strategy not specified (--low-cost, --medium-cost, --high-cost, --manual-cost)")); + eprintln!("{}", format_err!("cost strategy not specified (--low-cost, --medium-cost, --high-cost, --manual-cost)")); std::process::exit(1); } }; match update_deployment_costs(&mut deployment, priority) { Ok(_) => {} Err(message) => { - println!( + eprintln!( "{} unable to update costs\n{}", yellow!("warning:"), message @@ -620,7 +649,7 @@ pub fn main() { let write_plan = if default_deployment_path.exists() { let existing_deployment = load_deployment(&manifest, &default_deployment_path) .unwrap_or_else(|message| { - println!( + eprintln!( "{}", format_err!(format!( "unable to load {default_deployment_path}\n{message}", @@ -636,7 +665,7 @@ pub fn main() { if write_plan { let res = write_deployment(&deployment, &default_deployment_path, false); if let Err(message) = res { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); process::exit(1); } @@ -681,7 +710,7 @@ pub fn main() { let (deployment, _) = match generate_default_deployment(&manifest, network, false) { Ok(deployment) => deployment, Err(message) => { - println!("{}", red!(message)); + eprintln!("{}", red!(message)); std::process::exit(1); } }; @@ -705,7 +734,7 @@ pub fn main() { let deployment = match result { Ok(deployment) => deployment, Err(e) => { - println!("{}", e); + eprintln!("{}", e); std::process::exit(1); } }; @@ -726,7 +755,7 @@ pub fn main() { && !buffer.starts_with('y') && !buffer.starts_with('\n') { - println!("Deployment aborted"); + eprintln!("Deployment aborted"); std::process::exit(1); } } @@ -788,7 +817,7 @@ pub fn main() { }; match cmd { DeploymentEvent::Interrupted(message) => { - println!( + eprintln!( "{} Error publishing transactions: {}", red!("x"), message @@ -817,7 +846,7 @@ pub fn main() { network ), Err(message) => { - println!("{} Error publishing transactions: {}", red!("x"), message) + eprintln!("{} Error publishing transactions: {}", red!("x"), message) } } } @@ -825,7 +854,7 @@ pub fn main() { }, Command::Chainhooks => { let message = "This command is deprecated. Use the chainhooks library instead (https://github.com/hirosystems/chainhook)"; - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); std::process::exit(1); } Command::Contracts(subcommand) => match subcommand { @@ -840,7 +869,7 @@ pub fn main() { ) { Ok(changes) => changes, Err(message) => { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); std::process::exit(1); } }; @@ -859,7 +888,7 @@ pub fn main() { match generate::get_changes_for_rm_contract(&manifest.location, cmd.name) { Ok(changes) => changes, Err(message) => { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); std::process::exit(1); } }; @@ -873,7 +902,7 @@ pub fn main() { ); std::io::stdin().read_line(&mut answer).unwrap(); if !answer.trim().eq_ignore_ascii_case("y") { - println!("{} Not deleting contract files", yellow!("warning:")); + eprintln!("{} Not deleting contract files", yellow!("warning:")); std::process::exit(0); } if !execute_changes(changes) { @@ -1017,7 +1046,7 @@ pub fn main() { let code_source = match fs::read_to_string(&file) { Ok(code) => code, _ => { - println!("{} unable to read file: '{}'", red!("error:"), file); + eprintln!("{} unable to read file: '{}'", red!("error:"), file); std::process::exit(1); } }; @@ -1135,7 +1164,7 @@ pub fn main() { std::process::exit(exit_code); } Command::Integrate(cmd) => { - println!( + eprintln!( "{}", format_warn!("This command is deprecated. Use 'clarinet devnet start' instead"), ); @@ -1145,7 +1174,7 @@ pub fn main() { Command::DAP => match super::dap::run_dap() { Ok(_) => (), Err(e) => { - println!("{}", red!(e)); + eprintln!("{}", red!(e)); process::exit(1); } }, @@ -1153,7 +1182,7 @@ pub fn main() { Devnet::Package(cmd) => { let manifest = load_manifest_or_exit(cmd.manifest_path); if let Err(e) = Package::pack(cmd.package_file_name, manifest) { - println!("Could not execute the package command. {}", format_err!(e)); + eprintln!("Could not execute the package command. {}", format_err!(e)); process::exit(1); } } @@ -1166,7 +1195,7 @@ fn get_manifest_location_or_exit(path: Option) -> FileLocation { match get_manifest_location(path) { Some(manifest_location) => manifest_location, None => { - println!("Could not find Clarinet.toml"); + eprintln!("Could not find Clarinet.toml"); process::exit(1); } } @@ -1176,7 +1205,7 @@ fn get_manifest_location_or_warn(path: Option) -> Option { match get_manifest_location(path) { Some(manifest_location) => Some(manifest_location), None => { - println!( + eprintln!( "{} no manifest found, starting with default settings.", yellow!("note:") ); @@ -1190,7 +1219,7 @@ fn load_manifest_or_exit(path: Option) -> ProjectManifest { match ProjectManifest::from_location(&manifest_location) { Ok(manifest) => manifest, Err(message) => { - println!( + eprintln!( "{} syntax errors in Clarinet.toml\n{}", red!("error:"), message, @@ -1205,7 +1234,7 @@ fn load_manifest_or_warn(path: Option) -> Option { let manifest = match ProjectManifest::from_location(&manifest_location) { Ok(manifest) => manifest, Err(message) => { - println!( + eprintln!( "{} syntax errors in Clarinet.toml\n{}", red!("error:"), message, @@ -1289,7 +1318,7 @@ fn load_deployment_and_artifacts_or_exit( match result { Ok(deployment) => deployment, Err(e) => { - println!("{}", format_err!(e)); + eprintln!("{}", format_err!(e)); process::exit(1); } } @@ -1414,7 +1443,7 @@ fn load_deployment_if_exists( } } Err(message) => { - println!( + eprintln!( "{} unable to compute an updated plan\n{}", red!("error:"), message @@ -1458,6 +1487,24 @@ fn compare_wasm_artifacts( } } +fn sanitize_project_name(name: &str) -> String { + let sanitized: String = name + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '/' { + c + } else { + '_' + } + }) + .collect(); + if sanitized.is_empty() || sanitized.chars().all(|c| c == '_' || c == '/') { + eprintln!("{} Invalid project name", red!("error:")); + process::exit(1) + } + sanitized +} + fn execute_changes(changes: Vec) -> bool { let mut shared_config = None; @@ -1477,7 +1524,7 @@ fn execute_changes(changes: Vec) -> bool { let mut file = match File::create(options.path.clone()) { Ok(file) => file, Err(e) => { - println!( + eprintln!( "{} Unable to create file {}: {}", red!("error:"), options.path, @@ -1489,7 +1536,7 @@ fn execute_changes(changes: Vec) -> bool { match file.write_all(options.content.as_bytes()) { Ok(_) => (), Err(e) => { - println!( + eprintln!( "{} Unable to write file {}: {}", red!("error:"), options.path, @@ -1504,7 +1551,7 @@ fn execute_changes(changes: Vec) -> bool { match fs::create_dir_all(options.path.clone()) { Ok(_) => (), Err(e) => { - println!( + eprintln!( "{} Unable to create directory {}: {}", red!("error:"), options.path, @@ -1523,7 +1570,7 @@ fn execute_changes(changes: Vec) -> bool { let project_manifest_content = match manifest_location.read_content() { Ok(content) => content, Err(message) => { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); return false; } }; @@ -1532,7 +1579,7 @@ fn execute_changes(changes: Vec) -> bool { match toml::from_slice(&project_manifest_content[..]) { Ok(manifest) => manifest, Err(message) => { - println!( + eprintln!( "{} Failed to process manifest file: {}", red!("error:"), message @@ -1546,7 +1593,7 @@ fn execute_changes(changes: Vec) -> bool { ) { Ok(content) => content, Err(message) => { - println!("{}", format_err!(message)); + eprintln!("{}", format_err!(message)); return false; } } @@ -1577,7 +1624,7 @@ fn execute_changes(changes: Vec) -> bool { Changes::RemoveFile(options) => { if let Ok(entry) = fs::metadata(&options.path) { if !entry.is_file() { - println!( + eprintln!( "{} file doesn't exist at path {}", yellow!("warning:"), options.path @@ -1587,7 +1634,7 @@ fn execute_changes(changes: Vec) -> bool { } match fs::remove_file(&options.path) { Ok(_) => println!("{}", options.comment), - Err(e) => println!("error {}", e), + Err(e) => eprintln!("error {}", e), } } } @@ -1597,7 +1644,7 @@ fn execute_changes(changes: Vec) -> bool { let toml_value = match toml::Value::try_from(&project_manifest) { Ok(value) => value, Err(e) => { - println!("{} failed encoding config file ({})", red!("error:"), e); + eprintln!("{} failed encoding config file ({})", red!("error:"), e); return false; } }; @@ -1605,7 +1652,7 @@ fn execute_changes(changes: Vec) -> bool { let pretty_toml = match toml::ser::to_string_pretty(&toml_value) { Ok(value) => value, Err(e) => { - println!("{} failed formatting config file ({})", red!("error:"), e); + eprintln!("{} failed formatting config file ({})", red!("error:"), e); return false; } }; @@ -1614,7 +1661,7 @@ fn execute_changes(changes: Vec) -> bool { .location .write_content(pretty_toml.as_bytes()) { - println!( + eprintln!( "{} Unable to update manifest file - {}", red!("error:"), message @@ -1626,6 +1673,15 @@ fn execute_changes(changes: Vec) -> bool { true } +fn prompt_user_to_continue() { + println!("{}", yellow!("Do you want to continue? (y/N)")); + let mut buffer = String::new(); + std::io::stdin().read_line(&mut buffer).unwrap(); + if !buffer.trim().eq_ignore_ascii_case("y") { + std::process::exit(0); + } +} + fn display_separator() { println!("{}", yellow!("----------------------------")); } @@ -1740,7 +1796,7 @@ fn devnet_start(cmd: DevnetStart, global_settings: GlobalSettings) { let package_file = match File::open(package) { Ok(file) => file, Err(_) => { - println!("{} package file not found", red!("error:")); + eprintln!("{} package file not found", red!("error:")); std::process::exit(1); } }; @@ -1774,7 +1830,7 @@ fn devnet_start(cmd: DevnetStart, global_settings: GlobalSettings) { { Ok(deployment) => deployment, Err(message) => { - println!("{}", red!(message)); + eprintln!("{}", red!(message)); std::process::exit(1); } }; @@ -1802,7 +1858,7 @@ fn devnet_start(cmd: DevnetStart, global_settings: GlobalSettings) { let deployment = match result { Ok(deployment) => deployment, Err(e) => { - println!("{}", format_err!(e)); + eprintln!("{}", format_err!(e)); std::process::exit(1); } }; @@ -1810,7 +1866,7 @@ fn devnet_start(cmd: DevnetStart, global_settings: GlobalSettings) { let orchestrator = match DevnetOrchestrator::new(manifest, None, None, true) { Ok(orchestrator) => orchestrator, Err(e) => { - println!("{}", format_err!(e)); + eprintln!("{}", format_err!(e)); process::exit(1); } }; @@ -1826,7 +1882,7 @@ fn devnet_start(cmd: DevnetStart, global_settings: GlobalSettings) { } match start(orchestrator, deployment, None, !cmd.no_dashboard) { Err(e) => { - println!("{}", format_err!(e)); + eprintln!("{}", format_err!(e)); process::exit(1); } Ok(_) => { @@ -1865,4 +1921,25 @@ mod tests { assert!(result.is_ok(), "failed to generate completion for {shell}",); } } + + #[test] + fn test_sanitize_project_name() { + let sanitized = sanitize_project_name("hello_world"); + assert_eq!(sanitized, "hello_world"); + + let sanitized = sanitize_project_name("Hello_World"); + assert_eq!(sanitized, "Hello_World"); + + let sanitized = sanitize_project_name("Hello-World"); + assert_eq!(sanitized, "Hello-World"); + + let sanitized = sanitize_project_name("hello/world"); + assert_eq!(sanitized, "hello/world"); + + let sanitized = sanitize_project_name("H€llo/world"); + assert_eq!(sanitized, "H_llo/world"); + + let sanitized = sanitize_project_name("H€llo/world"); + assert_eq!(sanitized, "H_llo/world"); + } } diff --git a/components/clarinet-cli/src/generate/mod.rs b/components/clarinet-cli/src/generate/mod.rs index 3b453b384..ebd1c4b35 100644 --- a/components/clarinet-cli/src/generate/mod.rs +++ b/components/clarinet-cli/src/generate/mod.rs @@ -12,9 +12,15 @@ use self::contract::GetChangesForRmContract; pub fn get_changes_for_new_project( project_path: String, project_name: String, + use_current_dir: bool, telemetry_enabled: bool, ) -> Result, String> { - let mut command = GetChangesForNewProject::new(project_path, project_name, telemetry_enabled); + let mut command = GetChangesForNewProject::new( + project_path, + project_name, + use_current_dir, + telemetry_enabled, + ); command.run() } diff --git a/components/clarinet-cli/src/generate/project.rs b/components/clarinet-cli/src/generate/project.rs index 6a3069d72..8bb1c84ea 100644 --- a/components/clarinet-cli/src/generate/project.rs +++ b/components/clarinet-cli/src/generate/project.rs @@ -12,22 +12,38 @@ use super::changes::{Changes, DirectoryCreation, FileCreation}; pub struct GetChangesForNewProject { project_path: String, project_name: String, + use_current_dir: bool, changes: Vec, telemetry_enabled: bool, } impl GetChangesForNewProject { - pub fn new(project_path: String, project_name: String, telemetry_enabled: bool) -> Self { + pub fn new( + project_path: String, + project_name: String, + use_current_dir: bool, + telemetry_enabled: bool, + ) -> Self { + println!("project_path: {project_path}, project_name: {project_name}"); + let project_path = if use_current_dir { + project_path.clone() + } else { + format!("{}/{}", project_path, project_name) + }; + Self { project_path, project_name, + use_current_dir, changes: vec![], telemetry_enabled, } } pub fn run(&mut self) -> Result, String> { - self.create_root_directory(); + if !self.use_current_dir { + self.create_root_directory(); + } self.create_contracts_directory(); self.create_settings_directory(); self.create_tests_directory(); @@ -45,38 +61,19 @@ impl GetChangesForNewProject { } fn create_root_directory(&mut self) { - let dir = format!("{}/{}", self.project_path, self.project_name); let change = DirectoryCreation { comment: format!("{} {}", green!("Created directory"), self.project_name), name: self.project_name.clone(), - path: dir, + path: self.project_path.clone(), }; self.changes.push(Changes::AddDirectory(change)); } - #[allow(dead_code)] - fn create_clients_directory(&mut self) { - self.changes - .push(self.get_changes_for_new_root_dir("clients".into())); - } - fn create_contracts_directory(&mut self) { self.changes .push(self.get_changes_for_new_root_dir("contracts".into())); } - #[allow(dead_code)] - fn create_notebooks_directory(&mut self) { - self.changes - .push(self.get_changes_for_new_root_dir("notebooks".into())); - } - - #[allow(dead_code)] - fn create_scripts_directory(&mut self) { - self.changes - .push(self.get_changes_for_new_root_dir("scripts".into())); - } - fn create_settings_directory(&mut self) { self.changes .push(self.get_changes_for_new_root_dir("settings".into())); @@ -99,23 +96,9 @@ impl GetChangesForNewProject { } "# .into(); - let name = "settings.json".into(); - let path = format!( - "{}/{}/.vscode/{}", - self.project_path, self.project_name, name - ); - let change = FileCreation { - comment: format!( - "{} {}/.vscode/{}", - green!("Created file"), - self.project_name, - name - ), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + let name = ".vscode/settings.json".into(); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_vscode_tasks_json(&mut self) { @@ -140,23 +123,9 @@ impl GetChangesForNewProject { } "# .into(); - let name = "tasks.json".into(); - let path = format!( - "{}/{}/.vscode/{}", - self.project_path, self.project_name, name - ); - let change = FileCreation { - comment: format!( - "{} {}/.vscode/{}", - green!("Created file"), - self.project_name, - name - ), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + let name = ".vscode/tasks.json".into(); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_gitignore(&mut self) { @@ -176,38 +145,24 @@ node_modules "# .into(); let name = ".gitignore".into(); - let path = format!("{}/{}/{}", self.project_path, self.project_name, name); - let change = FileCreation { - comment: format!("{} {}/{}", green!("Created file"), self.project_name, name), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_gitattributes(&mut self) { - let content = r#" -tests/** linguist-vendored + let content = r#"tests/** linguist-vendored vitest.config.js linguist-vendored * text=lf "# .into(); let name = ".gitattributes".into(); - let path = format!("{}/{}/{}", self.project_path, self.project_name, name); - let change = FileCreation { - comment: format!("{} {}/{}", green!("Created file"), self.project_name, name), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_clarinet_toml(&mut self) { let content = format!( - r#" -[project] + r#"[project] name = "{}" description = "" authors = [] @@ -232,14 +187,8 @@ check_checker = {{ trusted_sender = false, trusted_caller = false, callee_filter self.project_name, self.telemetry_enabled ); let name = "Clarinet.toml".into(); - let path = format!("{}/{}/{}", self.project_path, self.project_name, name); - let change = FileCreation { - comment: format!("{} {}/{}", green!("Created file"), self.project_name, name), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_environment_testnet_toml(&mut self) { @@ -253,22 +202,8 @@ mnemonic = "" "# .into(); let name = "Testnet.toml".into(); - let path = format!( - "{}/{}/settings/{}", - self.project_path, self.project_name, name - ); - let change = FileCreation { - comment: format!( - "{} {}/settings/{}", - green!("Created file"), - self.project_name, - name - ), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_environment_mainnet_toml(&mut self) { @@ -281,23 +216,9 @@ deployment_fee_rate = 10 mnemonic = "" "# .into(); - let name = "Mainnet.toml".into(); - let path = format!( - "{}/{}/settings/{}", - self.project_path, self.project_name, name - ); - let change = FileCreation { - comment: format!( - "{} {}/settings/{}", - green!("Created file"), - self.project_name, - name - ), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + let name = "settings/Mainnet.toml".into(); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_environment_devnet_toml(&mut self) { @@ -469,23 +390,9 @@ btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" default_stacks_miner_mnemonic = DEFAULT_STACKS_MINER_MNEMONIC, default_stacks_faucet_mnemonic = DEFAULT_FAUCET_MNEMONIC, ); - let name = "Devnet.toml".into(); - let path = format!( - "{}/{}/settings/{}", - self.project_path, self.project_name, name - ); - let change = FileCreation { - comment: format!( - "{} {}/settings/{}", - green!("Created file"), - self.project_name, - name - ), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + let name = "settings/Devnet.toml".into(); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_nodejs_files(&mut self) { @@ -524,14 +431,8 @@ btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" self.project_name ); let name = "package.json".into(); - let path = format!("{}/{}/{}", self.project_path, self.project_name, name); - let change = FileCreation { - comment: format!("{} {}/{}", green!("Created file"), self.project_name, name), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_ts_config(&mut self) { @@ -564,14 +465,8 @@ btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" "# .into(); let name = "tsconfig.json".into(); - let path = format!("{}/{}/{}", self.project_path, self.project_name, name); - let change = FileCreation { - comment: format!("{} {}/{}", green!("Created file"), self.project_name, name), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)); } fn create_vitest_config(&mut self) { @@ -620,28 +515,27 @@ export default defineConfig({ "#.into(); let name = "vitest.config.js".into(); - let path = format!("{}/{}/{}", self.project_path, self.project_name, name); - let change = FileCreation { - comment: format!("{} {}/{}", green!("Created file"), self.project_name, name), - name, - content, - path, - }; - self.changes.push(Changes::AddFile(change)); + self.changes + .push(self.get_changes_for_new_file(name, content)) } fn get_changes_for_new_root_dir(&self, name: String) -> Changes { - let dir = format!("{}/{}", self.project_name, name); - let change = DirectoryCreation { - comment: format!( - "{} {}/{}", - green!("Created directory"), - self.project_name, - name - ), + let dir = format!("{}/{}", self.project_path, name); + Changes::AddDirectory(DirectoryCreation { + comment: format!("{} {}", green!("Created directory"), name), name, path: dir, - }; - Changes::AddDirectory(change) + }) + } + + fn get_changes_for_new_file(&self, name: String, content: String) -> Changes { + let path = format!("{}/{}", self.project_path, name); + + Changes::AddFile(FileCreation { + comment: format!("{} {}", green!("Created file"), name), + name, + content, + path, + }) } }