From 419442bba68cfe966f497bc2b97c2be71acef784 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sat, 13 Dec 2025 12:01:36 +0100 Subject: [PATCH] refactor(interactive): move interactive setting to taskrc --- executor.go | 14 +++++++++++ internal/flags/flags.go | 3 +++ requires.go | 12 ++++----- taskfile/ast/requires.go | 16 +++++------- taskrc/ast/taskrc.go | 2 ++ testdata/interactive_vars/Taskfile.yml | 20 ++++----------- website/src/docs/guide.md | 35 ++++++++++++++++---------- website/src/docs/reference/cli.md | 6 ++++- website/src/docs/reference/config.md | 14 +++++++++++ website/src/docs/reference/schema.md | 19 +++----------- website/src/public/schema-taskrc.json | 5 ++++ website/src/public/schema.json | 7 +----- 12 files changed, 86 insertions(+), 67 deletions(-) diff --git a/executor.go b/executor.go index 09bc3dc2..0e909b87 100644 --- a/executor.go +++ b/executor.go @@ -44,6 +44,7 @@ type ( AssumeYes bool AssumeTerm bool // Used for testing NoTTY bool + Interactive bool Dry bool Summary bool Parallel bool @@ -367,6 +368,19 @@ func (o *noTTYOption) ApplyToExecutor(e *Executor) { 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 // actually running them. func WithDry(dry bool) ExecutorOption { diff --git a/internal/flags/flags.go b/internal/flags/flags.go index b5d42716..62c899f1 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -80,6 +80,7 @@ var ( Timeout time.Duration CacheExpiryDuration time.Duration NoTTY bool + Interactive bool ) func init() { @@ -145,6 +146,7 @@ func init() { 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(&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.") // 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.WithAssumeYes(AssumeYes), task.WithNoTTY(NoTTY), + task.WithInteractive(Interactive), task.WithDry(Dry || Status), task.WithSummary(Summary), task.WithParallel(Parallel), diff --git a/requires.go b/requires.go index b0001b52..d934d328 100644 --- a/requires.go +++ b/requires.go @@ -9,7 +9,7 @@ import ( "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 // prompted for (meaning the task needs to be recompiled). 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 } + // 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 if e.NoTTY || (!e.AssumeTerm && !term.IsTerminal()) { return false, nil @@ -26,11 +31,6 @@ func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, erro var prompted bool for _, requiredVar := range t.Requires.Vars { - // Skip non-interactive vars - if !requiredVar.Interactive { - continue - } - // Skip if already set if _, ok := t.Vars.Get(requiredVar.Name); ok { continue diff --git a/taskfile/ast/requires.go b/taskfile/ast/requires.go index 6461bfa9..5a76e13f 100644 --- a/taskfile/ast/requires.go +++ b/taskfile/ast/requires.go @@ -23,9 +23,8 @@ func (r *Requires) DeepCopy() *Requires { } type VarsWithValidation struct { - Name string - Enum []string - Interactive bool + Name string + Enum []string } func (v *VarsWithValidation) DeepCopy() *VarsWithValidation { @@ -33,9 +32,8 @@ func (v *VarsWithValidation) DeepCopy() *VarsWithValidation { return nil } return &VarsWithValidation{ - Name: v.Name, - Enum: v.Enum, - Interactive: v.Interactive, + Name: v.Name, + Enum: v.Enum, } } @@ -54,16 +52,14 @@ func (v *VarsWithValidation) UnmarshalYAML(node *yaml.Node) error { case yaml.MappingNode: var vv struct { - Name string - Enum []string - Interactive bool + Name string + Enum []string } if err := node.Decode(&vv); err != nil { return errors.NewTaskfileDecodeError(err, node) } v.Name = vv.Name v.Enum = vv.Enum - v.Interactive = vv.Interactive return nil } diff --git a/taskrc/ast/taskrc.go b/taskrc/ast/taskrc.go index 8952315d..4d65b1ef 100644 --- a/taskrc/ast/taskrc.go +++ b/taskrc/ast/taskrc.go @@ -14,6 +14,7 @@ type TaskRC struct { Verbose *bool `yaml:"verbose"` DisableFuzzy *bool `yaml:"disable-fuzzy"` Concurrency *int `yaml:"concurrency"` + Interactive *bool `yaml:"interactive"` Remote Remote `yaml:"remote"` Failfast bool `yaml:"failfast"` 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.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy) t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency) + t.Interactive = cmp.Or(other.Interactive, t.Interactive) t.Failfast = cmp.Or(other.Failfast, t.Failfast) } diff --git a/testdata/interactive_vars/Taskfile.yml b/testdata/interactive_vars/Taskfile.yml index 049f27b4..2c1e4a85 100644 --- a/testdata/interactive_vars/Taskfile.yml +++ b/testdata/interactive_vars/Taskfile.yml @@ -4,8 +4,7 @@ tasks: simple: requires: vars: - - name: MY_VAR - interactive: true + - MY_VAR cmds: - echo "{{.MY_VAR}}" @@ -13,23 +12,14 @@ tasks: requires: vars: - name: ENV - interactive: true enum: [dev, staging, prod] cmds: - echo "{{.ENV}}" - non-interactive: + multiple: requires: vars: - - NON_INTERACTIVE_VAR + - VAR1 + - VAR2 cmds: - - echo "{{.NON_INTERACTIVE_VAR}}" - - mixed: - requires: - vars: - - name: INTERACTIVE_VAR - interactive: true - - NON_INTERACTIVE_VAR - cmds: - - echo "{{.INTERACTIVE_VAR}} {{.NON_INTERACTIVE_VAR}}" + - echo "{{.VAR1}} {{.VAR2}}" diff --git a/website/src/docs/guide.md b/website/src/docs/guide.md index aa67c554..0adec5b8 100644 --- a/website/src/docs/guide.md +++ b/website/src/docs/guide.md @@ -1129,14 +1129,20 @@ This is supported only for string variables. ### Prompting for missing variables interactively -If you want Task to prompt users for missing variables instead of failing, you -can mark a variable as `interactive: true`. When a variable is missing and has -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. +If you want Task to prompt users for missing required variables instead of +failing, you can enable interactive mode in your `.taskrc.yml`: ```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' tasks: @@ -1144,10 +1150,8 @@ tasks: requires: vars: - name: ENVIRONMENT - interactive: true enum: [dev, staging, prod] - - name: VERSION - interactive: true + - VERSION cmds: - echo "Deploying {{.VERSION}} to {{.ENVIRONMENT}}" ``` @@ -1158,6 +1162,8 @@ $ task deploy ❯ dev staging 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 @@ -1168,11 +1174,14 @@ $ task deploy ENVIRONMENT=prod VERSION=1.0.0 Deploying 1.0.0 to prod ``` -::: warning +::: info -Interactive prompts require a TTY. In non-interactive environments like CI -pipelines, use `--no-tty` to disable prompts (missing variables will cause an -error as usual), or provide all required variables explicitly. +Interactive prompts require a TTY (terminal). Task automatically detects +non-interactive environments like GitHub Actions, GitLab CI, and other CI +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. ::: diff --git a/website/src/docs/reference/cli.md b/website/src/docs/reference/cli.md index 260a8b2a..c59315ce 100644 --- a/website/src/docs/reference/cli.md +++ b/website/src/docs/reference/cli.md @@ -305,7 +305,11 @@ task deploy --yes Disable interactive prompts for missing variables. When a variable is marked 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 task deploy --no-tty diff --git a/website/src/docs/reference/config.md b/website/src/docs/reference/config.md index a0129e8f..0e305768 100644 --- a/website/src/docs/reference/config.md +++ b/website/src/docs/reference/config.md @@ -124,6 +124,20 @@ concurrency: 4 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 Here's a complete example of a `.taskrc.yml` file with all available options: diff --git a/website/src/docs/reference/schema.md b/website/src/docs/reference/schema.md index 9e526fe7..d0227d5d 100644 --- a/website/src/docs/reference/schema.md +++ b/website/src/docs/reference/schema.md @@ -632,7 +632,7 @@ tasks: #### `requires` - **Type**: `Requires` -- **Description**: Required variables with optional enums and interactive prompting +- **Description**: Required variables with optional enum validation ```yaml tasks: @@ -655,23 +655,10 @@ tasks: cmds: - echo "Deploying to {{.ENVIRONMENT}} with log level {{.LOG_LEVEL}}" - ./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 -if it is not already defined. For variables with `enum`, a selection menu is shown. -Use `--no-tty` to disable interactive prompts (useful for CI environments). +See [Prompting for missing variables interactively](/docs/guide#prompting-for-missing-variables-interactively) +for information on enabling interactive prompts for missing required variables. #### `watch` diff --git a/website/src/public/schema-taskrc.json b/website/src/public/schema-taskrc.json index 34c6ed53..d69b54be 100644 --- a/website/src/public/schema-taskrc.json +++ b/website/src/public/schema-taskrc.json @@ -70,6 +70,11 @@ "description": "When running tasks in parallel, stop all tasks if one fails.", "type": "boolean", "default": false + }, + "interactive": { + "description": "Prompt for missing required variables instead of failing. Requires a TTY.", + "type": "boolean", + "default": false } }, "additionalProperties": false diff --git a/website/src/public/schema.json b/website/src/public/schema.json index ab0c004c..e3dfa945 100644 --- a/website/src/public/schema.json +++ b/website/src/public/schema.json @@ -598,12 +598,7 @@ "type": "object", "properties": { "name": { "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 - } + "enum": { "type": "array", "items": { "type": "string" } } }, "required": ["name"], "additionalProperties": false