Skip to main content

futucli/cmd/
version_check.rs

1use std::time::Duration;
2
3use anyhow::Result;
4use serde_json::json;
5
6use crate::output::OutputFormat;
7
8pub use futu_core::update_check::{UpdateDecision, UpdateLevel};
9#[cfg(test)]
10pub use futu_core::update_check::{VersionManifest, decide_update};
11
12#[cfg(test)]
13const BUNDLED_VERSION_MANIFEST_JSON: &str = include_str!("../../../../docs-site/docs/version.json");
14
15pub async fn run_version(
16    check: bool,
17    url: Option<&str>,
18    timeout_ms: u64,
19    output: OutputFormat,
20) -> Result<()> {
21    let current_version = env!("CARGO_PKG_VERSION");
22    if !check {
23        return print_version_report(
24            output,
25            json!({
26                "current_version": current_version,
27                "update_check": "disabled",
28                "hint": "run `futucli version --check` to check https://futuapi.com/version.json",
29            }),
30        );
31    }
32
33    let decision = check_for_update(current_version, url, timeout_ms).await?;
34    print_version_report(
35        output,
36        json!({
37            "current_version": current_version,
38            "latest_version": decision.latest_version,
39            "level": decision.level,
40            "message": decision.message,
41            "action": decision.action,
42            "update_check_url": resolve_update_check_url(url),
43        }),
44    )
45}
46
47pub async fn check_for_update(
48    current_version: &str,
49    explicit_url: Option<&str>,
50    timeout_ms: u64,
51) -> Result<UpdateDecision> {
52    let url = resolve_update_check_url(explicit_url);
53    futu_core::update_check::check_for_update(
54        current_version,
55        &url,
56        Duration::from_millis(timeout_ms.max(1)),
57    )
58    .await
59}
60
61pub fn resolve_update_check_url(explicit_url: Option<&str>) -> String {
62    futu_core::update_check::resolve_update_check_url(explicit_url)
63}
64
65fn print_version_report(output: OutputFormat, value: serde_json::Value) -> Result<()> {
66    match output {
67        OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&value)?),
68        OutputFormat::Jsonl => println!("{}", serde_json::to_string(&value)?),
69        OutputFormat::Table | OutputFormat::Markdown => {
70            println!("futucli version");
71            if let Some(current) = value.get("current_version").and_then(|v| v.as_str()) {
72                println!("current_version: {current}");
73            }
74            if let Some(latest) = value.get("latest_version").and_then(|v| v.as_str()) {
75                println!("latest_version: {latest}");
76            }
77            if let Some(level) = value.get("level").and_then(|v| v.as_str()) {
78                println!("level: {level}");
79            }
80            if let Some(message) = value.get("message").and_then(|v| v.as_str()) {
81                println!("message: {message}");
82            }
83            if let Some(action) = value.get("action").and_then(|v| v.as_str()) {
84                println!("next: {action}");
85            }
86            if let Some(hint) = value.get("hint").and_then(|v| v.as_str()) {
87                println!("hint: {hint}");
88            }
89        }
90    }
91    Ok(())
92}
93
94#[cfg(test)]
95pub(crate) fn bundled_version_manifest() -> Result<VersionManifest> {
96    serde_json::from_str(BUNDLED_VERSION_MANIFEST_JSON).map_err(anyhow::Error::from)
97}