Skip to content

Commit de7cf1f

Browse files
eddietejedaclaude
andauthored
fix: address code review feedback from post-release audit (#106)
* fix: address code review feedback from post-release audit - query.rs: remove dead AsyncResponse.status field (was the source of the dead_code compiler warning; field was printed pre-polling but is unused now that the loop reads QueryRunResponse.status) - query.rs: reorder polling loop — poll first, then check deadline and sleep; eliminates the mandatory 500ms delay before the first check and ensures timeout is detected without an extra sleep cycle - skill.rs: add 120s timeout to download_and_extract_from_url HTTP client, matching the timeout used in perform_update; prevents an indefinite hang during hotdata update if the skills download stalls - main.rs: replace chained .unwrap() with .expect() in the databases tables help path for clearer panic messages if subcommand names change Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): execute brew upgrade directly for Homebrew installs Instead of printing instructions and returning, run_update() now shells out to `brew upgrade <formula>` when a Homebrew install is detected. brew is located by checking common install paths (/opt/homebrew/bin, /usr/local/bin) before falling back to PATH. If brew is not found or the upgrade fails, falls back to printing the manual command. On success, busts the update-available cache so the notice clears on the next run, matching the behaviour of the non-Homebrew upgrade path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 84095c0 commit de7cf1f

4 files changed

Lines changed: 85 additions & 19 deletions

File tree

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,11 +484,11 @@ fn main() {
484484
let mut cmd = Cli::command();
485485
cmd.build();
486486
cmd.find_subcommand_mut("databases")
487-
.unwrap()
487+
.expect("databases subcommand not found")
488488
.find_subcommand_mut("tables")
489-
.unwrap()
489+
.expect("tables subcommand not found")
490490
.print_help()
491-
.unwrap();
491+
.expect("failed to print help");
492492
}
493493
}
494494
},

src/query.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ pub struct QueryResponse {
1717
#[derive(Deserialize)]
1818
struct AsyncResponse {
1919
query_run_id: String,
20-
status: String,
2120
}
2221

2322
#[derive(Deserialize)]
@@ -184,17 +183,6 @@ pub fn execute(
184183
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(300);
185184

186185
loop {
187-
std::thread::sleep(std::time::Duration::from_millis(500));
188-
if std::time::Instant::now() > deadline {
189-
spinner.finish_and_clear();
190-
use crossterm::style::Stylize;
191-
eprintln!("{}", "query timed out after 5 minutes".red());
192-
eprintln!(
193-
"{}",
194-
format!("Check status with: hotdata query status {run_id}").dark_grey()
195-
);
196-
std::process::exit(1);
197-
}
198186
let run: QueryRunResponse = api.get(&format!("/query-runs/{run_id}"));
199187
match run.status.as_str() {
200188
"succeeded" => {
@@ -218,7 +206,7 @@ pub fn execute(
218206
eprintln!("{}", format!("query failed: {err}").red());
219207
std::process::exit(1);
220208
}
221-
"running" | "queued" | "pending" => continue,
209+
"running" | "queued" | "pending" => {}
222210
status => {
223211
spinner.finish_and_clear();
224212
use crossterm::style::Stylize;
@@ -230,6 +218,17 @@ pub fn execute(
230218
std::process::exit(2);
231219
}
232220
}
221+
if std::time::Instant::now() > deadline {
222+
spinner.finish_and_clear();
223+
use crossterm::style::Stylize;
224+
eprintln!("{}", "query timed out after 5 minutes".red());
225+
eprintln!(
226+
"{}",
227+
format!("Check status with: hotdata query status {run_id}").dark_grey()
228+
);
229+
std::process::exit(1);
230+
}
231+
std::thread::sleep(std::time::Duration::from_millis(500));
233232
}
234233
}
235234

src/skill.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,10 @@ fn download_and_extract_from_url(url: &str) -> Result<(), String> {
205205
// `resp.text()` and would corrupt the gzip stream). Log the
206206
// request line manually so `--debug` still shows the URL.
207207
crate::util::debug_request("GET", url, &[], None);
208-
let client = reqwest::blocking::Client::new();
208+
let client = reqwest::blocking::Client::builder()
209+
.timeout(std::time::Duration::from_secs(120))
210+
.build()
211+
.map_err(|e| format!("error creating HTTP client: {e}"))?;
209212
let resp = client
210213
.get(url)
211214
.send()

src/update.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,76 @@ pub fn maybe_print_update_notice(rx: Option<std::sync::mpsc::Receiver<Option<Ver
164164
);
165165
}
166166

167+
/// Try to run `brew upgrade <formula>` directly. Falls back to printing the
168+
/// manual instruction if `brew` is not on PATH or the command fails.
169+
fn run_homebrew_upgrade() {
170+
println!("Updating via Homebrew...");
171+
172+
// Locate `brew` — prefer the common install paths so the upgrade works
173+
// even if the user's shell profile hasn't been sourced in this context.
174+
let brew = ["brew", "/opt/homebrew/bin/brew", "/usr/local/bin/brew"]
175+
.iter()
176+
.find(|&&p| {
177+
if p == "brew" {
178+
which_brew()
179+
} else {
180+
std::path::Path::new(p).exists()
181+
}
182+
})
183+
.copied();
184+
185+
let Some(brew_bin) = brew else {
186+
eprintln!(
187+
"{}",
188+
"brew not found — run manually:".yellow()
189+
);
190+
println!(" {}", format!("brew upgrade {HOMEBREW_FORMULA}").cyan());
191+
return;
192+
};
193+
194+
let status = std::process::Command::new(brew_bin)
195+
.args(["upgrade", HOMEBREW_FORMULA])
196+
.status();
197+
198+
match status {
199+
Ok(s) if s.success() => {
200+
// Cache bust so the update notice clears on the next run.
201+
if let Ok(v) = fetch_latest_version() {
202+
write_cache(&UpdateCheckCache {
203+
checked_at: now_secs(),
204+
latest_version: v.to_string(),
205+
});
206+
}
207+
}
208+
Ok(s) => {
209+
eprintln!(
210+
"{}",
211+
format!("brew upgrade exited with status {s}").red()
212+
);
213+
std::process::exit(s.code().unwrap_or(1));
214+
}
215+
Err(e) => {
216+
eprintln!("{}", format!("error running brew: {e}").red());
217+
eprintln!("Run manually:");
218+
println!(" {}", format!("brew upgrade {HOMEBREW_FORMULA}").cyan());
219+
std::process::exit(1);
220+
}
221+
}
222+
}
223+
224+
fn which_brew() -> bool {
225+
std::process::Command::new("which")
226+
.arg("brew")
227+
.output()
228+
.map(|o| o.status.success())
229+
.unwrap_or(false)
230+
}
231+
167232
pub fn run_update() {
168233
let current = Version::parse(CURRENT_VERSION).expect("invalid package version");
169234

170235
if detect_install_method() == InstallMethod::Homebrew {
171-
println!("hotdata was installed via Homebrew. Update with:");
172-
println!(" {}", format!("brew upgrade {HOMEBREW_FORMULA}").cyan());
236+
run_homebrew_upgrade();
173237
return;
174238
}
175239

0 commit comments

Comments
 (0)