From dae4d3a450575b6fff491acaec445d9b86c618ed Mon Sep 17 00:00:00 2001 From: Vitas Civilis Date: Wed, 21 Oct 2026 16:28:02 +0300 Subject: [PATCH] fix(cli): only compare release numbers in the minimum version check A binary labelled from git describe picks up the nearest tag. With the skills-v1.0.0 tag on main that label is "skills", which parsed as 0.0.0 and was refused as older than the minimum required version, so a build from main could not start the interactive shell. Only a plain X.Y.Z version is compared now; "dev" and any other label skip the check, as "dev" already did. Release builds are unaffected. Signed-off-by: Vitas Civilis --- CHANGELOG.md | 1 + internal/cli/versioncheck.go | 25 ++++++++++++++++++++++++- internal/cli/versioncheck_test.go | 21 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6e336..5bdcdd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bring your own proxy. Route a command or the interactive shell through a proxy y - A wrong password or three consecutive connection failures mark an own route dead and, in `run`, kill the command, as with a hosted route - Own routes work with no login and with the Shellroute API unreachable; the version check is skipped for them - Hosted routes are unchanged +- A build whose version is not a release number (a developer build) is no longer blocked by the minimum version check ## v0.1.0 diff --git a/internal/cli/versioncheck.go b/internal/cli/versioncheck.go index 12d4a25..d00e848 100644 --- a/internal/cli/versioncheck.go +++ b/internal/cli/versioncheck.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" "github.com/shellroute/shellroute-cli/internal/api" @@ -23,9 +24,12 @@ type versionCache struct { // checkVersion checks if the CLI version is up to date. // Returns true if the CLI should proceed, false if blocked (version too old). +// Only a release number (X.Y.Z) is compared; "dev" and any other build label +// (a git describe of a non-release tag, "(devel)") cannot be older than a +// release and skip the check. func checkVersion(cfg *config.Config) bool { v := Version() - if flagSkipVersionCheck || v == "dev" { + if flagSkipVersionCheck || !isReleaseVersion(v) { return true } @@ -50,6 +54,25 @@ func checkVersion(cfg *config.Config) bool { return true } +// isReleaseVersion reports whether v is a plain release number such as "0.2.0". +func isReleaseVersion(v string) bool { + parts := strings.Split(v, ".") + if len(parts) != 3 { + return false + } + for _, p := range parts { + if p == "" { + return false + } + for _, r := range p { + if r < '0' || r > '9' { + return false + } + } + } + return true +} + func getCachedOrFetchVersion(cfg *config.Config) (*versionCache, error) { cacheFile := versionCacheFile() diff --git a/internal/cli/versioncheck_test.go b/internal/cli/versioncheck_test.go index bdbf309..6581d70 100644 --- a/internal/cli/versioncheck_test.go +++ b/internal/cli/versioncheck_test.go @@ -87,6 +87,27 @@ func TestVersionFunctionReturnsClean(t *testing.T) { } } +func TestIsReleaseVersion(t *testing.T) { + for v, want := range map[string]bool{"0.2.0": true, "10.0.3": true, "dev": false, "skills": false, "(devel)": false, "0.2": false, "0.2.0-rc1": false, "": false} { + if got := isReleaseVersion(v); got != want { + t.Errorf("isReleaseVersion(%q) = %v, want %v", v, got, want) + } + } +} + +// A build labelled from a non-release tag ("skills-v1.0.0-1-gabcdef" cleans to +// "skills") used to compare as 0.0.0 and be blocked by the minimum version. +func TestCheckVersionNonReleaseLabelSkips(t *testing.T) { + old := rawVersion + defer func() { rawVersion = old }() + for _, label := range []string{"skills-v1.0.0-1-gabcdef", "(devel)", "snapshot"} { + rawVersion = label + if !checkVersion(nil) { + t.Errorf("version label %q must not be blocked", label) + } + } +} + func TestCheckVersionDevSkips(t *testing.T) { old := rawVersion rawVersion = "dev"