1
0
mirror of https://github.com/go-task/task.git synced 2025-10-08 23:02:02 +02:00

feat: add some config to taskrc.yml (#2389)

Co-authored-by: Pete Davison <pd93.uk@outlook.com>
This commit is contained in:
Valentin Maerten
2025-09-10 17:57:52 +02:00
committed by GitHub
parent 534dfa089c
commit 0fdb5e8665
8 changed files with 208 additions and 21 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/go-task/task/v3/taskrc" "github.com/go-task/task/v3/taskrc"
"github.com/go-task/task/v3/taskrc/ast"
) )
const envPrefix = "TASK_X_" const envPrefix = "TASK_X_"
@@ -31,11 +32,15 @@ var (
var xList []Experiment var xList []Experiment
func Parse(dir string) { func Parse(dir string) {
config, _ := taskrc.GetConfig(dir)
ParseWithConfig(dir, config)
}
func ParseWithConfig(dir string, config *ast.TaskRC) {
// Read any .env files // Read any .env files
readDotEnv(dir) readDotEnv(dir)
config, _ := taskrc.GetConfig(dir)
// Initialize the experiments // Initialize the experiments
GentleForce = New("GENTLE_FORCE", config, 1) GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1) RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)

View File

@@ -5,7 +5,6 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"time" "time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -13,9 +12,10 @@ import (
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/experiments" "github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/sort" "github.com/go-task/task/v3/internal/sort"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
"github.com/go-task/task/v3/taskrc"
taskrcast "github.com/go-task/task/v3/taskrc/ast"
) )
const usage = `Usage: task [flags...] [task...] const usage = `Usage: task [flags...] [task...]
@@ -95,7 +95,9 @@ func init() {
// Parse the experiments // Parse the experiments
dir = cmp.Or(dir, filepath.Dir(entrypoint)) dir = cmp.Or(dir, filepath.Dir(entrypoint))
experiments.Parse(dir)
config, _ := taskrc.GetConfig(dir)
experiments.ParseWithConfig(dir, config)
// Parse the rest of the flags // Parse the rest of the flags
log.SetFlags(0) log.SetFlags(0)
@@ -104,10 +106,7 @@ func init() {
log.Print(usage) log.Print(usage)
pflag.PrintDefaults() pflag.PrintDefaults()
} }
offline, err := strconv.ParseBool(cmp.Or(env.GetTaskEnv("OFFLINE"), "false"))
if err != nil {
offline = false
}
pflag.BoolVar(&Version, "version", false, "Show Task version.") pflag.BoolVar(&Version, "version", false, "Show Task version.")
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.") pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.") pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
@@ -118,9 +117,9 @@ func init() {
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].") pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.") pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON") pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
pflag.BoolVar(&Insecure, "insecure", false, "Forces Task to download Taskfiles over insecure connections.") pflag.BoolVar(&Insecure, "insecure", getConfig(config, config.Remote.Insecure, false), "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.") pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
pflag.BoolVarP(&Verbose, "verbose", "v", false, "Enables verbose mode.") pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, config.Verbose, false), "Enables verbose mode.")
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.") pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.") pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.") pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
@@ -134,7 +133,7 @@ func init() {
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", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.IntVarP(&Concurrency, "concurrency", "C", 0, "Limit number of tasks to run concurrently.") pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, 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(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.") pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.") pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
@@ -150,12 +149,11 @@ func init() {
// Remote Taskfiles experiment will adds the "download" and "offline" flags // Remote Taskfiles experiment will adds the "download" and "offline" flags
if experiments.RemoteTaskfiles.Enabled() { if experiments.RemoteTaskfiles.Enabled() {
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.") pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&Offline, "offline", offline, "Forces Task to only use local or cached Taskfiles.") pflag.BoolVar(&Offline, "offline", getConfig(config, config.Remote.Offline, false), "Forces Task to only use local or cached Taskfiles.")
pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.") pflag.DurationVar(&Timeout, "timeout", getConfig(config, config.Remote.Timeout, time.Second*10), "Timeout for downloading remote Taskfiles.")
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.") pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
pflag.DurationVar(&CacheExpiryDuration, "expiry", 0, "Expiry duration for cached remote Taskfiles.") pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, config.Remote.Timeout, 0), "Expiry duration for cached remote Taskfiles.")
} }
pflag.Parse() pflag.Parse()
} }
@@ -251,3 +249,15 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithVersionCheck(true), task.WithVersionCheck(true),
) )
} }
// getConfig extracts a config value directly from a pointer field with a fallback default
func getConfig[T any](config *taskrcast.TaskRC, field *T, fallback T) T {
if config == nil {
return fallback
}
if field != nil {
return *field
}
return fallback
}

View File

@@ -1,27 +1,48 @@
package ast package ast
import ( import (
"cmp"
"maps" "maps"
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
) )
type TaskRC struct { type TaskRC struct {
Version *semver.Version `yaml:"version"` Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"`
Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"`
Experiments map[string]int `yaml:"experiments"` Experiments map[string]int `yaml:"experiments"`
} }
type Remote struct {
Insecure *bool `yaml:"insecure"`
Offline *bool `yaml:"offline"`
Timeout *time.Duration `yaml:"timeout"`
CacheExpiry *time.Duration `yaml:"cache-expiry"`
}
// Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC. // Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC.
func (t *TaskRC) Merge(other *TaskRC) { func (t *TaskRC) Merge(other *TaskRC) {
if other == nil { if other == nil {
return return
} }
if t.Version == nil && other.Version != nil {
t.Version = other.Version t.Version = cmp.Or(other.Version, t.Version)
}
if t.Experiments == nil && other.Experiments != nil { if t.Experiments == nil && other.Experiments != nil {
t.Experiments = other.Experiments t.Experiments = other.Experiments
} else if t.Experiments != nil && other.Experiments != nil { } else if t.Experiments != nil && other.Experiments != nil {
maps.Copy(t.Experiments, other.Experiments) maps.Copy(t.Experiments, other.Experiments)
} }
// Merge Remote fields
t.Remote.Insecure = cmp.Or(other.Remote.Insecure, t.Remote.Insecure)
t.Remote.Offline = cmp.Or(other.Remote.Offline, t.Remote.Offline)
t.Remote.Timeout = cmp.Or(other.Remote.Timeout, t.Remote.Timeout)
t.Remote.CacheExpiry = cmp.Or(other.Remote.CacheExpiry, t.Remote.CacheExpiry)
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
} }

View File

@@ -5,7 +5,8 @@ import { resolve } from 'path';
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'; import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
import { import {
groupIconMdPlugin, groupIconMdPlugin,
groupIconVitePlugin groupIconVitePlugin,
localIconLoader
} from 'vitepress-plugin-group-icons'; } from 'vitepress-plugin-group-icons';
import { team } from './team.ts'; import { team } from './team.ts';
import { taskDescription, taskName } from './meta.ts'; import { taskDescription, taskName } from './meta.ts';
@@ -99,7 +100,20 @@ export default defineConfig({
} }
}, },
vite: { vite: {
plugins: [groupIconVitePlugin()], plugins: [
groupIconVitePlugin({
customIcon: {
'.taskrc.yml': localIconLoader(
import.meta.url,
'./theme/icons/task.svg'
),
'Taskfile.yml': localIconLoader(
import.meta.url,
'./theme/icons/task.svg'
)
}
})
],
resolve: { resolve: {
alias: [ alias: [
{ {

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 375 375"><path fill="#29beb0" d="M 187.570312 190.933594 L 187.570312 375 L 30.070312 279.535156 L 30.070312 95.464844 Z"/><path fill="#69d2c8" d="M 187.570312 190.933594 L 187.570312 375 L 345.070312 279.535156 L 345.070312 95.464844 Z"/><path fill="#94dfd8" d="M 187.570312 190.933594 L 30.070312 95.464844 L 187.570312 0 L 345.070312 95.464844 Z"/></svg>

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -290,3 +290,64 @@ You can force Task to ignore the cache and download the latest version by using
the `--download` flag. the `--download` flag.
You can use the `--clear-cache` flag to clear all cached remote files. You can use the `--clear-cache` flag to clear all cached remote files.
## Configuration
This experiment adds a new `remote` section to the [configuration file](../reference/config.md).
- **Type**: `object`
- **Description**: Remote configuration settings for handling remote Taskfiles
```yaml
remote:
insecure: false
offline: false
timeout: "30s"
cache-expiry: "24h"
```
#### `insecure`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Allow insecure connections when fetching remote Taskfiles
```yaml
remote:
insecure: true
```
#### `offline`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Work in offline mode, preventing remote Taskfile fetching
```yaml
remote:
offline: true
```
#### `timeout`
- **Type**: `string`
- **Default**: Not specified
- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$`
- **Description**: Timeout duration for remote operations (e.g., '30s', '5m')
```yaml
remote:
timeout: "1m"
```
#### `cache-expiry`
- **Type**: `string`
- **Default**: Not specified
- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$`
- **Description**: Cache expiry duration for remote Taskfiles (e.g., '1h', '24h')
```yaml
remote:
cache-expiry: "6h"
```

View File

@@ -73,3 +73,44 @@ option_3: foo # Taken from $XDG_CONFIG_HOME/task/.taskrc.yml
The experiments section allows you to enable Task's experimental features. These The experiments section allows you to enable Task's experimental features. These
options are not enumerated here. Instead, please refer to our options are not enumerated here. Instead, please refer to our
[experiments documentation](../experiments/index.md) for more information. [experiments documentation](../experiments/index.md) for more information.
```yaml
experiments:
feature_name: 1
another_feature: 2
```
### `verbose`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Enable verbose output for all tasks
- **CLI equivalent**: [`-v, --verbose`](./cli.md#-v---verbose)
```yaml
verbose: true
```
### `concurrency`
- **Type**: `integer`
- **Minimum**: `1`
- **Description**: Number of concurrent tasks to run
- **CLI equivalent**: [`-C, --concurrency`](./cli.md#-c---concurrency-number)
```yaml
concurrency: 4
```
## Example Configuration
Here's a complete example of a `.taskrc.yml` file with all available options:
```yaml
# Global settings
verbose: true
concurrency: 2
# Enable experimental features
experiments:
REMOTE_TASKFILES: 1

View File

@@ -20,6 +20,40 @@
"enum": [0, 1] "enum": [0, 1]
} }
} }
},
"remote": {
"type": "object",
"description": "Remote configuration settings",
"properties": {
"insecure": {
"type": "boolean",
"description": "Forces Task to download Taskfiles over insecure connections."
},
"offline": {
"type": "boolean",
"description": "Forces Task to only use local or cached Taskfiles."
},
"timeout": {
"type": "string",
"description": "Timeout for downloading remote Taskfiles (e.g., '30s', '5m')",
"pattern": "^[0-9]+(ns|us|µs|ms|s|m|h)$"
},
"cache-expiry": {
"type": "string",
"description": "Expiry duration for cached remote Taskfiles (e.g., '1h', '24h')",
"pattern": "^[0-9]+(ns|us|µs|ms|s|m|h)$"
}
},
"additionalProperties": false
},
"verbose": {
"type": "boolean",
"description": "Enable verbose output"
},
"concurrency": {
"type": "integer",
"description": "Number of concurrent tasks to run",
"minimum": 1
} }
}, },
"additionalProperties": false "additionalProperties": false