mgrachev / update-informer

Update informer for CLI/GUI applications written in Rust 🦀
MIT License
209 stars 8 forks source link
contributor-friendly contributors-welcome hacktoberfest rust rust-lang rust-library update-checker update-notifier

Update-informer

CI Version Docs.rs Codecov Downloads

<img align="right" alt="update-informer" src="https://raw.githubusercontent.com/mgrachev/update-informer/main/logo.svg?sanitize=true">

Update informer for applications written in Rust 🦀

It checks for a new version on Crates.io, GitHub, Npm and PyPI. 🚀

Benefits

Idea

The idea is actually not new. This feature has long been present in the GitHub CLI application and npm.
There is also a popular JavaScript library.

Usage

Add update-informer to Cargo.toml:

[dependencies]
update-informer = "1.1"

By default, update-informer can only check on Crates.io and uses ureq as a default HTTP client. To enable support for other registries or change the HTTP client, use features:

[dependencies]
update-informer = { version = "1.1", default-features = false, features = ["github", "reqwest", "native-tls"] }

Available features:

Name Type Default?
crates Registry Yes
github Registry No
npm Registry No
pypi Registry No
ureq HTTP client Yes
reqwest HTTP client No
rustls-tls HTTP client feature Yes
native-tls HTTP client feature No

Checking for a new version

To check for a new version, use the UpdateInformer::check_version function.
This function takes the project name and current version as well as registry:

use update_informer::{registry, Check};

let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
let informer = update_informer::new(registry::Crates, name, version);

if let Some(version) = informer.check_version().ok().flatten()  {
    println!("New version is available: {}", version);
}

More examples you can find here.

Interval

Note that the first check will start only after the interval has expired. By default, the interval is 24 hours, but you can change it:

use std::time::Duration;
use update_informer::{registry, Check};

const EVERY_HOUR: Duration = Duration::from_secs(60 * 60);

let informer = update_informer::new(registry::Crates, "crate_name", "0.1.0").interval(EVERY_HOUR);
informer.check_version(); // The check will start only after an hour

Caching

By default, update-informer creates a file in the cache directory to avoid spam requests to the registry API.

In order not to cache requests, use a zero interval:

use std::time::Duration;
use update_informer::{registry, Check};

let informer = update_informer::new(registry::Crates, "crate_name", "0.1.0").interval(Duration::ZERO);
informer.check_version();

Request timeout

You can also change the request timeout. By default, it is 5 seconds:

use std::time::Duration;
use update_informer::{registry, Check};

const THIRTY_SECONDS: Duration = Duration::from_secs(30);

let informer = update_informer::new(registry::Crates, "crate_name", "0.1.0").timeout(THIRTY_SECONDS);
informer.check_version();

Implementing your own registry

You can implement your own registry to check updates. For example:

use update_informer::{http_client::{GenericHttpClient, HttpClient}, registry, Check, Package, Registry, Result};

#[derive(serde::Deserialize)]
struct Response {
    version: String,
}

struct YourOwnRegistry;
impl Registry for YourOwnRegistry {
    const NAME: &'static str = "your_own_registry";

    fn get_latest_version<T: HttpClient>(http_client: GenericHttpClient<T>, pkg: &Package) -> Result<Option<String>> {
        let url = "https://turbo.build/api/binaries/version";
        let resp = http_client.get::<Response>(&url)?;

        Ok(Some(resp.version))
    }
}

let informer = update_informer::new(YourOwnRegistry, "turbo", "0.1.0");
informer.check_version();

Using your own HTTP client

You can use your own HTTP client to check updates. For example, isahc:

use isahc::ReadResponseExt;
use std::time::Duration;
use serde::de::DeserializeOwned;
use update_informer::{http_client::{HeaderMap, HttpClient}, registry, Check};

struct YourOwnHttpClient;

impl HttpClient for YourOwnHttpClient {
    fn get<T: DeserializeOwned>(
        url: &str,
        _timeout: Duration,
        _headers: HeaderMap,
    ) -> update_informer::Result<T> {
        let json = isahc::get(url)?.json()?;
        Ok(json)
    }
}

let informer = update_informer::new(registry::Crates, "crate_name", "0.1.0").http_client(YourOwnHttpClient);
informer.check_version();

Tests

In order not to check for updates in tests, you can use the FakeUpdateInformer::check_version function, which returns the desired version:

use update_informer::{registry, Check};

let name = "crate_name";
let version = "0.1.0";

#[cfg(not(test))]
let informer = update_informer::new(registry::Crates, name, version);

#[cfg(test)]
let informer = update_informer::fake(registry::Crates, name, version, "1.0.0");

if let Some(version) = informer.check_version().ok().flatten() {
    println!("New version is available: {}", version);
}

Integration tests

To use the FakeUpdateInformer::check_version function in integration tests, you must first add the feature flag to Cargo.toml:

[features]
stub_check_version = []

Then use this feature flag in your code and integration tests:

use update_informer::{registry, Check};

let name = "crate_name";
let version = "0.1.0";

#[cfg(not(feature = "stub_check_version"))]
let informer = update_informer::new(registry::Crates, name, version);

#[cfg(feature = "stub_check_version")]
let informer = update_informer::fake(registry::Crates, name, version, "1.0.0");

informer.check_version();

Users

MSRV

Minimum Supported Rust Version: 1.56.1

Sponsors

update-informer is created & supported by Evrone

License

MIT