clone

one developer profile for links and private tcp services, reached peer to peer over iroh with no open ports

fix: show CLI compatibility as validation

Dami committed 8b2c091
commit
8b2c091ca03d59b6d6684e8bf0786b6d4fc46f9b
parent
94693f867da01b06306942884b656c86e83e0a3f
author
Dami <dami.etoile@gmail.com>
3 files changed, +50 -2
MCargo.lock1+
Mcrates/devsite-cli/Cargo.toml1+
Mcrates/devsite-cli/src/main.rs50+++++++++++++++++++++++++++++-
M Cargo.lock +1 -0
M crates/devsite-cli/Cargo.toml +1 -0
M crates/devsite-cli/src/main.rs +48 -2
@@ -10,6 +10,7 @@ use devsite_daemon::Daemon;
1010 use devsite_proto::{AccountId, ResourceId, SignedCapability};1111 use serde::{Deserialize, Serialize};1212 use std::collections::HashSet;13+use std::io::IsTerminal;1314 use std::net::{IpAddr, Ipv4Addr, SocketAddr};1415 use std::sync::Arc;1516 use std::time::{Duration, Instant};
@@ -501,6 +502,25 @@ fn enrolled_server(config: &DaemonConfig) -> Result<&str> {
501502 .context("this credential is not bound to a control-plane origin — log in again")502503 }503504 505+fn validate_cli_version(minimum: &str, current: &str) -> Result<()> {506+ let minimum = semver::Version::parse(minimum)507+ .context("server reported an invalid minimum CLI version")?;508+ let current =509+ semver::Version::parse(current).context("this CLI has invalid version metadata")?;510+ if current < minimum {511+ bail!("server requires CLI {minimum} or newer, but this is CLI {current}");512+ }513+ Ok(())514+}515+516+fn green_check() -> &'static str {517+ if std::io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none() {518+ "\x1b[32m✓\x1b[0m"519+ } else {520+ "✓"521+ }522+}523+504524 async fn login(paths: &Paths, server: &str, token: Option<String>, output: Output) -> Result<()> {505525 let ticket = match token {506526 Some(ticket) => ticket,
@@ -527,6 +547,8 @@ async fn login(paths: &Paths, server: &str, token: Option<String>, output: Outpu
527547 server_config.api_version528548 );529549 }550+ let cli_version = env!("CARGO_PKG_VERSION");551+ validate_cli_version(&server_config.minimum_cli_version, cli_version)?;530552 let pubkey: PubKeyResponse = bootstrap.get("/api/pubkey").await?;531553 let identity = paths.load_or_create_identity()?;532554 let endpoint_id = identity.public().to_string();
@@ -570,15 +592,19 @@ async fn login(paths: &Paths, server: &str, token: Option<String>, output: Outpu
570592 serde_json::json!({571593 "handle": handle,572594 "server": server.trim_end_matches('/'),595+ "cli_version": cli_version,573596 "minimum_cli_version": server_config.minimum_cli_version,597+ "cli_compatible": true,574598 "control_plane_key": control_plane_key,575599 }),576600 );577601 } else {578602 println!("signed in as {handle}");579603 println!(580- "server requires CLI {} or newer",581- server_config.minimum_cli_version604+ "{} CLI {} meets server requirement ({} or newer)",605+ green_check(),606+ cli_version,607+ server_config.minimum_cli_version,582608 );583609 println!("pinned control plane key {control_plane_key}");584610 }
@@ -1423,3 +1449,23 @@ async fn reload_config(paths: Paths, daemon: Arc<Daemon>, output: Output) -> Res
14231449 }14241450 }14251451 }1452+1453+#[cfg(test)]1454+mod tests {1455+ use super::validate_cli_version;1456+1457+ #[test]1458+ fn cli_version_validation_accepts_equal_or_newer_versions() {1459+ assert!(validate_cli_version("0.3.0", "0.3.0").is_ok());1460+ assert!(validate_cli_version("0.3.0", "0.4.0").is_ok());1461+ }1462+1463+ #[test]1464+ fn cli_version_validation_rejects_older_or_invalid_versions() {1465+ assert!(validate_cli_version("0.3.1", "0.3.0")1466+ .unwrap_err()1467+ .to_string()1468+ .contains("server requires CLI 0.3.1 or newer"));1469+ assert!(validate_cli_version("not-a-version", "0.3.0").is_err());1470+ }1471+}