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