mirror of
https://github.com/go-task/task.git
synced 2025-04-27 12:32:25 +02:00
feat: custom error codes (#1114)
This commit is contained in:
parent
9ec544817f
commit
f9c77acd96
@ -9,10 +9,12 @@
|
|||||||
#1107 by @danquah).
|
#1107 by @danquah).
|
||||||
- Add `.hg` (Mercurial) to the list of ignored directories when using `--watch`
|
- Add `.hg` (Mercurial) to the list of ignored directories when using `--watch`
|
||||||
(#1098 by @misery).
|
(#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)
|
- Enforce [gofumpt](https://github.com/mvdan/gofumpt) linter (#1099 by @pd93)
|
||||||
- Add `--sort` flag for use with `--list` and `--list-all` (#946, #1105 by
|
- 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
|
## v3.23.0 - 2023-03-26
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/v3"
|
"github.com/go-task/task/v3"
|
||||||
"github.com/go-task/task/v3/args"
|
"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/logger"
|
||||||
"github.com/go-task/task/v3/internal/sort"
|
"github.com/go-task/task/v3/internal/sort"
|
||||||
ver "github.com/go-task/task/v3/internal/version"
|
ver "github.com/go-task/task/v3/internal/version"
|
||||||
@ -43,6 +44,17 @@ Options:
|
|||||||
`
|
`
|
||||||
|
|
||||||
func main() {
|
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.SetFlags(0)
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
|
|
||||||
@ -107,12 +119,12 @@ func main() {
|
|||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
fmt.Printf("Task version: %s\n", ver.GetVersion())
|
fmt.Printf("Task version: %s\n", ver.GetVersion())
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if helpFlag {
|
if helpFlag {
|
||||||
pflag.Usage()
|
pflag.Usage()
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if init {
|
if init {
|
||||||
@ -123,25 +135,23 @@ func main() {
|
|||||||
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
|
if err := task.InitTaskfile(os.Stdout, wd); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if global && dir != "" {
|
if global && dir != "" {
|
||||||
log.Fatal("task: You can't set both --global and --dir")
|
log.Fatal("task: You can't set both --global and --dir")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if global {
|
if global {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("task: Failed to get user home directory: %w", err)
|
return fmt.Errorf("task: Failed to get user home directory: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
dir = home
|
dir = home
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir != "" && entrypoint != "" {
|
if dir != "" && entrypoint != "" {
|
||||||
log.Fatal("task: You can't set both --dir and --taskfile")
|
return errors.New("task: You can't set both --dir and --taskfile")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if entrypoint != "" {
|
if entrypoint != "" {
|
||||||
dir = filepath.Dir(entrypoint)
|
dir = filepath.Dir(entrypoint)
|
||||||
@ -150,16 +160,13 @@ func main() {
|
|||||||
|
|
||||||
if output.Name != "group" {
|
if output.Name != "group" {
|
||||||
if output.Group.Begin != "" {
|
if output.Group.Begin != "" {
|
||||||
log.Fatal("task: You can't set --output-group-begin without --output=group")
|
return errors.New("task: You can't set --output-group-begin without --output=group")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if output.Group.End != "" {
|
if output.Group.End != "" {
|
||||||
log.Fatal("task: You can't set --output-group-end without --output=group")
|
return errors.New("task: You can't set --output-group-end without --output=group")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if output.Group.ErrorOnly {
|
if output.Group.ErrorOnly {
|
||||||
log.Fatal("task: You can't set --output-group-error-only without --output=group")
|
return errors.New("task: You can't set --output-group-error-only without --output=group")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,23 +202,27 @@ func main() {
|
|||||||
|
|
||||||
listOptions := task.NewListOptions(list, listAll, listJson)
|
listOptions := task.NewListOptions(list, listAll, listJson)
|
||||||
if err := listOptions.Validate(); err != nil {
|
if err := listOptions.Validate(); err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listOptions.ShouldListTasks()) && silent {
|
if (listOptions.ShouldListTasks()) && silent {
|
||||||
e.ListTaskNames(listAll)
|
e.ListTaskNames(listAll)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.Setup(); err != nil {
|
if err := e.Setup(); err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if listOptions.ShouldListTasks() {
|
if listOptions.ShouldListTasks() {
|
||||||
if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil {
|
foundTasks, err := e.ListTasks(listOptions)
|
||||||
os.Exit(1)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return
|
if !foundTasks {
|
||||||
|
os.Exit(errors.CodeUnknown)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -221,7 +232,7 @@ func main() {
|
|||||||
|
|
||||||
tasksAndVars, cliArgs, err := getArgs()
|
tasksAndVars, cliArgs, err := getArgs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Taskfile.Version.Compare(taskfile.V3) >= 0 {
|
if e.Taskfile.Version.Compare(taskfile.V3) >= 0 {
|
||||||
@ -240,22 +251,20 @@ func main() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
if err := e.Status(ctx, calls...); err != nil {
|
return e.Status(ctx, calls...)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.Run(ctx, calls...); err != nil {
|
if err := e.Run(ctx, calls...); err != nil {
|
||||||
e.Logger.Errf(logger.Red, "%v", err)
|
e.Logger.Errf(logger.Red, "%v", err)
|
||||||
|
|
||||||
if exitCode {
|
if exitCode {
|
||||||
if err, ok := err.(*task.TaskRunError); ok {
|
if err, ok := err.(*errors.TaskRunError); ok {
|
||||||
os.Exit(err.ExitCode())
|
os.Exit(err.TaskExitCode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getArgs() ([]string, string, error) {
|
func getArgs() ([]string, string, error) {
|
||||||
|
@ -51,6 +51,35 @@ If `--` is given, all remaning arguments will be assigned to a special
|
|||||||
| | `--version` | `bool` | `false` | Show Task version. |
|
| | `--version` | `bool` | `false` | Show Task version. |
|
||||||
| `-w` | `--watch` | `bool` | `false` | Enables watch of the given task. |
|
| `-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
|
## JSON Output
|
||||||
|
|
||||||
When using the `--json` flag in combination with either the `--list` or
|
When using the `--json` flag in combination with either the `--list` or
|
||||||
|
78
errors.go
78
errors.go
@ -1,78 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrTaskfileAlreadyExists is returned on creating a Taskfile if one already exists
|
|
||||||
var ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
|
|
||||||
|
|
||||||
type taskNotFoundError struct {
|
|
||||||
taskName string
|
|
||||||
didYouMean string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *taskNotFoundError) Error() string {
|
|
||||||
if err.didYouMean != "" {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`task: Task %q does not exist. Did you mean %q?`,
|
|
||||||
err.taskName,
|
|
||||||
err.didYouMean,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`task: Task %q does not exist`, err.taskName)
|
|
||||||
}
|
|
||||||
|
|
||||||
type multipleTasksWithAliasError struct {
|
|
||||||
aliasName string
|
|
||||||
taskNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *multipleTasksWithAliasError) Error() string {
|
|
||||||
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.taskNames, ", "), err.aliasName)
|
|
||||||
}
|
|
||||||
|
|
||||||
type taskInternalError struct {
|
|
||||||
taskName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *taskInternalError) Error() string {
|
|
||||||
return fmt.Sprintf(`task: Task "%s" is internal`, err.taskName)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TaskRunError struct {
|
|
||||||
taskName string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *TaskRunError) Error() string {
|
|
||||||
return fmt.Sprintf(`task: Failed to run task %q: %v`, err.taskName, err.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *TaskRunError) ExitCode() int {
|
|
||||||
if c, ok := interp.IsExitStatus(err.err); ok {
|
|
||||||
return int(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaximumTaskCallExceededError is returned when a task is called too
|
|
||||||
// many times. In this case you probably have a cyclic dependendy or
|
|
||||||
// infinite loop
|
|
||||||
type MaximumTaskCallExceededError struct {
|
|
||||||
task string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MaximumTaskCallExceededError) Error() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`task: maximum task call exceeded (%d) for task %q: probably an cyclic dep or infinite loop`,
|
|
||||||
MaximumTaskCall,
|
|
||||||
e.task,
|
|
||||||
)
|
|
||||||
}
|
|
40
errors/errors.go
Normal file
40
errors/errors.go
Normal file
@ -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)
|
||||||
|
}
|
100
errors/errors_task.go
Normal file
100
errors/errors_task.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TaskNotFoundError is returned when the specified task is not found in the
|
||||||
|
// Taskfile.
|
||||||
|
type TaskNotFoundError struct {
|
||||||
|
TaskName string
|
||||||
|
DidYouMean string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskNotFoundError) Error() string {
|
||||||
|
if err.DidYouMean != "" {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`task: Task %q does not exist. Did you mean %q?`,
|
||||||
|
err.TaskName,
|
||||||
|
err.DidYouMean,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`task: Task %q does not exist`, err.TaskName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskNotFoundError) Code() int {
|
||||||
|
return CodeTaskNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskRunError is returned when a command in a task returns a non-zero exit
|
||||||
|
// code.
|
||||||
|
type TaskRunError struct {
|
||||||
|
TaskName string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskRunError) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Failed to run task %q: %v`, err.TaskName, err.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskRunError) Code() int {
|
||||||
|
return CodeTaskRunError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskRunError) TaskExitCode() int {
|
||||||
|
if c, ok := interp.IsExitStatus(err.Err); ok {
|
||||||
|
return int(c)
|
||||||
|
}
|
||||||
|
return err.Code()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskInternalError when the user attempts to invoke a task that is internal.
|
||||||
|
type TaskInternalError struct {
|
||||||
|
TaskName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskInternalError) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Task "%s" is internal`, err.TaskName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskInternalError) Code() int {
|
||||||
|
return CodeTaskInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskNameConflictError is returned when multiple tasks with the same name or
|
||||||
|
// alias are found.
|
||||||
|
type TaskNameConflictError struct {
|
||||||
|
AliasName string
|
||||||
|
TaskNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskNameConflictError) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.TaskNames, ", "), err.AliasName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskNameConflictError) Code() int {
|
||||||
|
return CodeTaskNameConflict
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskCalledTooManyTimesError is returned when the maximum task call limit is
|
||||||
|
// exceeded. This is to prevent infinite loops and cyclic dependencies.
|
||||||
|
type TaskCalledTooManyTimesError struct {
|
||||||
|
TaskName string
|
||||||
|
MaximumTaskCall int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskCalledTooManyTimesError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`task: maximum task call exceeded (%d) for task %q: probably an cyclic dep or infinite loop`,
|
||||||
|
err.MaximumTaskCall,
|
||||||
|
err.TaskName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TaskCalledTooManyTimesError) Code() int {
|
||||||
|
return CodeTaskCalledTooManyTimes
|
||||||
|
}
|
51
errors/errors_taskfile.go
Normal file
51
errors/errors_taskfile.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
|
||||||
|
// searching the filesystem.
|
||||||
|
type TaskfileNotFoundError struct {
|
||||||
|
Dir string
|
||||||
|
Walk bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err TaskfileNotFoundError) Error() string {
|
||||||
|
var walkText string
|
||||||
|
if err.Walk {
|
||||||
|
walkText = " (or any of the parent directories)"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`task: No Taskfile found in "%s"%s. Use "task --init" to create a new one`, err.Dir, walkText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err TaskfileNotFoundError) Code() int {
|
||||||
|
return CodeTaskfileNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskfileAlreadyExistsError is returned on creating a Taskfile if one already
|
||||||
|
// exists.
|
||||||
|
type TaskfileAlreadyExistsError struct{}
|
||||||
|
|
||||||
|
func (err TaskfileAlreadyExistsError) Error() string {
|
||||||
|
return "task: A Taskfile already exists"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err TaskfileAlreadyExistsError) Code() int {
|
||||||
|
return CodeTaskfileAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskfileInvalidError is returned when the Taskfile contains syntax errors or
|
||||||
|
// cannot be parsed for some reason.
|
||||||
|
type TaskfileInvalidError struct {
|
||||||
|
FilePath string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err TaskfileInvalidError) Error() string {
|
||||||
|
return fmt.Sprintf("task: Failed to parse %s:\n%v", err.FilePath, err.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err TaskfileInvalidError) Code() int {
|
||||||
|
return CodeTaskfileInvalid
|
||||||
|
}
|
3
init.go
3
init.go
@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ func InitTaskfile(w io.Writer, dir string) error {
|
|||||||
f := filepathext.SmartJoin(dir, defaultTaskfileName)
|
f := filepathext.SmartJoin(dir, defaultTaskfileName)
|
||||||
|
|
||||||
if _, err := os.Stat(f); err == nil {
|
if _, err := os.Stat(f); err == nil {
|
||||||
return ErrTaskfileAlreadyExists
|
return errors.TaskfileAlreadyExistsError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(f, []byte(defaultTaskfile), 0o644); err != nil {
|
if err := os.WriteFile(f, []byte(defaultTaskfile), 0o644); err != nil {
|
||||||
|
23
task.go
23
task.go
@ -10,6 +10,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
"github.com/go-task/task/v3/internal/compiler"
|
||||||
"github.com/go-task/task/v3/internal/env"
|
"github.com/go-task/task/v3/internal/env"
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
@ -77,7 +78,7 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
for _, call := range calls {
|
for _, call := range calls {
|
||||||
task, err := e.GetTask(call)
|
task, err := e.GetTask(call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*taskNotFoundError); ok {
|
if _, ok := err.(*errors.TaskNotFoundError); ok {
|
||||||
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -86,12 +87,12 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if task.Internal {
|
if task.Internal {
|
||||||
if _, ok := err.(*taskNotFoundError); ok {
|
if _, ok := err.(*errors.TaskNotFoundError); ok {
|
||||||
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &taskInternalError{taskName: call.Task}
|
return &errors.TaskInternalError{TaskName: call.Task}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[t.Task], 1) >= MaximumTaskCall {
|
if !e.Watch && atomic.AddInt32(e.taskCallCount[t.Task], 1) >= MaximumTaskCall {
|
||||||
return &MaximumTaskCallExceededError{task: t.Task}
|
return &errors.TaskCalledTooManyTimesError{TaskName: t.Task}
|
||||||
}
|
}
|
||||||
|
|
||||||
release := e.acquireConcurrencyLimit()
|
release := e.acquireConcurrencyLimit()
|
||||||
@ -203,7 +204,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return &TaskRunError{t.Task, err}
|
return &errors.TaskRunError{TaskName: t.Task, Err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" finished`, call.Task)
|
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" finished`, call.Task)
|
||||||
@ -372,9 +373,9 @@ func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
}
|
}
|
||||||
// If we found multiple tasks
|
// If we found multiple tasks
|
||||||
if len(aliasedTasks) > 1 {
|
if len(aliasedTasks) > 1 {
|
||||||
return nil, &multipleTasksWithAliasError{
|
return nil, &errors.TaskNameConflictError{
|
||||||
aliasName: call.Task,
|
AliasName: call.Task,
|
||||||
taskNames: aliasedTasks,
|
TaskNames: aliasedTasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we found no tasks
|
// If we found no tasks
|
||||||
@ -383,9 +384,9 @@ func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
if e.fuzzyModel != nil {
|
if e.fuzzyModel != nil {
|
||||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||||
}
|
}
|
||||||
return nil, &taskNotFoundError{
|
return nil, &errors.TaskNotFoundError{
|
||||||
taskName: call.Task,
|
TaskName: call.Task,
|
||||||
didYouMean: didYouMean,
|
DidYouMean: didYouMean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/go-task/task/v3"
|
"github.com/go-task/task/v3"
|
||||||
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
@ -770,7 +771,7 @@ func TestCyclicDep(t *testing.T) {
|
|||||||
Stderr: io.Discard,
|
Stderr: io.Discard,
|
||||||
}
|
}
|
||||||
require.NoError(t, e.Setup())
|
require.NoError(t, e.Setup())
|
||||||
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
|
assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskVersion(t *testing.T) {
|
func TestTaskVersion(t *testing.T) {
|
||||||
@ -1691,9 +1692,9 @@ func TestErrorCode(t *testing.T) {
|
|||||||
|
|
||||||
err := e.Run(context.Background(), taskfile.Call{Task: "test-exit-code"})
|
err := e.Run(context.Background(), taskfile.Call{Task: "test-exit-code"})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
casted, ok := err.(*task.TaskRunError)
|
casted, ok := err.(*errors.TaskRunError)
|
||||||
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
|
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
|
||||||
assert.Equal(t, 42, casted.ExitCode(), "unexpected exit code from task")
|
assert.Equal(t, 42, casted.TaskExitCode(), "unexpected exit code from task")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEvaluateSymlinksInPaths(t *testing.T) {
|
func TestEvaluateSymlinksInPaths(t *testing.T) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package read
|
package read
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -9,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/sysinfo"
|
"github.com/go-task/task/v3/internal/sysinfo"
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
@ -208,7 +208,7 @@ func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
|||||||
|
|
||||||
var t taskfile.Taskfile
|
var t taskfile.Taskfile
|
||||||
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
|
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
|
||||||
return nil, fmt.Errorf("task: Failed to parse %s:\n%w", filepathext.TryAbsToRel(file), err)
|
return nil, &errors.TaskfileInvalidError{FilePath: filepathext.TryAbsToRel(file), Err: err}
|
||||||
}
|
}
|
||||||
return &t, nil
|
return &t, nil
|
||||||
}
|
}
|
||||||
@ -229,7 +229,7 @@ func exists(path string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
|
return "", errors.TaskfileNotFoundError{Dir: path, Walk: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
func existsWalk(path string) (string, error) {
|
func existsWalk(path string) (string, error) {
|
||||||
@ -254,7 +254,7 @@ func existsWalk(path string) (string, error) {
|
|||||||
// Error if we reached the root directory and still haven't found a file
|
// Error if we reached the root directory and still haven't found a file
|
||||||
// OR if the user id of the directory changes
|
// OR if the user id of the directory changes
|
||||||
if path == parentPath || (parentOwner != owner) {
|
if path == parentPath || (parentOwner != owner) {
|
||||||
return "", fmt.Errorf(`task: No Taskfile found in "%s" (or any of the parent directories). Use "task --init" to create a new one`, origPath)
|
return "", errors.TaskfileNotFoundError{Dir: origPath, Walk: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
owner = parentOwner
|
owner = parentOwner
|
||||||
|
5
watch.go
5
watch.go
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/radovskyb/watcher"
|
"github.com/radovskyb/watcher"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/fingerprint"
|
"github.com/go-task/task/v3/internal/fingerprint"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
@ -102,8 +103,8 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isContextError(err error) bool {
|
func isContextError(err error) bool {
|
||||||
if taskRunErr, ok := err.(*TaskRunError); ok {
|
if taskRunErr, ok := err.(*errors.TaskRunError); ok {
|
||||||
err = taskRunErr.err
|
err = taskRunErr.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err == context.Canceled || err == context.DeadlineExceeded
|
return err == context.Canceled || err == context.DeadlineExceeded
|
||||||
|
Loading…
x
Reference in New Issue
Block a user