mod client;
mod interactive;
use crate::cli::commands::{GithubCmd, GithubSubCommand};
use crate::commands::cloudflare_config::require_interactive_terminal;
use crate::config::resolve_github_oauth2_key;
use crate::utils::{
find_xbp_config_upwards, git_remote_url_from_metadata, parse_github_repo_from_remote_url,
};
use std::env;
use std::path::PathBuf;
pub use client::{
create_comment, create_issue, ensure_label, get_issue, list_comments, list_issues, list_labels,
update_issue, CreateIssueInput, GithubComment, GithubIssue, ListIssuesFilter, UpdateIssueInput,
};
pub async fn run_github(cmd: GithubCmd) -> Result<(), String> {
let token = resolve_github_token()?;
let (owner, repo) = resolve_repo(cmd.owner.as_deref(), cmd.repo.as_deref())?;
match cmd.command {
None => interactive::run_hub(&token, &owner, &repo).await,
Some(GithubSubCommand::List(args)) => client::run_list(&token, &owner, &repo, args).await,
Some(GithubSubCommand::Show(args)) => client::run_show(&token, &owner, &repo, args).await,
Some(GithubSubCommand::Create(args)) => {
client::run_create(&token, &owner, &repo, args).await
}
Some(GithubSubCommand::Edit(args)) => client::run_edit(&token, &owner, &repo, args).await,
Some(GithubSubCommand::Status(args)) => {
client::run_status(&token, &owner, &repo, args).await
}
Some(GithubSubCommand::Labels(args)) => {
client::run_labels(&token, &owner, &repo, args).await
}
Some(GithubSubCommand::Assign(args)) => {
client::run_assign(&token, &owner, &repo, args).await
}
Some(GithubSubCommand::Comment(args)) => {
client::run_comment(&token, &owner, &repo, args).await
}
Some(GithubSubCommand::Comments(args)) => {
client::run_comments(&token, &owner, &repo, args).await
}
}
}
pub(crate) fn resolve_github_token() -> Result<String, String> {
resolve_github_oauth2_key().ok_or_else(|| {
"No GitHub token found. Configure one with `xbp config github set-key` (or set `github_oauth2_key` in global config).".to_string()
})
}
pub(crate) fn resolve_repo(
owner_override: Option<&str>,
repo_override: Option<&str>,
) -> Result<(String, String), String> {
if let (Some(owner), Some(repo)) = (owner_override, repo_override) {
let owner = owner.trim();
let repo = repo.trim();
if !owner.is_empty() && !repo.is_empty() {
return Ok((owner.to_string(), repo.to_string()));
}
}
let project_root = detect_project_root();
let remote = git_remote_url_from_metadata(&project_root, "origin")?.ok_or_else(|| {
format!(
"Could not resolve git remote `origin` under {}. Pass `--owner` and `--repo`.",
project_root.display()
)
})?;
parse_github_repo_from_remote_url(&remote).ok_or_else(|| {
format!("Remote is not a GitHub URL (`{remote}`). Pass `--owner` and `--repo`.")
})
}
fn detect_project_root() -> PathBuf {
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
for dir in cwd.ancestors() {
if dir.join(".git").exists() {
return dir.to_path_buf();
}
}
if let Some(found) = find_xbp_config_upwards(&cwd) {
return found.project_root;
}
cwd
}
pub(crate) fn require_interactive(context: &str) -> Result<(), String> {
require_interactive_terminal(context)
}