diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index e159020..cf91f71 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -128,7 +128,7 @@ jobs: console.error(`Run: npm install -g ${pkg}`); process.exit(1); } else { - throw e; + process.exit(e.status || 1); } } EOF diff --git a/src/commands/gitignore/preview.rs b/src/commands/gitignore/preview.rs index 9b0be99..509493a 100644 --- a/src/commands/gitignore/preview.rs +++ b/src/commands/gitignore/preview.rs @@ -3,7 +3,7 @@ use crate::utils::pretty_print; use crate::utils::progress; use crate::utils::remote::Fetcher; -use super::{GITHUB_RAW_BASE, ensure_gitignore_cache, find_template_in_cache}; +use super::{ensure_gitignore_cache, find_template_in_cache, GITHUB_RAW_BASE}; #[derive(clap::Args, Debug, Clone)] pub struct PreviewArgs { @@ -14,6 +14,10 @@ pub struct PreviewArgs { /// Update the gitignore cache #[arg(long = "update-cache")] pub update_cache: bool, + + /// Disable colored output + #[arg(long = "no-color")] + pub no_color: bool, } impl super::Runnable for PreviewArgs { @@ -28,17 +32,21 @@ impl super::Runnable for PreviewArgs { let cache = ensure_gitignore_cache(&mut cache_manager, self.update_cache)?; for template_name in &self.args { - preview_single_template(template_name, &cache)?; + preview_single_template(template_name, &cache, self.no_color)?; } Ok(()) } } -fn preview_single_template(template: &str, cache: &super::Cache) -> anyhow::Result<()> { +fn preview_single_template( + template: &str, + cache: &super::Cache, + no_color: bool, +) -> anyhow::Result<()> { // normalize template if it has the .gitignore ext let template = template.strip_suffix(".gitignore").unwrap_or(template); - + // Find the template path in cache let template_path = find_template_in_cache(template, cache)?; @@ -51,7 +59,10 @@ fn preview_single_template(template: &str, cache: &super::Cache) -> anyh pb.set_message(msg); pb.finish_and_clear(); - println!("\n === Preview: {} === \n", template); - pretty_print::print_highlighted("gitignore", &content); + if no_color { + println!("{}", content); + } else { + pretty_print::print_highlighted("gitignore", &content); + } Ok(()) } diff --git a/src/commands/issue/mod.rs b/src/commands/issue/mod.rs index 4641c62..f94c352 100644 --- a/src/commands/issue/mod.rs +++ b/src/commands/issue/mod.rs @@ -7,8 +7,7 @@ pub mod list; pub mod preview; // Global constants - these can stay in the main module file -const GITHUB_RAW_BASE: &str = - "https://raw.githubusercontent.com/rafaeljohn9/gitcraft/main/templates"; +const GITHUB_RAW_BASE: &str = "https://raw.githubusercontent.com/Byte-Barn/gitcraft/main/templates"; #[derive(Subcommand)] pub enum Command { diff --git a/src/commands/issue/preview.rs b/src/commands/issue/preview.rs index a036ad5..e568c6d 100644 --- a/src/commands/issue/preview.rs +++ b/src/commands/issue/preview.rs @@ -8,6 +8,10 @@ use super::GITHUB_RAW_BASE; pub struct PreviewArgs { #[arg(allow_hyphen_values = true)] pub templates: Vec, + + /// Disable colored output + #[arg(long = "no-color")] + pub no_color: bool, } impl super::Runnable for PreviewArgs { @@ -19,14 +23,14 @@ impl super::Runnable for PreviewArgs { } for template_name in &self.templates { - preview_single_template(template_name)?; + preview_single_template(template_name, self.no_color)?; } Ok(()) } } -fn preview_single_template(template: &str) -> anyhow::Result<()> { +fn preview_single_template(template: &str, no_color: bool) -> anyhow::Result<()> { let fetcher = Fetcher::new(); let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); @@ -36,6 +40,10 @@ fn preview_single_template(template: &str) -> anyhow::Result<()> { pb.set_message(msg); pb.finish_and_clear(); - pretty_print::print_highlighted("yml", &content); + if no_color { + println!("{}", content); + } else { + pretty_print::print_highlighted("yml", &content); + } Ok(()) } diff --git a/src/commands/license/mod.rs b/src/commands/license/mod.rs index 8c91e5c..c7c2757 100644 --- a/src/commands/license/mod.rs +++ b/src/commands/license/mod.rs @@ -51,7 +51,7 @@ fn ensure_spdx_license_cache( let should_update = cache_manager .should_update_cache::(SPDX_CACHE_NAME, CACHE_MAX_AGE_SECONDS)?; - if !should_update || !update_cache { + if !should_update && !update_cache { let cache = cache_manager.load_cache(SPDX_CACHE_NAME)?; // Only print if running in verbose/debug mode (not implemented here) // e.g., println!("Loaded license template cache ({} templates)", cache.entries.len()); @@ -94,7 +94,7 @@ fn ensure_github_api_license_cache( CACHE_MAX_AGE_SECONDS, )?; - if !should_update || update_cache { + if !should_update && !update_cache { let cache = cache_manager.load_cache(GITHUB_LICENSES_CACHE_NAME)?; // Only print if running in verbose/debug mode (not implemented here) // e.g., println!("Loaded GitHub licenses cache ({} licenses)", cache.entries.len()); diff --git a/src/commands/license/preview.rs b/src/commands/license/preview.rs index 66617ed..e087177 100644 --- a/src/commands/license/preview.rs +++ b/src/commands/license/preview.rs @@ -1,8 +1,8 @@ use colored::*; use super::{ - CHOOSEALICENSE_RAW_BASE_URL, SPDX_LICENSE_DETAILS_BASE_URL, SPDX_LICENSE_LIST_URL, - ensure_spdx_license_cache, + ensure_spdx_license_cache, CHOOSEALICENSE_RAW_BASE_URL, SPDX_LICENSE_DETAILS_BASE_URL, + SPDX_LICENSE_LIST_URL, }; use crate::utils::cache::{Cache, CacheManager}; diff --git a/src/commands/pr/mod.rs b/src/commands/pr/mod.rs index aac21bc..b451968 100644 --- a/src/commands/pr/mod.rs +++ b/src/commands/pr/mod.rs @@ -7,8 +7,7 @@ pub mod list; pub mod preview; // Global constants - these can stay in the main module file -const GITHUB_RAW_BASE: &str = - "https://raw.githubusercontent.com/rafaeljohn9/gitcraft/main/templates"; +const GITHUB_RAW_BASE: &str = "https://raw.githubusercontent.com/Byte-Barn/gitcraft/main/templates"; #[derive(Subcommand)] pub enum Command { diff --git a/src/commands/pr/preview.rs b/src/commands/pr/preview.rs index 2aeb49e..6f4b1b5 100644 --- a/src/commands/pr/preview.rs +++ b/src/commands/pr/preview.rs @@ -8,6 +8,10 @@ use super::GITHUB_RAW_BASE; pub struct PreviewArgs { #[arg(help = "PR template names to preview")] pub args: Vec, + + /// Disable colored output + #[arg(long = "no-color")] + pub no_color: bool, } impl super::Runnable for PreviewArgs { @@ -19,14 +23,14 @@ impl super::Runnable for PreviewArgs { } for template_name in &self.args { - preview_single_template(template_name)?; + preview_single_template(template_name, self.no_color)?; } Ok(()) } } -fn preview_single_template(template: &str) -> anyhow::Result<()> { +fn preview_single_template(template: &str, no_color: bool) -> anyhow::Result<()> { let fetcher = Fetcher::new(); let url = format!("{}/pr-templates/{}.md", GITHUB_RAW_BASE, template); @@ -36,6 +40,11 @@ fn preview_single_template(template: &str) -> anyhow::Result<()> { pb.set_message(msg); pb.finish_and_clear(); - pretty_print::print_highlighted("md", &content); + println!("\n === Preview: {} === \n", template); + if no_color { + println!("{}", content); + } else { + pretty_print::print_highlighted("md", &content); + } Ok(()) } diff --git a/src/utils/remote.rs b/src/utils/remote.rs index 5dce662..2e34e72 100644 --- a/src/utils/remote.rs +++ b/src/utils/remote.rs @@ -18,45 +18,64 @@ impl Fetcher { } } - /// Fetch raw content from a URL + /// Fetch raw content from a URL with retry logic pub fn fetch_content(&self, url: &str) -> anyhow::Result { - let response = self - .client - .get(url) - .send() - .map_err(|e| anyhow!("Failed to fetch from {}: {}", url, e))?; - - if !response.status().is_success() { - return Err(anyhow!( - "Request failed with status {}: {}", - response.status(), - url - )); - } - - response - .text() - .map_err(|e| anyhow!("Failed to read response: {}", e)) + self.retry(|| self.client.get(url).send()) + .and_then(|response| { + if !response.status().is_success() { + return Err(anyhow!( + "Failed to fetch from {}: HTTP {} ({})", + url, + response.status().as_u16(), + response.status().canonical_reason().unwrap_or("Unknown") + )); + } + Ok(response) + }) + .and_then(|response| { + response + .text() + .map_err(|e| anyhow!("Failed to read response: {}", e)) + }) } - /// Fetch and parse JSON from a URL + /// Fetch and parse JSON from a URL with retry logic pub fn fetch_json(&self, url: &str) -> anyhow::Result { - let response = self - .client - .get(url) - .send() - .map_err(|e| anyhow!("Failed to fetch JSON from {}: {}", url, e))?; + self.retry(|| self.client.get(url).send()) + .and_then(|response| { + if !response.status().is_success() { + return Err(anyhow!( + "JSON request failed with status {}: {}", + response.status(), + url + )); + } + Ok(response) + }) + .and_then(|response| { + response + .json() + .map_err(|e| anyhow!("Failed to parse JSON: {}", e)) + }) + } - if !response.status().is_success() { - return Err(anyhow!( - "JSON request failed with status {}: {}", - response.status(), - url - )); + /// Retry logic with exponential backoff (max 3 attempts) + fn retry(&self, mut f: F) -> anyhow::Result + where + F: FnMut() -> Result, + { + let mut attempts = 0; + loop { + match f() { + Ok(response) => return Ok(response), + Err(e) => { + attempts += 1; + if attempts >= 3 { + return Err(anyhow!("Request failed after 3 attempts: {}", e)); + } + std::thread::sleep(Duration::from_millis(100 * (2_u64.pow(attempts - 1)))); + } + } } - - response - .json() - .map_err(|e| anyhow!("Failed to parse JSON: {}", e)) } } diff --git a/tests/integration/issue_tests.rs b/tests/integration/issue_tests.rs index 86effbb..0b46a14 100644 --- a/tests/integration/issue_tests.rs +++ b/tests/integration/issue_tests.rs @@ -130,9 +130,7 @@ fn test_issue_add_invalid_type() { cmd.args(&["add", "issue", "invalid-template"]) .assert() .failure() - .stderr( - predicate::str::contains("Request failed").or(predicate::str::contains("not found")), - ); + .stderr(predicate::str::contains("Failed").or(predicate::str::contains("not found"))); } #[test] @@ -301,9 +299,7 @@ fn test_issue_preview_invalid_id() { cmd.args(&["preview", "issue", "not-a-template"]) .assert() .failure() - .stderr( - predicate::str::contains("Request failed").or(predicate::str::contains("not found")), - ); + .stderr(predicate::str::contains("Failed").or(predicate::str::contains("not found"))); } // -------- HELP COMMAND TEST -------- @@ -318,7 +314,9 @@ fn test_issue_help_command() { .assert() .success() .stdout(predicate::str::contains("Add an issue template")) - .stdout(predicate::str::contains("Usage: gitcraft add issue-template")) + .stdout(predicate::str::contains( + "Usage: gitcraft add issue-template", + )) .stdout(predicate::str::contains("--dir")) .stdout(predicate::str::contains("--force")) .stdout(predicate::str::contains("-o, --output"));