webui: cleanups (#1539)

Michael Muré created

Also, bump to go 1.25

Change summary

.github/workflows/build-and-test.yml |   2 
.github/workflows/release.yml        |   2 
.github/workflows/trunk.yml          |   2 
api/graphql/handler.go               |  13 --
commands/webui.go                    | 143 +++++++++++++----------------
go.mod                               |  28 ++---
go.sum                               |  40 ++++----
7 files changed, 102 insertions(+), 128 deletions(-)

Detailed changes

.github/workflows/build-and-test.yml 🔗

@@ -11,7 +11,7 @@ jobs:
   with-go:
     strategy:
       matrix:
-        go-version: [1.24.2]
+        go-version: [1.25.x]
         platform: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.platform }}
     steps:

.github/workflows/release.yml 🔗

@@ -30,7 +30,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
         with:
-          go-version: 1.24.2
+          go-version: 1.25.x
 
       - name: Build
         run: make

.github/workflows/trunk.yml 🔗

@@ -33,7 +33,7 @@ jobs:
 
       - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
         with:
-          go-version: 1.24.2
+          go-version: 1.25.x
 
       - name: Run benchmark
         run: go test -v ./... -bench=. -run=xxx -benchmem | tee output.txt

api/graphql/handler.go 🔗

@@ -20,13 +20,7 @@ import (
 	"github.com/git-bug/git-bug/cache"
 )
 
-// Handler is the root GraphQL http handler
-type Handler struct {
-	http.Handler
-	io.Closer
-}
-
-func NewHandler(mrc *cache.MultiRepoCache, errorOut io.Writer) Handler {
+func NewHandler(mrc *cache.MultiRepoCache, errorOut io.Writer) http.Handler {
 	rootResolver := resolvers.NewRootResolver(mrc)
 	config := graph.Config{Resolvers: rootResolver}
 
@@ -54,8 +48,5 @@ func NewHandler(mrc *cache.MultiRepoCache, errorOut io.Writer) Handler {
 		h.Use(&Tracer{Out: errorOut})
 	}
 
-	return Handler{
-		Handler: h,
-		Closer:  rootResolver,
-	}
+	return h
 }

commands/webui.go 🔗

@@ -30,7 +30,7 @@ import (
 const webUIOpenConfigKey = "git-bug.webui.open"
 
 type webUIOptions struct {
-	host      string
+	bind      string
 	port      int
 	open      bool
 	noOpen    bool
@@ -59,10 +59,10 @@ Available git config:
 	flags := cmd.Flags()
 	flags.SortFlags = false
 
-	flags.StringVar(&options.host, "host", "127.0.0.1", "Network address or hostname to listen to (default to 127.0.0.1)")
+	flags.StringVar(&options.bind, "bind", "127.0.0.1", "Network address to bind to (default to 127.0.0.1)")
+	flags.IntVarP(&options.port, "port", "p", 0, "Port to listen on (default to random available port)")
 	flags.BoolVar(&options.open, "open", false, "Automatically open the web UI in the default browser")
 	flags.BoolVar(&options.noOpen, "no-open", false, "Prevent the automatic opening of the web UI in the default browser")
-	flags.IntVarP(&options.port, "port", "p", 0, "Port to listen to (default to random available port)")
 	flags.BoolVar(&options.readOnly, "read-only", false, "Whether to run the web UI in read-only mode")
 	flags.BoolVar(&options.logErrors, "log-errors", false, "Whether to log errors")
 	flags.StringVarP(&options.query, "query", "q", "", "The query to open in the web UI bug list")
@@ -70,24 +70,8 @@ Available git config:
 	return cmd
 }
 
-func runWebUI(env *execenv.Env, opts webUIOptions) error {
-	if opts.port == 0 {
-		var err error
-		opts.port, err = freeport.GetFreePort()
-		if err != nil {
-			return err
-		}
-	}
-
-	addr := net.JoinHostPort(opts.host, strconv.Itoa(opts.port))
-	webUiAddr := fmt.Sprintf("http://%s", addr)
-	toOpen := webUiAddr
-
-	if len(opts.query) > 0 {
-		// Explicitly set the query parameter instead of going with a default one.
-		toOpen = fmt.Sprintf("%s/?q=%s", webUiAddr, url.QueryEscape(opts.query))
-	}
-
+// setupRoutes builds the router and registers all API and UI routes.
+func setupRoutes(env *execenv.Env, opts webUIOptions) (*mux.Router, func() error, error) {
 	router := mux.NewRouter()
 
 	// If the webUI is not read-only, use an authentication middleware with a
@@ -96,18 +80,15 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error {
 	if !opts.readOnly {
 		author, err := identity.GetUserIdentity(env.Repo)
 		if err != nil {
-			return err
+			return nil, nil, err
 		}
 		router.Use(auth.Middleware(author.Id()))
 	}
 
 	mrc := cache.NewMultiRepoCache()
-
 	_, events := mrc.RegisterDefaultRepository(env.Repo)
-
-	err := execenv.CacheBuildProgressBar(env, events)
-	if err != nil {
-		return err
+	if err := execenv.CacheBuildProgressBar(env, events); err != nil {
+		return nil, nil, err
 	}
 
 	var errOut io.Writer
@@ -117,46 +98,46 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error {
 
 	graphqlHandler := graphql.NewHandler(mrc, errOut)
 
-	// Routes
 	router.Path("/playground").Handler(playground.Handler("git-bug", "/graphql"))
 	router.Path("/graphql").Handler(graphqlHandler)
 	router.Path("/gitfile/{repo}/{hash}").Handler(httpapi.NewGitFileHandler(mrc))
 	router.Path("/upload/{repo}").Methods("POST").Handler(httpapi.NewGitUploadFileHandler(mrc))
 	router.PathPrefix("/").Handler(webui.NewHandler())
 
-	srv := &http.Server{
-		Addr:    addr,
-		Handler: router,
-	}
-
-	done := make(chan bool)
-
-	go func() {
-		<-env.Ctx.Done()
-		env.Out.Println("shutting down...")
-
-		ctxTeardown, cancelTeardown := context.WithTimeout(context.Background(), 30*time.Second)
-		defer cancelTeardown()
+	return router, mrc.Close, nil
+}
 
-		srv.SetKeepAlivesEnabled(false)
-		if err := srv.Shutdown(ctxTeardown); err != nil {
-			env.Err.Printf("Could not gracefully shutdown the WebUI: %v\n", err)
+func runWebUI(env *execenv.Env, opts webUIOptions) error {
+	router, closeRoutes, err := setupRoutes(env, opts)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err := closeRoutes(); err != nil {
+			env.Err.Println(err)
 		}
+	}()
 
-		// Teardown
-		err := graphqlHandler.Close()
+	if opts.port == 0 {
+		opts.port, err = freeport.GetFreePort()
 		if err != nil {
-			env.Err.Println(err)
+			return err
 		}
+	}
 
-		close(done)
-	}()
+	addr := net.JoinHostPort(opts.bind, strconv.Itoa(opts.port))
+	server := &http.Server{Addr: addr, Handler: router}
+	baseURL := "http://" + addr
 
-	env.Out.Printf("Web UI: %s\n", webUiAddr)
-	env.Out.Printf("Graphql API: http://%s/graphql\n", addr)
-	env.Out.Printf("Graphql Playground: http://%s/playground\n", addr)
+	env.Out.Printf("Web UI: %s\n", baseURL)
+	env.Out.Printf("Graphql API: %s/graphql\n", baseURL)
+	env.Out.Printf("Graphql Playground: %s/playground\n", baseURL)
 	env.Out.Printf("\n[ Press Ctrl+c to quit ]\n\n")
 
+	toOpen := baseURL
+	if len(opts.query) > 0 {
+		toOpen = fmt.Sprintf("%s/?q=%s", baseURL, url.QueryEscape(opts.query))
+	}
 	configOpen, err := env.Repo.AnyConfig().ReadBool(webUIOpenConfigKey)
 	if errors.Is(err, repository.ErrNoConfigEntry) {
 		// default to true
@@ -164,40 +145,44 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error {
 	} else if err != nil {
 		return err
 	}
-
-	shouldOpen := (configOpen && !opts.noOpen) || opts.open
-
-	if shouldOpen {
-		go func() {
-			const maxAttempts = 3
-			if isUp(toOpen, maxAttempts, 3*time.Second) {
-				err = open.Run(toOpen)
-				if err != nil {
-					env.Err.Println(err)
-					return
-				}
-
-				env.Out.Printf("opened your default browser to url: %s\n", toOpen)
-				return
-			}
-
-			env.Err.Printf(
-				"uh oh! it appears that the http server hasn't started.\n"+
-					"we failed to reach %s after %d attempts, exiting now.\n",
-				toOpen, maxAttempts,
-			)
-		}()
+	if (configOpen && !opts.noOpen) || opts.open {
+		go openWhenUp(env, toOpen)
 	}
 
-	err = srv.ListenAndServe()
-	if err != nil && err != http.ErrServerClosed {
+	go func() {
+		<-env.Ctx.Done()
+		env.Out.Println("shutting down...")
+		shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+		defer cancel()
+		server.SetKeepAlivesEnabled(false)
+		if err := server.Shutdown(shutdownCtx); err != nil {
+			env.Err.Printf("Could not gracefully shutdown the HTTP server: %v\n", err)
+		}
+	}()
+
+	if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
 		return err
 	}
-
-	<-done
 	return nil
 }
 
+func openWhenUp(env *execenv.Env, toOpen string) {
+	const maxAttempts = 3
+	if isUp(toOpen, maxAttempts, 3*time.Second) {
+		if err := open.Run(toOpen); err != nil {
+			env.Err.Println(err)
+			return
+		}
+		env.Out.Printf("opened your default browser to url: %s\n", toOpen)
+		return
+	}
+	env.Err.Printf(
+		"uh oh! it appears that the http server hasn't started.\n"+
+			"we failed to reach %s after %d attempts.\n",
+		toOpen, maxAttempts,
+	)
+}
+
 func isUp(url string, maxRetries int, initialDelay time.Duration) bool {
 	client := &http.Client{
 		Timeout: 5 * time.Second,

go.mod 🔗

@@ -1,8 +1,6 @@
 module github.com/git-bug/git-bug
 
-go 1.24.0
-
-toolchain go1.24.2
+go 1.25.0
 
 require (
 	github.com/99designs/gqlgen v0.17.73
@@ -32,17 +30,19 @@ require (
 	github.com/vbauerster/mpb/v8 v8.8.2
 	github.com/vektah/gqlparser/v2 v2.5.26
 	gitlab.com/gitlab-org/api/client-go v0.116.0
-	golang.org/x/crypto v0.45.0
-	golang.org/x/mod v0.29.0
-	golang.org/x/net v0.47.0
-	golang.org/x/oauth2 v0.27.0
-	golang.org/x/sync v0.18.0
-	golang.org/x/sys v0.38.0
-	golang.org/x/term v0.37.0
-	golang.org/x/text v0.31.0
+	golang.org/x/crypto v0.49.0
+	golang.org/x/mod v0.34.0
+	golang.org/x/net v0.52.0
+	golang.org/x/oauth2 v0.36.0
+	golang.org/x/sync v0.20.0
+	golang.org/x/sys v0.42.0
+	golang.org/x/term v0.41.0
+	golang.org/x/text v0.35.0
 	golang.org/x/vuln v1.1.3
 )
 
+tool github.com/99designs/gqlgen
+
 require (
 	dario.cat/mergo v1.0.1 // indirect
 	github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
@@ -113,14 +113,12 @@ require (
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
 	go.etcd.io/bbolt v1.4.0 // indirect
-	golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
+	golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
 	golang.org/x/time v0.3.0 // indirect
-	golang.org/x/tools v0.38.0 // indirect
+	golang.org/x/tools v0.42.0 // indirect
 	golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
 	golang.org/x/tools/godoc v0.1.0-deprecated // indirect
 	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
-
-tool github.com/99designs/gqlgen

go.sum 🔗

@@ -257,28 +257,28 @@ go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
 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/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
-golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
-golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
-golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -291,33 +291,33 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
-golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0=
+golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
 golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
 golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
 golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=