Skip to content
This repository has been archived by the owner on Aug 3, 2023. It is now read-only.

feat: message if wrangler needs updating #1190

Merged
merged 23 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7205a94
feat: message if wrangler needs updating
EverlastingBugstopper Apr 3, 2020
4ab15ff
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 1, 2020
eec4a44
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 5, 2020
621031c
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 15, 2020
8ffe32b
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 26, 2020
06eb545
Made new version only check once a day
jspspike May 26, 2020
bb02790
Added comments for checking version
jspspike May 26, 2020
2b67a5c
Passed bool instead of setting latest and current to the same thing
jspspike May 26, 2020
d67926b
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 26, 2020
c3a8031
Merge branch 'avery/check-for-updates' into josh/check-for-updates
EverlastingBugstopper May 26, 2020
7c06122
Added checked to WranglerVersion struct and added more comments
jspspike May 26, 2020
e1153fe
Changed get_version_disk to return an Option and added is_outdated
jspspike May 27, 2020
cae8584
Made check happen in is_outdated
jspspike May 27, 2020
54ed30d
Fixed undoing lint
jspspike May 27, 2020
0b4de3c
Merge pull request #1326 from cloudflare/josh/check-for-updates
EverlastingBugstopper May 27, 2020
9018775
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 27, 2020
b1a0b86
addresses clippy warnings
EverlastingBugstopper May 27, 2020
df1047c
Fetches latest version if version file is older than a day
jspspike May 27, 2020
d52e2a6
Merge pull request #1331 from cloudflare/josh/check-for-updates
EverlastingBugstopper May 27, 2020
a518de2
restructure version module
EverlastingBugstopper May 27, 2020
2dadbe0
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 28, 2020
1f4cc71
decouple global_user from global_config
EverlastingBugstopper May 28, 2020
64f007b
Merge branch 'master' into avery/check-for-updates
EverlastingBugstopper May 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod settings;
pub mod tail;
pub mod terminal;
pub mod upload;
pub mod version;
pub mod watch;
pub mod wranglerjs;

Expand Down
20 changes: 19 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ use wrangler::settings;
use wrangler::settings::global_user::GlobalUser;
use wrangler::settings::toml::TargetType;
use wrangler::terminal::{emoji, interactive, message, styles};
use wrangler::version::background_check_for_updates;

fn main() -> Result<(), ExitFailure> {
env_logger::init();
let latest_version_receiver = background_check_for_updates();
if let Ok(me) = env::current_exe() {
// If we're actually running as the installer then execute our
// self-installation, otherwise just continue as usual.
Expand All @@ -34,7 +36,23 @@ fn main() -> Result<(), ExitFailure> {
installer::install();
}
}
Ok(run()?)
run()?;
if let Ok(latest_version) = latest_version_receiver.try_recv() {
let latest_version = styles::highlight(latest_version.to_string());
let new_version_available = format!(
"A new version of Wrangler ({}) is available!",
latest_version
);
let update_message = "You can learn more about updating here:".to_string();
let update_docs_url =
styles::url("https://developers.cloudflare.com/workers/quickstart#updating-the-cli");

message::billboard(&format!(
"{}\n{}\n{}",
new_version_available, update_message, update_docs_url
));
}
Ok(())
}

#[allow(clippy::cognitive_complexity)]
Expand Down
9 changes: 7 additions & 2 deletions src/settings/global_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ impl From<GlobalUser> for Credentials {
}
}

pub fn get_global_config_path() -> Result<PathBuf, failure::Error> {
let home_dir = if let Ok(value) = env::var("WRANGLER_HOME") {
pub fn get_wrangler_home_dir() -> Result<PathBuf, failure::Error> {
let config_dir = if let Ok(value) = env::var("WRANGLER_HOME") {
log::info!("Using $WRANGLER_HOME: {}", value);
Path::new(&value).to_path_buf()
} else {
Expand All @@ -143,6 +143,11 @@ pub fn get_global_config_path() -> Result<PathBuf, failure::Error> {
.expect("Could not find home directory")
.join(".wrangler")
};
Ok(config_dir)
}

pub fn get_global_config_path() -> Result<PathBuf, failure::Error> {
let home_dir = get_wrangler_home_dir()?;
let global_config_file = home_dir.join("config").join(DEFAULT_CONFIG_FILE_NAME);
log::info!("Using global config file: {}", global_config_file.display());
Ok(global_config_file)
Expand Down
6 changes: 3 additions & 3 deletions src/terminal/styles.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use console::{style, StyledObject};

pub fn url(msg: &str) -> StyledObject<&str> {
pub fn url<D>(msg: D) -> StyledObject<D> {
style(msg).blue().bold()
}

pub fn warning(msg: &str) -> StyledObject<&str> {
pub fn warning<D>(msg: D) -> StyledObject<D> {
style(msg).red().bold()
}

pub fn highlight(msg: &str) -> StyledObject<&str> {
pub fn highlight<D>(msg: D) -> StyledObject<D> {
style(msg).yellow().bold()
}
21 changes: 21 additions & 0 deletions src/version/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::process::{Child, Command};

// wrapper around spawning child processes such that they
// have the same behavior as spawned threads i.e. a spawned
// child process using GuardedChild has the same lifetime as
// the main thread.
pub struct GuardedCommand(Child);

impl GuardedCommand {
pub fn spawn(mut command: Command) -> GuardedCommand {
GuardedCommand(command.spawn().expect("failed to execute child command"))
}
}

impl Drop for GuardedCommand {
fn drop(&mut self) {
if let Err(e) = self.0.kill() {
panic!("Failed to kill child process: {:?}", e);
}
}
}
5 changes: 5 additions & 0 deletions src/version/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod block;
EverlastingBugstopper marked this conversation as resolved.
Show resolved Hide resolved
mod version;

pub use block::GuardedCommand;
pub use version::background_check_for_updates;
155 changes: 155 additions & 0 deletions src/version/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::mpsc;
use std::thread;
use std::time::SystemTime;

use crate::settings::global_user::get_wrangler_home_dir;

use reqwest::header::USER_AGENT;
use semver::Version;
use serde::{Deserialize, Serialize};

const ONE_DAY: u64 = 60 * 60 * 24;

#[derive(Debug)]
pub struct WranglerVersion {
/// currently installed version of wrangler
pub current: Version,

/// latest version of wrangler on crates.io
pub latest: Version,

/// set to true if wrangler version has been checked within a day
pub checked: bool,
}

impl WranglerVersion {
pub fn is_outdated(&self) -> bool {
return !self.checked && (self.current != self.latest);
}
}

#[derive(Clone, Debug, Deserialize, Serialize)]
struct LastCheckedVersion {
/// latest version as of last time we checked
latest_version: String,

/// the last time we asked crates.io for the latest version
last_checked: SystemTime,
}

impl FromStr for LastCheckedVersion {
type Err = toml::de::Error;

fn from_str(serialized_toml: &str) -> Result<Self, Self::Err> {
toml::from_str(serialized_toml)
}
}

fn get_installed_version() -> Result<Version, failure::Error> {
let version = option_env!("CARGO_PKG_VERSION").unwrap_or_else(|| "unknown");
let parsed_version = Version::parse(version)?;
Ok(parsed_version)
}

fn check_wrangler_versions() -> Result<WranglerVersion, failure::Error> {
let config_dir = get_wrangler_home_dir()?;
let version_file = config_dir.join("version.toml");
let current_time = SystemTime::now();

let mut checked = false;
let current = get_installed_version()?;

let latest = match get_version_disk(&version_file) {
Some(last_checked_version) => {
let time_since_last_checked =
current_time.duration_since(last_checked_version.last_checked)?;

if time_since_last_checked.as_secs() < ONE_DAY {
checked = true;
}
Version::parse(&last_checked_version.latest_version)?
}
// If version.toml doesn't exist, fetch latest version
None => get_latest_version(&current.to_string(), &version_file, current_time)?,
};

Ok(WranglerVersion {
current,
latest,
checked,
})
}

/// Reads version out of version file, is `None` if file does not exist
EverlastingBugstopper marked this conversation as resolved.
Show resolved Hide resolved
fn get_version_disk(version_file: &PathBuf) -> Option<LastCheckedVersion> {
match fs::read_to_string(&version_file) {
Ok(contents) => match LastCheckedVersion::from_str(&contents) {
Ok(last_checked_version) => Some(last_checked_version),
Err(_) => None,
},
Err(_) => None,
}
}

fn get_latest_version(
installed_version: &str,
version_file: &PathBuf,
current_time: SystemTime,
) -> Result<Version, failure::Error> {
let latest_version = get_latest_version_from_api(installed_version)?;
let updated_file_contents = toml::to_string(&LastCheckedVersion {
latest_version: latest_version.to_string(),
last_checked: current_time,
})?;
fs::write(&version_file, updated_file_contents)?;
Ok(latest_version)
}

fn get_latest_version_from_api(installed_version: &str) -> Result<Version, failure::Error> {
let url = "https://crates.io/api/v1/crates/wrangler";
let user_agent = format!(
"wrangler/{} ({})",
installed_version,
env!("CARGO_PKG_REPOSITORY")
);
let response = reqwest::blocking::Client::new()
.get(url)
.header(USER_AGENT, user_agent)
.send()?
.error_for_status()?;
let text = response.text()?;
let crt: ApiResponse = serde_json::from_str(&text)?;
let version = Version::parse(&crt.info.max_version)?;
Ok(version)
}

#[derive(Deserialize, Debug)]
struct ApiResponse {
#[serde(rename = "crate")]
info: CrateInformation,
}

#[derive(Deserialize, Debug)]
struct CrateInformation {
max_version: String,
}

pub fn background_check_for_updates() -> mpsc::Receiver<Version> {
let (sender, receiver) = mpsc::channel();

let _detached_thread = thread::spawn(move || match check_wrangler_versions() {
Ok(wrangler_versions) => {
// If the wrangler version has not been checked within the last day and the versions
// are different, print out an update message
if wrangler_versions.is_outdated() {
let _ = sender.send(wrangler_versions.latest);
}
}
Err(e) => log::debug!("could not determine if update is needed:\n{}", e),
});

receiver
}