From de3face7dfb974dd02842fdc6436fde48e95dcc3 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Thu, 8 Oct 2026 14:13:42 +0100 Subject: [PATCH 1/3] fix: let --verbose and --debug override --quiet for errors The root pre-run ignores --quiet when --verbose or --debug is also set, but exitWithError still hid errors, e.g. when the legacy CLI could not start. Both now use isQuiet(). Co-Authored-By: Claude Opus 5.5 --- commands/root.go | 9 ++++-- integration-tests/quiet_errors_test.go | 38 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 integration-tests/quiet_errors_test.go diff --git a/commands/root.go b/commands/root.go index 0db2a8240..ab609a4f8 100644 --- a/commands/root.go +++ b/commands/root.go @@ -60,7 +60,7 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob // Completions must be fast and quiet. return } - quiet := viper.GetBool("quiet") && !viper.GetBool("debug") && !viper.GetBool("verbose") + quiet := isQuiet() if quiet { viper.Set("no-interaction", true) cmd.SetErr(io.Discard) @@ -294,6 +294,11 @@ func debugLogf(format string, v ...any) { fmt.Fprintf(color.Error, prefix+" "+strings.TrimSpace(format)+"\n", v...) } +// isQuiet reports whether quiet mode is on, which --debug and --verbose override. +func isQuiet() bool { + return viper.GetBool("quiet") && !viper.GetBool("debug") && !viper.GetBool("verbose") +} + func exitWithError(err error) { var execErr *exec.ExitError if errors.As(err, &execErr) { @@ -301,7 +306,7 @@ func exitWithError(err error) { debugLogf(err.Error()) os.Exit(exitCode) } - if !viper.GetBool("quiet") { + if !isQuiet() { fmt.Fprintln(color.Error, color.RedString(err.Error())) } os.Exit(1) diff --git a/integration-tests/quiet_errors_test.go b/integration-tests/quiet_errors_test.go new file mode 100644 index 000000000..cf8e43c1d --- /dev/null +++ b/integration-tests/quiet_errors_test.go @@ -0,0 +1,38 @@ +package tests + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestQuietErrors checks that --verbose and --debug override --quiet for errors before the legacy CLI runs. +func TestQuietErrors(t *testing.T) { + f := newCommandFactory(t, "", "") + // A file in place of the temporary directory makes the legacy CLI fail to start. + tmpFile := filepath.Join(t.TempDir(), "file") + require.NoError(t, os.WriteFile(tmpFile, nil, 0o600)) + f.extraEnv = append(f.extraEnv, EnvPrefix+"TMP="+tmpFile) + + cases := []struct { + args []string + wantError bool + }{ + {[]string{"-q"}, false}, + {[]string{"-qv"}, true}, + {[]string{"-q", "--debug"}, true}, + {nil, true}, + } + for _, c := range cases { + _, stderr, err := f.RunCombinedOutput(append([]string{"project:list"}, c.args...)...) + assertExitCode(t, 1, err) + if c.wantError { + assert.NotEmpty(t, stderr, "args: %v", c.args) + } else { + assert.Empty(t, stderr, "args: %v", c.args) + } + } +} From 63be299cca142236c1d7c60637b6c6cbce717c5d Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Thu, 8 Oct 2026 14:13:42 +0100 Subject: [PATCH 2/3] test(integration): stub the macOS browser launcher too On macOS the CLI opens URLs with `open`, which the fake browser did not stub, so login tests opened a real browser or waited for the timeout. Co-Authored-By: Claude Opus 5.5 --- integration-tests/tests.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/integration-tests/tests.go b/integration-tests/tests.go index 3db1ccb12..208f7c075 100644 --- a/integration-tests/tests.go +++ b/integration-tests/tests.go @@ -192,7 +192,10 @@ func (f *cmdFactory) fakeBrowser() { f.t.Skip("the fake browser is a shell script") } dir := f.t.TempDir() - require.NoError(f.t, os.WriteFile(filepath.Join(dir, "xdg-open"), []byte("#!/bin/sh\nexit 0\n"), 0o755)) + // The CLI uses "open" on macOS, and "xdg-open" on Linux. + for _, name := range []string{"open", "xdg-open"} { + require.NoError(f.t, os.WriteFile(filepath.Join(dir, name), []byte("#!/bin/sh\nexit 0\n"), 0o755)) + } f.extraEnv = append(f.extraEnv, "DISPLAY=:0", "PATH="+dir+string(os.PathListSeparator)+os.Getenv("PATH")) } From 57d5adef646c418cf657914a219518e68ab7ecff Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Thu, 8 Oct 2026 20:03:24 +0100 Subject: [PATCH 3/3] test(integration): check for the error itself in the quiet errors test The test only checked that stderr was not empty, which a debug line would also satisfy with --debug. Co-Authored-By: Claude Opus 5.5 --- integration-tests/quiet_errors_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/quiet_errors_test.go b/integration-tests/quiet_errors_test.go index cf8e43c1d..679e9a4f3 100644 --- a/integration-tests/quiet_errors_test.go +++ b/integration-tests/quiet_errors_test.go @@ -30,7 +30,7 @@ func TestQuietErrors(t *testing.T) { _, stderr, err := f.RunCombinedOutput(append([]string{"project:list"}, c.args...)...) assertExitCode(t, 1, err) if c.wantError { - assert.NotEmpty(t, stderr, "args: %v", c.args) + assert.Contains(t, stderr, "failed to initialize PHP CLI", "args: %v", c.args) } else { assert.Empty(t, stderr, "args: %v", c.args) }