diff --git a/lib/crates/fabro-cli/src/init.rs b/lib/crates/fabro-cli/src/init.rs index 755d57795..00fd50b3f 100644 --- a/lib/crates/fabro-cli/src/init.rs +++ b/lib/crates/fabro-cli/src/init.rs @@ -110,6 +110,59 @@ draft = true Ok(()) } +pub fn run_deinit() -> Result<()> { + let output = std::process::Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .output() + .context("failed to run git")?; + + if !output.status.success() { + bail!("not a git repository"); + } + + let repo_root = PathBuf::from( + String::from_utf8(output.stdout) + .context("git output was not valid UTF-8")? + .trim(), + ); + + let fabro_toml = repo_root.join("fabro.toml"); + if !fabro_toml.exists() { + bail!("not initialized — fabro.toml not found"); + } + + let green = console::Style::new().green(); + let dim = console::Style::new().dim(); + + std::fs::remove_file(&fabro_toml) + .with_context(|| format!("failed to remove {}", fabro_toml.display()))?; + eprintln!( + " {} {}", + green.apply_to("✔"), + dim.apply_to("removed fabro.toml") + ); + + let fabro_dir = repo_root.join("fabro"); + if fabro_dir.exists() { + std::fs::remove_dir_all(&fabro_dir) + .with_context(|| format!("failed to remove {}", fabro_dir.display()))?; + eprintln!( + " {} {}", + green.apply_to("✔"), + dim.apply_to("removed fabro/") + ); + } + + eprintln!( + "\n{}", + console::Style::new() + .bold() + .apply_to("Project deinitialized.") + ); + + Ok(()) +} + async fn check_github_app_installation() { // Get the git remote origin URL let output = match std::process::Command::new("git") diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 76b89b0ac..5cc51cade 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -130,6 +130,7 @@ enum Command { command: PrCommand, }, /// Skill management + #[command(hide = true)] Skill { #[command(subcommand)] command: SkillCommand, @@ -199,6 +200,8 @@ enum SystemCommand { enum RepoCommand { /// Initialize a new project Init, + /// Remove fabro.toml and fabro/ directory + Deinit, } #[derive(Subcommand)] @@ -450,6 +453,7 @@ async fn main_inner() -> (String, Result<()>) { Command::Doctor { .. } => "doctor", Command::Repo { command } => match command { RepoCommand::Init => "repo init", + RepoCommand::Deinit => "repo deinit", }, Command::Init => "init", Command::Install => "install", @@ -783,6 +787,9 @@ async fn main_inner() -> (String, Result<()>) { RepoCommand::Init => { init::run_init().await?; } + RepoCommand::Deinit => { + init::run_deinit()?; + } }, Command::Init => { eprintln!( diff --git a/lib/crates/fabro-cli/tests/scenario.rs b/lib/crates/fabro-cli/tests/scenario.rs index 846e986df..e019aa778 100644 --- a/lib/crates/fabro-cli/tests/scenario.rs +++ b/lib/crates/fabro-cli/tests/scenario.rs @@ -410,6 +410,64 @@ fn scenario_full_stack(sandbox: &str) { ); } +// --------------------------------------------------------------------------- +// repo deinit +// --------------------------------------------------------------------------- + +fn init_git_repo(path: &Path) { + std::process::Command::new("git") + .args(["init"]) + .current_dir(path) + .output() + .expect("git init should succeed"); +} + +fn init_fabro_project(path: &Path) { + std::fs::write(path.join("fabro.toml"), "version = 1\n").unwrap(); + let workflow_dir = path.join("fabro/workflows/hello"); + std::fs::create_dir_all(&workflow_dir).unwrap(); + std::fs::write(workflow_dir.join("workflow.fabro"), "digraph {}").unwrap(); + std::fs::write(workflow_dir.join("workflow.toml"), "version = 1\n").unwrap(); +} + +#[test] +fn test_repo_deinit_removes_fabro_toml_and_dir() { + let tmp = tempfile::tempdir().unwrap(); + init_git_repo(tmp.path()); + init_fabro_project(tmp.path()); + + assert!(tmp.path().join("fabro.toml").exists()); + assert!(tmp.path().join("fabro").exists()); + + fabro() + .args(["repo", "deinit"]) + .current_dir(tmp.path()) + .assert() + .success(); + + assert!( + !tmp.path().join("fabro.toml").exists(), + "fabro.toml should be removed" + ); + assert!( + !tmp.path().join("fabro").exists(), + "fabro/ directory should be removed" + ); +} + +#[test] +fn test_repo_deinit_fails_when_not_initialized() { + let tmp = tempfile::tempdir().unwrap(); + init_git_repo(tmp.path()); + + fabro() + .args(["repo", "deinit"]) + .current_dir(tmp.path()) + .assert() + .failure() + .stderr(predicates::str::contains("not initialized")); +} + // --------------------------------------------------------------------------- // Standalone tests (no sandbox parametrization) // ---------------------------------------------------------------------------