From e05c9f7793ed47f8864f8732cd65d809a40e1717 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Mon, 29 Dec 2025 16:45:41 +0100 Subject: [PATCH] fix(compiler): CLI vars have highest priority in scoped mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In scoped mode, CLI vars (e.g., `task foo VAR=value`) now correctly override task-level vars. This is achieved by: 1. Adding a `CLIVars` field to the Compiler struct 2. Storing CLI globals in this field after parsing 3. Applying CLI vars last in scoped mode to ensure they override everything The order of variable resolution in scoped mode is now: 1. OS env → {{.env.XXX}} 2. Root taskfile env → {{.env.XXX}} 3. Root taskfile vars → {{.VAR}} 4. Include taskfile env/vars (if applicable) 5. IncludeVars (vars passed via includes: section) 6. Task-level vars 7. CLI vars (highest priority) Legacy mode behavior is unchanged. --- cmd/task/task.go | 2 + compiler.go | 63 +++++++++++++++++--------- testdata/scoped_taskfiles/Taskfile.yml | 6 +++ 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/cmd/task/task.go b/cmd/task/task.go index b81e23dd..9274b247 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -174,6 +174,8 @@ func run() error { // Merge CLI variables first (e.g. FOO=bar) so they take priority over Taskfile defaults e.Taskfile.Vars.Merge(globals, nil) + // Store CLI vars for scoped mode where they need highest priority + e.Compiler.CLIVars = globals // Then ReverseMerge special variables so they're available for templating cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash) diff --git a/compiler.go b/compiler.go index f6a97814..1e24b3b3 100644 --- a/compiler.go +++ b/compiler.go @@ -26,6 +26,7 @@ type Compiler struct { TaskfileEnv *ast.Vars TaskfileVars *ast.Vars + CLIVars *ast.Vars // CLI vars passed via command line (e.g., task foo VAR=value) Graph *ast.TaskfileGraph Logger *logger.Logger @@ -210,38 +211,58 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (* } } - // Inject env namespace into result - result.Set("env", ast.Var{Value: envMap}) - } else { - // Legacy behavior: use merged vars - for k, v := range c.TaskfileEnv.All() { - if err := rangeFunc(k, v); err != nil { - return nil, err - } - } - for k, v := range c.TaskfileVars.All() { - if err := rangeFunc(k, v); err != nil { - return nil, err - } - } - if t != nil { - for k, v := range t.IncludeVars.All() { - if err := rangeFunc(k, v); err != nil { - return nil, err - } - } - for k, v := range t.IncludedTaskfileVars.All() { + // Apply task-level vars + if call != nil { + for k, v := range t.Vars.All() { if err := taskRangeFunc(k, v); err != nil { return nil, err } } } + + // CLI vars have highest priority - applied last to override everything + for k, v := range c.CLIVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } + } + + // Inject env namespace into result + result.Set("env", ast.Var{Value: envMap}) + + return result, nil + } + + // === LEGACY MODE === + // Legacy behavior: use merged vars + for k, v := range c.TaskfileEnv.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } + } + for k, v := range c.TaskfileVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } + } + if t != nil { + for k, v := range t.IncludeVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } + } + for k, v := range t.IncludedTaskfileVars.All() { + if err := taskRangeFunc(k, v); err != nil { + return nil, err + } + } } if t == nil || call == nil { return result, nil } + // Legacy order: CLI vars, then task vars (task vars override CLI) for k, v := range call.Vars.All() { if err := rangeFunc(k, v); err != nil { return nil, err diff --git a/testdata/scoped_taskfiles/Taskfile.yml b/testdata/scoped_taskfiles/Taskfile.yml index 739cd481..77185977 100644 --- a/testdata/scoped_taskfiles/Taskfile.yml +++ b/testdata/scoped_taskfiles/Taskfile.yml @@ -36,3 +36,9 @@ tasks: # In scoped mode, {{.ROOT_ENV}} should be empty (env not at root) # In legacy mode, {{.ROOT_ENV}} would have the value - echo "ROOT_ENV_AT_ROOT={{.ROOT_ENV}}" + + prout: + vars: + LOL: prout_from_root + cmds: + - echo "{{.LOL}}"