mirror of
https://github.com/go-task/task.git
synced 2025-02-03 13:22:11 +02:00
feat: unify prompts (#1344)
This commit is contained in:
parent
222cd8c8f8
commit
dc77286282
@ -2,6 +2,9 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Enabled the `--yes` flag for the
|
||||
[Remote Taskfiles experiment](https://taskfile.dev/experiments/remote-taskfiles)
|
||||
(#1344 by @pd93).
|
||||
- Add ability to set `watch: true` in a task to automatically run it in watch
|
||||
mode (#231, #1361 by @andreynering).
|
||||
- Fixed a bug on the watch mode where paths that contained `.git` (like
|
||||
|
@ -54,6 +54,16 @@ Taskfiles:
|
||||
code `104` (not trusted) and nothing will run. If you accept the prompt, the
|
||||
checksum will be updated and the remote Taskfile will run.
|
||||
|
||||
Sometimes you need to run Task in an environment that does not have an
|
||||
interactive terminal, so you are not able to accept a prompt. In these cases you
|
||||
are able to tell task to accept these prompts automatically by using the `--yes`
|
||||
flag. Before enabling this flag, you should:
|
||||
|
||||
1. Be sure that you trust the source and contents of the remote Taskfile.
|
||||
2. Consider using a pinned version of the remote Taskfile (e.g. A link
|
||||
containing a commit hash) to prevent Task from automatically accepting a
|
||||
prompt that says a remote Taskfile has changed.
|
||||
|
||||
Task currently supports both `http` and `https` URLs. However, the `http`
|
||||
requests will not execute by default unless you run the task with the
|
||||
`--insecure` flag. This is to protect you from accidentally running a remote
|
||||
|
@ -9,6 +9,14 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/term"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPromptCancelled = errors.New("prompt cancelled")
|
||||
ErrNoTerminal = errors.New("no terminal")
|
||||
)
|
||||
|
||||
type (
|
||||
@ -59,10 +67,13 @@ func envColor(env string, defaultColor color.Attribute) color.Attribute {
|
||||
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
|
||||
// with optional color.
|
||||
type Logger struct {
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Verbose bool
|
||||
Color bool
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Verbose bool
|
||||
Color bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
}
|
||||
|
||||
// Outf prints stuff to STDOUT.
|
||||
@ -108,16 +119,32 @@ func (l *Logger) VerboseErrf(color Color, s string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Prompt(color Color, s string, defaultValue string, continueValues ...string) (bool, error) {
|
||||
if len(continueValues) == 0 {
|
||||
return false, nil
|
||||
func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continueValues ...string) error {
|
||||
if l.AssumeYes {
|
||||
l.Outf(color, "%s [assuming yes]\n", prompt)
|
||||
return nil
|
||||
}
|
||||
l.Outf(color, "%s [%s/%s]\n", s, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
if !l.AssumeTerm && !term.IsTerminal() {
|
||||
return ErrNoTerminal
|
||||
}
|
||||
|
||||
if len(continueValues) == 0 {
|
||||
return errors.New("no continue values provided")
|
||||
}
|
||||
|
||||
l.Outf(color, "%s [%s/%s]\n", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
|
||||
|
||||
reader := bufio.NewReader(l.Stdin)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
return slices.Contains(continueValues, input), nil
|
||||
if !slices.Contains(continueValues, input) {
|
||||
return ErrPromptCancelled
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
11
setup.go
11
setup.go
@ -157,10 +157,13 @@ func (e *Executor) setupStdFiles() {
|
||||
|
||||
func (e *Executor) setupLogger() {
|
||||
e.Logger = &logger.Logger{
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
Stdin: e.Stdin,
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
AssumeYes: e.AssumeYes,
|
||||
AssumeTerm: e.AssumeTerm,
|
||||
}
|
||||
}
|
||||
|
||||
|
29
task.go
29
task.go
@ -1,13 +1,11 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -23,7 +21,6 @@ import (
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/internal/term"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
|
||||
"github.com/sajari/fuzzy"
|
||||
@ -37,11 +34,6 @@ const (
|
||||
MaximumTaskCall = 1000
|
||||
)
|
||||
|
||||
func shouldPromptContinue(input string) bool {
|
||||
input = strings.ToLower(strings.TrimSpace(input))
|
||||
return slices.Contains([]string{"y", "yes"}, input)
|
||||
}
|
||||
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Taskfile *taskfile.Taskfile
|
||||
@ -58,13 +50,13 @@ type Executor struct {
|
||||
Verbose bool
|
||||
Silent bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
AssumesTerm bool
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
@ -182,22 +174,13 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
if t.Prompt != "" && !e.AssumeYes {
|
||||
if !e.AssumesTerm && !term.IsTerminal() {
|
||||
if t.Prompt != "" {
|
||||
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||
}
|
||||
|
||||
e.Logger.Outf(logger.Yellow, "task: %q [y/N]: ", t.Prompt)
|
||||
|
||||
reader := bufio.NewReader(e.Stdin)
|
||||
userInput, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userInput = strings.ToLower(strings.TrimSpace(userInput))
|
||||
if !shouldPromptContinue(userInput) {
|
||||
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
20
task_test.go
20
task_test.go
@ -681,11 +681,11 @@ func TestPromptInSummary(t *testing.T) {
|
||||
inBuff.Write([]byte(test.input))
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdin: &inBuff,
|
||||
Stdout: &outBuff,
|
||||
Stderr: &errBuff,
|
||||
AssumesTerm: true,
|
||||
Dir: dir,
|
||||
Stdin: &inBuff,
|
||||
Stdout: &outBuff,
|
||||
Stderr: &errBuff,
|
||||
AssumeTerm: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
@ -709,11 +709,11 @@ func TestPromptWithIndirectTask(t *testing.T) {
|
||||
inBuff.Write([]byte("y\n"))
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdin: &inBuff,
|
||||
Stdout: &outBuff,
|
||||
Stderr: &errBuff,
|
||||
AssumesTerm: true,
|
||||
Dir: dir,
|
||||
Stdin: &inBuff,
|
||||
Stdout: &outBuff,
|
||||
Stderr: &errBuff,
|
||||
AssumeTerm: true,
|
||||
}
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
|
@ -86,19 +86,19 @@ func readTaskfile(
|
||||
checksum := checksum(b)
|
||||
cachedChecksum := cache.readChecksum(node)
|
||||
|
||||
// If the checksum doesn't exist, prompt the user to continue
|
||||
var msg string
|
||||
if cachedChecksum == "" {
|
||||
if cont, err := l.Prompt(logger.Yellow, fmt.Sprintf("The task you are attempting to run depends on the remote Taskfile at %q.\n--- Make sure you trust the source of this Taskfile before continuing ---\nContinue?", node.Location()), "n", "y", "yes"); err != nil {
|
||||
return nil, err
|
||||
} else if !cont {
|
||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||
}
|
||||
// If the checksum doesn't exist, prompt the user to continue
|
||||
msg = fmt.Sprintf("The task you are attempting to run depends on the remote Taskfile at %q.\n--- Make sure you trust the source of this Taskfile before continuing ---\nContinue?", node.Location())
|
||||
} else if checksum != cachedChecksum {
|
||||
// If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue
|
||||
if cont, err := l.Prompt(logger.Yellow, fmt.Sprintf("The Taskfile at %q has changed since you last used it!\n--- Make sure you trust the source of this Taskfile before continuing ---\nContinue?", node.Location()), "n", "y", "yes"); err != nil {
|
||||
return nil, err
|
||||
} else if !cont {
|
||||
msg = fmt.Sprintf("The Taskfile at %q has changed since you last used it!\n--- Make sure you trust the source of this Taskfile before continuing ---\nContinue?", node.Location())
|
||||
}
|
||||
if msg != "" {
|
||||
if err := l.Prompt(logger.Yellow, msg, "n", "y", "yes"); errors.Is(err, logger.ErrPromptCancelled) {
|
||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user