From 93cdccefcec6d3dffe0ce4cb5de69c75b46745c8 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 25 Jan 2026 22:14:19 +0100 Subject: [PATCH] feat(cmd): add ask option to commands Add an `ask` attribute to commands that shows a y/n confirmation before executing. If the user declines, the command is skipped but the task continues with the next command. Example: ```yaml cmds: - cmd: echo "Deploying..." ask: "Deploy to production?" - task: run-migrations ask: "Run database migrations?" ``` Behavior: - Default: asks for y/n confirmation - --yes: auto-confirms all asks - --dry: shows commands without asking This differs from task-level `prompt:` which cancels the entire task if declined. Command-level `ask:` only skips the individual command. --- task.go | 16 ++++++++++++++ taskfile/ast/cmd.go | 5 +++++ website/src/docs/reference/schema.md | 32 ++++++++++++++++++++++++++++ website/src/public/schema.json | 8 +++++++ 4 files changed, 61 insertions(+) diff --git a/task.go b/task.go index 0184793c..3e97aae4 100644 --- a/task.go +++ b/task.go @@ -365,6 +365,22 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in } } + // Handle ask attached to command (y/n confirmation) + if cmd.Ask != "" && !e.Dry { + if e.AssumeYes { + e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s [assuming yes]\n", t.Name(), cmd.Ask) + } else { + if err := e.Logger.Prompt(logger.Yellow, cmd.Ask, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) { + return &errors.TaskCancelledNoTerminalError{TaskName: call.Task} + } else if errors.Is(err, logger.ErrPromptCancelled) { + e.Logger.VerboseOutf(logger.Yellow, "task: [%s] ask declined - skipped\n", t.Name()) + return nil + } else if err != nil { + return err + } + } + } + switch { case cmd.Task != "": reacquire := e.releaseConcurrencyLimit() diff --git a/taskfile/ast/cmd.go b/taskfile/ast/cmd.go index 6f7a577d..406570a7 100644 --- a/taskfile/ast/cmd.go +++ b/taskfile/ast/cmd.go @@ -20,6 +20,7 @@ type Cmd struct { IgnoreError bool Defer bool Platforms []*Platform + Ask string } func (c *Cmd) DeepCopy() *Cmd { @@ -38,6 +39,7 @@ func (c *Cmd) DeepCopy() *Cmd { IgnoreError: c.IgnoreError, Defer: c.Defer, Platforms: deepcopy.Slice(c.Platforms), + Ask: c.Ask, } } @@ -65,6 +67,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error { IgnoreError bool `yaml:"ignore_error"` Defer *Defer Platforms []*Platform + Ask string } if err := node.Decode(&cmdStruct); err != nil { return errors.NewTaskfileDecodeError(err, node) @@ -98,6 +101,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error { c.If = cmdStruct.If c.Silent = cmdStruct.Silent c.IgnoreError = cmdStruct.IgnoreError + c.Ask = cmdStruct.Ask return nil } @@ -111,6 +115,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error { c.Shopt = cmdStruct.Shopt c.IgnoreError = cmdStruct.IgnoreError c.Platforms = cmdStruct.Platforms + c.Ask = cmdStruct.Ask return nil } diff --git a/website/src/docs/reference/schema.md b/website/src/docs/reference/schema.md index d0d42869..5b2d71c3 100644 --- a/website/src/docs/reference/schema.md +++ b/website/src/docs/reference/schema.md @@ -741,6 +741,8 @@ tasks: platforms: [linux, darwin] set: [errexit] shopt: [globstar] + if: '[ "$CI" = "true" ]' + ask: "Run this command?" ``` ### Task References @@ -857,6 +859,36 @@ tasks: if: '[ "{{.ITEM}}" != "b" ]' ``` +### Command Confirmations + +Use `ask` to request user confirmation before executing a command. If the +user declines (answers "n" or "no"), the command is skipped but the task +continues. + +```yaml +tasks: + deploy: + cmds: + - cmd: echo "Deploying to production..." + ask: "Deploy to production?" + - cmd: echo "Updating database..." + ask: "Run database migrations?" + - echo "Done!" # No ask, always runs +``` + +| Flag | Behavior | +|------|----------| +| (none) | Asks user for y/n confirmation | +| `--yes` | Auto-confirms all asks | +| `--dry` | Shows commands without asking | + +:::note + +This is different from the task-level `prompt:` which cancels the entire task +if declined. Command-level `ask:` only skips the individual command. + +::: + ## Shell Options ### Set Options diff --git a/website/src/public/schema.json b/website/src/public/schema.json index 28ae6611..298d8ac4 100644 --- a/website/src/public/schema.json +++ b/website/src/public/schema.json @@ -340,6 +340,10 @@ "if": { "description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.", "type": "string" + }, + "ask": { + "description": "A y/n confirmation shown before executing this task call. If declined, the task call is skipped.", + "type": "string" } }, "additionalProperties": false, @@ -381,6 +385,10 @@ "if": { "description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.", "type": "string" + }, + "ask": { + "description": "A y/n confirmation shown before executing this command. If declined, the command is skipped.", + "type": "string" } }, "additionalProperties": false,