summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek Iwańczuk <biwanczuk@gmail.com>2024-08-12 19:32:53 +0100
committerGitHub <noreply@github.com>2024-08-12 18:32:53 +0000
commit3c70b9435a649a53614bd3ae6f434c2a455d575a (patch)
treef9424c6f1d9206c0fb3a6291371294cf830060b8
parent76f4f202e7be3835bd3d95af9e1c485a4b4695fe (diff)
feat: `deno upgrade --rc` (#24905)
This commit adds the "--rc" flag to "deno upgrade" subcommand. This flag allows to upgrade to the latest "release candidate" release. The update checker was also updated to take this into account.
-rw-r--r--Cargo.lock2
-rw-r--r--cli/Cargo.toml2
-rw-r--r--cli/args/flags.rs38
-rw-r--r--cli/tools/upgrade.rs584
4 files changed, 508 insertions, 118 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 184af900d..79e9ddc2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1127,7 +1127,7 @@ dependencies = [
[[package]]
name = "deno"
-version = "1.45.5"
+version = "1.46.0-rc.0"
dependencies = [
"async-trait",
"base32",
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 8bf53dba4..fcf497692 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -2,7 +2,7 @@
[package]
name = "deno"
-version = "1.45.5"
+version = "1.46.0-rc.0"
authors.workspace = true
default-run = "deno"
edition.workspace = true
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 0c4b75f89..c6bb90430 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -398,6 +398,7 @@ pub struct TestFlags {
pub struct UpgradeFlags {
pub dry_run: bool,
pub force: bool,
+ pub release_candidate: bool,
pub canary: bool,
pub version: Option<String>,
pub output: Option<String>,
@@ -2908,6 +2909,13 @@ update to a different location, use the --output flag:
.help("Upgrade to canary builds")
.action(ArgAction::SetTrue),
)
+ .arg(
+ Arg::new("release-candidate")
+ .long("rc")
+ .help("Upgrade to a release candidate")
+ .conflicts_with_all(["canary", "version"])
+ .action(ArgAction::SetTrue),
+ )
.arg(ca_file_arg())
})
}
@@ -4568,11 +4576,13 @@ fn upgrade_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let dry_run = matches.get_flag("dry-run");
let force = matches.get_flag("force");
let canary = matches.get_flag("canary");
+ let release_candidate = matches.get_flag("release-candidate");
let version = matches.remove_one::<String>("version");
let output = matches.remove_one::<String>("output");
flags.subcommand = DenoSubcommand::Upgrade(UpgradeFlags {
dry_run,
force,
+ release_candidate,
canary,
version,
output,
@@ -5057,6 +5067,7 @@ mod tests {
force: true,
dry_run: true,
canary: false,
+ release_candidate: false,
version: None,
output: None,
}),
@@ -5075,6 +5086,7 @@ mod tests {
force: false,
dry_run: false,
canary: false,
+ release_candidate: false,
version: None,
output: Some(String::from("example.txt")),
}),
@@ -9039,6 +9051,7 @@ mod tests {
force: false,
dry_run: false,
canary: false,
+ release_candidate: false,
version: None,
output: None,
}),
@@ -9049,6 +9062,31 @@ mod tests {
}
#[test]
+ fn upgrade_release_candidate() {
+ let r = flags_from_vec(svec!["deno", "upgrade", "--rc"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
+ force: false,
+ dry_run: false,
+ canary: false,
+ release_candidate: true,
+ version: None,
+ output: None,
+ }),
+ ..Flags::default()
+ }
+ );
+
+ let r = flags_from_vec(svec!["deno", "upgrade", "--rc", "--canary"]);
+ assert!(r.is_err());
+
+ let r = flags_from_vec(svec!["deno", "upgrade", "--rc", "--version"]);
+ assert!(r.is_err());
+ }
+
+ #[test]
fn cache_with_cafile() {
let r = flags_from_vec(svec![
"deno",
diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs
index fe5c565f0..8821e92a7 100644
--- a/cli/tools/upgrade.rs
+++ b/cli/tools/upgrade.rs
@@ -93,12 +93,14 @@ trait VersionProvider: Clone {
async fn latest_version(
&self,
release_channel: ReleaseChannel,
- ) -> Result<String, AnyError>;
+ ) -> Result<AvailableVersion, AnyError>;
- // TODO(bartlomieju): what this one actually returns?
+ /// Returns either a semver or git hash. It's up to implementor to
+ /// decide which one is appropriate, but in general only "stable"
+ /// and "lts" versions use semver.
fn current_version(&self) -> Cow<str>;
- // TODO(bartlomieju): update to handle `Lts` and `Rc` channels
+ // TODO(bartlomieju): update to handle `Lts` channel
async fn get_current_exe_release_channel(
&self,
) -> Result<ReleaseChannel, AnyError>;
@@ -127,7 +129,7 @@ impl VersionProvider for RealVersionProvider {
async fn latest_version(
&self,
release_channel: ReleaseChannel,
- ) -> Result<String, AnyError> {
+ ) -> Result<AvailableVersion, AnyError> {
fetch_latest_version(
&self.http_client_provider.get_or_create()?,
release_channel,
@@ -140,12 +142,26 @@ impl VersionProvider for RealVersionProvider {
Cow::Borrowed(version::release_version_or_canary_commit_hash())
}
- // TODO(bartlomieju): update to handle `Lts` and `Rc` channels
+ // TODO(bartlomieju): update to handle `Lts` channel
async fn get_current_exe_release_channel(
&self,
) -> Result<ReleaseChannel, AnyError> {
if version::is_canary() {
- Ok(ReleaseChannel::Canary)
+ let rc_versions = get_rc_versions(
+ &self.http_client_provider.get_or_create()?,
+ self.check_kind,
+ )
+ .await?;
+
+ let is_current_exe_an_rc = rc_versions
+ .iter()
+ .any(|(hash, _)| hash == version::GIT_COMMIT_HASH);
+
+ if is_current_exe_an_rc {
+ Ok(ReleaseChannel::Rc)
+ } else {
+ Ok(ReleaseChannel::Canary)
+ }
} else {
Ok(ReleaseChannel::Stable)
}
@@ -176,20 +192,19 @@ impl<
}
pub fn should_check_for_new_version(&self) -> bool {
- match &self.maybe_file {
- Some(file) => {
- let last_check_age = self
- .env
- .current_time()
- .signed_duration_since(file.last_checked);
- last_check_age > chrono::Duration::hours(UPGRADE_CHECK_INTERVAL)
- }
- None => true,
- }
+ let Some(file) = &self.maybe_file else {
+ return true;
+ };
+
+ let last_check_age = self
+ .env
+ .current_time()
+ .signed_duration_since(file.last_checked);
+ last_check_age > chrono::Duration::hours(UPGRADE_CHECK_INTERVAL)
}
- /// Returns the version if a new one is available and it should be prompted about.
- pub fn should_prompt(&self) -> Option<String> {
+ /// Returns the current exe release channel and a version if a new one is available and it should be prompted about.
+ pub fn should_prompt(&self) -> Option<(ReleaseChannel, String)> {
let file = self.maybe_file.as_ref()?;
// If the current version saved is not the actually current version of the binary
// It means
@@ -217,7 +232,7 @@ impl<
.current_time()
.signed_duration_since(file.last_prompt);
if last_prompt_age > chrono::Duration::hours(UPGRADE_CHECK_INTERVAL) {
- Some(file.latest_version.clone())
+ Some((file.current_release_channel, file.latest_version.clone()))
} else {
None
}
@@ -238,6 +253,9 @@ fn get_minor_version(version: &str) -> &str {
}
fn print_release_notes(current_version: &str, new_version: &str) {
+ // TODO(bartlomieju): we might want to reconsider this one for RC releases.
+ // TODO(bartlomieju): also maybe just parse using `Version::standard` instead
+ // of using `get_minor_version`?
if get_minor_version(current_version) == get_minor_version(new_version) {
return;
}
@@ -274,8 +292,10 @@ pub fn check_for_upgrades(
}
let env = RealUpdateCheckerEnvironment::new(cache_file_path);
- let version_provider =
- RealVersionProvider::new(http_client_provider, UpgradeCheckKind::Execution);
+ let version_provider = RealVersionProvider::new(
+ http_client_provider.clone(),
+ UpgradeCheckKind::Execution,
+ );
let update_checker = UpdateChecker::new(env, version_provider);
if update_checker.should_check_for_new_version() {
@@ -294,16 +314,20 @@ pub fn check_for_upgrades(
});
}
+ // Don't bother doing any more computation if we're not in TTY environment.
+ let should_prompt =
+ log::log_enabled!(log::Level::Info) && std::io::stderr().is_terminal();
+
+ if !should_prompt {
+ return;
+ }
+
// Print a message if an update is available
- if let Some(upgrade_version) = update_checker.should_prompt() {
- if log::log_enabled!(log::Level::Info) && std::io::stderr().is_terminal() {
- if version::is_canary() {
- log::info!(
- "{} {}",
- colors::green("A new canary release of Deno is available."),
- colors::italic_gray("Run `deno upgrade --canary` to install it.")
- );
- } else {
+ if let Some((release_channel, upgrade_version)) =
+ update_checker.should_prompt()
+ {
+ match release_channel {
+ ReleaseChannel::Stable => {
log::info!(
"{} {} → {} {}",
colors::green("A new release of Deno is available:"),
@@ -312,16 +336,31 @@ pub fn check_for_upgrades(
colors::italic_gray("Run `deno upgrade` to install it.")
);
}
-
- update_checker.store_prompted();
+ ReleaseChannel::Canary => {
+ log::info!(
+ "{} {}",
+ colors::green("A new canary release of Deno is available."),
+ colors::italic_gray("Run `deno upgrade --canary` to install it.")
+ );
+ }
+ ReleaseChannel::Rc => {
+ log::info!(
+ "{} {}",
+ colors::green("A new release candidate of Deno is available."),
+ colors::italic_gray("Run `deno upgrade --rc` to install it.")
+ );
+ }
+ // TODO(bartlomieju)
+ ReleaseChannel::Lts => unreachable!(),
}
+
+ update_checker.store_prompted();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LspVersionUpgradeInfo {
pub latest_version: String,
- // TODO(bartlomieju): use `ReleaseChannel` instead
pub is_canary: bool,
}
@@ -346,34 +385,39 @@ async fn check_for_upgrades_for_lsp_with_provider(
let current_version = version_provider.current_version();
// Nothing to upgrade
- if current_version == latest_version {
+ if current_version == latest_version.version_or_hash {
return Ok(None);
}
match release_channel {
ReleaseChannel::Stable => {
if let Ok(current) = Version::parse_standard(&current_version) {
- if let Ok(latest) = Version::parse_standard(&latest_version) {
+ if let Ok(latest) =
+ Version::parse_standard(&latest_version.version_or_hash)
+ {
if current >= latest {
return Ok(None); // nothing to upgrade
}
}
}
Ok(Some(LspVersionUpgradeInfo {
- latest_version,
+ latest_version: latest_version.display,
is_canary: false,
}))
}
ReleaseChannel::Canary => Ok(Some(LspVersionUpgradeInfo {
- latest_version,
+ latest_version: latest_version.display,
+ is_canary: true,
+ })),
+
+ ReleaseChannel::Rc => Ok(Some(LspVersionUpgradeInfo {
+ latest_version: latest_version.display,
is_canary: true,
})),
// TODO(bartlomieju)
ReleaseChannel::Lts => unreachable!(),
- // TODO(bartlomieju)
- ReleaseChannel::Rc => unreachable!(),
}
}
@@ -396,18 +440,18 @@ async fn fetch_and_store_latest_version<
return;
};
- env.write_check_file(
- &CheckVersionFile {
- // put a date in the past here so that prompt can be shown on next run
- last_prompt: env
- .current_time()
- .sub(chrono::Duration::hours(UPGRADE_CHECK_INTERVAL + 1)),
- last_checked: env.current_time(),
- current_version: version_provider.current_version().to_string(),
- latest_version,
- }
- .serialize(),
- );
+ let version_file = CheckVersionFile {
+ // put a date in the past here so that prompt can be shown on next run
+ last_prompt: env
+ .current_time()
+ .sub(chrono::Duration::hours(UPGRADE_CHECK_INTERVAL + 1)),
+ last_checked: env.current_time(),
+ current_version: version_provider.current_version().to_string(),
+ latest_version: latest_version.version_or_hash,
+ current_release_channel: release_channel,
+ };
+
+ env.write_check_file(&version_file.serialize());
}
pub async fn upgrade(
@@ -437,29 +481,33 @@ pub async fn upgrade(
let requested_version =
RequestedVersion::from_upgrade_flags(upgrade_flags.clone())?;
- let maybe_install_version = match requested_version {
+ let maybe_selected_version_to_upgrade = match &requested_version {
RequestedVersion::Latest(channel) => {
find_latest_version_to_upgrade(
http_client_provider.clone(),
- channel,
+ *channel,
force_selection_of_new_version,
)
.await?
}
RequestedVersion::SpecificVersion(channel, version) => {
select_specific_version_for_upgrade(
- channel,
- version,
+ *channel,
+ version.clone(),
force_selection_of_new_version,
)?
}
};
- let Some(install_version) = maybe_install_version else {
+ let Some(selected_version_to_upgrade) = maybe_selected_version_to_upgrade
+ else {
return Ok(());
};
- let download_url = get_download_url(&install_version, upgrade_flags.canary)?;
+ let download_url = get_download_url(
+ &selected_version_to_upgrade.version_or_hash,
+ requested_version.is_canary(),
+ )?;
log::info!("{}", colors::gray(format!("Downloading {}", &download_url)));
let Some(archive_data) = download_package(&client, download_url).await?
else {
@@ -469,7 +517,10 @@ pub async fn upgrade(
log::info!(
"{}",
- colors::gray(format!("Deno is upgrading to version {}", &install_version))
+ colors::gray(format!(
+ "Deno is upgrading to version {}",
+ &selected_version_to_upgrade.display
+ ))
);
let temp_dir = tempfile::TempDir::new()?;
@@ -486,8 +537,11 @@ pub async fn upgrade(
if upgrade_flags.dry_run {
fs::remove_file(&new_exe_path)?;
log::info!("Upgraded successfully (dry run)");
- if !upgrade_flags.canary {
- print_release_notes(version::deno(), &install_version);
+ if !requested_version.is_canary() {
+ print_release_notes(
+ version::deno(),
+ &selected_version_to_upgrade.version_or_hash,
+ );
}
drop(temp_dir);
return Ok(());
@@ -507,11 +561,11 @@ pub async fn upgrade(
"{}",
colors::green(format!(
"\nUpgraded successfully to Deno v{}\n",
- install_version
+ selected_version_to_upgrade.display
))
);
- if !upgrade_flags.canary {
- print_release_notes(version::deno(), &install_version);
+ if !requested_version.is_canary() {
+ print_release_notes(version::deno(), &selected_version_to_upgrade.display);
}
drop(temp_dir); // delete the temp dir
@@ -526,10 +580,13 @@ enum RequestedVersion {
impl RequestedVersion {
fn from_upgrade_flags(upgrade_flags: UpgradeFlags) -> Result<Self, AnyError> {
let is_canary = upgrade_flags.canary;
+ let is_release_candidate = upgrade_flags.release_candidate;
let Some(passed_version) = upgrade_flags.version else {
let channel = if is_canary {
ReleaseChannel::Canary
+ } else if is_release_candidate {
+ ReleaseChannel::Rc
} else {
ReleaseChannel::Stable
};
@@ -556,13 +613,25 @@ impl RequestedVersion {
Ok(RequestedVersion::SpecificVersion(channel, passed_version))
}
+
+ /// Channels that use Git hashes as versions are considered canary.
+ pub fn is_canary(&self) -> bool {
+ match self {
+ Self::Latest(channel) => {
+ matches!(channel, ReleaseChannel::Canary | ReleaseChannel::Rc)
+ }
+ Self::SpecificVersion(channel, _) => {
+ matches!(channel, ReleaseChannel::Canary | ReleaseChannel::Rc)
+ }
+ }
+ }
}
fn select_specific_version_for_upgrade(
release_channel: ReleaseChannel,
version: String,
force: bool,
-) -> Result<Option<String>, AnyError> {
+) -> Result<Option<AvailableVersion>, AnyError> {
match release_channel {
ReleaseChannel::Stable => {
let current_is_passed = if !version::is_canary() {
@@ -576,7 +645,10 @@ fn select_specific_version_for_upgrade(
return Ok(None);
}
- Ok(Some(version))
+ Ok(Some(AvailableVersion {
+ version_or_hash: version.to_string(),
+ display: version,
+ }))
}
ReleaseChannel::Canary => {
let current_is_passed = version::GIT_COMMIT_HASH == version;
@@ -585,7 +657,10 @@ fn select_specific_version_for_upgrade(
return Ok(None);
}
- Ok(Some(version))
+ Ok(Some(AvailableVersion {
+ version_or_hash: version.to_string(),
+ display: version,
+ }))
}
// TODO(bartlomieju)
ReleaseChannel::Rc => unreachable!(),
@@ -598,14 +673,14 @@ async fn find_latest_version_to_upgrade(
http_client_provider: Arc<HttpClientProvider>,
release_channel: ReleaseChannel,
force: bool,
-) -> Result<Option<String>, AnyError> {
+) -> Result<Option<AvailableVersion>, AnyError> {
log::info!(
"{}",
colors::gray(&format!("Looking up {} version", release_channel.name()))
);
let client = http_client_provider.get_or_create()?;
- let latest_version =
+ let latest_version_found =
fetch_latest_version(&client, release_channel, UpgradeCheckKind::Execution)
.await?;
@@ -614,7 +689,9 @@ async fn find_latest_version_to_upgrade(
let current_version = version::deno();
let current_is_most_recent = if !version::is_canary() {
let current = Version::parse_standard(current_version).unwrap();
- let latest = Version::parse_standard(&latest_version).unwrap();
+ let latest =
+ Version::parse_standard(&latest_version_found.version_or_hash)
+ .unwrap();
current >= latest
} else {
false
@@ -623,21 +700,31 @@ async fn find_latest_version_to_upgrade(
if !force && current_is_most_recent {
(None, current_version)
} else {
- (Some(latest_version), current_version)
+ (Some(latest_version_found), current_version)
}
}
ReleaseChannel::Canary => {
let current_version = version::GIT_COMMIT_HASH;
- let current_is_most_recent = current_version == latest_version;
+ let current_is_most_recent =
+ current_version == latest_version_found.version_or_hash;
if !force && current_is_most_recent {
(None, current_version)
} else {
- (Some(latest_version), current_version)
+ (Some(latest_version_found), current_version)
+ }
+ }
+ ReleaseChannel::Rc => {
+ let current_version = version::GIT_COMMIT_HASH;
+ let current_is_most_recent =
+ current_version == latest_version_found.version_or_hash;
+
+ if !force && current_is_most_recent {
+ (None, current_version)
+ } else {
+ (Some(latest_version_found), current_version)
}
}
- // TODO(bartlomieju)
- ReleaseChannel::Rc => unreachable!(),
// TODO(bartlomieju)
ReleaseChannel::Lts => unreachable!(),
};
@@ -648,7 +735,7 @@ async fn find_latest_version_to_upgrade(
"{}",
color_print::cformat!(
"<g>Found latest version {}</>",
- newer_latest_version
+ newer_latest_version.display
)
);
} else {
@@ -665,7 +752,7 @@ async fn find_latest_version_to_upgrade(
Ok(maybe_newer_latest_version)
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
enum ReleaseChannel {
/// Stable version, eg. 1.45.4, 2.0.0, 2.1.0
Stable,
@@ -677,8 +764,7 @@ enum ReleaseChannel {
#[allow(unused)]
Lts,
- /// Release candidate
- #[allow(unused)]
+ /// Release candidate, poiting to a git hash
Rc,
}
@@ -691,26 +777,106 @@ impl ReleaseChannel {
Self::Lts => "LTS (long term support)",
}
}
+
+ fn serialize(&self) -> String {
+ match self {
+ Self::Stable => "stable",
+ Self::Canary => "canary",
+ Self::Rc => "rc",
+ Self::Lts => "lts",
+ }
+ .to_string()
+ }
+
+ fn deserialize(str_: &str) -> Result<Self, AnyError> {
+ Ok(match str_ {
+ "stable" => Self::Stable,
+ "canary" => Self::Canary,
+ "rc" => Self::Rc,
+ "lts" => Self::Lts,
+ unknown => bail!("Unrecognized release channel: {}", unknown),
+ })
+ }
+}
+
+// Return a list of available RC release in format of (<commit_hash>, <version_name>)
+fn parse_rc_versions_text(
+ text: &str,
+) -> Result<Vec<(String, String)>, AnyError> {
+ let lines: Vec<_> = text
+ .split("\n")
+ .map(|s| s.to_string())
+ .filter(|s| !s.is_empty())
+ .collect();
+ if lines.is_empty() {
+ bail!("No release candidates available");
+ }
+
+ let mut parsed = Vec::with_capacity(lines.len());
+
+ for line in lines {
+ let v = line.split(' ').collect::<Vec<_>>();
+ if v.len() != 2 {
+ bail!("Malformed list of RC releases");
+ }
+ parsed.push((v[0].to_string(), v[1].to_string()));
+ }
+
+ Ok(parsed)
+}
+
+async fn get_rc_versions(
+ client: &HttpClient,
+ check_kind: UpgradeCheckKind,
+) -> Result<Vec<(String, String)>, AnyError> {
+ let url =
+ get_latest_version_url(ReleaseChannel::Rc, env!("TARGET"), check_kind);
+ let text = client.download_text(url.parse()?).await?;
+ parse_rc_versions_text(&text)
+}
+
+#[derive(Debug, Clone, PartialEq)]
+struct AvailableVersion {
+ version_or_hash: String,
+ display: String,
}
async fn fetch_latest_version(
client: &HttpClient,
release_channel: ReleaseChannel,
check_kind: UpgradeCheckKind,
-) -> Result<String, AnyError> {
+) -> Result<AvailableVersion, AnyError> {
let url = get_latest_version_url(release_channel, env!("TARGET"), check_kind);
let text = client.download_text(url.parse()?).await?;
- Ok(normalize_version_from_server(release_channel, &text))
+ let version = normalize_version_from_server(release_channel, &text)?;
+ Ok(version)
}
fn normalize_version_from_server(
release_channel: ReleaseChannel,
text: &str,
-) -> String {
+) -> Result<AvailableVersion, AnyError> {
let text = text.trim();
match release_channel {
- ReleaseChannel::Stable => text.trim_start_matches('v').to_string(),
- ReleaseChannel::Canary => text.to_string(),
+ ReleaseChannel::Stable => {
+ let v = text.trim_start_matches('v').to_string();
+ Ok(AvailableVersion {
+ version_or_hash: v.to_string(),
+ display: v.to_string(),
+ })
+ }
+ ReleaseChannel::Canary => Ok(AvailableVersion {
+ version_or_hash: text.to_string(),
+ display: text.to_string(),
+ }),
+ ReleaseChannel::Rc => {
+ let lines = parse_rc_versions_text(text)?;
+ let latest = lines.last().unwrap();
+ Ok(AvailableVersion {
+ version_or_hash: latest.0.to_string(),
+ display: latest.1.to_string(),
+ })
+ }
_ => unreachable!(),
}
}
@@ -725,6 +891,7 @@ fn get_latest_version_url(
ReleaseChannel::Canary => {
Cow::Owned(format!("canary-{target_tuple}-latest.txt"))
}
+ ReleaseChannel::Rc => Cow::Borrowed("release-rc.txt"),
_ => unreachable!(),
};
let query_param = match check_kind {
@@ -864,13 +1031,14 @@ struct CheckVersionFile {
pub last_checked: chrono::DateTime<chrono::Utc>,
pub current_version: String,
pub latest_version: String,
+ pub current_release_channel: ReleaseChannel,
}
impl CheckVersionFile {
pub fn parse(content: String) -> Option<Self> {
let split_content = content.split('!').collect::<Vec<_>>();
- if split_content.len() != 4 {
+ if split_content.len() != 5 {
return None;
}
@@ -882,6 +1050,15 @@ impl CheckVersionFile {
if current_version.is_empty() {
return None;
}
+ let current_release_channel = split_content[4].trim().to_owned();
+ if current_release_channel.is_empty() {
+ return None;
+ }
+ let Ok(current_release_channel) =
+ ReleaseChannel::deserialize(&current_release_channel)
+ else {
+ return None;
+ };
let last_prompt = chrono::DateTime::parse_from_rfc3339(split_content[0])
.map(|dt| dt.with_timezone(&chrono::Utc))
@@ -895,16 +1072,18 @@ impl CheckVersionFile {
last_checked,
current_version,
latest_version,
+ current_release_channel,
})
}
fn serialize(&self) -> String {
format!(
- "{}!{}!{}!{}",
+ "{}!{}!{}!{}!{}",
self.last_prompt.to_rfc3339(),
self.last_checked.to_rfc3339(),
self.latest_version,
self.current_version,
+ self.current_release_channel.serialize(),
)
}
@@ -925,9 +1104,16 @@ mod test {
#[test]
fn test_parse_upgrade_check_file() {
- let file = CheckVersionFile::parse(
+ // NOTE(bartlomieju): pre-1.46 format
+ let maybe_file = CheckVersionFile::parse(
"2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2"
.to_string(),
+ );
+ assert!(maybe_file.is_none());
+ // NOTE(bartlomieju): post-1.46 format
+ let file = CheckVersionFile::parse(
+ "2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2!stable"
+ .to_string(),
)
.unwrap();
assert_eq!(
@@ -940,6 +1126,7 @@ mod test {
);
assert_eq!(file.latest_version, "1.2.3".to_string());
assert_eq!(file.current_version, "1.2.2".to_string());
+ assert_eq!(file.current_release_channel, ReleaseChannel::Stable);
let result =
CheckVersionFile::parse("2020-01-01T00:00:00+00:00!".to_string());
@@ -953,8 +1140,45 @@ mod test {
}
#[test]
+ fn test_parse_rc_versions_text() {
+ let rc_versions = parse_rc_versions_text(
+ r#"qwerw3452gbxcvbarwett234 v1.46.0-rc.0
+cvbnfhuertt23523452345 v1.46.0-rc.1
+sdfq3452345egasdfgsdgf v2.0.0-rc.0
+asdf456yegfncbvjwe4523 v2.0.0-rc.1
+hjr6562w34rgzcvh56734a v2.0.0-rc.2
+bdfgtd6wergsdg3243234v v2.0.0-rc.3
+"#,
+ )
+ .unwrap();
+
+ assert_eq!(rc_versions.len(), 6);
+ assert_eq!(rc_versions[3].0, "asdf456yegfncbvjwe4523");
+ assert_eq!(rc_versions[3].1, "v2.0.0-rc.1");
+
+ let rc_versions = parse_rc_versions_text(
+ r#"qwerw3452gbxcvbarwett234 v1.46.0-rc.0
+
+cvbnfhuertt23523452345 v1.46.0-rc.1
+"#,
+ )
+ .unwrap();
+
+ assert_eq!(rc_versions.len(), 2);
+ assert_eq!(rc_versions[1].0, "cvbnfhuertt23523452345");
+ assert_eq!(rc_versions[1].1, "v1.46.0-rc.1");
+
+ let err =
+ parse_rc_versions_text(r#"qwerw3452gbxcvbarwett234 v1.46.0-rc.0"#)
+ .unwrap_err();
+ assert_eq!(err.to_string(), "Malformed list of RC releases");
+ let err = parse_rc_versions_text("").unwrap_err();
+ assert_eq!(err.to_string(), "No release candidates available");
+ }
+
+ #[test]
fn test_serialize_upgrade_check_file() {
- let file = CheckVersionFile {
+ let mut file = CheckVersionFile {
last_prompt: chrono::DateTime::parse_from_rfc3339("2020-01-01T00:00:00Z")
.unwrap()
.with_timezone(&chrono::Utc),
@@ -965,19 +1189,35 @@ mod test {
.with_timezone(&chrono::Utc),
latest_version: "1.2.3".to_string(),
current_version: "1.2.2".to_string(),
+ current_release_channel: ReleaseChannel::Stable,
};
assert_eq!(
file.serialize(),
- "2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2"
+ "2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2!stable"
+ );
+ file.current_release_channel = ReleaseChannel::Canary;
+ assert_eq!(
+ file.serialize(),
+ "2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2!canary"
+ );
+ file.current_release_channel = ReleaseChannel::Rc;
+ assert_eq!(
+ file.serialize(),
+ "2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2!rc"
+ );
+ file.current_release_channel = ReleaseChannel::Lts;
+ assert_eq!(
+ file.serialize(),
+ "2020-01-01T00:00:00+00:00!2020-01-01T00:00:00+00:00!1.2.3!1.2.2!lts"
);
}
#[derive(Clone)]
struct TestUpdateCheckerEnvironment {
file_text: Rc<RefCell<String>>,
- is_canary: Rc<RefCell<bool>>,
+ release_channel: Rc<RefCell<ReleaseChannel>>,
current_version: Rc<RefCell<String>>,
- latest_version: Rc<RefCell<Result<String, String>>>,
+ latest_version: Rc<RefCell<Result<AvailableVersion, String>>>,
time: Rc<RefCell<chrono::DateTime<chrono::Utc>>>,
}
@@ -986,8 +1226,11 @@ mod test {
Self {
file_text: Default::default(),
current_version: Default::default(),
- is_canary: Default::default(),
- latest_version: Rc::new(RefCell::new(Ok("".to_string()))),
+ release_channel: Rc::new(RefCell::new(ReleaseChannel::Stable)),
+ latest_version: Rc::new(RefCell::new(Ok(AvailableVersion {
+ version_or_hash: "".to_string(),
+ display: "".to_string(),
+ }))),
time: Rc::new(RefCell::new(chrono::Utc::now())),
}
}
@@ -1008,15 +1251,18 @@ mod test {
}
pub fn set_latest_version(&self, version: &str) {
- *self.latest_version.borrow_mut() = Ok(version.to_string());
+ *self.latest_version.borrow_mut() = Ok(AvailableVersion {
+ version_or_hash: version.to_string(),
+ display: version.to_string(),
+ });
}
pub fn set_latest_version_err(&self, err: &str) {
*self.latest_version.borrow_mut() = Err(err.to_string());
}
- pub fn set_is_canary(&self, is_canary: bool) {
- *self.is_canary.borrow_mut() = is_canary;
+ pub fn set_release_channel(&self, channel: ReleaseChannel) {
+ *self.release_channel.borrow_mut() = channel;
}
}
@@ -1026,7 +1272,7 @@ mod test {
async fn latest_version(
&self,
_release_channel: ReleaseChannel,
- ) -> Result<String, AnyError> {
+ ) -> Result<AvailableVersion, AnyError> {
match self.latest_version.borrow().clone() {
Ok(result) => Ok(result),
Err(err) => bail!("{}", err),
@@ -1037,15 +1283,10 @@ mod test {
Cow::Owned(self.current_version.borrow().clone())
}
- // TODO(bartlomieju): update to handle `Lts` and `Rc` channels
async fn get_current_exe_release_channel(
&self,
) -> Result<ReleaseChannel, AnyError> {
- if *self.is_canary.borrow() {
- Ok(ReleaseChannel::Canary)
- } else {
- Ok(ReleaseChannel::Stable)
- }
+ Ok(*self.release_channel.borrow())
}
}
@@ -1083,25 +1324,37 @@ mod test {
// should not check for latest version because we just did
assert!(!checker.should_check_for_new_version());
// but should prompt
- assert_eq!(checker.should_prompt(), Some("1.1.0".to_string()));
+ assert_eq!(
+ checker.should_prompt(),
+ Some((ReleaseChannel::Stable, "1.1.0".to_string()))
+ );
// fast forward an hour and bump the latest version
env.add_hours(1);
env.set_latest_version("1.2.0");
assert!(!checker.should_check_for_new_version());
- assert_eq!(checker.should_prompt(), Some("1.1.0".to_string()));
+ assert_eq!(
+ checker.should_prompt(),
+ Some((ReleaseChannel::Stable, "1.1.0".to_string()))
+ );
// fast forward again and it should check for a newer version
env.add_hours(UPGRADE_CHECK_INTERVAL);
assert!(checker.should_check_for_new_version());
- assert_eq!(checker.should_prompt(), Some("1.1.0".to_string()));
+ assert_eq!(
+ checker.should_prompt(),
+ Some((ReleaseChannel::Stable, "1.1.0".to_string()))
+ );
fetch_and_store_latest_version(&env, &env).await;
// reload and store that we prompted
let checker = UpdateChecker::new(env.clone(), env.clone());
assert!(!checker.should_check_for_new_version());
- assert_eq!(checker.should_prompt(), Some("1.2.0".to_string()));
+ assert_eq!(
+ checker.should_prompt(),
+ Some((ReleaseChannel::Stable, "1.2.0".to_string()))
+ );
checker.store_prompted();
// reload and it should now say not to prompt
@@ -1112,7 +1365,10 @@ mod test {
// but if we fast forward past the upgrade interval it should prompt again
env.add_hours(UPGRADE_CHECK_INTERVAL + 1);
assert!(checker.should_check_for_new_version());
- assert_eq!(checker.should_prompt(), Some("1.2.0".to_string()));
+ assert_eq!(
+ checker.should_prompt(),
+ Some((ReleaseChannel::Stable, "1.2.0".to_string()))
+ );
// upgrade the version and it should stop prompting
env.set_current_version("1.2.0");
@@ -1128,6 +1384,21 @@ mod test {
fetch_and_store_latest_version(&env, &env).await;
assert!(checker.should_check_for_new_version());
assert_eq!(checker.should_prompt(), None);
+
+ // now switch to RC release
+ env.set_release_channel(ReleaseChannel::Rc);
+ env.set_current_version("1.46.0-rc.0");
+ env.set_latest_version("1.46.0-rc.1");
+ fetch_and_store_latest_version(&env, &env).await;
+ env.add_hours(UPGRADE_CHECK_INTERVAL + 1);
+
+ // We should check for new version and prompt
+ let checker = UpdateChecker::new(env.clone(), env.clone());
+ assert!(checker.should_check_for_new_version());
+ assert_eq!(
+ checker.should_prompt(),
+ Some((ReleaseChannel::Rc, "1.46.0-rc.1".to_string()))
+ );
}
#[tokio::test]
@@ -1140,6 +1411,7 @@ mod test {
last_checked: env.current_time(),
latest_version: "1.26.2".to_string(),
current_version: "1.27.0".to_string(),
+ current_release_channel: ReleaseChannel::Stable,
}
.serialize();
env.write_check_file(&file_content);
@@ -1162,6 +1434,7 @@ mod test {
last_checked: env.current_time(),
latest_version: "1.26.2".to_string(),
current_version: "1.25.0".to_string(),
+ current_release_channel: ReleaseChannel::Stable,
}
.serialize();
env.write_check_file(&file_content);
@@ -1231,11 +1504,43 @@ mod test {
);
assert_eq!(
get_latest_version_url(
- ReleaseChannel::Stable,
+ ReleaseChannel::Rc,
"x86_64-pc-windows-msvc",
UpgradeCheckKind::Lsp
),
- "https://dl.deno.land/release-latest.txt?lsp"
+ "https://dl.deno.land/release-rc.txt?lsp"
+ );
+ assert_eq!(
+ get_latest_version_url(
+ ReleaseChannel::Rc,
+ "aarch64-apple-darwin",
+ UpgradeCheckKind::Execution
+ ),
+ "https://dl.deno.land/release-rc.txt"
+ );
+ assert_eq!(
+ get_latest_version_url(
+ ReleaseChannel::Rc,
+ "aarch64-apple-darwin",
+ UpgradeCheckKind::Lsp
+ ),
+ "https://dl.deno.land/release-rc.txt?lsp"
+ );
+ assert_eq!(
+ get_latest_version_url(
+ ReleaseChannel::Rc,
+ "x86_64-pc-windows-msvc",
+ UpgradeCheckKind::Execution
+ ),
+ "https://dl.deno.land/release-rc.txt"
+ );
+ assert_eq!(
+ get_latest_version_url(
+ ReleaseChannel::Rc,
+ "x86_64-pc-windows-msvc",
+ UpgradeCheckKind::Lsp
+ ),
+ "https://dl.deno.land/release-rc.txt?lsp"
);
}
@@ -1243,24 +1548,46 @@ mod test {
fn test_normalize_version_server() {
// should strip v for stable
assert_eq!(
- normalize_version_from_server(ReleaseChannel::Stable, "v1.0.0"),
- "1.0.0"
+ normalize_version_from_server(ReleaseChannel::Stable, "v1.0.0").unwrap(),
+ AvailableVersion {
+ version_or_hash: "1.0.0".to_string(),
+ display: "1.0.0".to_string()
+ },
);
// should not replace v after start
assert_eq!(
normalize_version_from_server(
ReleaseChannel::Stable,
" v1.0.0-test-v\n\n "
- ),
- "1.0.0-test-v"
+ )
+ .unwrap(),
+ AvailableVersion {
+ version_or_hash: "1.0.0-test-v".to_string(),
+ display: "1.0.0-test-v".to_string()
+ }
);
// should not strip v for canary
assert_eq!(
normalize_version_from_server(
ReleaseChannel::Canary,
" v1452345asdf \n\n "
- ),
- "v1452345asdf"
+ )
+ .unwrap(),
+ AvailableVersion {
+ version_or_hash: "v1452345asdf".to_string(),
+ display: "v1452345asdf".to_string()
+ }
+ );
+ assert_eq!(
+ normalize_version_from_server(
+ ReleaseChannel::Rc,
+ "asdfq345wdfasdfasdf v1.46.0-rc.0\nasdfq345wdfasdfasdf v1.46.0-rc.1\n"
+ )
+ .unwrap(),
+ AvailableVersion {
+ version_or_hash: "asdfq345wdfasdfasdf".to_string(),
+ display: "v1.46.0-rc.1".to_string(),
+ },
);
}
@@ -1303,7 +1630,7 @@ mod test {
{
env.set_current_version("123");
env.set_latest_version("123");
- env.set_is_canary(true);
+ env.set_release_channel(ReleaseChannel::Canary);
let maybe_info = check_for_upgrades_for_lsp_with_provider(&env)
.await
.unwrap();
@@ -1323,5 +1650,30 @@ mod test {
})
);
}
+ // rc equal
+ {
+ env.set_release_channel(ReleaseChannel::Rc);
+ env.set_current_version("1.2.3-rc.0");
+ env.set_latest_version("1.2.3-rc.0");
+ let maybe_info = check_for_upgrades_for_lsp_with_provider(&env)
+ .await
+ .unwrap();
+ assert_eq!(maybe_info, None);
+ }
+ // canary different
+ {
+ env.set_latest_version("1.2.3-rc.0");
+ env.set_latest_version("1.2.3-rc.1");
+ let maybe_info = check_for_upgrades_for_lsp_with_provider(&env)
+ .await
+ .unwrap();
+ assert_eq!(
+ maybe_info,
+ Some(LspVersionUpgradeInfo {
+ latest_version: "1.2.3-rc.1".to_string(),
+ is_canary: true,
+ })
+ );
+ }
}
}