1use anyhow::Result;
2use clap::Args;
3use console::style;
4use tabled::{Table, settings::Style};
5
6use crate::config::Manifest;
7use crate::github::Client;
8
9#[derive(Args)]
10pub struct ReposCommand {
11 #[command(subcommand)]
12 action: ReposAction,
13}
14
15#[derive(clap::Subcommand)]
16enum ReposAction {
17 List,
19
20 Inspect {
22 name: String,
24 },
25}
26
27impl ReposCommand {
28 pub async fn run(
29 &self,
30 client: &Client,
31 manifest: &Manifest,
32 system: Option<&str>,
33 ) -> Result<()> {
34 match &self.action {
35 ReposAction::List => list_repos(client, manifest, system).await,
36 ReposAction::Inspect { name } => inspect_repo(client, name).await,
37 }
38 }
39}
40
41async fn list_repos(client: &Client, manifest: &Manifest, system: Option<&str>) -> Result<()> {
42 let repos = if let Some(sys) = system {
43 let excludes = manifest.exclude_patterns_for_system(sys);
44 let explicit = manifest.explicit_repos_for_system(sys);
45 client
46 .list_repos_for_system(sys, &excludes, &explicit)
47 .await?
48 } else {
49 client.list_repos().await?
50 };
51
52 if repos.is_empty() {
53 println!(" No repositories found.");
54 return Ok(());
55 }
56
57 let rows: Vec<[String; 4]> = repos
58 .iter()
59 .map(|r| {
60 [
61 r.name.clone(),
62 r.language.clone().unwrap_or_else(|| "-".to_owned()),
63 r.visibility.clone(),
64 r.default_branch.clone(),
65 ]
66 })
67 .collect();
68
69 let _table = Table::new(rows).with(Style::rounded()).to_string();
70
71 println!();
72 println!(
73 " {} repositories in {}{}\n",
74 style(repos.len()).bold().cyan(),
75 style(&client.org).bold(),
76 system
77 .map(|s| format!(" (system: {s})"))
78 .unwrap_or_default()
79 );
80
81 println!(
83 " {:40} {:15} {:12} {}",
84 style("Repository").bold().underlined(),
85 style("Language").bold().underlined(),
86 style("Visibility").bold().underlined(),
87 style("Branch").bold().underlined(),
88 );
89
90 for r in &repos {
91 println!(
92 " {:40} {:15} {:12} {}",
93 r.name,
94 r.language.as_deref().unwrap_or("-"),
95 r.visibility,
96 r.default_branch,
97 );
98 }
99
100 let _ = _table; Ok(())
102}
103
104async fn inspect_repo(client: &Client, name: &str) -> Result<()> {
105 let repo = client.get_repo(name).await?;
106 let security = client.get_security_state(name).await?;
107
108 println!();
109 println!(" {} {}", style("Repository:").bold(), repo.full_name);
110 println!(
111 " {} {}",
112 style("Description:").bold(),
113 repo.description.as_deref().unwrap_or("-")
114 );
115 println!(
116 " {} {}",
117 style("Language:").bold(),
118 repo.language.as_deref().unwrap_or("-")
119 );
120 println!(" {} {}", style("Visibility:").bold(), repo.visibility);
121 println!(
122 " {} {}",
123 style("Default Branch:").bold(),
124 repo.default_branch
125 );
126 println!(" {} {}", style("Archived:").bold(), repo.archived);
127
128 println!();
129 println!(" {}", style("Security Status:").bold().underlined());
130 print_feature(" Dependabot Alerts", security.dependabot_alerts);
131 print_feature(
132 " Dependabot Security Updates",
133 security.dependabot_security_updates,
134 );
135 print_feature(" Secret Scanning", security.secret_scanning);
136 print_feature(
137 " Secret Scanning AI",
138 security.secret_scanning_ai_detection,
139 );
140 print_feature(" Push Protection", security.push_protection);
141
142 Ok(())
143}
144
145fn print_feature(name: &str, enabled: bool) {
146 let icon = if enabled {
147 style("✅").green()
148 } else {
149 style("❌").red()
150 };
151 println!(
152 "{name}: {icon} {}",
153 if enabled { "enabled" } else { "disabled" }
154 );
155}