nu_command/network/
version_check.rs

1use nu_engine::command_prelude::*;
2use serde::Deserialize;
3use update_informer::{
4    Check, Package, Registry, Result as UpdateResult,
5    http_client::{GenericHttpClient, HttpClient},
6    registry,
7};
8
9use super::tls::tls;
10
11#[derive(Clone)]
12pub struct VersionCheck;
13
14impl Command for VersionCheck {
15    fn name(&self) -> &str {
16        "version check"
17    }
18
19    fn description(&self) -> &str {
20        "Checks to see if you have the latest version of nushell."
21    }
22
23    fn extra_description(&self) -> &str {
24        "If you're running nushell nightly, `version check` will check to see if you are running the latest nightly version. If you are running the nushell release, `version check` will check to see if you're running the latest release version."
25    }
26
27    fn signature(&self) -> Signature {
28        Signature::build("version check")
29            .category(Category::Platform)
30            .input_output_types(vec![(Type::Nothing, Type::String)])
31    }
32
33    fn examples(&self) -> Vec<Example> {
34        vec![Example {
35            description: "Check if you have the latest version of nushell",
36            example: "version check",
37            result: None,
38        }]
39    }
40
41    fn run(
42        &self,
43        _engine_state: &EngineState,
44        _stack: &mut Stack,
45        _call: &Call,
46        _input: PipelineData,
47    ) -> Result<PipelineData, ShellError> {
48        let version_check = check_for_latest_nushell_version();
49        Ok(version_check.into_pipeline_data())
50    }
51}
52
53pub struct NuShellNightly;
54
55impl Registry for NuShellNightly {
56    const NAME: &'static str = "nushell/nightly";
57
58    fn get_latest_version<T: HttpClient>(
59        http_client: GenericHttpClient<T>,
60        pkg: &Package,
61    ) -> UpdateResult<Option<String>> {
62        #[derive(Deserialize, Debug)]
63        struct Response {
64            tag_name: String,
65        }
66
67        let url = format!("https://api.github.com/repos/{}/releases", pkg);
68        let versions = http_client
69            .add_header("Accept", "application/vnd.github.v3+json")
70            .add_header("User-Agent", "update-informer")
71            .get::<Vec<Response>>(&url)?;
72
73        if let Some(v) = versions.first() {
74            // The nightly repo tags look like "0.102.0-nightly.4+23dc1b6"
75            // We want to return the "0.102.0-nightly.4" part because hustcer
76            // is changing the cargo.toml package.version to be that syntax
77            let up_through_plus = match v.tag_name.split('+').next() {
78                Some(v) => v,
79                None => &v.tag_name,
80            };
81            return Ok(Some(up_through_plus.to_string()));
82        }
83
84        Ok(None)
85    }
86}
87
88struct NativeTlsHttpClient;
89
90impl HttpClient for NativeTlsHttpClient {
91    fn get<T: serde::de::DeserializeOwned>(
92        url: &str,
93        timeout: std::time::Duration,
94        headers: update_informer::http_client::HeaderMap,
95    ) -> update_informer::Result<T> {
96        let agent = ureq::AgentBuilder::new()
97            .tls_connector(std::sync::Arc::new(tls(false)?))
98            .build();
99
100        let mut req = agent.get(url).timeout(timeout);
101
102        for (header, value) in headers {
103            req = req.set(header, value);
104        }
105
106        let json = req.call()?.into_json()?;
107
108        Ok(json)
109    }
110}
111
112pub fn check_for_latest_nushell_version() -> Value {
113    let current_version = env!("CARGO_PKG_VERSION").to_string();
114
115    let mut rec = Record::new();
116
117    if current_version.contains("nightly") {
118        rec.push("channel", Value::test_string("nightly"));
119
120        let nightly_pkg_name = "nushell/nightly";
121        // The .interval() determines how long the cached check lives. Setting it to std::time::Duration::ZERO
122        // means that there is essentially no cache and it will check for a new version each time you run nushell.
123        // Since this is run on demand, there isn't really a need to cache the check.
124        let informer =
125            update_informer::new(NuShellNightly, nightly_pkg_name, current_version.clone())
126                .http_client(NativeTlsHttpClient)
127                .interval(std::time::Duration::ZERO);
128
129        if let Ok(Some(new_version)) = informer.check_version() {
130            rec.push("current", Value::test_bool(false));
131            rec.push("latest", Value::test_string(format!("{}", new_version)));
132            Value::test_record(rec)
133        } else {
134            rec.push("current", Value::test_bool(true));
135            rec.push("latest", Value::test_string(current_version.clone()));
136            Value::test_record(rec)
137        }
138    } else {
139        rec.push("channel", Value::test_string("release"));
140
141        let normal_pkg_name = "nushell/nushell";
142        // By default, this update request is cached for 24 hours so it won't check for a new version
143        // each time you run nushell. Since this is run on demand, there isn't really a need to cache the check which
144        // is why we set the interval to std::time::Duration::ZERO.
145        let informer =
146            update_informer::new(registry::GitHub, normal_pkg_name, current_version.clone())
147                .interval(std::time::Duration::ZERO);
148
149        if let Ok(Some(new_version)) = informer.check_version() {
150            rec.push("current", Value::test_bool(false));
151            rec.push("latest", Value::test_string(format!("{}", new_version)));
152            Value::test_record(rec)
153        } else {
154            rec.push("current", Value::test_bool(true));
155            rec.push("latest", Value::test_string(current_version.clone()));
156            Value::test_record(rec)
157        }
158    }
159}