From c1114532fbcaaca27a16dbe8ee1407fd497d9fec Mon Sep 17 00:00:00 2001 From: Tomer Elmalem Date: Wed, 11 Mar 2026 09:14:46 -0700 Subject: [PATCH] Fix assorted CLI UX issues from issue triage - Quote project name in login suggestion to handle names with spaces (#1061) - Print errors to stderr instead of stdout in root error handler (#872) - Wrap STRIPE_API_KEY validation errors with env var context (#1316) - Add price.deleted trigger fixture (#1128) - Show helpful "Is your local server running?" on connection refused (#273) - Use STRIPE_DEVICE_NAME env var in login flow via GetDeviceName() (#1063) - Always display API version in stripe listen output (#401) Co-Authored-By: Claude Opus 4.6 Committed-By-Agent: claude --- pkg/cmd/listen.go | 10 ++++++-- pkg/cmd/root.go | 4 +-- pkg/config/profile.go | 2 +- pkg/fixtures/triggers.go | 1 + pkg/fixtures/triggers/price.deleted.json | 31 ++++++++++++++++++++++++ pkg/login/login.go | 3 ++- pkg/proxy/endpoint.go | 5 ++-- pkg/proxy/proxy.go | 2 +- 8 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 pkg/fixtures/triggers/price.deleted.json diff --git a/pkg/cmd/listen.go b/pkg/cmd/listen.go index c368d3fb1..0adffdff4 100644 --- a/pkg/cmd/listen.go +++ b/pkg/cmd/listen.go @@ -238,10 +238,16 @@ func (lc *listenCmd) createVisitor(logger *log.Logger, format string, printJSON color := ansi.Color(os.Stdout) localTime := time.Now().Format(timeLayout) - errStr := fmt.Sprintf("%s [%s] Failed to POST: %v\n", + postErr := ee.Error.(proxy.FailedToPostError) + errMsg := fmt.Sprintf("Failed to POST: %v", postErr) + if strings.Contains(postErr.Error(), "connection refused") { + errMsg = fmt.Sprintf("Failed to POST: Connection to %s was refused. Is your local server running?", postErr.URL) + } + + errStr := fmt.Sprintf("%s [%s] %s\n", color.Faint(localTime), color.Red("ERROR"), - ee.Error, + errMsg, ) fmt.Println(errStr) diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index eed18017e..e36992527 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -116,7 +116,7 @@ func Execute(ctx context.Context) { case requests.IsAPIKeyExpiredError(err): fmt.Fprintln(os.Stderr, "The API key provided has expired. Obtain a new key from the Dashboard or run `stripe login` and try again.") case isLoginRequiredError && projectNameFlag != "default": - fmt.Printf("You provided the project name \"%[1]s\" (either via the \"--project-name\" flag or the \"STRIPE_PROJECT_NAME\" environment variable), but no config for that project was found.\nPlease run `stripe login --project-name=%[1]s` to enable commands for this project.\n", projectNameFlag) + fmt.Fprintf(os.Stderr, "You provided the project name \"%[1]s\" (either via the \"--project-name\" flag or the \"STRIPE_PROJECT_NAME\" environment variable), but no config for that project was found.\nPlease run `stripe login --project-name=\"%[1]s\"` to enable commands for this project.\n", projectNameFlag) case isLoginRequiredError: // capitalize first letter of error because linter errRunes := []rune(errString) @@ -134,7 +134,7 @@ func Execute(ctx context.Context) { showSuggestion() default: - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) } os.Exit(1) diff --git a/pkg/config/profile.go b/pkg/config/profile.go index 89e87dfe8..516c99499 100644 --- a/pkg/config/profile.go +++ b/pkg/config/profile.go @@ -144,7 +144,7 @@ func (p *Profile) GetAPIKey(livemode bool) (string, error) { if envKey != "" { err := validators.APIKey(envKey) if err != nil { - return "", err + return "", fmt.Errorf("the STRIPE_API_KEY environment variable is set but invalid: %w", err) } return envKey, nil diff --git a/pkg/fixtures/triggers.go b/pkg/fixtures/triggers.go index 142776c44..a56040574 100644 --- a/pkg/fixtures/triggers.go +++ b/pkg/fixtures/triggers.go @@ -99,6 +99,7 @@ var Events = map[string]string{ "plan.deleted": "triggers/plan.deleted.json", "plan.updated": "triggers/plan.updated.json", "price.created": "triggers/price.created.json", + "price.deleted": "triggers/price.deleted.json", "price.updated": "triggers/price.updated.json", "product.created": "triggers/product.created.json", "product.deleted": "triggers/product.deleted.json", diff --git a/pkg/fixtures/triggers/price.deleted.json b/pkg/fixtures/triggers/price.deleted.json new file mode 100644 index 000000000..41494d572 --- /dev/null +++ b/pkg/fixtures/triggers/price.deleted.json @@ -0,0 +1,31 @@ +{ + "_meta": { + "template_version": 0 + }, + "fixtures": [ + { + "name": "product", + "path": "/v1/products", + "method": "post", + "params": { + "name": "myproduct", + "description": "(created by Stripe CLI)" + } + }, + { + "name": "price", + "path": "/v1/prices", + "method": "post", + "params": { + "product": "${product:id}", + "unit_amount": "1500", + "currency": "usd" + } + }, + { + "name": "product_deleted", + "path": "/v1/products/${product:id}", + "method": "delete" + } + ] +} diff --git a/pkg/login/login.go b/pkg/login/login.go index 73c991cf4..596f15e5b 100644 --- a/pkg/login/login.go +++ b/pkg/login/login.go @@ -11,7 +11,8 @@ import ( // Login is the main entrypoint for logging in to the CLI. func Login(ctx context.Context, baseURL string, config *config.Config) error { - links, err := GetLinks(ctx, baseURL, config.Profile.DeviceName) + deviceName, _ := config.Profile.GetDeviceName() + links, err := GetLinks(ctx, baseURL, deviceName) if err != nil { return err } diff --git a/pkg/proxy/endpoint.go b/pkg/proxy/endpoint.go index 858f7ac59..0d65be438 100644 --- a/pkg/proxy/endpoint.go +++ b/pkg/proxy/endpoint.go @@ -48,6 +48,7 @@ func (f EndpointResponseHandlerFunc) ProcessResponse(evtCtx eventContext, forwar // FailedToPostError describes a failure to send a POST request to an endpoint type FailedToPostError struct { Err error + URL string } func (f FailedToPostError) Error() string { @@ -123,7 +124,7 @@ func (c *EndpointClient) Post(evtCtx eventContext) error { resp, err := c.cfg.HTTPClient.Do(req) if err != nil { c.cfg.OutCh <- websocket.ErrorElement{ - Error: FailedToPostError{Err: err}, + Error: FailedToPostError{Err: err, URL: c.URL}, } return err } @@ -158,7 +159,7 @@ func (c *EndpointClient) PostV2(evtCtx eventContext) error { resp, err := http.DefaultClient.Do(req) if err != nil { c.cfg.OutCh <- websocket.ErrorElement{ - Error: FailedToPostError{Err: err}, + Error: FailedToPostError{Err: err, URL: c.URL}, } return err } diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 7ac417eaa..6ef2660bc 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -187,7 +187,7 @@ func (p *Proxy) Run(ctx context.Context) error { displayedAPIVersion := "" if p.cfg.UseLatestAPIVersion && session.LatestVersion != "" { displayedAPIVersion = "You are using Stripe API Version [" + session.LatestVersion + "]. " - } else if !p.cfg.UseLatestAPIVersion && session.DefaultVersion != "" { + } else if session.DefaultVersion != "" { displayedAPIVersion = "You are using Stripe API Version [" + session.DefaultVersion + "]. " }