refactor(interactive): move interactive setting to taskrc

This commit is contained in:
Valentin Maerten
2025-12-13 12:01:36 +01:00
parent 2f01add607
commit 419442bba6
12 changed files with 86 additions and 67 deletions

View File

@@ -44,6 +44,7 @@ type (
AssumeYes bool AssumeYes bool
AssumeTerm bool // Used for testing AssumeTerm bool // Used for testing
NoTTY bool NoTTY bool
Interactive bool
Dry bool Dry bool
Summary bool Summary bool
Parallel bool Parallel bool
@@ -367,6 +368,19 @@ func (o *noTTYOption) ApplyToExecutor(e *Executor) {
e.NoTTY = o.noTTY e.NoTTY = o.noTTY
} }
// WithInteractive tells the [Executor] to prompt for missing required variables.
func WithInteractive(interactive bool) ExecutorOption {
return &interactiveOption{interactive}
}
type interactiveOption struct {
interactive bool
}
func (o *interactiveOption) ApplyToExecutor(e *Executor) {
e.Interactive = o.interactive
}
// WithDry tells the [Executor] to output the commands that would be run without // WithDry tells the [Executor] to output the commands that would be run without
// actually running them. // actually running them.
func WithDry(dry bool) ExecutorOption { func WithDry(dry bool) ExecutorOption {

View File

@@ -80,6 +80,7 @@ var (
Timeout time.Duration Timeout time.Duration
CacheExpiryDuration time.Duration CacheExpiryDuration time.Duration
NoTTY bool NoTTY bool
Interactive bool
) )
func init() { func init() {
@@ -145,6 +146,7 @@ func init() {
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.") pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.") pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.") pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
Interactive = getConfig(config, func() *bool { return config.Interactive }, false)
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.") pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
// Gentle force experiment will override the force flag and add a new force-all flag // Gentle force experiment will override the force flag and add a new force-all flag
@@ -255,6 +257,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithDisableFuzzy(DisableFuzzy), task.WithDisableFuzzy(DisableFuzzy),
task.WithAssumeYes(AssumeYes), task.WithAssumeYes(AssumeYes),
task.WithNoTTY(NoTTY), task.WithNoTTY(NoTTY),
task.WithInteractive(Interactive),
task.WithDry(Dry || Status), task.WithDry(Dry || Status),
task.WithSummary(Summary), task.WithSummary(Summary),
task.WithParallel(Parallel), task.WithParallel(Parallel),

View File

@@ -9,7 +9,7 @@ import (
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
// promptForInteractiveVars prompts the user for any missing interactive variables // promptForInteractiveVars prompts the user for any missing required variables
// and injects them into the call's Vars. It returns true if any variables were // and injects them into the call's Vars. It returns true if any variables were
// prompted for (meaning the task needs to be recompiled). // prompted for (meaning the task needs to be recompiled).
func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, error) { func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, error) {
@@ -17,6 +17,11 @@ func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, erro
return false, nil return false, nil
} }
// Don't prompt if interactive mode is disabled
if !e.Interactive {
return false, nil
}
// Don't prompt if NoTTY is set or we're not in a terminal // Don't prompt if NoTTY is set or we're not in a terminal
if e.NoTTY || (!e.AssumeTerm && !term.IsTerminal()) { if e.NoTTY || (!e.AssumeTerm && !term.IsTerminal()) {
return false, nil return false, nil
@@ -26,11 +31,6 @@ func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, erro
var prompted bool var prompted bool
for _, requiredVar := range t.Requires.Vars { for _, requiredVar := range t.Requires.Vars {
// Skip non-interactive vars
if !requiredVar.Interactive {
continue
}
// Skip if already set // Skip if already set
if _, ok := t.Vars.Get(requiredVar.Name); ok { if _, ok := t.Vars.Get(requiredVar.Name); ok {
continue continue

View File

@@ -23,9 +23,8 @@ func (r *Requires) DeepCopy() *Requires {
} }
type VarsWithValidation struct { type VarsWithValidation struct {
Name string Name string
Enum []string Enum []string
Interactive bool
} }
func (v *VarsWithValidation) DeepCopy() *VarsWithValidation { func (v *VarsWithValidation) DeepCopy() *VarsWithValidation {
@@ -33,9 +32,8 @@ func (v *VarsWithValidation) DeepCopy() *VarsWithValidation {
return nil return nil
} }
return &VarsWithValidation{ return &VarsWithValidation{
Name: v.Name, Name: v.Name,
Enum: v.Enum, Enum: v.Enum,
Interactive: v.Interactive,
} }
} }
@@ -54,16 +52,14 @@ func (v *VarsWithValidation) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode: case yaml.MappingNode:
var vv struct { var vv struct {
Name string Name string
Enum []string Enum []string
Interactive bool
} }
if err := node.Decode(&vv); err != nil { if err := node.Decode(&vv); err != nil {
return errors.NewTaskfileDecodeError(err, node) return errors.NewTaskfileDecodeError(err, node)
} }
v.Name = vv.Name v.Name = vv.Name
v.Enum = vv.Enum v.Enum = vv.Enum
v.Interactive = vv.Interactive
return nil return nil
} }

View File

@@ -14,6 +14,7 @@ type TaskRC struct {
Verbose *bool `yaml:"verbose"` Verbose *bool `yaml:"verbose"`
DisableFuzzy *bool `yaml:"disable-fuzzy"` DisableFuzzy *bool `yaml:"disable-fuzzy"`
Concurrency *int `yaml:"concurrency"` Concurrency *int `yaml:"concurrency"`
Interactive *bool `yaml:"interactive"`
Remote Remote `yaml:"remote"` Remote Remote `yaml:"remote"`
Failfast bool `yaml:"failfast"` Failfast bool `yaml:"failfast"`
Experiments map[string]int `yaml:"experiments"` Experiments map[string]int `yaml:"experiments"`
@@ -56,5 +57,6 @@ func (t *TaskRC) Merge(other *TaskRC) {
t.Verbose = cmp.Or(other.Verbose, t.Verbose) t.Verbose = cmp.Or(other.Verbose, t.Verbose)
t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy) t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency) t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
t.Interactive = cmp.Or(other.Interactive, t.Interactive)
t.Failfast = cmp.Or(other.Failfast, t.Failfast) t.Failfast = cmp.Or(other.Failfast, t.Failfast)
} }

View File

@@ -4,8 +4,7 @@ tasks:
simple: simple:
requires: requires:
vars: vars:
- name: MY_VAR - MY_VAR
interactive: true
cmds: cmds:
- echo "{{.MY_VAR}}" - echo "{{.MY_VAR}}"
@@ -13,23 +12,14 @@ tasks:
requires: requires:
vars: vars:
- name: ENV - name: ENV
interactive: true
enum: [dev, staging, prod] enum: [dev, staging, prod]
cmds: cmds:
- echo "{{.ENV}}" - echo "{{.ENV}}"
non-interactive: multiple:
requires: requires:
vars: vars:
- NON_INTERACTIVE_VAR - VAR1
- VAR2
cmds: cmds:
- echo "{{.NON_INTERACTIVE_VAR}}" - echo "{{.VAR1}} {{.VAR2}}"
mixed:
requires:
vars:
- name: INTERACTIVE_VAR
interactive: true
- NON_INTERACTIVE_VAR
cmds:
- echo "{{.INTERACTIVE_VAR}} {{.NON_INTERACTIVE_VAR}}"

View File

@@ -1129,14 +1129,20 @@ This is supported only for string variables.
### Prompting for missing variables interactively ### Prompting for missing variables interactively
If you want Task to prompt users for missing variables instead of failing, you If you want Task to prompt users for missing required variables instead of
can mark a variable as `interactive: true`. When a variable is missing and has failing, you can enable interactive mode in your `.taskrc.yml`:
this flag, Task will display an interactive prompt to collect the value.
For variables with an `enum`, a selection menu is shown. For variables without
an enum, a text input is displayed.
```yaml ```yaml
# ~/.taskrc.yml
interactive: true
```
When enabled, Task will display an interactive prompt for any missing required
variable. For variables with an `enum`, a selection menu is shown. For variables
without an enum, a text input is displayed.
```yaml
# Taskfile.yml
version: '3' version: '3'
tasks: tasks:
@@ -1144,10 +1150,8 @@ tasks:
requires: requires:
vars: vars:
- name: ENVIRONMENT - name: ENVIRONMENT
interactive: true
enum: [dev, staging, prod] enum: [dev, staging, prod]
- name: VERSION - VERSION
interactive: true
cmds: cmds:
- echo "Deploying {{.VERSION}} to {{.ENVIRONMENT}}" - echo "Deploying {{.VERSION}} to {{.ENVIRONMENT}}"
``` ```
@@ -1158,6 +1162,8 @@ $ task deploy
dev dev
staging staging
prod prod
? Enter value for VERSION: 1.0.0
Deploying 1.0.0 to prod
``` ```
If the variable is already set (via CLI, environment, or Taskfile), no prompt If the variable is already set (via CLI, environment, or Taskfile), no prompt
@@ -1168,11 +1174,14 @@ $ task deploy ENVIRONMENT=prod VERSION=1.0.0
Deploying 1.0.0 to prod Deploying 1.0.0 to prod
``` ```
::: warning ::: info
Interactive prompts require a TTY. In non-interactive environments like CI Interactive prompts require a TTY (terminal). Task automatically detects
pipelines, use `--no-tty` to disable prompts (missing variables will cause an non-interactive environments like GitHub Actions, GitLab CI, and other CI
error as usual), or provide all required variables explicitly. pipelines where stdin/stdout are not connected to a terminal. In these cases,
prompts are skipped and missing variables will cause an error as usual.
You can also explicitly disable prompts with `--no-tty` if needed.
::: :::

View File

@@ -305,7 +305,11 @@ task deploy --yes
Disable interactive prompts for missing variables. When a variable is marked Disable interactive prompts for missing variables. When a variable is marked
with `interactive: true` in the Taskfile and is not provided, Task will error with `interactive: true` in the Taskfile and is not provided, Task will error
instead of prompting for input. Useful in CI environments. instead of prompting for input.
Note: Task automatically detects non-TTY environments (like CI pipelines) and
disables prompts. This flag is useful when you want to explicitly disable
prompts even when a TTY is available.
```bash ```bash
task deploy --no-tty task deploy --no-tty

View File

@@ -124,6 +124,20 @@ concurrency: 4
failfast: true failfast: true
``` ```
### `interactive`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Prompt for missing required variables instead of failing.
When enabled, Task will display an interactive prompt for any missing required
variable. Requires a TTY. Task automatically detects non-TTY environments
(CI pipelines, etc.) and skips prompts.
- **CLI override**: [`--no-tty`](./cli.md#--no-tty) to disable prompts
```yaml
interactive: true
```
## Example Configuration ## Example Configuration
Here's a complete example of a `.taskrc.yml` file with all available options: Here's a complete example of a `.taskrc.yml` file with all available options:

View File

@@ -632,7 +632,7 @@ tasks:
#### `requires` #### `requires`
- **Type**: `Requires` - **Type**: `Requires`
- **Description**: Required variables with optional enums and interactive prompting - **Description**: Required variables with optional enum validation
```yaml ```yaml
tasks: tasks:
@@ -655,23 +655,10 @@ tasks:
cmds: cmds:
- echo "Deploying to {{.ENVIRONMENT}} with log level {{.LOG_LEVEL}}" - echo "Deploying to {{.ENVIRONMENT}} with log level {{.LOG_LEVEL}}"
- ./deploy.sh - ./deploy.sh
# Interactive prompts for missing variables
interactive-deploy:
requires:
vars:
- name: ENVIRONMENT
interactive: true
enum: [development, staging, production]
- name: API_KEY
interactive: true
cmds:
- ./deploy.sh
``` ```
When `interactive: true` is set, Task will prompt the user for the variable value See [Prompting for missing variables interactively](/docs/guide#prompting-for-missing-variables-interactively)
if it is not already defined. For variables with `enum`, a selection menu is shown. for information on enabling interactive prompts for missing required variables.
Use `--no-tty` to disable interactive prompts (useful for CI environments).
#### `watch` #### `watch`

View File

@@ -70,6 +70,11 @@
"description": "When running tasks in parallel, stop all tasks if one fails.", "description": "When running tasks in parallel, stop all tasks if one fails.",
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"interactive": {
"description": "Prompt for missing required variables instead of failing. Requires a TTY.",
"type": "boolean",
"default": false
} }
}, },
"additionalProperties": false "additionalProperties": false

View File

@@ -598,12 +598,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "type": "string" }, "name": { "type": "string" },
"enum": { "type": "array", "items": { "type": "string" } }, "enum": { "type": "array", "items": { "type": "string" } }
"interactive": {
"type": "boolean",
"description": "If true, prompt the user for this variable if it is not set",
"default": false
}
}, },
"required": ["name"], "required": ["name"],
"additionalProperties": false "additionalProperties": false