Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
customize the cache directory for Remote Taskfiles (#2572 by @vmaerten).
- Zsh completion now supports zstyle verbose option to show or hide task
descriptions (#2571 by @vmaerten).
- Task now automatically enables colored output in CI environments (GitHub
Actions, GitLab CI, etc.) without requiring FORCE_COLOR=1 (#2569 by
@vmaerten).
- Added color taskrc option to explicitly enable or disable colored output
globally (#2569 by @vmaerten).

## v3.45.5 - 2025-11-11

Expand Down
27 changes: 26 additions & 1 deletion internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"time"

"github.com/fatih/color"
"github.com/spf13/pflag"

"github.com/go-task/task/v3"
Expand Down Expand Up @@ -140,7 +142,7 @@ func init() {
pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.")
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.BoolVarP(&Color, "color", "c", getConfig(config, func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
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.")
Expand All @@ -166,6 +168,29 @@ func init() {
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
}
pflag.Parse()

// Auto-detect color based on environment when not explicitly configured
// Priority: CLI flag > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
colorExplicitlySet := pflag.Lookup("color").Changed || (config != nil && config.Color != nil)
if !colorExplicitlySet {
if os.Getenv("NO_COLOR") != "" {
Color = false
color.NoColor = true
} else if os.Getenv("FORCE_COLOR") != "" || isCI() {
Color = true
color.NoColor = false // Force colors even without TTY
}
// Otherwise, let fatih/color auto-detect TTY
} else {
// Explicit config: sync with fatih/color
color.NoColor = !Color
}
}

// isCI returns true if running in a CI environment
func isCI() bool {
ci, _ := strconv.ParseBool(os.Getenv("CI"))
return ci
}

func Validate() error {
Expand Down
5 changes: 0 additions & 5 deletions internal/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package logger
import (
"bufio"
"io"
"os"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -96,10 +95,6 @@ func BrightRed() PrintFunc {
}

func envColor(name string, defaultColor color.Attribute) []color.Attribute {
if os.Getenv("FORCE_COLOR") != "" {
color.NoColor = false
}

// Fetch the environment variable
override := env.GetTaskEnv(name)

Expand Down
2 changes: 2 additions & 0 deletions taskrc/ast/taskrc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type TaskRC struct {
Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"`
Color *bool `yaml:"color"`
DisableFuzzy *bool `yaml:"disable-fuzzy"`
Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"`
Expand Down Expand Up @@ -55,6 +56,7 @@ func (t *TaskRC) Merge(other *TaskRC) {
}

t.Verbose = cmp.Or(other.Verbose, t.Verbose)
t.Color = cmp.Or(other.Color, t.Color)
t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
t.Failfast = cmp.Or(other.Failfast, t.Failfast)
Expand Down
12 changes: 12 additions & 0 deletions website/src/docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ experiments:
verbose: true
```

### `color`

- **Type**: `boolean`
- **Default**: `true`
- **Description**: Enable colored output. Colors are automatically enabled in CI environments (`CI=true`).
- **CLI equivalent**: [`-c, --color`](./cli.md#-c---color)

```yaml
color: false
```

### `disable-fuzzy`

- **Type**: `boolean`
Expand Down Expand Up @@ -131,6 +142,7 @@ Here's a complete example of a `.taskrc.yml` file with all available options:
```yaml
# Global settings
verbose: true
color: true
disable-fuzzy: false
concurrency: 2

Expand Down
4 changes: 4 additions & 0 deletions website/src/public/schema-taskrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
"type": "boolean",
"description": "Enable verbose output"
},
"color": {
"type": "boolean",
"description": "Enable colored output"
},
"disable-fuzzy": {
"type": "boolean",
"description": "Disable fuzzy matching for task names"
Expand Down
Loading