-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/init overwrite #1477
Feat/init overwrite #1477
Changes from 4 commits
07bdff2
14afce4
2e9dc77
e93da7a
5f163fe
98ea14b
192a902
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -47,6 +47,9 @@ pub struct Cmd { | |||||
long_help = "An optional flag to pass in a url for a frontend template repository." | ||||||
)] | ||||||
pub frontend_template: String, | ||||||
|
||||||
#[arg(short, long, long_help = "Overwrite all existing files.")] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the intent that the command will overwrite the entire directory such as remove the directory contents first, then start writing? Or is the intent that the command will overwrite individual files also included in the template and leave any additional files as they are? The term 'all' may introduce some ambiguity to the scope of what is removed, and given the destructive nature of this command we should try and limit the ambiguity. The term 'any' may limit the ambiguity, although not perfectly.
Suggested change
|
||||||
pub overwrite: bool, | ||||||
} | ||||||
|
||||||
fn possible_example_values() -> ValueParser { | ||||||
|
@@ -89,7 +92,12 @@ impl Cmd { | |||||
println!("ℹ️ Initializing project at {}", self.project_path); | ||||||
let project_path = Path::new(&self.project_path); | ||||||
|
||||||
init(project_path, &self.frontend_template, &self.with_example)?; | ||||||
init( | ||||||
project_path, | ||||||
&self.frontend_template, | ||||||
&self.with_example, | ||||||
self.overwrite, | ||||||
)?; | ||||||
|
||||||
Ok(()) | ||||||
} | ||||||
|
@@ -103,13 +111,14 @@ fn init( | |||||
project_path: &Path, | ||||||
frontend_template: &str, | ||||||
with_examples: &[String], | ||||||
overwrite: bool, | ||||||
) -> Result<(), Error> { | ||||||
// create a project dir, and copy the contents of the base template (contract-init-template) into it | ||||||
create_dir_all(project_path).map_err(|e| { | ||||||
eprintln!("Error creating new project directory: {project_path:?}"); | ||||||
e | ||||||
})?; | ||||||
copy_template_files(project_path)?; | ||||||
copy_template_files(project_path, overwrite)?; | ||||||
|
||||||
if !check_internet_connection() { | ||||||
println!("⚠️ It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); | ||||||
|
@@ -127,7 +136,7 @@ fn init( | |||||
clone_repo(frontend_template, fe_template_dir.path())?; | ||||||
|
||||||
// copy the frontend template files into the project | ||||||
copy_frontend_files(fe_template_dir.path(), project_path)?; | ||||||
copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; | ||||||
} | ||||||
|
||||||
// if there are --with-example flags, include the example contracts | ||||||
|
@@ -142,17 +151,17 @@ fn init( | |||||
clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; | ||||||
|
||||||
// copy the example contracts into the project | ||||||
copy_example_contracts(examples_dir.path(), project_path, with_examples)?; | ||||||
copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; | ||||||
} | ||||||
|
||||||
Ok(()) | ||||||
} | ||||||
|
||||||
fn copy_template_files(project_path: &Path) -> Result<(), Error> { | ||||||
fn copy_template_files(project_path: &Path, overwrite: bool) -> Result<(), Error> { | ||||||
for item in TemplateFiles::iter() { | ||||||
let mut to = project_path.join(item.as_ref()); | ||||||
|
||||||
if file_exists(&to) { | ||||||
let exists = file_exists(&to); | ||||||
if exists && !overwrite { | ||||||
println!( | ||||||
"ℹ️ Skipped creating {} as it already exists", | ||||||
&to.to_string_lossy() | ||||||
|
@@ -184,7 +193,11 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { | |||||
to = project_path.join(item_parent_path).join("Cargo.toml"); | ||||||
} | ||||||
|
||||||
println!("➕ Writing {}", &to.to_string_lossy()); | ||||||
if exists { | ||||||
println!("🔄 Overwriting {}", &to.to_string_lossy()); | ||||||
} else { | ||||||
println!("➕ Writing {}", &to.to_string_lossy()); | ||||||
} | ||||||
write(&to, file_contents).map_err(|e| { | ||||||
eprintln!("Error writing file: {to:?}"); | ||||||
e | ||||||
|
@@ -193,7 +206,7 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { | |||||
Ok(()) | ||||||
} | ||||||
|
||||||
fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { | ||||||
fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { | ||||||
let contents_to_exclude_from_copy = [ | ||||||
".git", | ||||||
".github", | ||||||
|
@@ -223,24 +236,32 @@ fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { | |||||
eprintln!("Error creating directory: {new_path:?}"); | ||||||
e | ||||||
})?; | ||||||
copy_contents(&path, &new_path)?; | ||||||
copy_contents(&path, &new_path, overwrite)?; | ||||||
} else { | ||||||
if file_exists(&new_path) { | ||||||
let exists = file_exists(&new_path); | ||||||
if exists { | ||||||
let mut appended = false; | ||||||
if new_path.to_string_lossy().contains(".gitignore") { | ||||||
appended = true; | ||||||
append_contents(&path, &new_path)?; | ||||||
} | ||||||
if new_path.to_string_lossy().contains("README.md") { | ||||||
appended = true; | ||||||
append_contents(&path, &new_path)?; | ||||||
} | ||||||
BlaineHeffron marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
println!( | ||||||
"ℹ️ Skipped creating {} as it already exists", | ||||||
&new_path.to_string_lossy() | ||||||
); | ||||||
continue; | ||||||
if overwrite && !appended { | ||||||
println!("🔄 Overwriting {}", &new_path.to_string_lossy()); | ||||||
} else { | ||||||
println!( | ||||||
"ℹ️ Skipped creating {} as it already exists", | ||||||
&new_path.to_string_lossy() | ||||||
); | ||||||
continue; | ||||||
} | ||||||
} else { | ||||||
println!("➕ Writing {}", &new_path.to_string_lossy()); | ||||||
BlaineHeffron marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
println!("➕ Writing {}", &new_path.to_string_lossy()); | ||||||
copy(&path, &new_path).map_err(|e| { | ||||||
eprintln!( | ||||||
"Error copying from {:?} to {:?}", | ||||||
|
@@ -302,7 +323,12 @@ fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { | |||||
Ok(()) | ||||||
} | ||||||
|
||||||
fn copy_example_contracts(from: &Path, to: &Path, contracts: &[String]) -> Result<(), Error> { | ||||||
fn copy_example_contracts( | ||||||
from: &Path, | ||||||
to: &Path, | ||||||
contracts: &[String], | ||||||
overwrite: bool, | ||||||
) -> Result<(), Error> { | ||||||
let project_contracts_path = to.join("contracts"); | ||||||
for contract in contracts { | ||||||
println!("ℹ️ Initializing example contract: {contract}"); | ||||||
|
@@ -315,7 +341,7 @@ fn copy_example_contracts(from: &Path, to: &Path, contracts: &[String]) -> Resul | |||||
e | ||||||
})?; | ||||||
|
||||||
copy_contents(&from_contract_path, &to_contract_path)?; | ||||||
copy_contents(&from_contract_path, &to_contract_path, overwrite)?; | ||||||
edit_contract_cargo_file(&to_contract_path)?; | ||||||
} | ||||||
|
||||||
|
@@ -354,9 +380,9 @@ fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { | |||||
Ok(()) | ||||||
} | ||||||
|
||||||
fn copy_frontend_files(from: &Path, to: &Path) -> Result<(), Error> { | ||||||
fn copy_frontend_files(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { | ||||||
println!("ℹ️ Initializing with frontend template"); | ||||||
copy_contents(from, to)?; | ||||||
copy_contents(from, to, overwrite)?; | ||||||
edit_package_json_files(to) | ||||||
} | ||||||
|
||||||
|
@@ -447,7 +473,13 @@ fn get_merged_file_delimiter(file_path: &Path) -> String { | |||||
#[cfg(test)] | ||||||
mod tests { | ||||||
use itertools::Itertools; | ||||||
use std::fs::{self, read_to_string}; | ||||||
use std::{ | ||||||
collections::HashMap, | ||||||
fs::{self, read_to_string}, | ||||||
path::PathBuf, | ||||||
time::SystemTime, | ||||||
}; | ||||||
use walkdir::WalkDir; | ||||||
|
||||||
use super::*; | ||||||
|
||||||
|
@@ -458,7 +490,8 @@ mod tests { | |||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); | ||||||
let with_examples = vec![]; | ||||||
init(project_dir.as_path(), "", &with_examples).unwrap(); | ||||||
let overwrite = false; | ||||||
init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); | ||||||
|
||||||
assert_base_template_files_exist(&project_dir); | ||||||
assert_default_hello_world_contract_files_exist(&project_dir); | ||||||
|
@@ -476,7 +509,8 @@ mod tests { | |||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); | ||||||
let with_examples = ["alloc".to_owned()]; | ||||||
init(project_dir.as_path(), "", &with_examples).unwrap(); | ||||||
let overwrite = false; | ||||||
init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); | ||||||
|
||||||
assert_base_template_files_exist(&project_dir); | ||||||
assert_default_hello_world_contract_files_exist(&project_dir); | ||||||
|
@@ -499,7 +533,8 @@ mod tests { | |||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join("project"); | ||||||
let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; | ||||||
init(project_dir.as_path(), "", &with_examples).unwrap(); | ||||||
let overwrite = false; | ||||||
init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); | ||||||
|
||||||
assert_base_template_files_exist(&project_dir); | ||||||
assert_default_hello_world_contract_files_exist(&project_dir); | ||||||
|
@@ -523,7 +558,8 @@ mod tests { | |||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join("project"); | ||||||
let with_examples = ["invalid_example".to_owned(), "atomic_swap".to_owned()]; | ||||||
assert!(init(project_dir.as_path(), "", &with_examples,).is_err()); | ||||||
let overwrite = false; | ||||||
assert!(init(project_dir.as_path(), "", &with_examples, overwrite).is_err()); | ||||||
|
||||||
temp_dir.close().unwrap(); | ||||||
} | ||||||
|
@@ -533,10 +569,12 @@ mod tests { | |||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); | ||||||
let with_examples = vec![]; | ||||||
let overwrite = false; | ||||||
init( | ||||||
project_dir.as_path(), | ||||||
"https://github.com/stellar/soroban-astro-template", | ||||||
&with_examples, | ||||||
overwrite, | ||||||
) | ||||||
.unwrap(); | ||||||
|
||||||
|
@@ -556,15 +594,73 @@ mod tests { | |||||
temp_dir.close().unwrap(); | ||||||
} | ||||||
|
||||||
#[test] | ||||||
fn test_init_with_overwrite() { | ||||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); | ||||||
let with_examples = vec![]; | ||||||
|
||||||
// First initialization | ||||||
init( | ||||||
project_dir.as_path(), | ||||||
"https://github.com/stellar/soroban-astro-template", | ||||||
&with_examples, | ||||||
false, | ||||||
) | ||||||
.unwrap(); | ||||||
|
||||||
// Get initial modification times | ||||||
let initial_mod_times = get_mod_times(&project_dir); | ||||||
|
||||||
// Second initialization with overwrite | ||||||
init( | ||||||
project_dir.as_path(), | ||||||
"https://github.com/stellar/soroban-astro-template", | ||||||
&with_examples, | ||||||
true, // overwrite = true | ||||||
) | ||||||
.unwrap(); | ||||||
|
||||||
// Get new modification times | ||||||
let new_mod_times = get_mod_times(&project_dir); | ||||||
|
||||||
// Compare modification times | ||||||
for (path, initial_time) in initial_mod_times { | ||||||
let new_time = new_mod_times.get(&path).expect("File should still exist"); | ||||||
assert!( | ||||||
new_time > &initial_time, | ||||||
"File {} should have a later modification time", | ||||||
path.display() | ||||||
); | ||||||
} | ||||||
|
||||||
temp_dir.close().unwrap(); | ||||||
} | ||||||
|
||||||
fn get_mod_times(dir: &Path) -> HashMap<PathBuf, SystemTime> { | ||||||
let mut mod_times = HashMap::new(); | ||||||
for entry in WalkDir::new(dir) { | ||||||
let entry = entry.unwrap(); | ||||||
if entry.file_type().is_file() { | ||||||
let path = entry.path().to_owned(); | ||||||
let metadata = fs::metadata(&path).unwrap(); | ||||||
mod_times.insert(path, metadata.modified().unwrap()); | ||||||
} | ||||||
} | ||||||
mod_times | ||||||
} | ||||||
|
||||||
#[test] | ||||||
fn test_init_from_within_an_existing_project() { | ||||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join("./"); | ||||||
let with_examples = vec![]; | ||||||
let overwrite = false; | ||||||
init( | ||||||
project_dir.as_path(), | ||||||
"https://github.com/stellar/soroban-astro-template", | ||||||
&with_examples, | ||||||
overwrite, | ||||||
) | ||||||
.unwrap(); | ||||||
|
||||||
|
@@ -591,10 +687,12 @@ mod tests { | |||||
let temp_dir = tempfile::tempdir().unwrap(); | ||||||
let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); | ||||||
let with_examples = vec![]; | ||||||
let overwrite = false; | ||||||
init( | ||||||
project_dir.as_path(), | ||||||
"https://github.com/stellar/soroban-astro-template", | ||||||
&with_examples, | ||||||
overwrite, | ||||||
) | ||||||
.unwrap(); | ||||||
|
||||||
|
@@ -603,6 +701,7 @@ mod tests { | |||||
project_dir.as_path(), | ||||||
"https://github.com/stellar/soroban-astro-template", | ||||||
&with_examples, | ||||||
overwrite, | ||||||
) | ||||||
.unwrap(); | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should add the short option
-o
, opened a PR removing it and why is mentioned there: