From ccbbeb829953e289f97c709562445e502338591c Mon Sep 17 00:00:00 2001 From: Sachin Iyer Date: Thu, 12 Feb 2026 02:55:04 +0000 Subject: [PATCH 1/2] Add Rust CI workflow Runs on pushes to main and all PRs: - cargo fmt --check - cargo clippy -- -D warnings - cargo check - cargo test - cargo audit (security vulnerability scan) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1a48006 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + name: Formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy -- -D warnings + + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo check + + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo test + + audit: + name: Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: rustsec/audit-check@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} From 45b41e61dc5149c22b151e33e30646454010f3cc Mon Sep 17 00:00:00 2001 From: Sachin Iyer Date: Thu, 12 Feb 2026 03:22:35 +0000 Subject: [PATCH 2/2] Fix formatting to pass cargo fmt --check Co-Authored-By: Claude Opus 4.6 --- src/api/client.rs | 16 +++++++--------- src/api/types.rs | 20 ++++---------------- src/commands/auth.rs | 6 ++---- src/commands/bugs.rs | 43 +++++++++++++++++++++++++++++++------------ src/commands/repos.rs | 10 ++++++++-- src/config/storage.rs | 4 ++-- src/upgrade.rs | 21 ++++++++------------- 7 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/api/client.rs b/src/api/client.rs index f16a686..d01cc66 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -97,9 +97,7 @@ impl ApiClient { Ok(api_err) => { format!( "{} ({}): {}", - api_err.error_type, - api_err.status_code, - api_err.message + api_err.error_type, api_err.status_code, api_err.message ) } Err(_) => { @@ -153,7 +151,8 @@ impl ApiClient { } } let path_and_query = format!("{}?{}", url.path(), url.query().unwrap_or("")); - self.request(reqwest::Method::GET, &path_and_query, None).await + self.request(reqwest::Method::GET, &path_and_query, None) + .await } pub async fn get_bug(&self, bug_id: &BugId) -> Result { @@ -175,8 +174,7 @@ impl ApiClient { notes: notes.map(String::from), }; let body = serde_json::to_value(request)?; - self.request(reqwest::Method::POST, &path, Some(body)) - .await + self.request(reqwest::Method::POST, &path, Some(body)).await } pub async fn list_repos(&self, limit: u32, offset: u32) -> Result { @@ -187,7 +185,8 @@ impl ApiClient { query.append_pair("offset", &offset.to_string()); } let path_and_query = format!("{}?{}", url.path(), url.query().unwrap_or("")); - self.request(reqwest::Method::GET, &path_and_query, None).await + self.request(reqwest::Method::GET, &path_and_query, None) + .await } } @@ -195,8 +194,7 @@ fn check_version_compatibility(api_version: &str) -> Result<()> { // CLI v0.1.x supports API v1.x const SUPPORTED_API_VERSIONS: &str = "^1.0"; - let api_version = - Version::parse(api_version).context("Failed to parse API version")?; + let api_version = Version::parse(api_version).context("Failed to parse API version")?; let requirement = VersionReq::parse(SUPPORTED_API_VERSIONS)?; diff --git a/src/api/types.rs b/src/api/types.rs index 5dca279..c2b3a72 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -218,10 +218,7 @@ impl crate::output::Formattable for Bug { // Wrap long fields for better table display let wrapped_title = crate::utils::wrap_text(&self.title, 50); - let wrapped_file = crate::utils::wrap_path( - self.file_path.as_deref().unwrap_or("-"), - 40 - ); + let wrapped_file = crate::utils::wrap_path(self.file_path.as_deref().unwrap_or("-"), 40); vec![ Cell::new(self.id.as_str()), @@ -239,23 +236,14 @@ impl crate::output::Formattable for Repo { } fn to_csv_row(&self) -> Vec { - vec![ - self.full_name.clone(), - self.org_name.clone(), - ] + vec![self.full_name.clone(), self.org_name.clone()] } fn table_headers() -> Vec { - vec![ - Cell::new("Repository"), - Cell::new("Organization"), - ] + vec![Cell::new("Repository"), Cell::new("Organization")] } fn to_table_row(&self) -> Vec { - vec![ - Cell::new(&self.full_name), - Cell::new(&self.org_name), - ] + vec![Cell::new(&self.full_name), Cell::new(&self.org_name)] } } diff --git a/src/commands/auth.rs b/src/commands/auth.rs index bf945e5..fb00ba1 100644 --- a/src/commands/auth.rs +++ b/src/commands/auth.rs @@ -38,10 +38,8 @@ pub async fn handle(command: &AuthCommands, cli: &crate::Cli) -> Result<()> { } // Test the token by making an API call - let client = crate::api::client::ApiClient::new( - cli.api_url.clone(), - Some(token.clone()), - )?; + let client = + crate::api::client::ApiClient::new(cli.api_url.clone(), Some(token.clone()))?; let user_info = client .get_current_user() diff --git a/src/commands/bugs.rs b/src/commands/bugs.rs index 08d318f..6e93d95 100644 --- a/src/commands/bugs.rs +++ b/src/commands/bugs.rs @@ -130,7 +130,9 @@ async fn resolve_repo_id( let mut offset = 0; loop { - let repos = client.list_repos(limit, offset).await + let repos = client + .list_repos(limit, offset) + .await .context("Failed to fetch repositories while resolving identifier")?; if let Some(repo) = repos.repos.iter().find(|r| r.full_name == repo_identifier) { @@ -155,7 +157,9 @@ async fn resolve_repo_id( let mut matching_repos = Vec::new(); loop { - let repos = client.list_repos(limit, offset).await + let repos = client + .list_repos(limit, offset) + .await .context("Failed to fetch repositories while resolving identifier")?; let page_size = repos.repos.len(); @@ -209,7 +213,8 @@ pub async fn handle(command: &BugCommands, cli: &crate::Cli) -> Result<()> { format, } => { // Resolve owner/repo or repo to internal repo ID - let resolved_repo_id = resolve_repo_id(&client, repo).await + let resolved_repo_id = resolve_repo_id(&client, repo) + .await .context("Failed to resolve repository identifier")?; let offset = crate::utils::page_to_offset(*page, *limit); @@ -225,10 +230,11 @@ pub async fn handle(command: &BugCommands, cli: &crate::Cli) -> Result<()> { BugCommands::Show { bug_id } => { use crate::api::types::BugId; - let bug_id = BugId::new(bug_id) - .map_err(|e| anyhow::anyhow!(e))?; + let bug_id = BugId::new(bug_id).map_err(|e| anyhow::anyhow!(e))?; - let bug = client.get_bug(&bug_id).await + let bug = client + .get_bug(&bug_id) + .await .context("Failed to fetch bug details")?; println!("{}", "Bug Details".bold()); @@ -236,7 +242,10 @@ pub async fn handle(command: &BugCommands, cli: &crate::Cli) -> Result<()> { println!("Title: {}", bug.title); println!("Report: {}", bug.summary); println!("File: {}", bug.file_path.as_deref().unwrap_or("-")); - println!("Created: {}", crate::utils::format_datetime(bug.created_at)); + println!( + "Created: {}", + crate::utils::format_datetime(bug.created_at) + ); println!( "Security: {}", bug.is_security_vulnerability @@ -246,7 +255,10 @@ pub async fn handle(command: &BugCommands, cli: &crate::Cli) -> Result<()> { if let Some(review) = bug.review { println!("\nReview:"); println!(" State: {}", review.state); - println!(" Date: {}", crate::utils::format_datetime(review.created_at)); + println!( + " Date: {}", + crate::utils::format_datetime(review.created_at) + ); if let Some(reason) = review.dismissal_reason { println!(" Reason: {}", reason); } @@ -271,17 +283,24 @@ pub async fn handle(command: &BugCommands, cli: &crate::Cli) -> Result<()> { bail!("--dismissal-reason is required when state is 'dismissed'"); } - let bug_id = BugId::new(bug_id) - .map_err(|e| anyhow::anyhow!(e))?; + let bug_id = BugId::new(bug_id).map_err(|e| anyhow::anyhow!(e))?; let dismissal_reason_str = dismissal_reason.as_ref().map(|r| r.as_str()); client - .update_bug_review(&bug_id, state.as_str(), dismissal_reason_str, notes.as_deref()) + .update_bug_review( + &bug_id, + state.as_str(), + dismissal_reason_str, + notes.as_deref(), + ) .await .context("Failed to update bug review")?; - println!("{}", format!("✓ Updated bug review to: {}", state.as_str()).green()); + println!( + "{}", + format!("✓ Updated bug review to: {}", state.as_str()).green() + ); Ok(()) } } diff --git a/src/commands/repos.rs b/src/commands/repos.rs index bfbc8df..ee3b5cc 100644 --- a/src/commands/repos.rs +++ b/src/commands/repos.rs @@ -23,10 +23,16 @@ pub async fn handle(command: &RepoCommands, cli: &crate::Cli) -> Result<()> { let client = cli.create_client()?; match command { - RepoCommands::List { limit, page, format } => { + RepoCommands::List { + limit, + page, + format, + } => { let offset = crate::utils::page_to_offset(*page, *limit); - let repos = client.list_repos(*limit, offset).await + let repos = client + .list_repos(*limit, offset) + .await .context("Failed to fetch repositories")?; crate::output::output_list(&repos.repos, repos.total, format) diff --git a/src/config/storage.rs b/src/config/storage.rs index 35c27d7..45cdec8 100644 --- a/src/config/storage.rs +++ b/src/config/storage.rs @@ -18,8 +18,8 @@ pub fn config_path() -> Result { PathBuf::from(xdg_config).join("detail-cli") } else if cfg!(windows) { // Windows: use LOCALAPPDATA - let local_app_data = std::env::var("LOCALAPPDATA") - .context("LOCALAPPDATA environment variable not set")?; + let local_app_data = + std::env::var("LOCALAPPDATA").context("LOCALAPPDATA environment variable not set")?; PathBuf::from(local_app_data).join("detail-cli") } else { // Others: use ~/.config diff --git a/src/upgrade.rs b/src/upgrade.rs index 09b6d66..3e6cf43 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -62,21 +62,16 @@ fn print_update_success(result: &axoupdater::UpdateResult) { let new_version = result.new_version.to_string(); eprintln!(); + eprintln!("{}", "─".repeat(60).dimmed()); eprintln!( "{}", - "─".repeat(60).dimmed() - ); - eprintln!( - "{}", - format!("✓ Updated Detail CLI from v{} to v{}", old_version, new_version).green() - ); - eprintln!( - "{}", - " Changes will apply on next run".dimmed() - ); - eprintln!( - "{}", - "─".repeat(60).dimmed() + format!( + "✓ Updated Detail CLI from v{} to v{}", + old_version, new_version + ) + .green() ); + eprintln!("{}", " Changes will apply on next run".dimmed()); + eprintln!("{}", "─".repeat(60).dimmed()); eprintln!(); }