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 Init {
17 #[arg(short, long)]
19 name: Option<String>,
20 },
21 Apply {
23 #[arg(short, long)]
25 force: bool,
26 },
27 Check,
29 Edit,
31 Info,
33 Run {
35 script: String,
37 },
38 Require {
40 name: String,
42 #[arg(short, long)]
44 description: Option<String>,
45 #[arg(short, long)]
47 pattern: Option<String>,
48 #[arg(short, long)]
50 example: Option<String>,
51 },
52}
53
54#[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 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 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 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 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}