Skip to main content

jar_genesis/
github.rs

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
13/// Run a `gh` CLI command and return stdout.
14pub 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
27/// Post a comment on a PR.
28pub fn pr_comment(pr: u64, body: &str) -> Result<(), GhError> {
29    gh(&["pr", "comment", &pr.to_string(), "--body", body])?;
30    Ok(())
31}
32
33/// Get PR details as JSON.
34pub 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
40/// Watch PR checks until they pass or fail.
41pub 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
51/// Merge a PR with a specific head commit SHA and custom subject.
52pub 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
66/// Trigger a workflow via workflow_dispatch.
67pub 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        // Need to leak the string to get a &str — this is fine for CLI usage
73        args.push(Box::leak(kv.into_boxed_str()));
74    }
75    gh(&args)?;
76    Ok(())
77}
78
79/// Fetch paginated API results.
80pub 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}