envx_cli/
project.rs

1use clap::command;
2use clap::{Args, Subcommand};
3use color_eyre::Result;
4use comfy_table::Table;
5use envx_core::{EnvVarManager, ProfileManager, ProjectConfig, ProjectManager, RequiredVar, ValidationReport};
6
7#[derive(Args)]
8pub struct ProjectArgs {
9    #[command(subcommand)]
10    pub command: ProjectCommands,
11}
12
13#[derive(Subcommand)]
14pub enum ProjectCommands {
15    /// Initialize a new project configuration
16    Init {
17        /// Project name
18        #[arg(short, long)]
19        name: Option<String>,
20    },
21    /// Apply project configuration
22    Apply {
23        /// Force apply even with validation errors
24        #[arg(short, long)]
25        force: bool,
26    },
27    /// Validate project configuration
28    Check,
29    /// Edit project configuration
30    Edit,
31    /// Show project information
32    Info,
33    /// Run a project script
34    Run {
35        /// Script name
36        script: String,
37    },
38    /// Add a required variable
39    Require {
40        /// Variable name
41        name: String,
42        /// Description
43        #[arg(short, long)]
44        description: Option<String>,
45        /// Validation pattern (regex)
46        #[arg(short, long)]
47        pattern: Option<String>,
48        /// Example value
49        #[arg(short, long)]
50        example: Option<String>,
51    },
52}
53
54/// Handle project-related commands.
55///
56/// # Errors
57///
58/// This function will return an error if:
59/// - Project manager initialization fails
60/// - Environment variable manager operations fail
61/// - Project configuration file cannot be found, read, or written
62/// - Project validation fails (when not using --force)
63/// - Profile manager operations fail
64/// - Script execution fails
65/// - Required variable configuration cannot be updated
66/// - File I/O operations fail during project operations
67#[allow(clippy::too_many_lines)]
68pub fn handle_project(args: ProjectArgs) -> Result<()> {
69    match args.command {
70        ProjectCommands::Init { name } => {
71            let manager = ProjectManager::new()?;
72            manager.init(name)?;
73        }
74
75        ProjectCommands::Apply { force } => {
76            let mut project = ProjectManager::new()?;
77            let mut env_manager = EnvVarManager::new();
78            let mut profile_manager = ProfileManager::new()?;
79
80            if let Some(project_dir) = project.find_and_load()? {
81                println!("šŸ“ Found project at: {}", project_dir.display());
82
83                // Validate first
84                let report = project.validate(&env_manager)?;
85
86                if !report.success && !force {
87                    print_validation_report(&report);
88                    return Err(color_eyre::eyre::eyre!(
89                        "Validation failed. Use --force to apply anyway."
90                    ));
91                }
92
93                // Apply configuration
94                project.apply(&mut env_manager, &mut profile_manager)?;
95                println!("āœ… Applied project configuration");
96
97                if !report.warnings.is_empty() {
98                    println!("\nāš ļø  Warnings:");
99                    for warning in &report.warnings {
100                        println!("  - {}: {}", warning.var_name, warning.message);
101                    }
102                }
103            } else {
104                return Err(color_eyre::eyre::eyre!(
105                    "No .envx/config.yaml found in current or parent directories"
106                ));
107            }
108        }
109
110        ProjectCommands::Check => {
111            let mut project = ProjectManager::new()?;
112            let env_manager = EnvVarManager::new();
113
114            if project.find_and_load()?.is_some() {
115                let report = project.validate(&env_manager)?;
116                print_validation_report(&report);
117
118                if !report.success {
119                    std::process::exit(1);
120                }
121            } else {
122                return Err(color_eyre::eyre::eyre!("No project configuration found"));
123            }
124        }
125
126        ProjectCommands::Edit => {
127            let _ = ProjectManager::new()?;
128            let config_path = std::env::current_dir()?.join(".envx").join("config.yaml");
129
130            if !config_path.exists() {
131                return Err(color_eyre::eyre::eyre!(
132                    "No .envx/config.yaml found. Run 'envx init' first."
133                ));
134            }
135
136            #[cfg(windows)]
137            {
138                std::process::Command::new("notepad").arg(&config_path).spawn()?;
139            }
140
141            #[cfg(unix)]
142            {
143                let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
144                std::process::Command::new(editor).arg(&config_path).spawn()?;
145            }
146
147            println!("šŸ“ Opening config in editor...");
148        }
149
150        ProjectCommands::Info => {
151            let mut project = ProjectManager::new()?;
152
153            if let Some(project_dir) = project.find_and_load()? {
154                // Load and display config
155                let config_path = project_dir.join(".envx").join("config.yaml");
156                let content = std::fs::read_to_string(&config_path)?;
157
158                println!("šŸ“ Project Directory: {}", project_dir.display());
159                println!("\nšŸ“„ Configuration:");
160                println!("{content}");
161            } else {
162                return Err(color_eyre::eyre::eyre!("No project configuration found"));
163            }
164        }
165
166        ProjectCommands::Run { script } => {
167            let mut project = ProjectManager::new()?;
168            let mut env_manager = EnvVarManager::new();
169
170            if project.find_and_load()?.is_some() {
171                project.run_script(&script, &mut env_manager)?;
172                println!("āœ… Script '{script}' completed");
173            } else {
174                return Err(color_eyre::eyre::eyre!("No project configuration found"));
175            }
176        }
177
178        ProjectCommands::Require {
179            name,
180            description,
181            pattern,
182            example,
183        } => {
184            let config_path = std::env::current_dir()?.join(".envx").join("config.yaml");
185
186            if !config_path.exists() {
187                return Err(color_eyre::eyre::eyre!(
188                    "No .envx/config.yaml found. Run 'envx init' first."
189                ));
190            }
191
192            // Load, modify, and save config
193            let mut config = ProjectConfig::load(&config_path)?;
194            config.required.push(RequiredVar {
195                name: name.clone(),
196                description,
197                pattern,
198                example,
199            });
200            config.save(&config_path)?;
201
202            println!("āœ… Added required variable: {name}");
203        }
204    }
205
206    Ok(())
207}
208
209fn print_validation_report(report: &ValidationReport) {
210    if report.success {
211        println!("āœ… All required variables are set!");
212        return;
213    }
214
215    if !report.missing.is_empty() {
216        println!("āŒ Missing required variables:");
217        let mut table = Table::new();
218        table.set_header(vec!["Variable", "Description", "Example"]);
219
220        for var in &report.missing {
221            table.add_row(vec![
222                var.name.clone(),
223                var.description.clone().unwrap_or_default(),
224                var.example.clone().unwrap_or_default(),
225            ]);
226        }
227
228        println!("{table}");
229    }
230
231    if !report.errors.is_empty() {
232        println!("\nāŒ Validation errors:");
233        for error in &report.errors {
234            println!("  - {}: {}", error.var_name, error.message);
235        }
236    }
237}