diff --git a/codex-rs/tui/src/update_versions.rs b/codex-rs/tui/src/update_versions.rs index 29ff04369b..725a30b427 100644 --- a/codex-rs/tui/src/update_versions.rs +++ b/codex-rs/tui/src/update_versions.rs @@ -16,6 +16,32 @@ pub(crate) fn is_source_build_version(version: &str) -> bool { parse_version(version) == Some((0, 0, 0)) } +/// Whether an official stable TUI release is newer than the connected app server. +pub(crate) fn is_official_server_older(client: &str, server: &str) -> bool { + fn stable_version(version: &str) -> Option<(u64, u64, u64)> { + fn component(value: Option<&str>) -> Option { + let value = value?; + if value.is_empty() + || !value.bytes().all(|byte| byte.is_ascii_digit()) + || (value.len() > 1 && value.starts_with('0')) + { + return None; + } + value.parse().ok() + } + + let mut parts = version.split('.'); + let version = ( + component(parts.next())?, + component(parts.next())?, + component(parts.next())?, + ); + (parts.next().is_none() && version != (0, 0, 0)).then_some(version) + } + + matches!((stable_version(client), stable_version(server)), (Some(client), Some(server)) if client > server) +} + fn parse_version(v: &str) -> Option<(u64, u64, u64)> { let mut iter = v.trim().split('.'); let maj = iter.next()?.parse::().ok()?; @@ -67,4 +93,26 @@ mod tests { assert_eq!(parse_version(" 1.2.3 \n"), Some((1, 2, 3))); assert_eq!(is_newer(" 1.2.3 ", "1.2.2"), Some(true)); } + + #[test] + fn official_server_version_comparison() { + assert!(is_official_server_older("0.152.1", "0.152.0")); + assert!(is_official_server_older("0.153.0", "0.152.1")); + assert!(!is_official_server_older("0.152.0", "0.152.0")); + assert!(!is_official_server_older("0.152.0", "0.153.0")); + for version in [ + "0.0.0", + "0.0.0.0", + "0.153.0-alpha.1", + "unknown", + "0.153", + "0.153.0.1", + " 0.153.0", + "+0.153.0", + "0.0153.0", + ] { + assert!(!is_official_server_older(version, "0.152.0")); + assert!(!is_official_server_older("0.153.0", version)); + } + } }