diff --git a/crates/templates/src/manager.rs b/crates/templates/src/manager.rs index 5adb524ae3..cfc1fa84ff 100644 --- a/crates/templates/src/manager.rs +++ b/crates/templates/src/manager.rs @@ -782,6 +782,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -816,6 +817,7 @@ mod tests { values, accept_defaults: true, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -882,6 +884,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -910,6 +913,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -935,6 +939,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -992,6 +997,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1020,6 +1026,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1072,6 +1079,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1100,6 +1108,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1125,6 +1134,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1166,6 +1176,7 @@ mod tests { values, accept_defaults: false, no_vcs: true, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1205,6 +1216,7 @@ mod tests { values, accept_defaults: false, no_vcs: true, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1238,6 +1250,7 @@ mod tests { values, accept_defaults: false, no_vcs: true, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); } @@ -1288,6 +1301,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1316,6 +1330,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template.run(options).silent().await.unwrap(); @@ -1377,6 +1392,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; template @@ -1387,6 +1403,104 @@ mod tests { } } + #[tokio::test] + async fn cannot_generate_over_existing_files_by_default() { + let temp_dir = tempdir().unwrap(); + let store = TemplateStore::new(temp_dir.path()); + let manager = TemplateManager { store }; + let source = TemplateSource::File(project_root()); + + manager + .install(&source, &InstallOptions::default(), &DiscardingReporter) + .await + .unwrap(); + + let template = manager.get("http-rust").unwrap().unwrap(); + + let dest_temp_dir = tempdir().unwrap(); + let output_dir = dest_temp_dir.path().join("myproj"); + + tokio::fs::create_dir_all(&output_dir).await.unwrap(); + let manifest_path = output_dir.join("spin.toml"); + tokio::fs::write(&manifest_path, "cookies").await.unwrap(); + + let values = [ + ("project-description".to_owned(), "my desc".to_owned()), + ("http-path".to_owned(), "/path/...".to_owned()), + ] + .into_iter() + .collect(); + let options = RunOptions { + variant: crate::template::TemplateVariantInfo::NewApplication, + output_path: output_dir.clone(), + name: "my project".to_owned(), + values, + accept_defaults: false, + no_vcs: false, + allow_overwrite: false, + }; + + template + .run(options) + .silent() + .await + .expect_err("generate into existing dir should have failed"); + + assert!(tokio::fs::read_to_string(&manifest_path) + .await + .unwrap() + .contains("cookies")); + } + + #[tokio::test] + async fn can_generate_over_existing_files_if_so_configured() { + let temp_dir = tempdir().unwrap(); + let store = TemplateStore::new(temp_dir.path()); + let manager = TemplateManager { store }; + let source = TemplateSource::File(project_root()); + + manager + .install(&source, &InstallOptions::default(), &DiscardingReporter) + .await + .unwrap(); + + let template = manager.get("http-rust").unwrap().unwrap(); + + let dest_temp_dir = tempdir().unwrap(); + let output_dir = dest_temp_dir.path().join("myproj"); + + tokio::fs::create_dir_all(&output_dir).await.unwrap(); + let manifest_path = output_dir.join("spin.toml"); + tokio::fs::write(&manifest_path, "cookies").await.unwrap(); + + let values = [ + ("project-description".to_owned(), "my desc".to_owned()), + ("http-path".to_owned(), "/path/...".to_owned()), + ] + .into_iter() + .collect(); + let options = RunOptions { + variant: crate::template::TemplateVariantInfo::NewApplication, + output_path: output_dir.clone(), + name: "my project".to_owned(), + values, + accept_defaults: false, + no_vcs: false, + allow_overwrite: true, + }; + + template + .run(options) + .silent() + .await + .expect("generate into existing dir should have succeeded"); + + assert!(tokio::fs::read_to_string(&manifest_path) + .await + .unwrap() + .contains("[[trigger.http]]")); + } + #[tokio::test] async fn cannot_new_a_component_only_template() { let temp_dir = tempdir().unwrap(); @@ -1447,6 +1561,7 @@ mod tests { values, accept_defaults: false, no_vcs: false, + allow_overwrite: false, }; let err = template diff --git a/crates/templates/src/run.rs b/crates/templates/src/run.rs index f2223b2933..ee79739b95 100644 --- a/crates/templates/src/run.rs +++ b/crates/templates/src/run.rs @@ -40,6 +40,9 @@ pub struct RunOptions { pub accept_defaults: bool, /// If true, do not create a .gitignore file pub no_vcs: bool, + /// Skip the overwrite prompt if the output directory already contains files + /// (or, if silent, allow overwrite instead of erroring). + pub allow_overwrite: bool, } impl Run { @@ -93,11 +96,13 @@ impl Run { // TODO: rationalise `path` and `dir` let to = self.generation_target_dir(); - match interaction.allow_generate_into(&to) { - Cancellable::Cancelled => return Ok(None), - Cancellable::Ok(_) => (), - Cancellable::Err(e) => return Err(e), - }; + if !self.options.allow_overwrite { + match interaction.allow_generate_into(&to) { + Cancellable::Cancelled => return Ok(None), + Cancellable::Ok(_) => (), + Cancellable::Err(e) => return Err(e), + }; + } self.validate_provided_values()?; diff --git a/crates/templates/src/test_built_ins/mod.rs b/crates/templates/src/test_built_ins/mod.rs index 51cb127575..aeb335b8a4 100644 --- a/crates/templates/src/test_built_ins/mod.rs +++ b/crates/templates/src/test_built_ins/mod.rs @@ -39,6 +39,7 @@ async fn new_fileserver_creates_assets_dir() -> anyhow::Result<()> { values: HashMap::new(), accept_defaults: true, no_vcs: false, + allow_overwrite: false, }; manager .get("static-fileserver")? @@ -85,6 +86,7 @@ async fn add_fileserver_creates_assets_dir_next_to_manifest() -> anyhow::Result< values: HashMap::new(), accept_defaults: true, no_vcs: false, + allow_overwrite: false, }; manager .get("http-empty")? @@ -104,6 +106,7 @@ async fn add_fileserver_creates_assets_dir_next_to_manifest() -> anyhow::Result< values: fs_settings, accept_defaults: true, no_vcs: false, + allow_overwrite: false, }; manager .get("static-fileserver")? diff --git a/src/commands/new.rs b/src/commands/new.rs index 728ae396d9..97096529cd 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + io::IsTerminal, path::{Path, PathBuf}, str::FromStr, }; @@ -66,6 +67,15 @@ pub struct TemplateNewCommandCore { /// An optional argument that allows to skip creating .gitignore #[clap(long = "no-vcs", takes_value = false)] pub no_vcs: bool, + + /// If the output directory already contains files, generate the new files into + /// it without confirming, overwriting any existing files with the same names. + #[clap( + long = "allow-overwrite", + alias = "allow-overwrites", + takes_value = false + )] + pub allow_overwrite: bool, } /// Scaffold a new application based on a template. @@ -184,9 +194,16 @@ impl TemplateNewCommandCore { values, accept_defaults: self.accept_defaults, no_vcs: self.no_vcs, + allow_overwrite: self.allow_overwrite, }; - template.run(options).interactive().await + let run = template.run(options); + + if std::io::stderr().is_terminal() { + run.interactive().await + } else { + run.silent().await + } } // Try to guess if the user is using v1 or v2 syntax, and fix things up so