mirror of
https://github.com/go-task/task.git
synced 2025-12-16 11:47:44 +01:00
feat: unify prompts (#1344)
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## Unreleased
|
## 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
|
- Add ability to set `watch: true` in a task to automatically run it in watch
|
||||||
mode (#231, #1361 by @andreynering).
|
mode (#231, #1361 by @andreynering).
|
||||||
- Fixed a bug on the watch mode where paths that contained `.git` (like
|
- 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
|
code `104` (not trusted) and nothing will run. If you accept the prompt, the
|
||||||
checksum will be updated and the remote Taskfile will run.
|
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`
|
Task currently supports both `http` and `https` URLs. However, the `http`
|
||||||
requests will not execute by default unless you run the task with the
|
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
|
`--insecure` flag. This is to protect you from accidentally running a remote
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"golang.org/x/exp/slices"
|
"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 (
|
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,
|
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
|
||||||
// with optional color.
|
// with optional color.
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
Stdout io.Writer
|
Stdin io.Reader
|
||||||
Stderr io.Writer
|
Stdout io.Writer
|
||||||
Verbose bool
|
Stderr io.Writer
|
||||||
Color bool
|
Verbose bool
|
||||||
|
Color bool
|
||||||
|
AssumeYes bool
|
||||||
|
AssumeTerm bool // Used for testing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outf prints stuff to STDOUT.
|
// 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) {
|
func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continueValues ...string) error {
|
||||||
if len(continueValues) == 0 {
|
if l.AssumeYes {
|
||||||
return false, nil
|
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')
|
input, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
input = strings.TrimSpace(strings.ToLower(input))
|
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() {
|
func (e *Executor) setupLogger() {
|
||||||
e.Logger = &logger.Logger{
|
e.Logger = &logger.Logger{
|
||||||
Stdout: e.Stdout,
|
Stdin: e.Stdin,
|
||||||
Stderr: e.Stderr,
|
Stdout: e.Stdout,
|
||||||
Verbose: e.Verbose,
|
Stderr: e.Stderr,
|
||||||
Color: e.Color,
|
Verbose: e.Verbose,
|
||||||
|
Color: e.Color,
|
||||||
|
AssumeYes: e.AssumeYes,
|
||||||
|
AssumeTerm: e.AssumeTerm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
task.go
29
task.go
@@ -1,13 +1,11 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -23,7 +21,6 @@ import (
|
|||||||
"github.com/go-task/task/v3/internal/sort"
|
"github.com/go-task/task/v3/internal/sort"
|
||||||
"github.com/go-task/task/v3/internal/summary"
|
"github.com/go-task/task/v3/internal/summary"
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"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/go-task/task/v3/taskfile"
|
||||||
|
|
||||||
"github.com/sajari/fuzzy"
|
"github.com/sajari/fuzzy"
|
||||||
@@ -37,11 +34,6 @@ const (
|
|||||||
MaximumTaskCall = 1000
|
MaximumTaskCall = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
func shouldPromptContinue(input string) bool {
|
|
||||||
input = strings.ToLower(strings.TrimSpace(input))
|
|
||||||
return slices.Contains([]string{"y", "yes"}, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executor executes a Taskfile
|
// Executor executes a Taskfile
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
Taskfile *taskfile.Taskfile
|
Taskfile *taskfile.Taskfile
|
||||||
@@ -58,13 +50,13 @@ type Executor struct {
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
Silent bool
|
Silent bool
|
||||||
AssumeYes bool
|
AssumeYes bool
|
||||||
|
AssumeTerm bool // Used for testing
|
||||||
Dry bool
|
Dry bool
|
||||||
Summary bool
|
Summary bool
|
||||||
Parallel bool
|
Parallel bool
|
||||||
Color bool
|
Color bool
|
||||||
Concurrency int
|
Concurrency int
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
AssumesTerm bool
|
|
||||||
|
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
@@ -182,22 +174,13 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
release := e.acquireConcurrencyLimit()
|
release := e.acquireConcurrencyLimit()
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
if t.Prompt != "" && !e.AssumeYes {
|
if t.Prompt != "" {
|
||||||
if !e.AssumesTerm && !term.IsTerminal() {
|
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||||
}
|
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||||
|
|
||||||
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) {
|
|
||||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
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))
|
inBuff.Write([]byte(test.input))
|
||||||
|
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Stdin: &inBuff,
|
Stdin: &inBuff,
|
||||||
Stdout: &outBuff,
|
Stdout: &outBuff,
|
||||||
Stderr: &errBuff,
|
Stderr: &errBuff,
|
||||||
AssumesTerm: true,
|
AssumeTerm: true,
|
||||||
}
|
}
|
||||||
require.NoError(t, e.Setup())
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
@@ -709,11 +709,11 @@ func TestPromptWithIndirectTask(t *testing.T) {
|
|||||||
inBuff.Write([]byte("y\n"))
|
inBuff.Write([]byte("y\n"))
|
||||||
|
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Stdin: &inBuff,
|
Stdin: &inBuff,
|
||||||
Stdout: &outBuff,
|
Stdout: &outBuff,
|
||||||
Stderr: &errBuff,
|
Stderr: &errBuff,
|
||||||
AssumesTerm: true,
|
AssumeTerm: true,
|
||||||
}
|
}
|
||||||
require.NoError(t, e.Setup())
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
|||||||
@@ -86,19 +86,19 @@ func readTaskfile(
|
|||||||
checksum := checksum(b)
|
checksum := checksum(b)
|
||||||
cachedChecksum := cache.readChecksum(node)
|
cachedChecksum := cache.readChecksum(node)
|
||||||
|
|
||||||
// If the checksum doesn't exist, prompt the user to continue
|
var msg string
|
||||||
if cachedChecksum == "" {
|
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 {
|
// If the checksum doesn't exist, prompt the user to continue
|
||||||
return nil, err
|
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 !cont {
|
|
||||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
|
||||||
}
|
|
||||||
} else if checksum != cachedChecksum {
|
} else if checksum != cachedChecksum {
|
||||||
// If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue
|
// 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 {
|
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())
|
||||||
return nil, err
|
}
|
||||||
} else if !cont {
|
if msg != "" {
|
||||||
|
if err := l.Prompt(logger.Yellow, msg, "n", "y", "yes"); errors.Is(err, logger.ErrPromptCancelled) {
|
||||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user