Skip to main content

git_same/
checks.rs

1//! System requirements checking.
2//!
3//! Provides reusable requirement checks for both the CLI `init` command
4//! and the TUI init screen.
5
6use crate::auth::{gh_cli, ssh};
7use std::process::Command;
8
9/// Result of a single requirement check.
10#[derive(Debug, Clone)]
11pub struct CheckResult {
12    /// Human-readable name of the check (e.g., "Git CLI").
13    pub name: String,
14    /// Whether the check passed.
15    pub passed: bool,
16    /// Detail message (e.g., "git 2.43.0" or "not found").
17    pub message: String,
18    /// Suggested action to fix a failure.
19    pub suggestion: Option<String>,
20    /// Whether this is a critical requirement (false = warning only).
21    pub critical: bool,
22}
23
24/// Run all requirement checks.
25///
26/// Returns a list of check results for: git, gh CLI, gh authentication,
27/// SSH keys, and SSH GitHub access.
28pub async fn check_requirements() -> Vec<CheckResult> {
29    match tokio::task::spawn_blocking(check_requirements_sync).await {
30        Ok(results) => results,
31        Err(e) => vec![CheckResult {
32            name: "System checks".to_string(),
33            passed: false,
34            message: format!("failed to run checks: {}", e),
35            suggestion: Some("Try running checks again".to_string()),
36            critical: false,
37        }],
38    }
39}
40
41/// Run all requirement checks synchronously.
42pub fn check_requirements_sync() -> Vec<CheckResult> {
43    vec![
44        check_git_installed(),
45        check_gh_installed(),
46        check_gh_authenticated(),
47        check_ssh_keys(),
48        check_ssh_github_access(),
49    ]
50}
51
52/// Check if git is installed and get its version.
53fn check_git_installed() -> CheckResult {
54    match Command::new("git").arg("--version").output() {
55        Ok(output) if output.status.success() => {
56            let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
57            CheckResult {
58                name: "Git".to_string(),
59                passed: true,
60                message: version,
61                suggestion: None,
62                critical: true,
63            }
64        }
65        _ => CheckResult {
66            name: "Git".to_string(),
67            passed: false,
68            message: "not found".to_string(),
69            suggestion: Some("Install git: https://git-scm.com/downloads".to_string()),
70            critical: true,
71        },
72    }
73}
74
75/// Check if the GitHub CLI is installed.
76fn check_gh_installed() -> CheckResult {
77    if gh_cli::is_installed() {
78        let version = Command::new("gh")
79            .arg("--version")
80            .output()
81            .ok()
82            .map(|o| {
83                String::from_utf8_lossy(&o.stdout)
84                    .lines()
85                    .next()
86                    .unwrap_or("")
87                    .trim()
88                    .to_string()
89            })
90            .unwrap_or_else(|| "installed".to_string());
91        CheckResult {
92            name: "GitHub CLI".to_string(),
93            passed: true,
94            message: version,
95            suggestion: None,
96            critical: true,
97        }
98    } else {
99        CheckResult {
100            name: "GitHub CLI".to_string(),
101            passed: false,
102            message: "not found".to_string(),
103            suggestion: Some("Install from https://cli.github.com/".to_string()),
104            critical: true,
105        }
106    }
107}
108
109/// Check if the user is authenticated with the GitHub CLI.
110fn check_gh_authenticated() -> CheckResult {
111    if !gh_cli::is_installed() {
112        return CheckResult {
113            name: "GitHub Auth".to_string(),
114            passed: false,
115            message: "gh CLI not installed".to_string(),
116            suggestion: Some("Install gh CLI first, then run: gh auth login".to_string()),
117            critical: true,
118        };
119    }
120
121    if gh_cli::is_authenticated() {
122        let username = gh_cli::get_username().unwrap_or_else(|_| "authenticated".to_string());
123        CheckResult {
124            name: "GitHub Auth".to_string(),
125            passed: true,
126            message: format!("logged in as {}", username),
127            suggestion: None,
128            critical: true,
129        }
130    } else {
131        CheckResult {
132            name: "GitHub Auth".to_string(),
133            passed: false,
134            message: "not authenticated".to_string(),
135            suggestion: Some("Run: gh auth login".to_string()),
136            critical: true,
137        }
138    }
139}
140
141/// Check if SSH keys are present.
142fn check_ssh_keys() -> CheckResult {
143    if ssh::has_ssh_keys() {
144        let keys = ssh::get_ssh_key_files();
145        let key_names: Vec<String> = keys
146            .iter()
147            .filter_map(|p| p.file_name().map(|f| f.to_string_lossy().to_string()))
148            .collect();
149        CheckResult {
150            name: "SSH Keys".to_string(),
151            passed: true,
152            message: key_names.join(", "),
153            suggestion: None,
154            critical: false,
155        }
156    } else {
157        CheckResult {
158            name: "SSH Keys".to_string(),
159            passed: false,
160            message: "no SSH keys found in ~/.ssh".to_string(),
161            suggestion: Some(
162                "Generate a key: ssh-keygen -t ed25519 -C \"your_email@example.com\"".to_string(),
163            ),
164            critical: false,
165        }
166    }
167}
168
169/// Check if SSH access to GitHub works.
170fn check_ssh_github_access() -> CheckResult {
171    if ssh::has_github_ssh_access() {
172        CheckResult {
173            name: "SSH GitHub".to_string(),
174            passed: true,
175            message: "authenticated".to_string(),
176            suggestion: None,
177            critical: false,
178        }
179    } else {
180        CheckResult {
181            name: "SSH GitHub".to_string(),
182            passed: false,
183            message: "cannot reach github.com via SSH".to_string(),
184            suggestion: Some(
185                "Add your SSH key to GitHub: https://github.com/settings/keys".to_string(),
186            ),
187            critical: false,
188        }
189    }
190}
191
192#[cfg(test)]
193#[path = "checks_tests.rs"]
194mod tests;