feat: auto-detect color output in CI environments (#2569)

This commit is contained in:
Valentin Maerten
2025-12-18 08:40:37 +01:00
committed by GitHub
parent b710259bfa
commit 6660afc8d2
6 changed files with 49 additions and 6 deletions

View File

@@ -54,6 +54,11 @@
customize the cache directory for Remote Taskfiles (#2572 by @vmaerten). customize the cache directory for Remote Taskfiles (#2572 by @vmaerten).
- Zsh completion now supports zstyle verbose option to show or hide task - Zsh completion now supports zstyle verbose option to show or hide task
descriptions (#2571 by @vmaerten). 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 ## v3.45.5 - 2025-11-11

View File

@@ -5,8 +5,10 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"time" "time"
"github.com/fatih/color"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
@@ -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.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.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.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.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.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.")
@@ -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.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
} }
pflag.Parse() 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 { func Validate() error {

View File

@@ -3,7 +3,6 @@ package logger
import ( import (
"bufio" "bufio"
"io" "io"
"os"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -96,10 +95,6 @@ func BrightRed() PrintFunc {
} }
func envColor(name string, defaultColor color.Attribute) []color.Attribute { func envColor(name string, defaultColor color.Attribute) []color.Attribute {
if os.Getenv("FORCE_COLOR") != "" {
color.NoColor = false
}
// Fetch the environment variable // Fetch the environment variable
override := env.GetTaskEnv(name) override := env.GetTaskEnv(name)

View File

@@ -12,6 +12,7 @@ import (
type TaskRC struct { type TaskRC struct {
Version *semver.Version `yaml:"version"` Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"` Verbose *bool `yaml:"verbose"`
Color *bool `yaml:"color"`
DisableFuzzy *bool `yaml:"disable-fuzzy"` DisableFuzzy *bool `yaml:"disable-fuzzy"`
Concurrency *int `yaml:"concurrency"` Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"` Remote Remote `yaml:"remote"`
@@ -55,6 +56,7 @@ func (t *TaskRC) Merge(other *TaskRC) {
} }
t.Verbose = cmp.Or(other.Verbose, t.Verbose) 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.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency) t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
t.Failfast = cmp.Or(other.Failfast, t.Failfast) t.Failfast = cmp.Or(other.Failfast, t.Failfast)

View File

@@ -91,6 +91,17 @@ experiments:
verbose: true 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` ### `disable-fuzzy`
- **Type**: `boolean` - **Type**: `boolean`
@@ -131,6 +142,7 @@ Here's a complete example of a `.taskrc.yml` file with all available options:
```yaml ```yaml
# Global settings # Global settings
verbose: true verbose: true
color: true
disable-fuzzy: false disable-fuzzy: false
concurrency: 2 concurrency: 2

View File

@@ -57,6 +57,10 @@
"type": "boolean", "type": "boolean",
"description": "Enable verbose output" "description": "Enable verbose output"
}, },
"color": {
"type": "boolean",
"description": "Enable colored output"
},
"disable-fuzzy": { "disable-fuzzy": {
"type": "boolean", "type": "boolean",
"description": "Disable fuzzy matching for task names" "description": "Disable fuzzy matching for task names"