callisto_cli/commands/
init.rs1use std::process::ExitCode;
2
3use callisto_graph::commands::InitOptions;
4use callisto_model::ApplyPermit;
5use dialoguer::Confirm;
6
7use crate::cli::{GlobalArgs, InitArgs, OutputFormat};
8use crate::error::CliError;
9use crate::output::write_json;
10use crate::render;
11use crate::runner::CliCommandRunner;
12use crate::tty;
13use crate::workspace::load_workspace;
14
15pub fn handle(args: InitArgs, global: &GlobalArgs) -> Result<ExitCode, CliError> {
16 let runner = CliCommandRunner;
17 let ws = load_workspace(global, &runner)?;
18
19 if !args.yes && tty::is_interactive() {
20 let confirm = Confirm::new()
21 .with_prompt(format!("Initialize Callisto configuration in `{}`?", ws.root.display()))
22 .default(true)
23 .interact()
24 .map_err(|e| CliError::Other(format!("Interactive prompt failed: {e}")))?;
25
26 if !confirm {
27 println!("Initialization cancelled.");
28 return Ok(ExitCode::SUCCESS);
29 }
30 }
31
32 let opts = InitOptions { yes: args.yes };
33
34 let permit = ApplyPermit::granted_unless_dry_run(global.dry_run);
39 let report = callisto_graph::commands::init(&ws, &opts, permit.as_ref())?;
40
41 if permit.is_none() && global.format == OutputFormat::Text {
42 println!("[DRY-RUN] Init plan calculated (no files written):");
43 }
44
45 match global.format {
46 OutputFormat::Json => write_json(&mut std::io::stdout(), &report)?,
47 OutputFormat::Text => render::render_init(&report, &mut std::io::stdout())?,
48 }
49
50 Ok(ExitCode::SUCCESS)
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 fn empty_workspace() -> tempfile::TempDir {
58 let tmp = tempfile::TempDir::new().unwrap();
59 std::fs::write(
60 tmp.path().join("Cargo.toml"),
61 "[workspace]\nmembers = []\nresolver = \"2\"\n",
62 )
63 .unwrap();
64 tmp
65 }
66
67 #[test]
68 fn handle_dry_run_text_output_carries_the_dry_run_marker() {
69 let tmp = empty_workspace();
70 let global = GlobalArgs {
71 format: OutputFormat::Text,
72 cwd: tmp.path().to_path_buf(),
73 dry_run: true,
74 };
75
76 let result = handle(InitArgs { yes: true }, &global);
77 assert_eq!(result.unwrap(), ExitCode::SUCCESS);
78 assert!(
79 !tmp.path().join("callisto.toml").exists(),
80 "dry-run must not write callisto.toml"
81 );
82 }
83
84 #[test]
85 fn handle_json_format_applies_for_real_and_writes_config() {
86 let tmp = empty_workspace();
87 let global = GlobalArgs {
88 format: OutputFormat::Json,
89 cwd: tmp.path().to_path_buf(),
90 dry_run: false,
91 };
92
93 let result = handle(InitArgs { yes: true }, &global);
94 assert_eq!(result.unwrap(), ExitCode::SUCCESS);
95 assert!(
96 tmp.path().join("callisto.toml").exists(),
97 "a real (non-dry-run) init must write callisto.toml"
98 );
99 }
100}