Skip to content
Open
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
9 changes: 7 additions & 2 deletions experiments/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/joho/godotenv"

"github.com/go-task/task/v3/taskrc"
"github.com/go-task/task/v3/taskrc/ast"
)

const envPrefix = "TASK_X_"
Expand All @@ -31,11 +32,15 @@ var (
var xList []Experiment

func Parse(dir string) {
config, _ := taskrc.GetConfig(dir)

ParseWithConfig(dir, config)
}

func ParseWithConfig(dir string, config *ast.TaskRC) {
// Read any .env files
readDotEnv(dir)

config, _ := taskrc.GetConfig(dir)

// Initialize the experiments
GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
Expand Down
38 changes: 24 additions & 14 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"time"

"github.com/spf13/pflag"

"github.com/go-task/task/v3"
"github.com/go-task/task/v3/errors"
"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/taskfile/ast"
"github.com/go-task/task/v3/taskrc"
taskrcast "github.com/go-task/task/v3/taskrc/ast"
)

const usage = `Usage: task [flags...] [task...]
Expand Down Expand Up @@ -95,7 +95,9 @@ func init() {

// Parse the experiments
dir = cmp.Or(dir, filepath.Dir(entrypoint))
experiments.Parse(dir)

config, _ := taskrc.GetConfig(dir)
experiments.ParseWithConfig(dir, config)

// Parse the rest of the flags
log.SetFlags(0)
Expand All @@ -104,10 +106,7 @@ func init() {
log.Print(usage)
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.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
Expand All @@ -118,9 +117,9 @@ func init() {
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(&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(&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(&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.")
Expand All @@ -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.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.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.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.")
Expand All @@ -150,12 +149,11 @@ func init() {
// Remote Taskfiles experiment will adds the "download" and "offline" flags
if experiments.RemoteTaskfiles.Enabled() {
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.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
pflag.BoolVar(&Offline, "offline", getConfig(config, config.Remote.Offline, false), "Forces Task to only use local or cached 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.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()
}

Expand Down Expand Up @@ -251,3 +249,15 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
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
}
27 changes: 24 additions & 3 deletions taskrc/ast/taskrc.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
package ast

import (
"cmp"
"maps"
"time"

"github.com/Masterminds/semver/v3"
)

type TaskRC struct {
Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"`
Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"`
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.
func (t *TaskRC) Merge(other *TaskRC) {
if other == nil {
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 {
t.Experiments = other.Experiments
} else if t.Experiments != nil && other.Experiments != nil {
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)
}
18 changes: 16 additions & 2 deletions website/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { resolve } from 'path';
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
import {
groupIconMdPlugin,
groupIconVitePlugin
groupIconVitePlugin,
localIconLoader
} from 'vitepress-plugin-group-icons';
import { team } from './team.ts';
import { ogUrl, taskDescription, taskName } from './meta.ts';
Expand Down Expand Up @@ -107,7 +108,20 @@ export default defineConfig({
}
},
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: {
alias: [
{
Expand Down
1 change: 1 addition & 0 deletions website/.vitepress/theme/icons/task.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions website/src/docs/experiments/remote-taskfiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,64 @@ You can force Task to ignore the cache and download the latest version by using
the `--download` flag.

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"
```

41 changes: 41 additions & 0 deletions website/src/docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,44 @@ option_3: foo # Taken from $XDG_CONFIG_HOME/task/.taskrc.yml
The experiments section allows you to enable Task's experimental features. These
options are not enumerated here. Instead, please refer to our
[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
34 changes: 34 additions & 0 deletions website/src/public/schema-taskrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,40 @@
"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
Expand Down
Loading