diff --git a/executor.go b/executor.go index 0e909b87..85516890 100644 --- a/executor.go +++ b/executor.go @@ -54,9 +54,11 @@ type ( Failfast bool // I/O - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + rawStdout io.Writer // unwrapped stdout for prompts + rawStderr io.Writer // unwrapped stderr for prompts // Internal Taskfile *ast.Taskfile @@ -71,6 +73,7 @@ type ( fuzzyModel *fuzzy.Model fuzzyModelOnce sync.Once + promptMutex sync.Mutex concurrencySemaphore chan struct{} taskCallCount map[string]*int32 mkdirMutexMap map[string]*sync.Mutex diff --git a/internal/output/sync_writer.go b/internal/output/sync_writer.go new file mode 100644 index 00000000..8cddd030 --- /dev/null +++ b/internal/output/sync_writer.go @@ -0,0 +1,28 @@ +package output + +import ( + "io" + "sync" +) + +// SyncWriter wraps an io.Writer with a mutex to synchronize writes. +// This is used to prevent output from interleaving with interactive prompts. +type SyncWriter struct { + w io.Writer + mu *sync.Mutex +} + +// NewSyncWriter creates a new SyncWriter that uses the provided mutex. +func NewSyncWriter(w io.Writer, mu *sync.Mutex) *SyncWriter { + return &SyncWriter{ + w: w, + mu: mu, + } +} + +// Write implements io.Writer with synchronized access. +func (sw *SyncWriter) Write(p []byte) (n int, err error) { + sw.mu.Lock() + defer sw.mu.Unlock() + return sw.w.Write(p) +} diff --git a/requires.go b/requires.go index d934d328..9d4c1089 100644 --- a/requires.go +++ b/requires.go @@ -27,7 +27,16 @@ func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, erro return false, nil } - prompter := prompt.New() + // Lock to prevent multiple parallel prompts from interleaving + e.promptMutex.Lock() + defer e.promptMutex.Unlock() + + // Use raw stderr for prompts to avoid deadlock with SyncWriter + prompter := &prompt.Prompter{ + Stdin: e.Stdin, + Stdout: e.rawStdout, + Stderr: e.rawStderr, + } var prompted bool for _, requiredVar := range t.Requires.Vars { diff --git a/setup.go b/setup.go index 2fc3a6bf..32a59c23 100644 --- a/setup.go +++ b/setup.go @@ -179,6 +179,17 @@ func (e *Executor) setupStdFiles() { if e.Stderr == nil { e.Stderr = os.Stderr } + + // Keep raw references for interactive prompts + e.rawStdout = e.Stdout + e.rawStderr = e.Stderr + + // Wrap with synchronized writers when interactive mode is enabled + // to prevent output from interleaving with prompts + if e.Interactive { + e.Stdout = output.NewSyncWriter(e.Stdout, &e.promptMutex) + e.Stderr = output.NewSyncWriter(e.Stderr, &e.promptMutex) + } } func (e *Executor) setupLogger() { diff --git a/testdata/interactive_vars/.taskrc.yml b/testdata/interactive_vars/.taskrc.yml new file mode 100644 index 00000000..9f4af056 --- /dev/null +++ b/testdata/interactive_vars/.taskrc.yml @@ -0,0 +1 @@ +interactive: true diff --git a/testdata/interactive_vars/Taskfile.yml b/testdata/interactive_vars/Taskfile.yml index 2c1e4a85..d26dfdc4 100644 --- a/testdata/interactive_vars/Taskfile.yml +++ b/testdata/interactive_vars/Taskfile.yml @@ -1,25 +1,66 @@ version: "3" tasks: - simple: - requires: - vars: - - MY_VAR + main: + desc: Main task with nested deps + deps: + - dep-a + - dep-b cmds: - - echo "{{.MY_VAR}}" + - echo "Main task done!" - with-enum: - requires: - vars: - - name: ENV - enum: [dev, staging, prod] + dep-a: + desc: Dependency A + deps: + - leaf-a1 + - leaf-a2 cmds: - - echo "{{.ENV}}" + - echo "Dep A done" - multiple: + dep-b: + desc: Dependency B + deps: + - leaf-b1 + - leaf-b2 + cmds: + - echo "Dep B done" + + leaf-a1: + desc: Leaf A1 with enum requires: vars: - - VAR1 - - VAR2 + - name: VAR_A1 + enum: + - alpha + - beta + - gamma cmds: - - echo "{{.VAR1}} {{.VAR2}}" + - echo "Leaf A1 {{.VAR_A1}}" + + leaf-a2: + desc: Leaf A2 with text + requires: + vars: + - VAR_A2 + cmds: + - echo "Leaf A2 {{.VAR_A2}}" + + leaf-b1: + desc: Leaf B1 with enum + requires: + vars: + - name: VAR_B1 + enum: + - one + - two + - three + cmds: + - echo "Leaf B1 {{.VAR_B1}}" + + leaf-b2: + desc: Leaf B2 with text + requires: + vars: + - VAR_B2 + cmds: + - echo "Leaf B2 {{.VAR_B2}}"