Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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.
90 changes: 90 additions & 0 deletions website/src/docs/reference/cli.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up for debate, but I'm not convinced that these flags should be documented here. I've always seen the experiments as independent from the rest of Task and fully documented within their experiments page. This means that the main docs are stable and not changing all the time.

The intention would then be to move the experiment docs to the appropriate place when released.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get your point, but it still feels a bit odd to me that it’s in reference/config.yml but not in cli.md.
Personally, I think all the flags you see in task -h should also show up in cli.md.

Remote experiments are used a lot, and it’s not always clear which flags exist or what they actually do just from the experiment page. That’s why I suggested documenting it, but with a note like:

This flag is linked to an experiment (see XXXXX) and could be removed at any time.

That said, if you both prefer not to include it here, I’m fine with removing it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for the config reference, I would include a section for the experiments: key, but not include the specific experiments. Just say something along the lines of "please refer to the experiments pages for more details" and then describe the name of the key and values over there.

I don't think specific experiments should be described in any pages outside the experiments docs themselves.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean but I've removed all remote and gentle force stuff from CLI.md

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! Let me know if this is what you had in mind 🙂

Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,96 @@ Set watch interval (default: `5s`). Must be a valid
task build --watch --interval 1s
```

### Gentle Force <Badge type="warning" text="Gentle Force experiment" />

::: info

These flags are only available when the
[Gentle Force experiment](../experiments/gentle-force.md) is enabled.

:::

When the Gentle Force experiment is enabled, the behavior of the `--force` flag changes and a new `--force-all` flag becomes available:

#### `--force-all`

Force execution of the called task and all its dependent tasks.

```bash
task deploy --force-all
```

#### `-f, --force` (Modified behavior)

When Gentle Force is enabled, this flag only forces execution of the directly called task (not its dependencies).

```bash
task deploy --force
```

::: tip

Without the Gentle Force experiment, `--force` behaves like `--force-all` does when the experiment is enabled.

:::

### Remote Taskfiles <Badge type="warning" text="Remote experimentation" />

::: info

These flags are only available when the
[Remote Taskfiles experiment](../experiments/remote-taskfiles.md) is enabled.

:::

#### `--insecure`

Allow insecure connections when fetching remote Taskfiles.

```bash
task --taskfile https://example.com/Taskfile.yml --insecure
```

#### `--download`

Downloads a cached version of a remote Taskfile.

```bash
task --download --taskfile https://example.com/Taskfile.yml
```

#### `--offline`

Force Task to only use local or cached Taskfiles.

```bash
task build --offline
```

#### `--timeout <duration>`

Set timeout for downloading remote Taskfiles.

```bash
task --taskfile https://example.com/Taskfile.yml --timeout 30s
```

#### `--clear-cache`

Clear the remote cache.

```bash
task --clear-cache
```

#### `--expiry <duration>`

Set expiry duration for cached remote Taskfiles.

```bash
task --taskfile https://example.com/Taskfile.yml --expiry 1h
```

### Interactive

#### `-y, --yes`
Expand Down
Loading
Loading