From da681fef6ba365432b4fd78d6145f09819f40a54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:43:12 +0000 Subject: [PATCH 01/10] Initial plan From 291bf9b79ed9f9897dfbb9f8f4c2b3e4a7322fa6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:56:20 +0000 Subject: [PATCH 02/10] Fix verbose extension error output and add update check for failed extensions Co-authored-by: hemarina <104857065+hemarina@users.noreply.github.com> --- cli/azd/cmd/middleware/extensions.go | 63 ++++++++++++++++++- cli/azd/extensions/azure.ai.agents/go.mod | 2 +- .../extensions/microsoft.azd.concurx/go.mod | 2 +- .../extensions/microsoft.azd.concurx/go.sum | 33 +++++++++- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index ef36a6eff0b..be910ce8972 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -20,6 +20,7 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/ioc" "github.com/azure/azure-dev/cli/azd/pkg/output" + "github.com/azure/azure-dev/cli/azd/pkg/output/ux" "github.com/fatih/color" ) @@ -152,7 +153,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } if _, err := m.extensionRunner.Invoke(ctx, ext, options); err != nil { - m.console.Message(ctx, err.Error()) + log.Printf("extension '%s' invocation failed: %v", ext.Id, err) ext.Fail(err) } }() @@ -185,6 +186,12 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A // Check for failed extensions and display warnings if len(failedExtensions) > 0 { + // Check for available updates for failed extensions (best-effort, non-blocking) + updateWarnings := m.checkUpdatesForExtensions(ctx, failedExtensions) + for _, w := range updateWarnings { + m.console.MessageUxItem(ctx, w) + } + m.console.Message(ctx, output.WithWarningFormat("WARNING: Extension startup failures detected")) m.console.Message(ctx, "The following extensions failed to initialize within the timeout period:") for _, ext := range failedExtensions { @@ -205,6 +212,60 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A return next(ctx) } +// checkUpdatesForExtensions checks if newer versions are available for the given extensions. +// It refreshes the registry cache if expired, then returns update warnings for any extensions +// that have a newer version available. +func (m *ExtensionsMiddleware) checkUpdatesForExtensions( + ctx context.Context, + failedExts []*extensions.Extension, +) []*ux.WarningMessage { + cacheManager, err := extensions.NewRegistryCacheManager() + if err != nil { + log.Printf("failed to create cache manager for update check: %v", err) + return nil + } + + // Refresh the cache for each unique source (sequentially to avoid concurrent file writes) + seenSources := map[string]struct{}{} + for _, ext := range failedExts { + if ext.Source == "" { + continue + } + if _, seen := seenSources[ext.Source]; seen { + continue + } + seenSources[ext.Source] = struct{}{} + if cacheManager.IsExpiredOrMissing(ctx, ext.Source) { + sourceExts, err := m.extensionManager.FindExtensions(ctx, &extensions.FilterOptions{ + Source: ext.Source, + }) + if err != nil { + log.Printf("failed to fetch extensions for source %s: %v", ext.Source, err) + continue + } + if err := cacheManager.Set(ctx, ext.Source, sourceExts); err != nil { + log.Printf("failed to cache extensions for source %s: %v", ext.Source, err) + } + } + } + + checker := extensions.NewUpdateChecker(cacheManager) + + var warnings []*ux.WarningMessage + for _, ext := range failedExts { + result, err := checker.CheckForUpdate(ctx, ext) + if err != nil { + log.Printf("failed to check for update for %s: %v", ext.Id, err) + continue + } + if result.HasUpdate { + warnings = append(warnings, extensions.FormatUpdateWarning(result)) + } + } + + return warnings +} + // isDebug checks if AZD_EXT_DEBUG environment variable is set to a truthy value func isDebug() bool { debugValue := os.Getenv("AZD_EXT_DEBUG") diff --git a/cli/azd/extensions/azure.ai.agents/go.mod b/cli/azd/extensions/azure.ai.agents/go.mod index a1c79f24d61..9cc76d6f3d8 100644 --- a/cli/azd/extensions/azure.ai.agents/go.mod +++ b/cli/azd/extensions/azure.ai.agents/go.mod @@ -18,6 +18,7 @@ require ( github.com/mark3labs/mcp-go v0.41.1 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v3 v3.0.4 google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff google.golang.org/grpc v1.76.0 @@ -79,7 +80,6 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/theckman/yacspin v0.13.12 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect diff --git a/cli/azd/extensions/microsoft.azd.concurx/go.mod b/cli/azd/extensions/microsoft.azd.concurx/go.mod index 137c3e31f72..47e7842562d 100644 --- a/cli/azd/extensions/microsoft.azd.concurx/go.mod +++ b/cli/azd/extensions/microsoft.azd.concurx/go.mod @@ -15,6 +15,7 @@ require ( require ( dario.cat/mergo v1.0.2 // indirect + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect @@ -36,7 +37,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/drone/envsubst v1.0.3 // indirect - github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/cli/azd/extensions/microsoft.azd.concurx/go.sum b/cli/azd/extensions/microsoft.azd.concurx/go.sum index c93961191fc..e9b7f3a7f94 100644 --- a/cli/azd/extensions/microsoft.azd.concurx/go.sum +++ b/cli/azd/extensions/microsoft.azd.concurx/go.sum @@ -9,6 +9,7 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDo github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b h1:g9SuFmxM/WucQFKTMSP+irxyf5m0RiUJreBDhGI6jSA= github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b/go.mod h1:XjvqMUpGd3Xn9Jtzk/4GEBCSoBX0eB2RyriXgne0IdM= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= @@ -62,6 +63,7 @@ github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNE github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -70,8 +72,6 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= -github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -99,6 +99,7 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -121,8 +122,10 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -130,6 +133,7 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -175,6 +179,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -188,6 +193,7 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= @@ -206,23 +212,46 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff h1:A90eA31Wq6HOMIQlLfzFwzqGKBTuaVztYu/g8sn+8Zc= From 19b8566c70c401820e034f15b23ac69b54ad54f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:23:27 +0000 Subject: [PATCH 03/10] Address review: differentiate timeout vs startup errors; remove network I/O from failure path Co-authored-by: spboyer <7681382+spboyer@users.noreply.github.com> --- cli/azd/cmd/middleware/extensions.go | 82 +++++++++++++++------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index be910ce8972..8b98384b7b2 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -5,6 +5,7 @@ package middleware import ( "context" + "errors" "fmt" "log" "os" @@ -32,6 +33,13 @@ var ( } ) +// extensionStartFailure captures a failed extension along with the nature of the failure. +type extensionStartFailure struct { + extension *extensions.Extension + // timedOut is true when the failure was caused by the ready-wait context deadline being exceeded. + timedOut bool +} + type ExtensionsMiddleware struct { extensionManager *extensions.Manager extensionRunner *extensions.Runner @@ -104,7 +112,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A forceColor := !color.NoColor var wg sync.WaitGroup var mu sync.Mutex - var failedExtensions []*extensions.Extension + var failures []extensionStartFailure // Track total time for all extensions to become ready allExtensionsStartTime := time.Now() @@ -171,7 +179,10 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A // Track failed extensions for warning display mu.Lock() - failedExtensions = append(failedExtensions, ext) + failures = append(failures, extensionStartFailure{ + extension: ext, + timedOut: errors.Is(err, context.DeadlineExceeded), + }) mu.Unlock() } else { elapsed := time.Since(startTime) @@ -185,23 +196,35 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A // Check for failed extensions and display warnings - if len(failedExtensions) > 0 { - // Check for available updates for failed extensions (best-effort, non-blocking) - updateWarnings := m.checkUpdatesForExtensions(ctx, failedExtensions) + if len(failures) > 0 { + // Collect just the Extension pointers for the update check. + failedExts := make([]*extensions.Extension, len(failures)) + for i, f := range failures { + failedExts[i] = f.extension + } + + // Check for available updates using only cached data (no network requests in the failure path). + updateWarnings := m.checkUpdatesForExtensions(ctx, failedExts) for _, w := range updateWarnings { m.console.MessageUxItem(ctx, w) } m.console.Message(ctx, output.WithWarningFormat("WARNING: Extension startup failures detected")) - m.console.Message(ctx, "The following extensions failed to initialize within the timeout period:") - for _, ext := range failedExtensions { - m.console.Message(ctx, fmt.Sprintf(" - %s (%s)", ext.DisplayName, ext.Id)) + m.console.Message(ctx, "The following extensions failed to initialize:") + for _, f := range failures { + m.console.Message(ctx, fmt.Sprintf(" - %s (%s)", f.extension.DisplayName, f.extension.Id)) } m.console.Message(ctx, "") - m.console.Message( - ctx, - "Some features may be unavailable. Increase timeout with AZD_EXT_TIMEOUT= if needed.", - ) + + hasTimeouts := slices.ContainsFunc(failures, func(f extensionStartFailure) bool { return f.timedOut }) + if hasTimeouts { + m.console.Message( + ctx, + "Some features may be unavailable. Increase timeout with AZD_EXT_TIMEOUT= if needed.", + ) + } else { + m.console.Message(ctx, "Some features may be unavailable.") + } m.console.Message(ctx, "") } @@ -212,9 +235,9 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A return next(ctx) } -// checkUpdatesForExtensions checks if newer versions are available for the given extensions. -// It refreshes the registry cache if expired, then returns update warnings for any extensions -// that have a newer version available. +// checkUpdatesForExtensions checks if newer versions are available for the given extensions +// using only already-cached registry data. No network requests are made; if the cache for a +// source is expired or missing the extension is simply skipped. func (m *ExtensionsMiddleware) checkUpdatesForExtensions( ctx context.Context, failedExts []*extensions.Extension, @@ -225,34 +248,15 @@ func (m *ExtensionsMiddleware) checkUpdatesForExtensions( return nil } - // Refresh the cache for each unique source (sequentially to avoid concurrent file writes) - seenSources := map[string]struct{}{} - for _, ext := range failedExts { - if ext.Source == "" { - continue - } - if _, seen := seenSources[ext.Source]; seen { - continue - } - seenSources[ext.Source] = struct{}{} - if cacheManager.IsExpiredOrMissing(ctx, ext.Source) { - sourceExts, err := m.extensionManager.FindExtensions(ctx, &extensions.FilterOptions{ - Source: ext.Source, - }) - if err != nil { - log.Printf("failed to fetch extensions for source %s: %v", ext.Source, err) - continue - } - if err := cacheManager.Set(ctx, ext.Source, sourceExts); err != nil { - log.Printf("failed to cache extensions for source %s: %v", ext.Source, err) - } - } - } - checker := extensions.NewUpdateChecker(cacheManager) var warnings []*ux.WarningMessage for _, ext := range failedExts { + // Only consult already-cached data to avoid blocking network I/O in the failure path. + if ext.Source == "" || cacheManager.IsExpiredOrMissing(ctx, ext.Source) { + continue + } + result, err := checker.CheckForUpdate(ctx, ext) if err != nil { log.Printf("failed to check for update for %s: %v", ext.Id, err) From 7e6127d2c106ad0b40f8f63729807b9537fb5638 Mon Sep 17 00:00:00 2001 From: hemarina Date: Tue, 24 Feb 2026 23:16:37 -0800 Subject: [PATCH 04/10] consider pack raw error, need discussion --- cli/azd/cmd/middleware/extensions.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index 8b98384b7b2..355e23a9b3f 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -17,6 +17,7 @@ import ( "github.com/azure/azure-dev/cli/azd/cmd/actions" "github.com/azure/azure-dev/cli/azd/internal/grpcserver" "github.com/azure/azure-dev/cli/azd/internal/tracing" + "github.com/azure/azure-dev/cli/azd/pkg/exec" "github.com/azure/azure-dev/cli/azd/pkg/extensions" "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/ioc" @@ -161,7 +162,20 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } if _, err := m.extensionRunner.Invoke(ctx, ext, options); err != nil { + // Log full error (including stdout/stderr) for --debug. log.Printf("extension '%s' invocation failed: %v", ext.Id, err) + + // Show a concise reason to the user (exit code without raw stdout/stderr). + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + m.console.Message(ctx, fmt.Sprintf( + "Extension '%s' failed to start (exit code: %d). Run with --debug for details.", + ext.Id, exitErr.ExitCode)) + } else { + m.console.Message(ctx, fmt.Sprintf( + "Extension '%s' failed to start: %v", + ext.Id, err)) + } ext.Fail(err) } }() From 37ebcb2bffd579ab8805010dcf80cf9d15743b75 Mon Sep 17 00:00:00 2001 From: hemarina Date: Tue, 24 Feb 2026 23:16:57 -0800 Subject: [PATCH 05/10] add case when first element is latest version --- cli/azd/pkg/extensions/registry_cache.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/cli/azd/pkg/extensions/registry_cache.go b/cli/azd/pkg/extensions/registry_cache.go index 87b0c70342a..d3e54a64496 100644 --- a/cli/azd/pkg/extensions/registry_cache.go +++ b/cli/azd/pkg/extensions/registry_cache.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/Masterminds/semver/v3" "github.com/azure/azure-dev/cli/azd/pkg/config" "github.com/azure/azure-dev/cli/azd/pkg/osutil" ) @@ -181,8 +182,21 @@ func (m *RegistryCacheManager) GetExtensionLatestVersion( if len(ext.Versions) == 0 { return "", fmt.Errorf("extension %s has no versions", extensionId) } - // Latest version is the last element in the Versions slice - return ext.Versions[len(ext.Versions)-1].Version, nil + // The registry may return versions in ascending or descending order. + // Compare the first and last elements to pick the highest version. + first := ext.Versions[0].Version + last := ext.Versions[len(ext.Versions)-1].Version + + firstSv, errFirst := semver.NewVersion(first) + lastSv, errLast := semver.NewVersion(last) + if errFirst != nil || errLast != nil { + return "", fmt.Errorf("extension %s has no valid semver versions", extensionId) + } + + if firstSv.GreaterThan(lastSv) { + return first, nil + } + return last, nil } } From 147bcedde994dbb8529c5210c660a808b6fdc08a Mon Sep 17 00:00:00 2001 From: hemarina Date: Wed, 25 Feb 2026 15:13:05 -0800 Subject: [PATCH 06/10] update version check UI --- cli/azd/pkg/extensions/update_checker.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cli/azd/pkg/extensions/update_checker.go b/cli/azd/pkg/extensions/update_checker.go index a1da3316c68..8c4ceab2c3b 100644 --- a/cli/azd/pkg/extensions/update_checker.go +++ b/cli/azd/pkg/extensions/update_checker.go @@ -119,17 +119,16 @@ func FormatUpdateWarning(result *UpdateCheckResult) *ux.WarningMessage { return &ux.WarningMessage{ Description: fmt.Sprintf( - "A new version of extension '%s' is available: %s -> %s", - name, - result.InstalledVersion, - result.LatestVersion, + "The following extensions are outdated:\n - %s (installed: %s, latest: %s)", + name, result.InstalledVersion, result.LatestVersion, ), HidePrefix: false, Hints: []string{ - fmt.Sprintf("To upgrade: %s", - output.WithHighLightFormat("azd extension upgrade %s", result.ExtensionId)), - fmt.Sprintf("To upgrade all: %s", - output.WithHighLightFormat("azd extension upgrade --all")), + fmt.Sprintf("Fix by running:\n\t%s\n\t%s", + output.WithHighLightFormat("azd extension update --all"), + output.WithHighLightFormat("azd extension update %s", result.ExtensionId)), + fmt.Sprintf("If you don't use these extensions, you can uninstall them:\n\t%s", + output.WithHighLightFormat("azd extension uninstall %s", result.ExtensionId)), }, } } From 3ddca451c87326eddad6aacef7b74b11e857cbe3 Mon Sep 17 00:00:00 2001 From: hemarina Date: Wed, 25 Feb 2026 16:36:24 -0800 Subject: [PATCH 07/10] fix bug for preview version --- cli/azd/pkg/extensions/registry_cache.go | 36 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/cli/azd/pkg/extensions/registry_cache.go b/cli/azd/pkg/extensions/registry_cache.go index d3e54a64496..3913751ee95 100644 --- a/cli/azd/pkg/extensions/registry_cache.go +++ b/cli/azd/pkg/extensions/registry_cache.go @@ -182,8 +182,9 @@ func (m *RegistryCacheManager) GetExtensionLatestVersion( if len(ext.Versions) == 0 { return "", fmt.Errorf("extension %s has no versions", extensionId) } - // The registry may return versions in ascending or descending order. - // Compare the first and last elements to pick the highest version. + + // Compare first and last elements to find the highest version. + // If the highest is a prerelease (e.g. "preview"), fall back to the second version. first := ext.Versions[0].Version last := ext.Versions[len(ext.Versions)-1].Version @@ -193,10 +194,35 @@ func (m *RegistryCacheManager) GetExtensionLatestVersion( return "", fmt.Errorf("extension %s has no valid semver versions", extensionId) } - if firstSv.GreaterThan(lastSv) { - return first, nil + latest := first + latestSv := firstSv + if lastSv.GreaterThan(firstSv) { + latest = last + latestSv = lastSv + } + + // If the highest version is a prerelease, walk through subsequent versions + // until we find a stable (non-prerelease) version. + // If all versions are prereleases, return the highest prerelease. + if latestSv.Prerelease() != "" && len(ext.Versions) > 1 { + start := 1 + end := len(ext.Versions) + step := 1 + if lastSv.GreaterThan(firstSv) { + // ascending order: walk backwards from second-to-last + start = len(ext.Versions) - 2 + end = -1 + step = -1 + } + for i := start; i != end; i += step { + sv, err := semver.NewVersion(ext.Versions[i].Version) + if err == nil && sv.Prerelease() == "" { + return ext.Versions[i].Version, nil + } + } } - return last, nil + + return latest, nil } } From 9b88ddbe18726d565313ce2a5531654926e849f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:40:00 +0000 Subject: [PATCH 08/10] go mod tidy: revert unrelated extension module changes; fix upgrade command and prerelease version detection Co-authored-by: hemarina <104857065+hemarina@users.noreply.github.com> --- .../extensions/microsoft.azd.concurx/go.mod | 2 +- .../extensions/microsoft.azd.concurx/go.sum | 33 +----------- cli/azd/pkg/extensions/registry_cache.go | 51 +++++-------------- cli/azd/pkg/extensions/update_checker.go | 4 +- cli/azd/pkg/extensions/update_checker_test.go | 3 +- .../pkg/extensions/update_integration_test.go | 3 +- 6 files changed, 23 insertions(+), 73 deletions(-) diff --git a/cli/azd/extensions/microsoft.azd.concurx/go.mod b/cli/azd/extensions/microsoft.azd.concurx/go.mod index 47e7842562d..137c3e31f72 100644 --- a/cli/azd/extensions/microsoft.azd.concurx/go.mod +++ b/cli/azd/extensions/microsoft.azd.concurx/go.mod @@ -15,7 +15,6 @@ require ( require ( dario.cat/mergo v1.0.2 // indirect - github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect @@ -37,6 +36,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/drone/envsubst v1.0.3 // indirect + github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/cli/azd/extensions/microsoft.azd.concurx/go.sum b/cli/azd/extensions/microsoft.azd.concurx/go.sum index e9b7f3a7f94..c93961191fc 100644 --- a/cli/azd/extensions/microsoft.azd.concurx/go.sum +++ b/cli/azd/extensions/microsoft.azd.concurx/go.sum @@ -9,7 +9,6 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDo github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b h1:g9SuFmxM/WucQFKTMSP+irxyf5m0RiUJreBDhGI6jSA= github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b/go.mod h1:XjvqMUpGd3Xn9Jtzk/4GEBCSoBX0eB2RyriXgne0IdM= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= @@ -63,7 +62,6 @@ github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNE github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -72,6 +70,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -99,7 +99,6 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -122,10 +121,8 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -133,7 +130,6 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -179,7 +175,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -193,7 +188,6 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= @@ -212,46 +206,23 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff h1:A90eA31Wq6HOMIQlLfzFwzqGKBTuaVztYu/g8sn+8Zc= diff --git a/cli/azd/pkg/extensions/registry_cache.go b/cli/azd/pkg/extensions/registry_cache.go index 3913751ee95..638561e5640 100644 --- a/cli/azd/pkg/extensions/registry_cache.go +++ b/cli/azd/pkg/extensions/registry_cache.go @@ -183,46 +183,23 @@ func (m *RegistryCacheManager) GetExtensionLatestVersion( return "", fmt.Errorf("extension %s has no versions", extensionId) } - // Compare first and last elements to find the highest version. - // If the highest is a prerelease (e.g. "preview"), fall back to the second version. - first := ext.Versions[0].Version - last := ext.Versions[len(ext.Versions)-1].Version - - firstSv, errFirst := semver.NewVersion(first) - lastSv, errLast := semver.NewVersion(last) - if errFirst != nil || errLast != nil { - return "", fmt.Errorf("extension %s has no valid semver versions", extensionId) - } - - latest := first - latestSv := firstSv - if lastSv.GreaterThan(firstSv) { - latest = last - latestSv = lastSv - } - - // If the highest version is a prerelease, walk through subsequent versions - // until we find a stable (non-prerelease) version. - // If all versions are prereleases, return the highest prerelease. - if latestSv.Prerelease() != "" && len(ext.Versions) > 1 { - start := 1 - end := len(ext.Versions) - step := 1 - if lastSv.GreaterThan(firstSv) { - // ascending order: walk backwards from second-to-last - start = len(ext.Versions) - 2 - end = -1 - step = -1 + // Find the highest version using semver comparison, regardless of list order. + var latestSv *semver.Version + var latestVersion string + for _, v := range ext.Versions { + sv, err := semver.NewVersion(v.Version) + if err != nil { + continue } - for i := start; i != end; i += step { - sv, err := semver.NewVersion(ext.Versions[i].Version) - if err == nil && sv.Prerelease() == "" { - return ext.Versions[i].Version, nil - } + if latestSv == nil || sv.GreaterThan(latestSv) { + latestSv = sv + latestVersion = v.Version } } - - return latest, nil + if latestVersion == "" { + return "", fmt.Errorf("extension %s has no valid semver versions", extensionId) + } + return latestVersion, nil } } diff --git a/cli/azd/pkg/extensions/update_checker.go b/cli/azd/pkg/extensions/update_checker.go index 8c4ceab2c3b..56435af18d8 100644 --- a/cli/azd/pkg/extensions/update_checker.go +++ b/cli/azd/pkg/extensions/update_checker.go @@ -125,8 +125,8 @@ func FormatUpdateWarning(result *UpdateCheckResult) *ux.WarningMessage { HidePrefix: false, Hints: []string{ fmt.Sprintf("Fix by running:\n\t%s\n\t%s", - output.WithHighLightFormat("azd extension update --all"), - output.WithHighLightFormat("azd extension update %s", result.ExtensionId)), + output.WithHighLightFormat("azd extension upgrade --all"), + output.WithHighLightFormat("azd extension upgrade %s", result.ExtensionId)), fmt.Sprintf("If you don't use these extensions, you can uninstall them:\n\t%s", output.WithHighLightFormat("azd extension uninstall %s", result.ExtensionId)), }, diff --git a/cli/azd/pkg/extensions/update_checker_test.go b/cli/azd/pkg/extensions/update_checker_test.go index f8335feda8b..0a347622dc8 100644 --- a/cli/azd/pkg/extensions/update_checker_test.go +++ b/cli/azd/pkg/extensions/update_checker_test.go @@ -154,8 +154,9 @@ func Test_FormatUpdateWarning(t *testing.T) { require.Contains(t, warning.Description, "2.0.0") require.False(t, warning.HidePrefix) require.Len(t, warning.Hints, 2) + require.Contains(t, warning.Hints[0], "azd extension upgrade --all") require.Contains(t, warning.Hints[0], "azd extension upgrade test.extension") - require.Contains(t, warning.Hints[1], "azd extension upgrade --all") + require.Contains(t, warning.Hints[1], "azd extension uninstall test.extension") } func Test_FormatUpdateWarning_NoDisplayName(t *testing.T) { diff --git a/cli/azd/pkg/extensions/update_integration_test.go b/cli/azd/pkg/extensions/update_integration_test.go index 29824f37721..ac578fb2540 100644 --- a/cli/azd/pkg/extensions/update_integration_test.go +++ b/cli/azd/pkg/extensions/update_integration_test.go @@ -167,8 +167,9 @@ func Test_Integration_UpdateCheck_FullFlow(t *testing.T) { require.Contains(t, warning.Description, "2.0.0") require.False(t, warning.HidePrefix) require.Len(t, warning.Hints, 2) + require.Contains(t, warning.Hints[0], "azd extension upgrade --all") require.Contains(t, warning.Hints[0], "azd extension upgrade test.extension") - require.Contains(t, warning.Hints[1], "azd extension upgrade --all") + require.Contains(t, warning.Hints[1], "azd extension uninstall test.extension") }) } From 841970682689e0af3b542c6cb15ebc6a46d3ae28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 02:04:01 +0000 Subject: [PATCH 09/10] Swap upgrade hint order: specific extension upgrade first, then --all Co-authored-by: hemarina <104857065+hemarina@users.noreply.github.com> --- cli/azd/pkg/extensions/update_checker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azd/pkg/extensions/update_checker.go b/cli/azd/pkg/extensions/update_checker.go index 56435af18d8..a10b23052b3 100644 --- a/cli/azd/pkg/extensions/update_checker.go +++ b/cli/azd/pkg/extensions/update_checker.go @@ -125,8 +125,8 @@ func FormatUpdateWarning(result *UpdateCheckResult) *ux.WarningMessage { HidePrefix: false, Hints: []string{ fmt.Sprintf("Fix by running:\n\t%s\n\t%s", - output.WithHighLightFormat("azd extension upgrade --all"), - output.WithHighLightFormat("azd extension upgrade %s", result.ExtensionId)), + output.WithHighLightFormat("azd extension upgrade %s", result.ExtensionId), + output.WithHighLightFormat("azd extension upgrade --all")), fmt.Sprintf("If you don't use these extensions, you can uninstall them:\n\t%s", output.WithHighLightFormat("azd extension uninstall %s", result.ExtensionId)), }, From 98eb1e8728dd33730df64932e440c942d1ef013e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:59:52 +0000 Subject: [PATCH 10/10] Revert GetExtensionLatestVersion to original; update test to match original prerelease behavior Co-authored-by: hemarina <104857065+hemarina@users.noreply.github.com> --- cli/azd/pkg/extensions/registry_cache.go | 51 ++++++++++++++----- cli/azd/pkg/extensions/update_checker_test.go | 6 +-- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/cli/azd/pkg/extensions/registry_cache.go b/cli/azd/pkg/extensions/registry_cache.go index 638561e5640..3913751ee95 100644 --- a/cli/azd/pkg/extensions/registry_cache.go +++ b/cli/azd/pkg/extensions/registry_cache.go @@ -183,23 +183,46 @@ func (m *RegistryCacheManager) GetExtensionLatestVersion( return "", fmt.Errorf("extension %s has no versions", extensionId) } - // Find the highest version using semver comparison, regardless of list order. - var latestSv *semver.Version - var latestVersion string - for _, v := range ext.Versions { - sv, err := semver.NewVersion(v.Version) - if err != nil { - continue + // Compare first and last elements to find the highest version. + // If the highest is a prerelease (e.g. "preview"), fall back to the second version. + first := ext.Versions[0].Version + last := ext.Versions[len(ext.Versions)-1].Version + + firstSv, errFirst := semver.NewVersion(first) + lastSv, errLast := semver.NewVersion(last) + if errFirst != nil || errLast != nil { + return "", fmt.Errorf("extension %s has no valid semver versions", extensionId) + } + + latest := first + latestSv := firstSv + if lastSv.GreaterThan(firstSv) { + latest = last + latestSv = lastSv + } + + // If the highest version is a prerelease, walk through subsequent versions + // until we find a stable (non-prerelease) version. + // If all versions are prereleases, return the highest prerelease. + if latestSv.Prerelease() != "" && len(ext.Versions) > 1 { + start := 1 + end := len(ext.Versions) + step := 1 + if lastSv.GreaterThan(firstSv) { + // ascending order: walk backwards from second-to-last + start = len(ext.Versions) - 2 + end = -1 + step = -1 } - if latestSv == nil || sv.GreaterThan(latestSv) { - latestSv = sv - latestVersion = v.Version + for i := start; i != end; i += step { + sv, err := semver.NewVersion(ext.Versions[i].Version) + if err == nil && sv.Prerelease() == "" { + return ext.Versions[i].Version, nil + } } } - if latestVersion == "" { - return "", fmt.Errorf("extension %s has no valid semver versions", extensionId) - } - return latestVersion, nil + + return latest, nil } } diff --git a/cli/azd/pkg/extensions/update_checker_test.go b/cli/azd/pkg/extensions/update_checker_test.go index 0a347622dc8..59e5f00acf2 100644 --- a/cli/azd/pkg/extensions/update_checker_test.go +++ b/cli/azd/pkg/extensions/update_checker_test.go @@ -200,7 +200,8 @@ func Test_UpdateChecker_PrereleaseVersions(t *testing.T) { updateChecker := NewUpdateChecker(cacheManager) - // Installed stable version should see prerelease as update + // When the only newer version is a prerelease, the original code falls back to the latest + // stable version (1.0.0). Since the installed version is already 1.0.0, no update is available. extension := &Extension{ Id: "test.extension", DisplayName: "Test Extension", @@ -210,8 +211,7 @@ func Test_UpdateChecker_PrereleaseVersions(t *testing.T) { result, err := updateChecker.CheckForUpdate(ctx, extension) require.NoError(t, err) - // semver: 2.0.0-beta.1 is considered less than 2.0.0 but greater than 1.0.0 - require.True(t, result.HasUpdate) + require.False(t, result.HasUpdate) } func Test_UpdateChecker_InvalidVersions(t *testing.T) {