nu_command/network/
version_check.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use nu_engine::command_prelude::*;
use serde::Deserialize;
use update_informer::{
    http_client::{GenericHttpClient, HttpClient},
    registry, Check, Package, Registry, Result as UpdateResult,
};

#[derive(Clone)]
pub struct VersionCheck;

impl Command for VersionCheck {
    fn name(&self) -> &str {
        "version check"
    }

    fn description(&self) -> &str {
        "Checks to see if you have the latest version of nushell."
    }

    fn extra_description(&self) -> &str {
        "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."
    }

    fn signature(&self) -> Signature {
        Signature::build("version check")
            .category(Category::Platform)
            .input_output_types(vec![(Type::Nothing, Type::String)])
    }

    fn examples(&self) -> Vec<Example> {
        vec![Example {
            description: "Check if you have the latest version of nushell",
            example: "version check",
            result: None,
        }]
    }

    fn run(
        &self,
        _engine_state: &EngineState,
        _stack: &mut Stack,
        _call: &Call,
        _input: PipelineData,
    ) -> Result<PipelineData, ShellError> {
        let version_check = check_for_latest_nushell_version();
        Ok(version_check.into_pipeline_data())
    }
}

pub struct NuShellNightly;

impl Registry for NuShellNightly {
    const NAME: &'static str = "nushell/nightly";

    fn get_latest_version<T: HttpClient>(
        http_client: GenericHttpClient<T>,
        pkg: &Package,
    ) -> UpdateResult<Option<String>> {
        #[derive(Deserialize, Debug)]
        struct Response {
            tag_name: String,
        }

        let url = format!("https://api.github.com/repos/{}/releases", pkg);
        let versions = http_client
            .add_header("Accept", "application/vnd.github.v3+json")
            .add_header("User-Agent", "update-informer")
            .get::<Vec<Response>>(&url)?;

        if let Some(v) = versions.first() {
            // The nightly repo tags look like "0.102.0-nightly.4+23dc1b6"
            // We want to return the "0.102.0-nightly.4" part because hustcer
            // is changing the cargo.toml package.version to be that syntax
            let up_through_plus = match v.tag_name.split('+').next() {
                Some(v) => v,
                None => &v.tag_name,
            };
            return Ok(Some(up_through_plus.to_string()));
        }

        Ok(None)
    }
}

struct NativeTlsHttpClient;

impl HttpClient for NativeTlsHttpClient {
    fn get<T: serde::de::DeserializeOwned>(
        url: &str,
        timeout: std::time::Duration,
        headers: update_informer::http_client::HeaderMap,
    ) -> update_informer::Result<T> {
        let agent = ureq::AgentBuilder::new()
            .tls_connector(std::sync::Arc::new(native_tls::TlsConnector::new()?))
            .build();

        let mut req = agent.get(url).timeout(timeout);

        for (header, value) in headers {
            req = req.set(header, value);
        }

        let json = req.call()?.into_json()?;

        Ok(json)
    }
}

pub fn check_for_latest_nushell_version() -> Value {
    let current_version = env!("CARGO_PKG_VERSION").to_string();

    let mut rec = Record::new();

    if current_version.contains("nightly") {
        rec.push("channel", Value::test_string("nightly"));

        let nightly_pkg_name = "nushell/nightly";
        // The .interval() determines how long the cached check lives. Setting it to std::time::Duration::ZERO
        // means that there is essentially no cache and it will check for a new version each time you run nushell.
        // Since this is run on demand, there isn't really a need to cache the check.
        let informer =
            update_informer::new(NuShellNightly, nightly_pkg_name, current_version.clone())
                .http_client(NativeTlsHttpClient)
                .interval(std::time::Duration::ZERO);

        if let Ok(Some(new_version)) = informer.check_version() {
            rec.push("current", Value::test_bool(false));
            rec.push("latest", Value::test_string(format!("{}", new_version)));
            Value::test_record(rec)
        } else {
            rec.push("current", Value::test_bool(true));
            rec.push("latest", Value::test_string(current_version.clone()));
            Value::test_record(rec)
        }
    } else {
        rec.push("channel", Value::test_string("release"));

        let normal_pkg_name = "nushell/nushell";
        // By default, this update request is cached for 24 hours so it won't check for a new version
        // each time you run nushell. Since this is run on demand, there isn't really a need to cache the check which
        // is why we set the interval to std::time::Duration::ZERO.
        let informer =
            update_informer::new(registry::GitHub, normal_pkg_name, current_version.clone())
                .interval(std::time::Duration::ZERO);

        if let Ok(Some(new_version)) = informer.check_version() {
            rec.push("current", Value::test_bool(false));
            rec.push("latest", Value::test_string(format!("{}", new_version)));
            Value::test_record(rec)
        } else {
            rec.push("current", Value::test_bool(true));
            rec.push("latest", Value::test_string(current_version.clone()));
            Value::test_record(rec)
        }
    }
}