ferro_cli/commands/
ci_init.rs1use console::style;
7use std::fs;
8use std::path::Path;
9
10use crate::project::{find_project_root, package_name};
11use crate::templates::ci_workflow::{render_ci_workflow, CiWorkflowContext};
12
13pub fn run(force: bool) {
14 let root = match find_project_root(None) {
15 Ok(r) => r,
16 Err(_) => {
17 eprintln!(
18 "{} Cargo.toml not found (searched upward from CWD)",
19 style("Error:").red().bold()
20 );
21 std::process::exit(1);
22 }
23 };
24
25 match generate_in(&root, force) {
26 Ok(path) => {
27 println!("{} Generated {}", style("✓").green(), path.display());
28 }
29 Err(GenerateError::Exists(path)) => {
30 eprintln!(
31 "{} {} already exists (use --force)",
32 style("Error:").red().bold(),
33 path.display()
34 );
35 std::process::exit(1);
36 }
37 Err(GenerateError::Io(e)) => {
38 eprintln!("{} {}", style("Error:").red().bold(), e);
39 std::process::exit(1);
40 }
41 }
42}
43
44#[derive(Debug)]
45enum GenerateError {
46 Exists(std::path::PathBuf),
47 Io(std::io::Error),
48}
49
50impl From<std::io::Error> for GenerateError {
51 fn from(e: std::io::Error) -> Self {
52 GenerateError::Io(e)
53 }
54}
55
56fn generate_in(root: &Path, force: bool) -> Result<std::path::PathBuf, GenerateError> {
57 let workflows_dir = root.join(".github").join("workflows");
58 let ci_yml = workflows_dir.join("ci.yml");
59
60 if ci_yml.exists() && !force {
61 return Err(GenerateError::Exists(ci_yml));
62 }
63
64 fs::create_dir_all(&workflows_dir)?;
65
66 let pkg = package_name(root);
67 let content = render_ci_workflow(&CiWorkflowContext { package_name: &pkg });
68 fs::write(&ci_yml, content)?;
69 Ok(ci_yml)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use tempfile::TempDir;
76
77 fn write_min_project(td: &TempDir) {
78 fs::write(
79 td.path().join("Cargo.toml"),
80 "[package]\nname = \"sample\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
81 )
82 .unwrap();
83 }
84
85 #[test]
86 fn writes_ci_yml_under_github_workflows() {
87 let td = TempDir::new().unwrap();
88 write_min_project(&td);
89 let path = generate_in(td.path(), false).unwrap();
90 assert!(path.ends_with(".github/workflows/ci.yml"));
91 let body = fs::read_to_string(&path).unwrap();
92 assert!(body.contains("Swatinem/rust-cache@v2"));
93 assert!(body.contains("cargo fmt --all -- --check"));
94 }
95
96 #[test]
97 fn refuses_to_overwrite_without_force() {
98 let td = TempDir::new().unwrap();
99 write_min_project(&td);
100 generate_in(td.path(), false).unwrap();
101 match generate_in(td.path(), false) {
102 Err(GenerateError::Exists(_)) => {}
103 other => panic!("expected Exists error, got {other:?}"),
104 }
105 }
106
107 #[test]
108 fn force_overwrites_existing_with_identical_content() {
109 let td = TempDir::new().unwrap();
110 write_min_project(&td);
111 let path = generate_in(td.path(), false).unwrap();
112 let first = fs::read_to_string(&path).unwrap();
113 fs::write(&path, "stale\n").unwrap();
115 let path2 = generate_in(td.path(), true).unwrap();
116 let second = fs::read_to_string(&path2).unwrap();
117 assert_eq!(first, second);
118 }
119}