diff --git a/cmd/task/task.go b/cmd/task/task.go index 889fa0adb5..d985c5a2aa 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -68,6 +68,7 @@ func main() { entrypoint string output string color bool + yes bool ) pflag.BoolVar(&versionFlag, "version", false, "show Task version") @@ -86,6 +87,7 @@ func main() { pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`) pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]") pflag.BoolVarP(&color, "color", "c", true, "colored output") + pflag.BoolVarP(&yes, "yes", "y", false, "automatic yes to warning prompts") pflag.Parse() if versionFlag { @@ -131,6 +133,7 @@ func main() { Summary: summary, Parallel: parallel, Color: color, + Yes: yes, Stdin: os.Stdin, Stdout: os.Stdout, diff --git a/docs/usage.md b/docs/usage.md index a3a6a30449..67ebfc980d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -920,3 +920,47 @@ so task know which files to watch. [gotemplate]: https://golang.org/pkg/text/template/ [minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify + +## Warning prompts + +You can add both task level and command level warning prompts to your Taskfile. + +Here is how `warning` can be used at both levels: + +```yaml +version: '3' + +tasks: + delete-things: + desc: Remove both important and unimportant files + warning: Are you sure you want to run this task? + cmds: + - rm -rf /unimportant-files + - cmd: rm -rf /important-files + warning: Are you sure you want to run this command? +``` + +In this example, a warning prompt will be presented both at the start of the task, +and again when the task reaches the command with a warning. + +> NOTE: as with [silent mode](#silent-mode) and [ignore errors](#ignore-errors), +> to add an option at the command level requires prefixing the target command with `cmd:` + +### Behavior of denied warnings + +When a task level warning is denied, the task is skipped. Command level warnings have +the same behavior: when denied, the task continues on to the next command (if any). + +### Automatic confirmation + +The `[-y | --yes]` option passed with a task call enables the ability to automatically +confirm a task with warnings. When provided, all warnings, at both task and command +levels are confirmed. + +To build off the example above: + +```bash +$ task delete-things -y +``` + +Will run the entire task without warning. Use with caution! diff --git a/internal/taskfile/cmd.go b/internal/taskfile/cmd.go index c992dd30c8..4ed7043a47 100644 --- a/internal/taskfile/cmd.go +++ b/internal/taskfile/cmd.go @@ -11,6 +11,7 @@ type Cmd struct { Silent bool Task string Vars *Vars + Warning string IgnoreError bool } @@ -41,11 +42,13 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error { var cmdStruct struct { Cmd string Silent bool + Warning string IgnoreError bool `yaml:"ignore_error"` } if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" { c.Cmd = cmdStruct.Cmd c.Silent = cmdStruct.Silent + c.Warning = cmdStruct.Warning c.IgnoreError = cmdStruct.IgnoreError return nil } diff --git a/internal/taskfile/task.go b/internal/taskfile/task.go index 40c66b58a2..2083e059be 100644 --- a/internal/taskfile/task.go +++ b/internal/taskfile/task.go @@ -25,6 +25,7 @@ type Task struct { Silent bool Method string Prefix string + Warning string IgnoreError bool } @@ -69,6 +70,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { Silent bool Method string Prefix string + Warning string IgnoreError bool `yaml:"ignore_error"` } if err := unmarshal(&task); err == nil { @@ -87,6 +89,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { t.Silent = task.Silent t.Method = task.Method t.Prefix = task.Prefix + t.Warning = task.Warning t.IgnoreError = task.IgnoreError return nil diff --git a/internal/taskfile/taskfile.go b/internal/taskfile/taskfile.go index d9a17ac016..96d20f686e 100644 --- a/internal/taskfile/taskfile.go +++ b/internal/taskfile/taskfile.go @@ -16,6 +16,7 @@ type Taskfile struct { Env *Vars Tasks Tasks Silent bool + Warning string } // UnmarshalYAML implements yaml.Unmarshaler interface @@ -30,6 +31,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error { Env *Vars Tasks Tasks Silent bool + Warning string } if err := unmarshal(&taskfile); err != nil { return err @@ -43,6 +45,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error { tf.Env = taskfile.Env tf.Tasks = taskfile.Tasks tf.Silent = taskfile.Silent + tf.Warning = taskfile.Warning if tf.Expansions <= 0 { tf.Expansions = 2 } diff --git a/task.go b/task.go index 2807dd7fed..88896e655b 100644 --- a/task.go +++ b/task.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "strings" "sync" "sync/atomic" @@ -42,6 +43,7 @@ type Executor struct { Summary bool Parallel bool Color bool + Yes bool Stdin io.Reader Stdout io.Writer @@ -255,6 +257,14 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return &MaximumTaskCallExceededError{task: call.Task} } + if t.Warning != "" && !e.Yes { + response := promptWithWarning(t.Warning) + if !isConfirmed(response) { + // Skip task + return nil + } + } + if err := e.runDeps(ctx, t); err != nil { return err } @@ -345,6 +355,14 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } return nil case cmd.Cmd != "": + if cmd.Warning != "" && !e.Yes { + response := promptWithWarning(cmd.Warning) + if !isConfirmed(response) { + // Skip command + return nil + } + } + if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) { e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd) } @@ -399,3 +417,23 @@ func getEnviron(t *taskfile.Task) []string { } return environ } + +func promptWithWarning(warning string) string { + fmt.Printf("%s (y/N): ", warning) + + var response string + fmt.Scanln(&response) + + return response +} + +func isConfirmed(response string) bool { + affirmativeResponses := []string{"y", "yes"} + + for _, affirm := range affirmativeResponses { + if strings.ToLower(response) == affirm { + return true + } + } + return false +} diff --git a/variables.go b/variables.go index 4470d29652..8a2c937f26 100644 --- a/variables.go +++ b/variables.go @@ -41,6 +41,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { Vars: nil, Env: nil, Silent: origTask.Silent, + Warning: origTask.Warning, Method: r.Replace(origTask.Method), Prefix: r.Replace(origTask.Prefix), IgnoreError: origTask.IgnoreError, @@ -77,6 +78,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { new.Cmds[i] = &taskfile.Cmd{ Task: r.Replace(cmd.Task), Silent: cmd.Silent, + Warning: cmd.Warning, Cmd: r.Replace(cmd.Cmd), Vars: r.ReplaceVars(cmd.Vars), IgnoreError: cmd.IgnoreError,