summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/args/flags.rs2
-rw-r--r--cli/main.rs24
-rw-r--r--cli/tools/upgrade.rs166
-rw-r--r--cli/version.rs8
4 files changed, 181 insertions, 19 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 5e5d80c4a..b49737661 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -506,6 +506,8 @@ static ENV_VARIABLES_HELP: &str = r#"ENVIRONMENT VARIABLES:
(defaults to $HOME/.deno/bin)
DENO_NO_PROMPT Set to disable permission prompts on access
(alternative to passing --no-prompt on invocation)
+ DENO_NO_UPDATE_CHECK Set to disable checking if newer Deno version is
+ available
DENO_WEBGPU_TRACE Directory to use for wgpu traces
DENO_JOBS Number of parallel workers used for the --parallel
flag with the test subcommand. Defaults to number
diff --git a/cli/main.rs b/cli/main.rs
index f0cac4f38..5faf9c401 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -807,6 +807,29 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
Ok(0)
}
+fn check_for_upgrades(cache_dir: PathBuf) {
+ // Run a background task that checks for available upgrades. If an earlier
+ // run of this background task found a new version of Deno, the new version
+ // number (or commit hash for canary builds) is returned.
+ let maybe_upgrade_version = tools::upgrade::check_for_upgrades(cache_dir);
+
+ // Print a message if an update is available, unless:
+ // * stderr is not a tty
+ // * we're already running the 'deno upgrade' command.
+ if let Some(upgrade_version) = maybe_upgrade_version {
+ if atty::is(atty::Stream::Stderr) {
+ eprint!(
+ "{} ",
+ colors::green(format!("Deno {upgrade_version} has been released."))
+ );
+ eprintln!(
+ "{}",
+ colors::italic_gray("Run `deno upgrade` to install it.")
+ );
+ }
+ }
+}
+
async fn run_command(
flags: Flags,
run_flags: RunFlags,
@@ -824,6 +847,7 @@ async fn run_command(
// map specified and bare specifier is used on the command line - this should
// probably call `ProcState::resolve` instead
let ps = ProcState::build(flags).await?;
+ check_for_upgrades(ps.dir.root.clone());
let main_module = if NpmPackageReference::from_str(&run_flags.script).is_ok()
{
ModuleSpecifier::parse(&run_flags.script)?
diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs
index bd1fe0c31..0e828925f 100644
--- a/cli/tools/upgrade.rs
+++ b/cli/tools/upgrade.rs
@@ -3,6 +3,7 @@
//! This module provides feature to upgrade deno executable
use crate::args::UpgradeFlags;
+use crate::version;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::futures::StreamExt;
@@ -15,12 +16,81 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
+use std::time::Duration;
static ARCHIVE_NAME: Lazy<String> =
Lazy::new(|| format!("deno-{}.zip", env!("TARGET")));
const RELEASE_URL: &str = "https://github.com/denoland/deno/releases";
+// How often query server for new version. In hours.
+const UPGRADE_CHECK_INTERVAL: i64 = 24;
+const UPGRADE_CHECK_FILE_NAME: &str = "latest.txt";
+
+const UPGRADE_CHECK_FETCH_DELAY: Duration = Duration::from_millis(500);
+
+pub fn check_for_upgrades(cache_dir: PathBuf) -> Option<String> {
+ if env::var("DENO_NO_UPDATE_CHECK").is_ok() {
+ return None;
+ }
+
+ let p = cache_dir.join(UPGRADE_CHECK_FILE_NAME);
+ let content = match std::fs::read_to_string(&p) {
+ Ok(file) => file,
+ Err(_) => "".to_string(),
+ };
+
+ let (last_checked, latest_version) = parse_upgrade_check_file(content);
+
+ if latest_version.is_none() || latest_version.as_ref().unwrap().is_empty() {
+ let last_checked_dt = last_checked.and_then(|last_checked| {
+ chrono::DateTime::parse_from_rfc3339(&last_checked)
+ .map(|dt| dt.with_timezone(&chrono::Utc))
+ .ok()
+ });
+
+ let should_check = match last_checked_dt {
+ Some(last_checked_dt) => {
+ let last_check_age =
+ chrono::Utc::now().signed_duration_since(last_checked_dt);
+ last_check_age > chrono::Duration::hours(UPGRADE_CHECK_INTERVAL)
+ }
+ None => true,
+ };
+
+ if should_check {
+ tokio::spawn(async move {
+ // Sleep for a small amount of time to not unnecessarily impact startup
+ // time.
+ tokio::time::sleep(UPGRADE_CHECK_FETCH_DELAY).await;
+
+ // Fetch latest version or commit hash from server.
+ let client = match build_http_client(None) {
+ Ok(client) => client,
+ Err(_) => return,
+ };
+ let latest_version = match if version::is_canary() {
+ get_latest_canary_version(&client).await
+ } else {
+ get_latest_release_version(&client).await
+ } {
+ Ok(latest_version) => latest_version,
+ Err(_) => return,
+ };
+
+ let contents =
+ serialize_upgrade_check_file(chrono::Utc::now(), latest_version);
+ let _ =
+ std::fs::write(cache_dir.join(UPGRADE_CHECK_FILE_NAME), contents);
+ });
+ }
+ }
+
+ // Return `Some(version)` if a new version is available, `None` otherwise.
+ latest_version
+ .filter(|v| v != version::release_version_or_canary_commit_hash())
+}
+
pub async fn upgrade(upgrade_flags: UpgradeFlags) -> Result<(), AnyError> {
let old_exe_path = std::env::current_exe()?;
let permissions = fs::metadata(&old_exe_path)?.permissions();
@@ -29,17 +99,7 @@ pub async fn upgrade(upgrade_flags: UpgradeFlags) -> Result<(), AnyError> {
bail!("You do not have write permission to {:?}", old_exe_path);
}
- let mut client_builder = Client::builder();
-
- // If we have been provided a CA Certificate, add it into the HTTP client
- let ca_file = upgrade_flags.ca_file.or_else(|| env::var("DENO_CERT").ok());
- if let Some(ca_file) = ca_file {
- let buf = std::fs::read(ca_file)?;
- let cert = reqwest::Certificate::from_pem(&buf)?;
- client_builder = client_builder.add_root_certificate(cert);
- }
-
- let client = client_builder.build()?;
+ let client = build_http_client(upgrade_flags.ca_file)?;
let install_version = match upgrade_flags.version {
Some(passed_version) => {
@@ -73,8 +133,10 @@ pub async fn upgrade(upgrade_flags: UpgradeFlags) -> Result<(), AnyError> {
}
None => {
let latest_version = if upgrade_flags.canary {
+ println!("Looking up latest canary version");
get_latest_canary_version(&client).await?
} else {
+ println!("Looking up latest version");
get_latest_release_version(&client).await?
};
@@ -140,31 +202,44 @@ pub async fn upgrade(upgrade_flags: UpgradeFlags) -> Result<(), AnyError> {
Ok(())
}
+fn build_http_client(
+ ca_file: Option<String>,
+) -> Result<reqwest::Client, AnyError> {
+ let mut client_builder =
+ Client::builder().user_agent(version::get_user_agent());
+
+ // If we have been provided a CA Certificate, add it into the HTTP client
+ let ca_file = ca_file.or_else(|| env::var("DENO_CERT").ok());
+ if let Some(ca_file) = ca_file {
+ let buf = std::fs::read(ca_file)?;
+ let cert = reqwest::Certificate::from_pem(&buf)?;
+ client_builder = client_builder.add_root_certificate(cert);
+ }
+
+ let client = client_builder.build()?;
+
+ Ok(client)
+}
+
async fn get_latest_release_version(
client: &Client,
) -> Result<String, AnyError> {
- println!("Looking up latest version");
-
let res = client
- .get(&format!("{}/latest", RELEASE_URL))
+ .get("https://dl.deno.land/release-latest.txt")
.send()
.await?;
- let version = res.url().path_segments().unwrap().last().unwrap();
-
+ let version = res.text().await?.trim().to_string();
Ok(version.replace('v', ""))
}
async fn get_latest_canary_version(
client: &Client,
) -> Result<String, AnyError> {
- println!("Looking up latest version");
-
let res = client
.get("https://dl.deno.land/canary-latest.txt")
.send()
.await?;
let version = res.text().await?.trim().to_string();
-
Ok(version)
}
@@ -311,3 +386,56 @@ fn check_exe(exe_path: &Path) -> Result<(), AnyError> {
assert!(output.status.success());
Ok(())
}
+
+fn parse_upgrade_check_file(
+ content: String,
+) -> (Option<String>, Option<String>) {
+ let (mut last_checked, mut latest_version) = (None, None);
+ let split_content = content.split('!').collect::<Vec<_>>();
+
+ if split_content.len() == 2 {
+ last_checked = Some(split_content[0].to_owned());
+
+ if !split_content[1].is_empty() {
+ latest_version = Some(split_content[1].to_owned());
+ }
+ }
+
+ (last_checked, latest_version)
+}
+
+#[test]
+fn test_parse_upgrade_check_file() {
+ let (last_checked, latest_version) =
+ parse_upgrade_check_file("2020-01-01T00:00:00+00:00!1.2.3".to_string());
+ assert_eq!(last_checked, Some("2020-01-01T00:00:00+00:00".to_string()));
+ assert_eq!(latest_version, Some("1.2.3".to_string()));
+
+ let (last_checked, latest_version) =
+ parse_upgrade_check_file("2020-01-01T00:00:00+00:00!".to_string());
+ assert_eq!(last_checked, Some("2020-01-01T00:00:00+00:00".to_string()));
+ assert_eq!(latest_version, None);
+
+ let (last_checked, latest_version) =
+ parse_upgrade_check_file("2020-01-01T00:00:00+00:00".to_string());
+ assert_eq!(last_checked, None);
+ assert_eq!(latest_version, None);
+}
+
+fn serialize_upgrade_check_file(
+ dt: chrono::DateTime<chrono::Utc>,
+ version: String,
+) -> String {
+ format!("{}!{}", dt.to_rfc3339(), version)
+}
+
+#[test]
+fn test_serialize_upgrade_check_file() {
+ let s = serialize_upgrade_check_file(
+ chrono::DateTime::parse_from_rfc3339("2020-01-01T00:00:00Z")
+ .unwrap()
+ .with_timezone(&chrono::Utc),
+ "1.2.3".to_string(),
+ );
+ assert_eq!(s, "2020-01-01T00:00:00+00:00!1.2.3");
+}
diff --git a/cli/version.rs b/cli/version.rs
index 00301c85d..1a96eb234 100644
--- a/cli/version.rs
+++ b/cli/version.rs
@@ -14,6 +14,14 @@ pub fn is_canary() -> bool {
option_env!("DENO_CANARY").is_some()
}
+pub fn release_version_or_canary_commit_hash() -> &'static str {
+ if is_canary() {
+ GIT_COMMIT_HASH
+ } else {
+ env!("CARGO_PKG_VERSION")
+ }
+}
+
pub fn get_user_agent() -> String {
format!("Deno/{}", deno())
}