diff --git a/Cargo.lock b/Cargo.lock index 2cdb25ef..17ac43ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,16 @@ name = "cfg-if" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "chrono" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clap" version = "2.32.0" @@ -578,6 +588,14 @@ name = "normalize-line-endings" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-integer" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.6" @@ -1278,6 +1296,7 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "binary-install 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "curl 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", "dialoguer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1408,6 +1427,7 @@ dependencies = [ "checksum cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" "checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" "checksum clicolors-control 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f84dec9bc083ce2503908cd305af98bd363da6f54bf8d4bf0ac14ee749ad5d1" "checksum clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9" @@ -1455,6 +1475,7 @@ dependencies = [ "checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum normalize-line-endings 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2e0a1a39eab95caf4f5556da9289b9e68f0aafac901b2ce80daaf020d3b733a8" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum openssl 0.10.19 (registry+https://github.com/rust-lang/crates.io-index)" = "84321fb9004c3bce5611188a644d6171f895fa2889d155927d528782edb21c5d" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" diff --git a/Cargo.toml b/Cargo.toml index 670689af..ce4732dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ toml = "0.4" which = "2.0.0" binary-install = "0.0.2" walkdir = "2" +chrono = "0.4.6" [dev-dependencies] assert_cmd = "0.10.2" diff --git a/src/build/mod.rs b/src/build/mod.rs index 2e8d3dd0..65df0f61 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -4,6 +4,7 @@ use child; use command::build::BuildProfile; use emoji; use failure::{Error, ResultExt}; +use manifest::Crate; use std::path::Path; use std::process::Command; use std::str; @@ -48,6 +49,24 @@ fn rustc_minor_version() -> Option { otry!(pieces.next()).parse().ok() } +/// Checks and returns local and latest versions of wasm-pack +pub fn check_wasm_pack_versions() -> Result<(String, String), Error> { + match wasm_pack_local_version() { + Some(local) => { + match Crate::return_wasm_pack_latest_version() { + Some(latest) => Ok((local, latest)), + None => Ok((local, "".to_string())) + } + }, + None => bail!("We can't figure out what your wasm-pack version is, make sure the installation path is correct.") + } +} + +fn wasm_pack_local_version() -> Option { + let output = env!("CARGO_PKG_VERSION"); + Some(output.to_string()) +} + /// Run `cargo build` targetting `wasm32-unknown-unknown`. pub fn cargo_build_wasm( path: &Path, diff --git a/src/command/build.rs b/src/command/build.rs index 79e3790c..303028d2 100644 --- a/src/command/build.rs +++ b/src/command/build.rs @@ -255,6 +255,12 @@ impl Build { Ok(()) } + /// Returns local and latest wasm-pack versions. + pub fn return_wasm_pack_versions() -> Result<(String, String), Error> { + let (local, latest) = build::check_wasm_pack_versions()?; + Ok((local, latest)) + } + fn get_process_steps(mode: BuildMode) -> Vec<(&'static str, BuildStep)> { macro_rules! steps { ($($name:ident),+) => { diff --git a/src/lib.rs b/src/lib.rs index e8b7ceb6..30c59bd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ extern crate serde_json; #[macro_use] extern crate structopt; extern crate binary_install; +extern crate chrono; +extern crate curl; extern crate dialoguer; extern crate log; extern crate toml; diff --git a/src/main.rs b/src/main.rs index 2ad82952..8784f4a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,34 @@ extern crate env_logger; #[macro_use] extern crate failure; extern crate human_panic; +extern crate log; extern crate structopt; extern crate wasm_pack; extern crate which; use std::env; use std::panic; +use std::sync::mpsc; +use std::thread; use structopt::StructOpt; +use wasm_pack::command::build::Build; use wasm_pack::{command::run_wasm_pack, Cli}; mod installer; +fn background_check_for_updates() -> mpsc::Receiver<(String, String)> { + let (sender, receiver) = mpsc::channel(); + let _detached_thread = thread::spawn(move || { + if let Ok((local, latest)) = Build::return_wasm_pack_versions() { + if !local.is_empty() && !latest.is_empty() && local != latest { + sender.send((local, latest)).unwrap(); + } + } + }); + + receiver +} + fn main() { env_logger::init(); @@ -29,6 +46,8 @@ fn main() { } fn run() -> Result<(), failure::Error> { + let update_available = background_check_for_updates(); + // Deprecate `init` if let Some("init") = env::args().nth(1).as_ref().map(|arg| arg.as_str()) { println!("wasm-pack init is deprecated, consider using wasm-pack build"); @@ -49,6 +68,12 @@ fn run() -> Result<(), failure::Error> { let args = Cli::from_args(); run_wasm_pack(args.cmd)?; + + if let Ok(update_available) = update_available.try_recv() { + println!("There's a newer version of wasm-pack available, the new version is: {}, you are using: {}. \ + To update, navigate to: https://rustwasm.github.io/wasm-pack/installer/", update_available.1, update_available.0); + } + Ok(()) } diff --git a/src/manifest/mod.rs b/src/manifest/mod.rs index 25047d62..d55d374f 100644 --- a/src/manifest/mod.rs +++ b/src/manifest/mod.rs @@ -11,13 +11,18 @@ use self::npm::{ repository::Repository, CommonJSPackage, ESModulesPackage, NoModulesPackage, NpmPackage, }; use cargo_metadata::Metadata; +use chrono::offset; +use chrono::DateTime; use command::build::{BuildProfile, Target}; +use curl::easy; use failure::{Error, ResultExt}; use serde::{self, Deserialize}; use serde_json; use std::collections::BTreeSet; +use std::io::Write; use strsim::levenshtein; use toml; +use which; use PBAR; const WASM_PACK_METADATA_KEY: &str = "package.metadata.wasm-pack"; @@ -113,6 +118,119 @@ struct CargoWasmPackProfileWasmBindgen { dwarf_debug_info: Option, } +struct Collector(Vec); + +impl easy::Handler for Collector { + fn write(&mut self, data: &[u8]) -> Result { + self.0.extend_from_slice(data); + Ok(data.len()) + } +} + +/// Struct for storing information received from crates.io +#[derive(Deserialize, Debug)] +pub struct Crate { + #[serde(rename = "crate")] + crt: CrateInformation, +} + +#[derive(Deserialize, Debug)] +struct CrateInformation { + max_version: String, +} + +impl Crate { + /// Returns latest wasm-pack version + pub fn return_wasm_pack_latest_version() -> Option { + let current_time = chrono::offset::Local::now(); + Crate::return_wasm_pack_file().and_then(|contents| { + let last_updated = Crate::return_stamp_file_value(&contents, "created") + .and_then(|t| Some(DateTime::parse_from_str(t.as_str(), "%+").unwrap())); + let version = Crate::return_stamp_file_value(&contents, "version").and_then(|v| { + if current_time + .signed_duration_since(last_updated.unwrap()) + .num_hours() + > 24 + { + return Crate::return_api_call_result(current_time); + } else { + return Some(v); + } + }); + version + }); + return Crate::return_api_call_result(current_time); + } + + fn return_api_call_result(current_time: DateTime) -> Option { + Crate::return_latest_wasm_pack_version().and_then(|v| { + Crate::override_stamp_file(current_time, &v); + Some(v) + }) + } + + fn override_stamp_file(current_time: DateTime, version: &String) { + if let Ok(path) = which::which("wasm-pack") { + let file = fs::OpenOptions::new() + .read(true) + .write(true) + .append(true) + .create(true) + .open(path.with_extension("stamp")); + + if let Ok(()) = file.as_ref().unwrap().set_len(0) { + if let Err(_) = write!( + file.unwrap(), + "created {:?}\nversion {}", + current_time, + version + ) {} + } + } + } + + /// Return stamp file where metadata is stored. + fn return_wasm_pack_file() -> Option { + if let Ok(path) = which::which("wasm-pack") { + if let Ok(file) = fs::read_to_string(path.with_extension("stamp")) { + return Some(file); + } + } + None + } + + /// Returns wasm-pack latest version (if it's received) by executing check_wasm_pack_latest_version function. + fn return_latest_wasm_pack_version() -> Option { + if let Ok(crt) = Crate::check_wasm_pack_latest_version() { + return Some(crt.crt.max_version); + } + None + } + + /// Read the stamp file and return value assigned to a certain key. + fn return_stamp_file_value(file: &String, word: &str) -> Option { + let created = file + .lines() + .find(|line| line.starts_with(word)) + .and_then(|l| l.split_whitespace().nth(1)); + + let value = created.map(|s| s.to_string()); + + value + } + + /// Call to the crates.io api and return the latest version of `wasm-pack` + fn check_wasm_pack_latest_version() -> Result { + let mut easy = easy::Easy2::new(Collector(Vec::new())); + easy.get(true)?; + easy.url("https://crates.io/api/v1/crates/wasm-pack")?; + easy.perform()?; + let contents = easy.get_ref(); + let result = String::from_utf8_lossy(&contents.0); + Ok(serde_json::from_str(result.into_owned().as_str())?) + } +} + impl CargoWasmPackProfile { fn default_dev() -> Self { CargoWasmPackProfile {