1use std::process::Command;
2
3use sr_core::error::ReleaseError;
4use sr_core::release::VcsProvider;
5
6pub struct GitHubProvider {
8 owner: String,
9 repo: String,
10 hostname: String,
11}
12
13impl GitHubProvider {
14 pub fn new(owner: String, repo: String, hostname: String) -> Self {
15 Self {
16 owner,
17 repo,
18 hostname,
19 }
20 }
21
22 fn base_url(&self) -> String {
23 format!("https://{}/{}/{}", self.hostname, self.owner, self.repo)
24 }
25}
26
27impl VcsProvider for GitHubProvider {
28 fn create_release(
29 &self,
30 tag: &str,
31 name: &str,
32 body: &str,
33 prerelease: bool,
34 ) -> Result<String, ReleaseError> {
35 let repo_slug = format!("{}/{}", self.owner, self.repo);
36
37 let mut args = vec![
38 "release", "create", tag, "--repo", &repo_slug, "--title", name, "--notes", body,
39 ];
40
41 if prerelease {
42 args.push("--prerelease");
43 }
44
45 let output = Command::new("gh")
46 .args(&args)
47 .output()
48 .map_err(|e| ReleaseError::Vcs(format!("failed to run gh: {e}")))?;
49
50 if !output.status.success() {
51 let stderr = String::from_utf8_lossy(&output.stderr);
52 return Err(ReleaseError::Vcs(format!(
53 "gh release create failed: {stderr}"
54 )));
55 }
56
57 let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
58 Ok(url)
59 }
60
61 fn compare_url(&self, base: &str, head: &str) -> Result<String, ReleaseError> {
62 Ok(format!("{}/compare/{base}...{head}", self.base_url()))
63 }
64
65 fn release_exists(&self, tag: &str) -> Result<bool, ReleaseError> {
66 let repo_slug = format!("{}/{}", self.owner, self.repo);
67 let output = Command::new("gh")
68 .args(["release", "view", tag, "--repo", &repo_slug])
69 .output()
70 .map_err(|e| ReleaseError::Vcs(format!("failed to run gh: {e}")))?;
71 Ok(output.status.success())
72 }
73
74 fn repo_url(&self) -> Option<String> {
75 Some(self.base_url())
76 }
77
78 fn delete_release(&self, tag: &str) -> Result<(), ReleaseError> {
79 let repo_slug = format!("{}/{}", self.owner, self.repo);
80 let output = Command::new("gh")
81 .args(["release", "delete", tag, "--repo", &repo_slug, "--yes"])
82 .output()
83 .map_err(|e| ReleaseError::Vcs(format!("failed to run gh: {e}")))?;
84 if !output.status.success() {
85 let stderr = String::from_utf8_lossy(&output.stderr);
86 return Err(ReleaseError::Vcs(format!(
87 "gh release delete failed: {stderr}"
88 )));
89 }
90 Ok(())
91 }
92
93 fn upload_assets(&self, tag: &str, files: &[&str]) -> Result<(), ReleaseError> {
94 let repo_slug = format!("{}/{}", self.owner, self.repo);
95 let mut args = vec!["release", "upload", tag, "--repo", &repo_slug, "--clobber"];
96 args.extend(files);
97
98 let output = Command::new("gh")
99 .args(&args)
100 .output()
101 .map_err(|e| ReleaseError::Vcs(format!("failed to run gh: {e}")))?;
102
103 if !output.status.success() {
104 let stderr = String::from_utf8_lossy(&output.stderr);
105 return Err(ReleaseError::Vcs(format!(
106 "gh release upload failed: {stderr}"
107 )));
108 }
109
110 Ok(())
111 }
112}