fix(summer): send a delegate's stderr to the root's error writer through bonfire.ErrWriter (IN-13, quick-261028-rqd)

This commit is contained in:
Jakub Zych
2026-10-28 20:50:39 +01:00
parent f3f87f3cc4
commit 4914cade5a
8 changed files with 145 additions and 29 deletions

View File

@@ -53,18 +53,26 @@ func OnGlobals(fn func(Globals)) RootOption {
// It accepts command values rather than a party registry to avoid an import cycle.
// When out is the process stdout, the error block of Execute goes to stderr.
func NewRoot(name string, commands []Command, out io.Writer, opts ...RootOption) (*cobra.Command, error) {
root, err := NewRootIO(name, commands, os.Stdin, out, out, opts...)
root, console, err := newRootIO(name, commands, os.Stdin, out, out, opts...)
if err != nil {
return nil, err
}
if out == nil || out == os.Stdout {
root.SetErr(os.Stderr)
console.errStream = os.Stderr
}
return root, nil
}
// NewRootIO is NewRoot with injected stdin/stdout/stderr for tests.
func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Writer, opts ...RootOption) (*cobra.Command, error) {
root, _, err := newRootIO(name, commands, in, out, errW, opts...)
return root, err
}
// newRootIO builds the root of NewRootIO and returns its console too, so
// NewRoot can record where it moved the error stream.
func newRootIO(name string, commands []Command, in io.Reader, out, errW io.Writer, opts ...RootOption) (*cobra.Command, *console, error) {
if out == nil {
out = os.Stdout
}
@@ -84,6 +92,7 @@ func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Write
cfg.version = BuildVersion()
}
output := newConsole(in, out, errW, os.Getenv, nil, nil)
output.errStream = errW
root := &cobra.Command{
Use: name,
SilenceUsage: true,
@@ -116,24 +125,24 @@ func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Write
}
for _, command := range commands {
if seen[command.Name] {
return nil, fmt.Errorf("%w: %q", ErrDuplicateCommand, command.Name)
return nil, nil, fmt.Errorf("%w: %q", ErrDuplicateCommand, command.Name)
}
seen[command.Name] = true
cmd, err := st.wrap(command, true)
if err != nil {
return nil, err
return nil, nil, err
}
root.AddCommand(cmd)
}
listCmd, err := st.wrap(st.listCommand(), false)
if err != nil {
return nil, err
return nil, nil, err
}
root.AddCommand(listCmd)
helpCmd, err := st.wrap(st.helpCommand(), false)
if err != nil {
return nil, err
return nil, nil, err
}
root.SetHelpCommand(helpCmd)
root.SetHelpFunc(func(*cobra.Command, []string) {
@@ -160,7 +169,7 @@ func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Write
}
return nil
}
return root, nil
return root, output, nil
}
// rootState is what the list, help and error rendering of one root share.