Skip to content

Commit 12cdb0f

Browse files
committed
Improve non-interactive behaviour of spin new
Signed-off-by: itowlson <[email protected]>
1 parent 66ede35 commit 12cdb0f

File tree

4 files changed

+146
-6
lines changed

4 files changed

+146
-6
lines changed

crates/templates/src/manager.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ mod tests {
782782
values,
783783
accept_defaults: false,
784784
no_vcs: false,
785+
allow_overwrite: false,
785786
};
786787

787788
template.run(options).silent().await.unwrap();
@@ -816,6 +817,7 @@ mod tests {
816817
values,
817818
accept_defaults: true,
818819
no_vcs: false,
820+
allow_overwrite: false,
819821
};
820822

821823
template.run(options).silent().await.unwrap();
@@ -882,6 +884,7 @@ mod tests {
882884
values,
883885
accept_defaults: false,
884886
no_vcs: false,
887+
allow_overwrite: false,
885888
};
886889

887890
template.run(options).silent().await.unwrap();
@@ -910,6 +913,7 @@ mod tests {
910913
values,
911914
accept_defaults: false,
912915
no_vcs: false,
916+
allow_overwrite: false,
913917
};
914918

915919
template.run(options).silent().await.unwrap();
@@ -935,6 +939,7 @@ mod tests {
935939
values,
936940
accept_defaults: false,
937941
no_vcs: false,
942+
allow_overwrite: false,
938943
};
939944

940945
template.run(options).silent().await.unwrap();
@@ -992,6 +997,7 @@ mod tests {
992997
values,
993998
accept_defaults: false,
994999
no_vcs: false,
1000+
allow_overwrite: false,
9951001
};
9961002

9971003
template.run(options).silent().await.unwrap();
@@ -1020,6 +1026,7 @@ mod tests {
10201026
values,
10211027
accept_defaults: false,
10221028
no_vcs: false,
1029+
allow_overwrite: false,
10231030
};
10241031

10251032
template.run(options).silent().await.unwrap();
@@ -1072,6 +1079,7 @@ mod tests {
10721079
values,
10731080
accept_defaults: false,
10741081
no_vcs: false,
1082+
allow_overwrite: false,
10751083
};
10761084

10771085
template.run(options).silent().await.unwrap();
@@ -1100,6 +1108,7 @@ mod tests {
11001108
values,
11011109
accept_defaults: false,
11021110
no_vcs: false,
1111+
allow_overwrite: false,
11031112
};
11041113

11051114
template.run(options).silent().await.unwrap();
@@ -1125,6 +1134,7 @@ mod tests {
11251134
values,
11261135
accept_defaults: false,
11271136
no_vcs: false,
1137+
allow_overwrite: false,
11281138
};
11291139

11301140
template.run(options).silent().await.unwrap();
@@ -1166,6 +1176,7 @@ mod tests {
11661176
values,
11671177
accept_defaults: false,
11681178
no_vcs: true,
1179+
allow_overwrite: false,
11691180
};
11701181

11711182
template.run(options).silent().await.unwrap();
@@ -1205,6 +1216,7 @@ mod tests {
12051216
values,
12061217
accept_defaults: false,
12071218
no_vcs: true,
1219+
allow_overwrite: false,
12081220
};
12091221

12101222
template.run(options).silent().await.unwrap();
@@ -1238,6 +1250,7 @@ mod tests {
12381250
values,
12391251
accept_defaults: false,
12401252
no_vcs: true,
1253+
allow_overwrite: false,
12411254
};
12421255
template.run(options).silent().await.unwrap();
12431256
}
@@ -1288,6 +1301,7 @@ mod tests {
12881301
values,
12891302
accept_defaults: false,
12901303
no_vcs: false,
1304+
allow_overwrite: false,
12911305
};
12921306

12931307
template.run(options).silent().await.unwrap();
@@ -1316,6 +1330,7 @@ mod tests {
13161330
values,
13171331
accept_defaults: false,
13181332
no_vcs: false,
1333+
allow_overwrite: false,
13191334
};
13201335

13211336
template.run(options).silent().await.unwrap();
@@ -1377,6 +1392,7 @@ mod tests {
13771392
values,
13781393
accept_defaults: false,
13791394
no_vcs: false,
1395+
allow_overwrite: false,
13801396
};
13811397

13821398
template
@@ -1387,6 +1403,104 @@ mod tests {
13871403
}
13881404
}
13891405

1406+
#[tokio::test]
1407+
async fn cannot_generate_over_existing_files_by_default() {
1408+
let temp_dir = tempdir().unwrap();
1409+
let store = TemplateStore::new(temp_dir.path());
1410+
let manager = TemplateManager { store };
1411+
let source = TemplateSource::File(project_root());
1412+
1413+
manager
1414+
.install(&source, &InstallOptions::default(), &DiscardingReporter)
1415+
.await
1416+
.unwrap();
1417+
1418+
let template = manager.get("http-rust").unwrap().unwrap();
1419+
1420+
let dest_temp_dir = tempdir().unwrap();
1421+
let output_dir = dest_temp_dir.path().join("myproj");
1422+
1423+
tokio::fs::create_dir_all(&output_dir).await.unwrap();
1424+
let manifest_path = output_dir.join("spin.toml");
1425+
tokio::fs::write(&manifest_path, "cookies").await.unwrap();
1426+
1427+
let values = [
1428+
("project-description".to_owned(), "my desc".to_owned()),
1429+
("http-path".to_owned(), "/path/...".to_owned()),
1430+
]
1431+
.into_iter()
1432+
.collect();
1433+
let options = RunOptions {
1434+
variant: crate::template::TemplateVariantInfo::NewApplication,
1435+
output_path: output_dir.clone(),
1436+
name: "my project".to_owned(),
1437+
values,
1438+
accept_defaults: false,
1439+
no_vcs: false,
1440+
allow_overwrite: false,
1441+
};
1442+
1443+
template
1444+
.run(options)
1445+
.silent()
1446+
.await
1447+
.expect_err("generate into existing dir should have failed");
1448+
1449+
assert!(tokio::fs::read_to_string(&manifest_path)
1450+
.await
1451+
.unwrap()
1452+
.contains("cookies"));
1453+
}
1454+
1455+
#[tokio::test]
1456+
async fn can_generate_over_existing_files_if_so_configured() {
1457+
let temp_dir = tempdir().unwrap();
1458+
let store = TemplateStore::new(temp_dir.path());
1459+
let manager = TemplateManager { store };
1460+
let source = TemplateSource::File(project_root());
1461+
1462+
manager
1463+
.install(&source, &InstallOptions::default(), &DiscardingReporter)
1464+
.await
1465+
.unwrap();
1466+
1467+
let template = manager.get("http-rust").unwrap().unwrap();
1468+
1469+
let dest_temp_dir = tempdir().unwrap();
1470+
let output_dir = dest_temp_dir.path().join("myproj");
1471+
1472+
tokio::fs::create_dir_all(&output_dir).await.unwrap();
1473+
let manifest_path = output_dir.join("spin.toml");
1474+
tokio::fs::write(&manifest_path, "cookies").await.unwrap();
1475+
1476+
let values = [
1477+
("project-description".to_owned(), "my desc".to_owned()),
1478+
("http-path".to_owned(), "/path/...".to_owned()),
1479+
]
1480+
.into_iter()
1481+
.collect();
1482+
let options = RunOptions {
1483+
variant: crate::template::TemplateVariantInfo::NewApplication,
1484+
output_path: output_dir.clone(),
1485+
name: "my project".to_owned(),
1486+
values,
1487+
accept_defaults: false,
1488+
no_vcs: false,
1489+
allow_overwrite: true,
1490+
};
1491+
1492+
template
1493+
.run(options)
1494+
.silent()
1495+
.await
1496+
.expect("generate into existing dir should have succeeded");
1497+
1498+
assert!(tokio::fs::read_to_string(&manifest_path)
1499+
.await
1500+
.unwrap()
1501+
.contains("[[trigger.http]]"));
1502+
}
1503+
13901504
#[tokio::test]
13911505
async fn cannot_new_a_component_only_template() {
13921506
let temp_dir = tempdir().unwrap();
@@ -1447,6 +1561,7 @@ mod tests {
14471561
values,
14481562
accept_defaults: false,
14491563
no_vcs: false,
1564+
allow_overwrite: false,
14501565
};
14511566

14521567
let err = template

crates/templates/src/run.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ pub struct RunOptions {
4040
pub accept_defaults: bool,
4141
/// If true, do not create a .gitignore file
4242
pub no_vcs: bool,
43+
/// Skip the overwrite prompt if the output directory already contains files
44+
/// (or, if silent, allow overwrite instead of erroring).
45+
pub allow_overwrite: bool,
4346
}
4447

4548
impl Run {
@@ -93,11 +96,13 @@ impl Run {
9396
// TODO: rationalise `path` and `dir`
9497
let to = self.generation_target_dir();
9598

96-
match interaction.allow_generate_into(&to) {
97-
Cancellable::Cancelled => return Ok(None),
98-
Cancellable::Ok(_) => (),
99-
Cancellable::Err(e) => return Err(e),
100-
};
99+
if !self.options.allow_overwrite {
100+
match interaction.allow_generate_into(&to) {
101+
Cancellable::Cancelled => return Ok(None),
102+
Cancellable::Ok(_) => (),
103+
Cancellable::Err(e) => return Err(e),
104+
};
105+
}
101106

102107
self.validate_provided_values()?;
103108

crates/templates/src/test_built_ins/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ async fn new_fileserver_creates_assets_dir() -> anyhow::Result<()> {
3939
values: HashMap::new(),
4040
accept_defaults: true,
4141
no_vcs: false,
42+
allow_overwrite: false,
4243
};
4344
manager
4445
.get("static-fileserver")?
@@ -85,6 +86,7 @@ async fn add_fileserver_creates_assets_dir_next_to_manifest() -> anyhow::Result<
8586
values: HashMap::new(),
8687
accept_defaults: true,
8788
no_vcs: false,
89+
allow_overwrite: false,
8890
};
8991
manager
9092
.get("http-empty")?
@@ -104,6 +106,7 @@ async fn add_fileserver_creates_assets_dir_next_to_manifest() -> anyhow::Result<
104106
values: fs_settings,
105107
accept_defaults: true,
106108
no_vcs: false,
109+
allow_overwrite: false,
107110
};
108111
manager
109112
.get("static-fileserver")?

src/commands/new.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
collections::HashMap,
3+
io::IsTerminal,
34
path::{Path, PathBuf},
45
str::FromStr,
56
};
@@ -66,6 +67,15 @@ pub struct TemplateNewCommandCore {
6667
/// An optional argument that allows to skip creating .gitignore
6768
#[clap(long = "no-vcs", takes_value = false)]
6869
pub no_vcs: bool,
70+
71+
/// If the output directory already contains files, generate the new files into
72+
/// it without confirming, overwriting any existing files with the same names.
73+
#[clap(
74+
long = "allow-overwrite",
75+
alias = "allow-overwrites",
76+
takes_value = false
77+
)]
78+
pub allow_overwrite: bool,
6979
}
7080

7181
/// Scaffold a new application based on a template.
@@ -184,9 +194,16 @@ impl TemplateNewCommandCore {
184194
values,
185195
accept_defaults: self.accept_defaults,
186196
no_vcs: self.no_vcs,
197+
allow_overwrite: self.allow_overwrite,
187198
};
188199

189-
template.run(options).interactive().await
200+
let run = template.run(options);
201+
202+
if std::io::stderr().is_terminal() {
203+
run.interactive().await
204+
} else {
205+
run.silent().await
206+
}
190207
}
191208

192209
// Try to guess if the user is using v1 or v2 syntax, and fix things up so

0 commit comments

Comments
 (0)