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
1 change: 0 additions & 1 deletion .taskrc.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
experiments:
GENTLE_FORCE: 0
REMOTE_TASKFILES: 0
ENV_PRECEDENCE: 0
22 changes: 9 additions & 13 deletions completion/fish/task.fish
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,16 @@ complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'verbose outp
complete -c $GO_TASK_PROGNAME -l version -d 'show version'
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'watch mode, re-run on changes'
complete -c $GO_TASK_PROGNAME -s y -l yes -d 'assume yes to all prompts'
complete -c $GO_TASK_PROGNAME -l offline -d 'use only local or cached Taskfiles'
complete -c $GO_TASK_PROGNAME -l timeout -d 'timeout for remote Taskfile downloads'
complete -c $GO_TASK_PROGNAME -l expiry -d 'cache expiry duration'
complete -c $GO_TASK_PROGNAME -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
complete -c $GO_TASK_PROGNAME -l cacert -d 'custom CA certificate for TLS' -r
complete -c $GO_TASK_PROGNAME -l cert -d 'client certificate for mTLS' -r
complete -c $GO_TASK_PROGNAME -l cert-key -d 'client certificate private key' -r
complete -c $GO_TASK_PROGNAME -l download -d 'download remote Taskfile'
complete -c $GO_TASK_PROGNAME -l clear-cache -d 'clear remote Taskfile cache'

# Experimental flags (dynamically checked at completion time via -n condition)
# GentleForce experiment
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled GENTLE_FORCE" -l force-all -d 'force execution of task and all dependencies'

# RemoteTaskfiles experiment - Options
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l offline -d 'use only local or cached Taskfiles'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l timeout -d 'timeout for remote Taskfile downloads'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l expiry -d 'cache expiry duration'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cacert -d 'custom CA certificate for TLS' -r
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert -d 'client certificate for mTLS' -r
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert-key -d 'client certificate private key' -r

# RemoteTaskfiles experiment - Operations
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l download -d 'download remote Taskfile'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l clear-cache -d 'clear remote Taskfile cache'
25 changes: 10 additions & 15 deletions completion/ps/task.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ Register-ArgumentCompleter -CommandName $cmdNames -ScriptBlock {
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'watch mode'),
[CompletionResult]::new('--watch', '--watch', [CompletionResultType]::ParameterName, 'watch mode'),
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'assume yes'),
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes')
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes'),
[CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles'),
[CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout'),
[CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry'),
[CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory'),
[CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate'),
[CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate'),
[CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key'),
[CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile'),
[CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
)

# Experimental flags (dynamically added based on enabled experiments)
Expand All @@ -73,20 +82,6 @@ Register-ArgumentCompleter -CommandName $cmdNames -ScriptBlock {
$completions += [CompletionResult]::new('--force-all', '--force-all', [CompletionResultType]::ParameterName, 'force all dependencies')
}

if ($experiments -match '\* REMOTE_TASKFILES:.*on') {
# Options
$completions += [CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles')
$completions += [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout')
$completions += [CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry')
$completions += [CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory')
$completions += [CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate')
$completions += [CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate')
$completions += [CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key')
# Operations
$completions += [CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile')
$completions += [CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
}

return $completions.Where{ $_.CompletionText.StartsWith($commandName) }
}

Expand Down
31 changes: 9 additions & 22 deletions completion/zsh/_task
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ _task() {
'(-v --verbose)'{-v,--verbose}'[verbose mode]'
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]'
'(-y --yes)'{-y,--yes}'[assume yes to all prompts]'
'(--offline --clear-cache)--download[download remote Taskfile]'
'(--offline --download)--offline[use only local or cached Taskfiles]'
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
'(--expiry)--expiry[cache expiry duration]:duration: '
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
'(--cert)--cert[client certificate for mTLS]:file:_files'
'(--cert-key)--cert-key[client certificate private key]:file:_files'
)

# Experimental flags (dynamically added based on enabled experiments)
Expand All @@ -128,18 +136,6 @@ _task() {
standard_args+=('(--force-all)--force-all[force execution of task and all dependencies]')
fi

if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
standard_args+=(
'(--offline --download)--offline[use only local or cached Taskfiles]'
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
'(--expiry)--expiry[cache expiry duration]:duration: '
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
'(--cert)--cert[client certificate for mTLS]:file:_files'
'(--cert-key)--cert-key[client certificate private key]:file:_files'
)
fi

operation_args=(
# Task names completion (can be specified multiple times)
'(operation)*: :__task_list'
Expand All @@ -150,17 +146,8 @@ _task() {
'(*)'{-i,--init}'[create new Taskfile.yml]'
'(- *)'{-h,--help}'[show help]'
'(- *)--version[show version and exit]'
)

# Experimental operations (dynamically added based on enabled experiments)
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
standard_args+=(
'(--offline --clear-cache)--download[download remote Taskfile]'
)
operation_args+=(
'(* --download)--clear-cache[clear remote Taskfile cache]'
)
fi
)

_arguments -S $standard_args $operation_args
}
Expand Down
8 changes: 6 additions & 2 deletions experiments/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package experiments

import (
"cmp"
"fmt"
"strconv"
"strings"
Expand All @@ -24,12 +25,15 @@ func (err InvalidValueError) Error() string {
}

type InactiveError struct {
Name string
Name string
Reason string
}

func (err InactiveError) Error() string {
reason := cmp.Or(err.Reason, "is inactive and cannot be enabled")
return fmt.Sprintf(
"task: Experiment %q is inactive and cannot be enabled",
"task: Experiment %q %s",
err.Name,
reason,
)
}
46 changes: 37 additions & 9 deletions experiments/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,54 @@ import (
)

type Experiment struct {
Name string // The name of the experiment.
AllowedValues []int // The values that can enable this experiment.
Value int // The version of the experiment that is enabled.
Name string // The name of the experiment.
AllowedValues []int // The values that can enable this experiment.
Value int // The version of the experiment that is enabled.
InactiveReason string // If not active, the reason why it is inactive.
}

// New creates a new experiment with the given name and sets the values that can
// enable it.
func New(xName string, config *ast.TaskRC, allowedValues ...int) Experiment {
func getValue(xName string, config *ast.TaskRC) int {
var value int
if config != nil {
value = config.Experiments[xName]
}

if value == 0 {
value, _ = strconv.Atoi(getEnv(xName))
}
return value
}

// New creates a new experiment with the given name and sets the values that can
// enable it.
func New(xName string, config *ast.TaskRC, allowedValues ...int) Experiment {
x := Experiment{
Name: xName,
AllowedValues: allowedValues,
Value: value,
Value: getValue(xName, config),
}
xList = append(xList, x)
return x
}

// NewReleased creates a new experiment that is released and no longer needs to
// be enabled. It will always be inactive and cannot be enabled.
func NewReleased(xName string, config *ast.TaskRC) Experiment {
x := Experiment{
Name: xName,
Value: getValue(xName, config),
InactiveReason: "is released and no longer needs to be enabled",
}
xList = append(xList, x)
return x
}

// NewAbandoned creates a new experiment that has been abandoned and is no
// longer supported. It will always be inactive and cannot be enabled.
func NewAbandoned(xName string, config *ast.TaskRC) Experiment {
x := Experiment{
Name: xName,
Value: getValue(xName, config),
InactiveReason: "has been abandoned and is no longer supported",
}
xList = append(xList, x)
return x
Expand All @@ -46,7 +73,8 @@ func (x Experiment) Active() bool {
func (x Experiment) Valid() error {
if !x.Active() && x.Value != 0 {
return &InactiveError{
Name: x.Name,
Name: x.Name,
Reason: x.InactiveReason,
}
}
if !x.Enabled() && x.Value != 0 {
Expand Down
17 changes: 9 additions & 8 deletions experiments/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ const envPrefix = "TASK_X_"

// Active experiments.
var (
GentleForce Experiment
RemoteTaskfiles Experiment
EnvPrecedence Experiment
GentleForce Experiment
EnvPrecedence Experiment
)

// Inactive experiments. These are experiments that cannot be enabled, but are
// preserved for error handling.
var (
AnyVariables Experiment
MapVariables Experiment
AnyVariables Experiment
MapVariables Experiment
RemoteTaskfiles Experiment
)

// An internal list of all the initialized experiments used for iterating.
Expand All @@ -41,10 +41,11 @@ func ParseWithConfig(dir string, config *ast.TaskRC) {
readDotEnv(dir)
// Initialize the experiments
GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
EnvPrecedence = New("ENV_PRECEDENCE", config, 1)
AnyVariables = New("ANY_VARIABLES", config)
MapVariables = New("MAP_VARIABLES", config)
// Inactive experiments
AnyVariables = NewReleased("ANY_VARIABLES", config)
MapVariables = NewReleased("MAP_VARIABLES", config)
RemoteTaskfiles = NewReleased("REMOTE_TASKFILES", config)
}

// Validate checks if any experiments have been enabled while being inactive.
Expand Down
23 changes: 10 additions & 13 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ func init() {
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, "FAILFAST", func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
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(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&Offline, "offline", getConfig(config, "REMOTE_OFFLINE", func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, "REMOTE_TRUSTED_HOSTS", func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
pflag.DurationVar(&Timeout, "timeout", getConfig(config, "REMOTE_TIMEOUT", func() *time.Duration { return 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", getConfig(config, "REMOTE_CACHE_EXPIRY", func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, "REMOTE_CACHE_DIR", func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
pflag.StringVar(&CACert, "cacert", getConfig(config, "REMOTE_CACERT", func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
pflag.StringVar(&Cert, "cert", getConfig(config, "REMOTE_CERT", func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
pflag.StringVar(&CertKey, "cert-key", getConfig(config, "REMOTE_CERT_KEY", func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")

// Gentle force experiment will override the force flag and add a new force-all flag
if experiments.GentleForce.Enabled() {
Expand All @@ -164,19 +174,6 @@ func init() {
pflag.BoolVarP(&ForceAll, "force", "f", false, "Forces execution even when the task is up-to-date.")
}

// 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", getConfig(config, "REMOTE_OFFLINE", func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, "REMOTE_TRUSTED_HOSTS", func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
pflag.DurationVar(&Timeout, "timeout", getConfig(config, "REMOTE_TIMEOUT", func() *time.Duration { return 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", getConfig(config, "REMOTE_CACHE_EXPIRY", func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, "REMOTE_CACHE_DIR", func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
pflag.StringVar(&CACert, "cacert", getConfig(config, "REMOTE_CACERT", func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
pflag.StringVar(&Cert, "cert", getConfig(config, "REMOTE_CERT", func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
pflag.StringVar(&CertKey, "cert-key", getConfig(config, "REMOTE_CERT_KEY", func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
}
pflag.Parse()

// Auto-detect color based on environment when not explicitly configured
Expand Down
4 changes: 0 additions & 4 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1073,8 +1073,6 @@ func TestIncludesMultiLevel(t *testing.T) {
}

func TestIncludesRemote(t *testing.T) {
enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)

dir := "testdata/includes_remote"
os.RemoveAll(filepath.Join(dir, ".task", "remote"))

Expand Down Expand Up @@ -1277,8 +1275,6 @@ func TestIncludesEmptyMain(t *testing.T) {
}

func TestIncludesHttp(t *testing.T) {
enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)

dir, err := filepath.Abs("testdata/includes_http")
require.NoError(t, err)

Expand Down
5 changes: 0 additions & 5 deletions taskfile/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (

giturls "github.com/chainguard-dev/git-urls"

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/fsext"
)

Expand Down Expand Up @@ -67,9 +65,6 @@ func NewNode(
default:
node, err = NewFileNode(entrypoint, dir, opts...)
}
if _, isRemote := node.(RemoteNode); isRemote && !experiments.RemoteTaskfiles.Enabled() {
return nil, errors.New("task: Remote taskfiles are not enabled. You can read more about this experiment and how to enable it at https://taskfile.dev/experiments/remote-taskfiles")
}

return node, err
}
Expand Down
4 changes: 4 additions & 0 deletions website/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ export default defineConfig({
text: 'Guide',
link: '/docs/guide'
},
{
text: 'Remote Taskfiles',
link: '/docs/remote-taskfiles'
},
{
text: 'Reference',
collapsed: true,
Expand Down
Loading
Loading