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