Add documentation comments to exported symbols across all remaining smaller packages including cmd/gen-docs, context, internal/browser, internal/gh, internal/ghcmd, internal/ghinstance, internal/ghrepo, internal/keyring, internal/run, internal/safepaths, internal/tableprinter, internal/text, internal/update, pkg/cmd/accessibility, pkg/cmd/actions, pkg/cmd/alias, pkg/cmd/api, pkg/cmd/browse, pkg/cmd/cache, pkg/cmd/completion, pkg/cmd/copilot, pkg/cmd/factory, pkg/cmd/gpg-key, pkg/cmd/label, pkg/cmd/licenses, pkg/cmd/org, pkg/cmd/preview, pkg/cmd/ssh-key, pkg/cmd/version, pkg/extensions, pkg/jsoncolor, pkg/markdown, pkg/option, pkg/set, pkg/ssh, pkg/surveyext, test, internal/authflow, and utils. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
102 lines
2.2 KiB
Go
102 lines
2.2 KiB
Go
package run
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/cli/cli/v2/utils"
|
|
)
|
|
|
|
// Runnable is typically an exec.Cmd or its stub in tests
|
|
type Runnable interface {
|
|
Output() ([]byte, error)
|
|
Run() error
|
|
}
|
|
|
|
// PrepareCmd extends exec.Cmd with extra error reporting features and provides a
|
|
// hook to stub command execution in tests
|
|
var PrepareCmd = func(cmd *exec.Cmd) Runnable {
|
|
return &cmdWithStderr{cmd}
|
|
}
|
|
|
|
// cmdWithStderr augments exec.Cmd by adding stderr to the error message
|
|
type cmdWithStderr struct {
|
|
*exec.Cmd
|
|
}
|
|
|
|
// Output runs the cmdWithStderr and returns its output.
|
|
func (c cmdWithStderr) Output() ([]byte, error) {
|
|
if isVerbose, _ := utils.IsDebugEnabled(); isVerbose {
|
|
_ = printArgs(os.Stderr, c.Cmd.Args)
|
|
}
|
|
out, err := c.Cmd.Output()
|
|
if c.Cmd.Stderr != nil || err == nil {
|
|
return out, err
|
|
}
|
|
cmdErr := &CmdError{
|
|
Args: c.Cmd.Args,
|
|
Err: err,
|
|
}
|
|
var exitError *exec.ExitError
|
|
if errors.As(err, &exitError) {
|
|
cmdErr.Stderr = bytes.NewBuffer(exitError.Stderr)
|
|
}
|
|
return out, cmdErr
|
|
}
|
|
|
|
// Run executes the cmdWithStderr command.
|
|
func (c cmdWithStderr) Run() error {
|
|
if isVerbose, _ := utils.IsDebugEnabled(); isVerbose {
|
|
_ = printArgs(os.Stderr, c.Cmd.Args)
|
|
}
|
|
if c.Cmd.Stderr != nil {
|
|
return c.Cmd.Run()
|
|
}
|
|
errStream := &bytes.Buffer{}
|
|
c.Cmd.Stderr = errStream
|
|
err := c.Cmd.Run()
|
|
if err != nil {
|
|
err = &CmdError{
|
|
Args: c.Cmd.Args,
|
|
Err: err,
|
|
Stderr: errStream,
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// CmdError provides more visibility into why an exec.Cmd had failed
|
|
type CmdError struct {
|
|
Args []string
|
|
Err error
|
|
Stderr *bytes.Buffer
|
|
}
|
|
|
|
// Error returns the error message for CmdError.
|
|
func (e CmdError) Error() string {
|
|
msg := e.Stderr.String()
|
|
if msg != "" && !strings.HasSuffix(msg, "\n") {
|
|
msg += "\n"
|
|
}
|
|
return fmt.Sprintf("%s%s: %s", msg, e.Args[0], e.Err)
|
|
}
|
|
|
|
// Unwrap returns the underlying error for CmdError.
|
|
func (e CmdError) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
func printArgs(w io.Writer, args []string) error {
|
|
if len(args) > 0 {
|
|
// print commands, but omit the full path to an executable
|
|
args = append([]string{filepath.Base(args[0])}, args[1:]...)
|
|
}
|
|
_, err := fmt.Fprintf(w, "%v\n", args)
|
|
return err
|
|
}
|