Skip to content

Commit

Permalink
feat: custom error codes (#1114)
Browse files Browse the repository at this point in the history
  • Loading branch information
pd93 authored Apr 15, 2023
1 parent 9ec5448 commit f9c77ac
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 129 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
#1107 by @danquah).
- Add `.hg` (Mercurial) to the list of ignored directories when using `--watch`
(#1098 by @misery).
- More improvements to the release tool (#1096 by @pd93)
- More improvements to the release tool (#1096 by @pd93).
- Enforce [gofumpt](https://github.com/mvdan/gofumpt) linter (#1099 by @pd93)
- Add `--sort` flag for use with `--list` and `--list-all` (#946, #1105 by
@pd93)
@pd93).
- Task now has [custom exit codes](https://taskfile.dev/api/#exit-codes)
depending on the error (#1114 by @pd93).

## v3.23.0 - 2023-03-26

Expand Down
65 changes: 37 additions & 28 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/go-task/task/v3"
"github.com/go-task/task/v3/args"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/sort"
ver "github.com/go-task/task/v3/internal/version"
Expand Down Expand Up @@ -43,6 +44,17 @@ Options:
`

func main() {
if err := run(); err != nil {
if err, ok := err.(errors.TaskError); ok {
log.Print(err.Error())
os.Exit(err.Code())
}
os.Exit(errors.CodeUnknown)
}
os.Exit(errors.CodeOk)
}

func run() error {
log.SetFlags(0)
log.SetOutput(os.Stderr)

Expand Down Expand Up @@ -107,12 +119,12 @@ func main() {

if versionFlag {
fmt.Printf("Task version: %s\n", ver.GetVersion())
return
return nil
}

if helpFlag {
pflag.Usage()
return
return nil
}

if init {
Expand All @@ -123,25 +135,23 @@ func main() {
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
log.Fatal(err)
}
return
return nil
}

if global && dir != "" {
log.Fatal("task: You can't set both --global and --dir")
return
return nil
}
if global {
home, err := os.UserHomeDir()
if err != nil {
log.Fatal("task: Failed to get user home directory: %w", err)
return
return fmt.Errorf("task: Failed to get user home directory: %w", err)
}
dir = home
}

if dir != "" && entrypoint != "" {
log.Fatal("task: You can't set both --dir and --taskfile")
return
return errors.New("task: You can't set both --dir and --taskfile")
}
if entrypoint != "" {
dir = filepath.Dir(entrypoint)
Expand All @@ -150,16 +160,13 @@ func main() {

if output.Name != "group" {
if output.Group.Begin != "" {
log.Fatal("task: You can't set --output-group-begin without --output=group")
return
return errors.New("task: You can't set --output-group-begin without --output=group")
}
if output.Group.End != "" {
log.Fatal("task: You can't set --output-group-end without --output=group")
return
return errors.New("task: You can't set --output-group-end without --output=group")
}
if output.Group.ErrorOnly {
log.Fatal("task: You can't set --output-group-error-only without --output=group")
return
return errors.New("task: You can't set --output-group-error-only without --output=group")
}
}

Expand Down Expand Up @@ -195,23 +202,27 @@ func main() {

listOptions := task.NewListOptions(list, listAll, listJson)
if err := listOptions.Validate(); err != nil {
log.Fatal(err)
return err
}

if (listOptions.ShouldListTasks()) && silent {
e.ListTaskNames(listAll)
return
return nil
}

if err := e.Setup(); err != nil {
log.Fatal(err)
return err
}

if listOptions.ShouldListTasks() {
if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil {
os.Exit(1)
foundTasks, err := e.ListTasks(listOptions)
if err != nil {
return err
}
if !foundTasks {
os.Exit(errors.CodeUnknown)
}
return
return nil
}

var (
Expand All @@ -221,7 +232,7 @@ func main() {

tasksAndVars, cliArgs, err := getArgs()
if err != nil {
log.Fatal(err)
return err
}

if e.Taskfile.Version.Compare(taskfile.V3) >= 0 {
Expand All @@ -240,22 +251,20 @@ func main() {
ctx := context.Background()

if status {
if err := e.Status(ctx, calls...); err != nil {
log.Fatal(err)
}
return
return e.Status(ctx, calls...)
}

if err := e.Run(ctx, calls...); err != nil {
e.Logger.Errf(logger.Red, "%v", err)

if exitCode {
if err, ok := err.(*task.TaskRunError); ok {
os.Exit(err.ExitCode())
if err, ok := err.(*errors.TaskRunError); ok {
os.Exit(err.TaskExitCode())
}
}
os.Exit(1)
return err
}
return nil
}

func getArgs() ([]string, string, error) {
Expand Down
29 changes: 29 additions & 0 deletions docs/docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,35 @@ If `--` is given, all remaning arguments will be assigned to a special
| | `--version` | `bool` | `false` | Show Task version. |
| `-w` | `--watch` | `bool` | `false` | Enables watch of the given task. |

## Exit Codes

Task will sometimes exit with specific exit codes. These codes are split into three groups with the following ranges:

- General errors (0-99)
- Taskfile errors (100-199)
- Task errors (200-299)

A full list of the exit codes and their descriptions can be found below:

| Code | Description |
| ---- | ------------------------------------------------------------ |
| 0 | Success |
| 1 | An unknown error occurred |
| 100 | No Taskfile was found |
| 101 | A Taskfile already exists when trying to initialize one |
| 102 | The Taskfile is invalid or cannot be parsed |
| 200 | The specified task could not be found |
| 201 | An error occurred while executing a command inside of a task |
| 202 | The user tried to invoke a task that is internal |
| 203 | There a multiple tasks with the same name or alias |
| 204 | A task was called too many times |

These codes can also be found in the repository in [`errors/errors.go`](https://github.com/go-task/task/blob/master/errors/errors.go).

:::info
When Task is run with the `-x`/`--exit-code` flag, the exit code of any failed commands will be passed through to the user instead.
:::

## JSON Output

When using the `--json` flag in combination with either the `--list` or
Expand Down
78 changes: 0 additions & 78 deletions errors.go

This file was deleted.

40 changes: 40 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package errors

import "errors"

// General exit codes
const (
CodeOk int = iota // Used when the program exits without errors
CodeUnknown // Used when no other exit code is appropriate
)

// Taskfile related exit codes
const (
CodeTaskfileNotFound int = iota + 100
CodeTaskfileAlreadyExists
CodeTaskfileInvalid
)

// Task related exit codes
const (
CodeTaskNotFound int = iota + 200
CodeTaskRunError
CodeTaskInternal
CodeTaskNameConflict
CodeTaskCalledTooManyTimes
)

// TaskError extends the standard error interface with a Code method. This code will
// be used as the exit code of the program which allows the user to distinguish
// between different types of errors.
type TaskError interface {
error
Code() int
}

// New returns an error that formats as the given text. Each call to New returns
// a distinct error value even if the text is identical. This wraps the standard
// errors.New function so that we don't need to alias that package.
func New(text string) error {
return errors.New(text)
}
Loading

0 comments on commit f9c77ac

Please sign in to comment.