Skip to content

Commit 170928d

Browse files
make sure beta versions are checked
1 parent 2761775 commit 170928d

File tree

4 files changed

+63
-59
lines changed

4 files changed

+63
-59
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rand = "0.9"
3636
reqwest = { version = "0.12", features = ["rustls-tls", "json"] }
3737
rustls = { version = "0.23", features = ["ring"] }
3838
serde = { version = "1", features = ["derive"] }
39+
semver = "1.0"
3940
serde_json = "1.0.145"
4041
serde_yml = "0.0.12"
4142
tokio = { version = "1.34.0", features = ["full"] }

lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ postcard.workspace = true
3030
quinn.workspace = true
3131
rand.workspace = true
3232
reqwest.workspace = true
33+
semver.workspace = true
3334
serde.workspace = true
3435
serde_json.workspace = true
3536
serde_yml.workspace = true

lib/src/update.rs

Lines changed: 60 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::process::Command;
44
use std::time::{SystemTime, UNIX_EPOCH};
55

66
use chrono::{DateTime, Utc};
7+
use semver::{BuildMetadata, Prerelease, Version};
78
use n0_error::{Result, StackResultExt, StdResultExt};
89
use serde::{Deserialize, Serialize};
910

@@ -13,6 +14,37 @@ const GITHUB_API_BASE: &str = "https://api.github.com";
1314
const REPO_OWNER: &str = "datum-cloud";
1415
const REPO_NAME: &str = "app";
1516

17+
/// Parse a tag or Cargo-style version into a [`Version`] for ordering.
18+
/// Accepts optional `v` prefix, optional minor/patch (default 0), and optional pre-release after `-`.
19+
/// Strips build metadata (`+...`). Invalid numeric core or invalid pre-release identifiers return `None`.
20+
fn parse_update_version(raw: &str) -> Option<Version> {
21+
let s = raw.trim().trim_start_matches('v');
22+
let s = s.split('+').next()?.trim();
23+
let (core, pre_str) = match s.split_once('-') {
24+
Some((c, p)) => (c.trim(), p.trim()),
25+
None => (s, ""),
26+
};
27+
if core.is_empty() {
28+
return None;
29+
}
30+
let nums: Vec<&str> = core.split('.').collect();
31+
let major = nums.first()?.parse::<u64>().ok()?;
32+
let minor = nums.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
33+
let patch = nums.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
34+
let pre = if pre_str.is_empty() {
35+
Prerelease::EMPTY
36+
} else {
37+
Prerelease::new(pre_str).ok()?
38+
};
39+
Some(Version {
40+
major,
41+
minor,
42+
patch,
43+
pre,
44+
build: BuildMetadata::EMPTY,
45+
})
46+
}
47+
1648
/// Which GitHub release stream to follow for automatic updates.
1749
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1850
#[serde(rename_all = "lowercase")]
@@ -121,12 +153,6 @@ pub struct UpdateInfo {
121153
pub download_size: u64,
122154
}
123155

124-
struct VersionParts {
125-
major: u32,
126-
minor: u32,
127-
patch: u32,
128-
}
129-
130156
fn release_eligible_for_channel(release: &GitHubRelease, channel: UpdateChannel) -> bool {
131157
if release.draft || release.tag_name == "rolling" {
132158
return false;
@@ -306,60 +332,15 @@ impl UpdateChecker {
306332
tag.trim_start_matches('v').to_string()
307333
}
308334

309-
/// Compare semantic version strings - returns true if version1 > version2
310-
/// Handles semantic versions like "0.0.3", "0.1.0", "1.0.0", etc.
335+
/// Compare semantic version strings true if `version1` is strictly newer than `version2`.
336+
/// Uses full SemVer 2.0 ordering (including pre-release); build metadata is ignored.
311337
pub(crate) fn is_newer_version(version1: &str, version2: &str) -> bool {
312-
let v1_parts = Self::parse_semantic_version(version1);
313-
let v2_parts = Self::parse_semantic_version(version2);
314-
315-
// Compare major, minor, patch versions
316-
match v1_parts.major.cmp(&v2_parts.major) {
317-
std::cmp::Ordering::Greater => return true,
318-
std::cmp::Ordering::Less => return false,
319-
std::cmp::Ordering::Equal => {}
320-
}
321-
322-
match v1_parts.minor.cmp(&v2_parts.minor) {
323-
std::cmp::Ordering::Greater => return true,
324-
std::cmp::Ordering::Less => return false,
325-
std::cmp::Ordering::Equal => {}
326-
}
327-
328-
match v1_parts.patch.cmp(&v2_parts.patch) {
329-
std::cmp::Ordering::Greater => true,
330-
std::cmp::Ordering::Less => false,
331-
std::cmp::Ordering::Equal => {
332-
// If versions are equal, check for pre-release/build metadata
333-
// For now, if versions are equal, consider it not newer
334-
false
335-
}
336-
}
337-
}
338-
339-
/// Parse semantic version string (e.g., "0.0.3" or "0.0.3-beta")
340-
fn parse_semantic_version(version: &str) -> VersionParts {
341-
// Remove any pre-release or build metadata (everything after '-')
342-
let version = version.split('-').next().unwrap_or(version);
343-
344-
let parts: Vec<&str> = version.split('.').collect();
345-
346-
let major = parts
347-
.get(0)
348-
.and_then(|s| s.parse::<u32>().ok())
349-
.unwrap_or(0);
350-
let minor = parts
351-
.get(1)
352-
.and_then(|s| s.parse::<u32>().ok())
353-
.unwrap_or(0);
354-
let patch = parts
355-
.get(2)
356-
.and_then(|s| s.parse::<u32>().ok())
357-
.unwrap_or(0);
358-
359-
VersionParts {
360-
major,
361-
minor,
362-
patch,
338+
match (
339+
parse_update_version(version1),
340+
parse_update_version(version2),
341+
) {
342+
(Some(v1), Some(v2)) => v1 > v2,
343+
_ => false,
363344
}
364345
}
365346

@@ -874,4 +855,24 @@ mod tests {
874855
let picked = select_release_for_channel(releases, UpdateChannel::Stable, "2.0.0", |_| true);
875856
assert!(picked.is_none());
876857
}
858+
859+
#[test]
860+
fn newer_orders_prerelease_suffixes() {
861+
assert!(UpdateChecker::is_newer_version("1.0.0-beta.2", "1.0.0-beta.1"));
862+
assert!(!UpdateChecker::is_newer_version("1.0.0-beta.1", "1.0.0-beta.2"));
863+
assert!(UpdateChecker::is_newer_version("1.0.0-rc.1", "1.0.0-beta.99"));
864+
}
865+
866+
#[test]
867+
fn release_newer_than_prerelease_same_core() {
868+
assert!(UpdateChecker::is_newer_version("1.0.0", "1.0.0-beta.99"));
869+
assert!(!UpdateChecker::is_newer_version("1.0.0-beta.99", "1.0.0"));
870+
}
871+
872+
#[test]
873+
fn newer_accepts_v_prefix_build_metadata_ignored() {
874+
assert!(UpdateChecker::is_newer_version("v1.2.4", "1.2.3"));
875+
// SemVer: build metadata does not affect precedence
876+
assert!(!UpdateChecker::is_newer_version("1.2.3+build2", "1.2.3+build1"));
877+
}
877878
}

0 commit comments

Comments
 (0)