ggen_cli_lib/cmds/ci/
trigger.rs1use clap::{Args, Subcommand};
21use ggen_utils::error::Result;
22#[derive(Args, Debug)]
25pub struct TriggerArgs {
26 #[command(subcommand)]
27 pub action: TriggerAction,
28}
29
30#[derive(Subcommand, Debug)]
31pub enum TriggerAction {
32 Workflow(WorkflowTriggerArgs),
34
35 All(AllTriggerArgs),
37
38 Local(LocalTriggerArgs),
40}
41
42#[derive(Args, Debug)]
43pub struct WorkflowTriggerArgs {
44 #[arg(long)]
46 pub workflow: String,
47
48 #[arg(long, default_value = "main")]
50 pub branch: String,
51
52 #[arg(long)]
54 pub inputs: Option<Vec<String>>,
55}
56
57#[derive(Args, Debug)]
58pub struct AllTriggerArgs {
59 #[arg(long, default_value = "main")]
61 pub branch: String,
62
63 #[arg(long)]
65 pub wait: bool,
66}
67
68#[derive(Args, Debug)]
69pub struct LocalTriggerArgs {
70 #[arg(long, default_value = "all")]
72 pub workflow: String,
73
74 #[arg(long)]
76 pub light: bool,
77
78 #[arg(long)]
80 pub dry_run: bool,
81}
82
83pub async fn run(args: &TriggerArgs) -> Result<()> {
84 match &args.action {
85 TriggerAction::Workflow(workflow_args) => trigger_workflow(workflow_args).await,
86 TriggerAction::All(all_args) => trigger_all_workflows(all_args).await,
87 TriggerAction::Local(local_args) => trigger_local_testing(local_args).await,
88 }
89}
90
91fn validate_workflow_name(workflow: &str) -> Result<()> {
93 if workflow.trim().is_empty() {
95 return Err(ggen_utils::error::Error::new(
96 "Workflow name cannot be empty",
97 ));
98 }
99
100 if workflow.len() > 200 {
102 return Err(ggen_utils::error::Error::new(
103 "Workflow name too long (max 200 characters)",
104 ));
105 }
106
107 if !workflow
109 .chars()
110 .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.')
111 {
112 return Err(ggen_utils::error::Error::new(
113 "Invalid workflow name format: only alphanumeric characters, dashes, underscores, and dots allowed",
114 ));
115 }
116
117 Ok(())
118}
119
120fn validate_branch_name(branch: &str) -> Result<()> {
122 if branch.trim().is_empty() {
124 return Err(ggen_utils::error::Error::new("Branch name cannot be empty"));
125 }
126
127 if branch.len() > 200 {
129 return Err(ggen_utils::error::Error::new(
130 "Branch name too long (max 200 characters)",
131 ));
132 }
133
134 if !branch
136 .chars()
137 .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '/' || c == '.')
138 {
139 return Err(ggen_utils::error::Error::new(
140 "Invalid branch name format: only alphanumeric characters, dashes, underscores, slashes, and dots allowed",
141 ));
142 }
143
144 Ok(())
145}
146
147fn validate_inputs(inputs: &Option<Vec<String>>) -> Result<()> {
149 if let Some(inputs) = inputs {
150 for input in inputs {
151 if input.trim().is_empty() {
153 return Err(ggen_utils::error::Error::new(
154 "Input parameter cannot be empty",
155 ));
156 }
157
158 if input.len() > 1000 {
160 return Err(ggen_utils::error::Error::new(
161 "Input parameter too long (max 1000 characters)",
162 ));
163 }
164
165 if !input.contains('=') {
167 return Err(ggen_utils::error::Error::new_fmt(format_args!(
168 "Invalid input format: '{}'. Expected 'key=value'",
169 input
170 )));
171 }
172 }
173 }
174
175 Ok(())
176}
177
178async fn trigger_workflow(args: &WorkflowTriggerArgs) -> Result<()> {
179 validate_workflow_name(&args.workflow)?;
181 validate_branch_name(&args.branch)?;
182 validate_inputs(&args.inputs)?;
183
184 println!("๐ Triggering workflow: {}", args.workflow);
185
186 let mut cmd = std::process::Command::new("gh");
187 cmd.args(["workflow", "run", &args.workflow]);
188 cmd.arg("--ref").arg(&args.branch);
189
190 if let Some(inputs) = &args.inputs {
191 for input in inputs {
192 cmd.arg("--input").arg(input);
193 }
194 }
195
196 let output = cmd.output()?;
197
198 if !output.status.success() {
199 let stderr = String::from_utf8_lossy(&output.stderr);
200 return Err(ggen_utils::error::Error::new_fmt(format_args!(
201 "Failed to trigger workflow {}: {}",
202 args.workflow, stderr
203 )));
204 }
205
206 let stdout = String::from_utf8_lossy(&output.stdout);
207 println!("โ
Workflow {} triggered successfully", args.workflow);
208 println!("{}", stdout);
209 Ok(())
210}
211
212async fn trigger_all_workflows(args: &AllTriggerArgs) -> Result<()> {
213 validate_branch_name(&args.branch)?;
215
216 println!("๐ Triggering all workflows on branch: {}", args.branch);
217
218 let mut list_cmd = std::process::Command::new("gh");
220 list_cmd.args(["workflow", "list"]);
221
222 let list_output = list_cmd.output().map_err(ggen_utils::error::Error::from)?;
223
224 if !list_output.status.success() {
225 let stderr = String::from_utf8_lossy(&list_output.stderr);
226 return Err(ggen_utils::error::Error::new_fmt(format_args!(
227 "Failed to list workflows: {}",
228 stderr
229 )));
230 }
231
232 let stdout = String::from_utf8_lossy(&list_output.stdout);
233 let workflows: Vec<&str> = stdout
234 .lines()
235 .filter_map(|line| line.split_whitespace().next())
236 .collect();
237
238 println!("๐ Found {} workflows to trigger", workflows.len());
239
240 for workflow in workflows {
241 println!("๐ Triggering workflow: {}", workflow);
242
243 let mut cmd = std::process::Command::new("gh");
244 cmd.args(["workflow", "run", workflow]);
245 cmd.arg("--ref").arg(&args.branch);
246
247 let output = cmd.output()?;
248
249 if !output.status.success() {
250 let stderr = String::from_utf8_lossy(&output.stderr);
251 println!("โ Failed to trigger workflow {}: {}", workflow, stderr);
252 continue;
253 }
254
255 println!("โ
Workflow {} triggered", workflow);
256 }
257
258 if args.wait {
259 println!("โณ Waiting for workflows to complete...");
260 println!("Use 'ggen ci workflow status' to check progress");
263 }
264
265 Ok(())
266}
267
268async fn trigger_local_testing(args: &LocalTriggerArgs) -> Result<()> {
269 validate_workflow_name(&args.workflow)?;
271
272 println!("๐งช Running local testing with act");
273
274 let mut cmd = std::process::Command::new("cargo");
275 cmd.args(["make"]);
276
277 match args.workflow.as_str() {
278 "all" => {
279 if args.light {
280 cmd.arg("act-all-light");
281 } else {
282 cmd.arg("act-all");
283 }
284 }
285 "lint" => {
286 if args.light {
287 cmd.arg("act-lint-light");
288 } else {
289 cmd.arg("act-lint");
290 }
291 }
292 "test" => {
293 if args.light {
294 cmd.arg("act-test-light");
295 } else {
296 cmd.arg("act-test");
297 }
298 }
299 "build" => {
300 if args.light {
301 cmd.arg("act-build-light");
302 } else {
303 cmd.arg("act-build");
304 }
305 }
306 _ => {
307 return Err(ggen_utils::error::Error::new_fmt(format_args!(
308 "Unknown workflow: {}. Valid options: all, lint, test, build",
309 args.workflow
310 )));
311 }
312 }
313
314 if args.dry_run {
315 cmd.arg("act-dry-run");
316 }
317
318 let output = cmd.output()?;
319
320 if !output.status.success() {
321 let stderr = String::from_utf8_lossy(&output.stderr);
322 return Err(ggen_utils::error::Error::new_fmt(format_args!(
323 "Local testing failed: {}",
324 stderr
325 )));
326 }
327
328 let stdout = String::from_utf8_lossy(&output.stdout);
329 println!("โ
Local testing completed successfully");
330 println!("{}", stdout);
331 Ok(())
332}