diff --git a/cli/azd/cmd/auto_install.go b/cli/azd/cmd/auto_install.go index 5984e59e16e..19c0eba16d9 100644 --- a/cli/azd/cmd/auto_install.go +++ b/cli/azd/cmd/auto_install.go @@ -24,6 +24,7 @@ import ( "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" + "github.com/azure/azure-dev/cli/azd/pkg/output" "github.com/azure/azure-dev/cli/azd/pkg/output/ux" "github.com/azure/azure-dev/cli/azd/pkg/project" "github.com/azure/azure-dev/cli/azd/pkg/update" @@ -460,7 +461,9 @@ func ExecuteWithAutoInstall(ctx context.Context, rootContainer *ioc.NestedContai // This also enables the global options to be set in the container for support during extension framework callbacks. globalOpts := &internal.GlobalCommandOptions{} if err := ParseGlobalFlags(os.Args[1:], globalOpts); err != nil { - result.Err = fmt.Errorf("Warning: failed to parse global flags: %w", err) + // main.go only exits on result.Err — print here so users see pre-cobra parse failures. + fmt.Fprintln(os.Stderr, output.WithErrorFormat("ERROR: %s", err.Error())) + result.Err = err return result } @@ -762,6 +765,13 @@ func ParseGlobalFlags(args []string, opts *internal.GlobalCommandOptions) error // command parser after the extension is loaded. globalFlagSet.ParseErrorsAllowlist = pflag.ParseErrorsAllowlist{UnknownFlags: true} + // Register --output/-o on the pre-parse set only (not in CreateGlobalFlagSet, which + // would shadow per-command output.AddOutputParam and cause GetCommandFormatter to reject + // the empty default). Without this, pflag's UnknownFlags allowlist walks attached short + // values like "-o" char-by-char and trips on "-e" (a known string flag), failing + // with `flag needs an argument: 'e' in -e`. + globalFlagSet.StringP("output", "o", "", "The output format (json, table, none).") + // Parse the arguments - unknown flags will be silently ignored err := globalFlagSet.Parse(args) diff --git a/cli/azd/cmd/auto_install_test.go b/cli/azd/cmd/auto_install_test.go index 5875e005ada..11f5ebb6451 100644 --- a/cli/azd/cmd/auto_install_test.go +++ b/cli/azd/cmd/auto_install_test.go @@ -754,3 +754,36 @@ func TestParseGlobalFlags_ExtensionCompatibility(t *testing.T) { }) } } + +// TestParseGlobalFlags_OutputAttachedShortForm verifies that all common -o / --output +// attachment forms are accepted without error. Before the fix, pflag's UnknownFlags +// allowlist walked "-otable" char-by-char and tripped on the known string flag "-e", +// returning `flag needs an argument: 'e' in -e`. +func TestParseGlobalFlags_OutputAttachedShortForm(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + }{ + {"attached", []string{"-otable"}}, + {"attached-json", []string{"-ojson"}}, + {"space-separated", []string{"-o", "table"}}, + {"equals", []string{"-o=table"}}, + {"long-space", []string{"--output", "table"}}, + {"long-equals", []string{"--output=table"}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + opts := &internal.GlobalCommandOptions{} + err := ParseGlobalFlags(tc.args, opts) + // We only assert no error here. The -o/--output flag is registered on the + // pre-parse FlagSet solely to prevent pflag from walking attached short + // values char-by-char and failing on "-e"; the parsed value is intentionally + // not captured into GlobalCommandOptions (Cobra handles --output per command). + require.NoError(t, err, "ParseGlobalFlags must not error for args: %v", tc.args) + }) + } +}