facto_core/forges/
manager.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::config::Config;
5use crate::forges::*;
6use crate::http;
7
8pub struct ForgeManager {
9 forges: HashMap<String, Arc<dyn Forge>>,
10}
11
12impl ForgeManager {
13 pub fn new(config: &Config) -> Result<Self, http::HttpClientError> {
14 let mut forges: HashMap<String, Arc<dyn Forge>> = HashMap::new();
15 let default = http::default_client()?;
16
17 for id in &config.forges.enabled {
18 let forge: Option<Arc<dyn Forge>> = match id.as_str() {
19 "github" => {
20 let c = match &config.forges.github_token {
21 Some(t) => http::bearer_client(t)?,
22 None => default.clone(),
23 };
24 Some(Arc::new(crate::forges::github::GitHub::new(c)))
25 }
26 "gitlab" => {
27 let c = match &config.forges.gitlab_token {
28 Some(t) => http::private_token_client(t)?,
29 None => default.clone(),
30 };
31 Some(Arc::new(crate::forges::gitlab::GitLab::new(
32 c,
33 &config.forges.gitlab_base_url,
34 "gitlab",
35 "GitLab",
36 )))
37 }
38 "codeberg" => Some(Arc::new(crate::forges::codeberg::Codeberg::new(
39 default.clone(),
40 ))),
41 _ => {
42 tracing::warn!("unknown forge: {}", id);
43 None
44 }
45 };
46
47 if let Some(f) = forge {
48 forges.insert(id.clone(), f);
49 }
50 }
51
52 Ok(Self { forges })
53 }
54
55 pub fn list_forges(&self) -> Vec<(String, String)> {
56 self.forges
57 .iter()
58 .map(|(id, f)| (id.clone(), f.display_name().to_string()))
59 .collect()
60 }
61
62 pub fn get_forge(&self, id: &str) -> Option<&Arc<dyn Forge>> {
63 self.forges.get(id)
64 }
65
66 pub async fn pin_github_action(
70 &self,
71 action: &str,
72 tag: Option<&str>,
73 ) -> Result<crate::models::ActionPin, crate::forges::ForgeError> {
74 let parts: Vec<&str> = action.splitn(2, '/').collect();
75 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
76 return Err(crate::forges::ForgeError::Parse(
77 "action must be in owner/repo format (e.g. \"actions/checkout\")".into(),
78 ));
79 }
80 let (owner, repo) = (parts[0], parts[1]);
81
82 let forge = self
83 .get_forge("github")
84 .ok_or(crate::forges::ForgeError::NotSupported)?;
85
86 let tag = match tag {
87 Some(t) => t.to_string(),
88 None => {
89 let releases = forge.list_releases(owner, repo, 20).await?;
90 releases
91 .into_iter()
92 .find(|r| !r.prerelease && !r.draft)
93 .map(|r| r.tag)
94 .ok_or_else(|| {
95 crate::forges::ForgeError::Parse(format!(
96 "no stable release found for {action}"
97 ))
98 })?
99 }
100 };
101
102 let pin = forge.resolve_tag(owner, repo, &tag).await?;
103 let pinned = format!("{}@{} # {}", action, pin.commit_sha, tag);
104 Ok(crate::models::ActionPin {
105 pinned,
106 action: action.to_string(),
107 tag,
108 commit_sha: pin.commit_sha,
109 url: pin.url,
110 })
111 }
112}