1use std::process::Command;
2
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum GhError {
7 #[error("gh command failed: {0}")]
8 CommandFailed(String),
9 #[error("io error: {0}")]
10 Io(#[from] std::io::Error),
11}
12
13pub fn gh(args: &[&str]) -> Result<String, GhError> {
15 let output = Command::new("gh").args(args).output()?;
16 if !output.status.success() {
17 let stderr = String::from_utf8_lossy(&output.stderr);
18 return Err(GhError::CommandFailed(format!(
19 "gh {} failed: {}",
20 args.join(" "),
21 stderr.trim()
22 )));
23 }
24 Ok(String::from_utf8_lossy(&output.stdout).to_string())
25}
26
27pub fn pr_comment(pr: u64, body: &str) -> Result<(), GhError> {
29 gh(&["pr", "comment", &pr.to_string(), "--body", body])?;
30 Ok(())
31}
32
33pub fn pr_view(pr: u64, fields: &str) -> Result<serde_json::Value, GhError> {
35 let output = gh(&["pr", "view", &pr.to_string(), "--json", fields])?;
36 serde_json::from_str(&output)
37 .map_err(|e| GhError::CommandFailed(format!("failed to parse PR JSON: {e}")))
38}
39
40pub fn pr_checks_watch(pr: u64) -> Result<(), GhError> {
42 let status = Command::new("gh")
43 .args(["pr", "checks", &pr.to_string(), "--watch", "--fail-fast"])
44 .status()?;
45 if !status.success() {
46 return Err(GhError::CommandFailed("PR checks failed".to_string()));
47 }
48 Ok(())
49}
50
51pub fn pr_merge(pr: u64, head_sha: &str, subject: &str) -> Result<(), GhError> {
53 gh(&[
54 "pr",
55 "merge",
56 &pr.to_string(),
57 "--merge",
58 "--match-head-commit",
59 head_sha,
60 "--subject",
61 subject,
62 ])?;
63 Ok(())
64}
65
66pub fn workflow_run(workflow: &str, inputs: &[(&str, &str)]) -> Result<(), GhError> {
68 let mut args = vec!["workflow", "run", workflow, "--ref", "master"];
69 for (key, value) in inputs {
70 args.push("-f");
71 let kv = format!("{key}={value}");
72 args.push(Box::leak(kv.into_boxed_str()));
74 }
75 gh(&args)?;
76 Ok(())
77}
78
79pub fn api_paginate(path: &str, jq: &str) -> Result<serde_json::Value, GhError> {
81 let output = gh(&["api", path, "--paginate", "--jq", jq])?;
82 serde_json::from_str(&output)
83 .map_err(|e| GhError::CommandFailed(format!("failed to parse API JSON: {e}")))
84}