From 32a7ff0c1058a8f907afc9320ed46ca468b2d8ca Mon Sep 17 00:00:00 2001 From: euxaristia <25621994+euxaristia@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:38:42 -0400 Subject: [PATCH 1/4] fix(gl): point doctor's version check at Gitlawb/node, not the frozen Gitlawb/releases repo check_version queried https://api.github.com/repos/gitlawb/releases/releases/latest. Gitlawb/releases is a separate repo that stopped receiving tags after v0.3.8 (2026-03-30). Releases have shipped directly on Gitlawb/node since v0.4.0, via release-please, and install.sh already treats Gitlawb/node as the canonical release source. The old query target meant gl doctor's version check could never again detect a real update. Points the check at Gitlawb/node instead, matching install.sh's own default, and makes the GitHub API base injectable so the fix is covered by a test against a mocked server rather than the real GitHub API. Closes #197 --- crates/gl/src/doctor.rs | 43 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/gl/src/doctor.rs b/crates/gl/src/doctor.rs index a7d20587..d521ce79 100644 --- a/crates/gl/src/doctor.rs +++ b/crates/gl/src/doctor.rs @@ -16,6 +16,7 @@ use std::path::PathBuf; use crate::http::NodeClient; const PUBLIC_NODE: &str = "https://node.gitlawb.com"; +const GITHUB_API_BASE: &str = "https://api.github.com"; #[derive(Args)] pub struct DoctorArgs { @@ -263,7 +264,7 @@ pub async fn run(args: DoctorArgs) -> Result<()> { // ── 7. Version up to date ───────────────────────────────────────────── let current = env!("CARGO_PKG_VERSION"); - checks.push(check_version(current).await); + checks.push(check_version(current, GITHUB_API_BASE).await); // ── Render ──────────────────────────────────────────────────────────── for check in &checks { @@ -319,8 +320,9 @@ fn which_in_path(name: &str) -> bool { .unwrap_or(false) } -/// Fetch the latest release tag from gitlawb/releases and compare to current version. -async fn check_version(current: &'static str) -> Check { +/// Fetch the latest release tag from Gitlawb/node (the actual release repo — see install.sh's +/// `GITLAWB_RELEASE_REPO` default) and compare to current version. +async fn check_version(current: &'static str, github_api_base: &str) -> Check { let client = match reqwest::Client::builder() .user_agent(format!("gl/{current}")) .timeout(std::time::Duration::from_secs(5)) @@ -331,7 +333,9 @@ async fn check_version(current: &'static str) -> Check { }; let resp = match client - .get("https://api.github.com/repos/gitlawb/releases/releases/latest") + .get(format!( + "{github_api_base}/repos/Gitlawb/node/releases/latest" + )) .send() .await { @@ -417,4 +421,35 @@ mod tests { fn test_which_in_path_missing_binary() { assert!(!which_in_path("this-binary-does-not-exist-gl-test-12345")); } + + #[tokio::test] + async fn test_check_version_queries_gitlawb_node_releases() { + let mut server = mockito::Server::new_async().await; + let _m = server + .mock("GET", "/repos/Gitlawb/node/releases/latest") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"tag_name":"v9.9.9"}"#) + .create_async() + .await; + + let check = check_version("0.1.0", &server.url()).await; + assert!(matches!(check.state, CheckState::Warn)); + assert!(check.detail.contains("v9.9.9")); + } + + #[tokio::test] + async fn test_check_version_up_to_date() { + let mut server = mockito::Server::new_async().await; + let _m = server + .mock("GET", "/repos/Gitlawb/node/releases/latest") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"tag_name":"v0.1.0"}"#) + .create_async() + .await; + + let check = check_version("0.1.0", &server.url()).await; + assert!(matches!(check.state, CheckState::Ok)); + } } From 67d52be5133bffcf842013a97981b7d9a4da87a7 Mon Sep 17 00:00:00 2001 From: euxaristia <25621994+euxaristia@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:45:26 -0400 Subject: [PATCH 2/4] fix(gl): check HTTP status before parsing the release response; strengthen up-to-date test Addresses CodeRabbit review on #198: - check_version now checks resp.status().is_success() before parsing the body, so a GitHub rate-limit or other API error surfaces as its actual HTTP status instead of a generic "could not parse latest tag". - test_check_version_up_to_date now also asserts the detail message, so the test can't silently pass on an unmatched mock request (mockito's fallback for an unhandled request is a 501, which check_version also maps to Ok). --- crates/gl/src/doctor.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/gl/src/doctor.rs b/crates/gl/src/doctor.rs index d521ce79..0083d404 100644 --- a/crates/gl/src/doctor.rs +++ b/crates/gl/src/doctor.rs @@ -343,6 +343,13 @@ async fn check_version(current: &'static str, github_api_base: &str) -> Check { Err(_) => return Check::pass("version", format!("v{current} (offline — could not check)")), }; + if !resp.status().is_success() { + return Check::pass( + "version", + format!("v{current} (GitHub API returned HTTP {})", resp.status()), + ); + } + let body: serde_json::Value = match resp.json().await { Ok(v) => v, Err(_) => return Check::pass("version", format!("v{current} (invalid response)")), @@ -451,5 +458,6 @@ mod tests { let check = check_version("0.1.0", &server.url()).await; assert!(matches!(check.state, CheckState::Ok)); + assert!(check.detail.contains("up to date")); } } From bd57c9c1bbae52e6aa14ca36e7e1cf8794b7812c Mon Sep 17 00:00:00 2001 From: euxaristia <25621994+euxaristia@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:25:16 -0400 Subject: [PATCH 3/4] test(gl): add unit test for non-2xx check_version response --- crates/gl/src/doctor.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/gl/src/doctor.rs b/crates/gl/src/doctor.rs index 0083d404..5b9e7f36 100644 --- a/crates/gl/src/doctor.rs +++ b/crates/gl/src/doctor.rs @@ -460,4 +460,19 @@ mod tests { assert!(matches!(check.state, CheckState::Ok)); assert!(check.detail.contains("up to date")); } + + #[tokio::test] + async fn test_check_version_http_error() { + let mut server = mockito::Server::new_async().await; + let _m = server + .mock("GET", "/repos/Gitlawb/node/releases/latest") + .with_status(403) + .create_async() + .await; + + let check = check_version("0.1.0", &server.url()).await; + assert!(matches!(check.state, CheckState::Ok)); + assert!(check.detail.contains("GitHub API returned HTTP 403")); + } } + From a46f8915d336884b3a25d35f48217203c85c57cf Mon Sep 17 00:00:00 2001 From: euxaristia <25621994+euxaristia@users.noreply.github.com> Date: Wed, 15 Jul 2026 07:57:33 -0400 Subject: [PATCH 4/4] fix(gl): remove trailing blank line at EOF in doctor.rs cargo fmt --all -- --check fails on the extra terminal blank line, and the fmt + clippy workflow runs that check as a required gate. --- crates/gl/src/doctor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/gl/src/doctor.rs b/crates/gl/src/doctor.rs index 5b9e7f36..1a5d96be 100644 --- a/crates/gl/src/doctor.rs +++ b/crates/gl/src/doctor.rs @@ -475,4 +475,3 @@ mod tests { assert!(check.detail.contains("GitHub API returned HTTP 403")); } } -