mirror of
https://github.com/go-task/task.git
synced 2025-06-23 00:38:19 +02:00
feat: add a new .taskrc.yml to enable experiments (#1982)
This commit is contained in:
@ -2,13 +2,16 @@ package experiments
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/slicesext"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InvalidValueError struct {
|
type InvalidValueError struct {
|
||||||
Name string
|
Name string
|
||||||
AllowedValues []string
|
AllowedValues []int
|
||||||
Value string
|
Value int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err InvalidValueError) Error() string {
|
func (err InvalidValueError) Error() string {
|
||||||
@ -16,7 +19,7 @@ func (err InvalidValueError) Error() string {
|
|||||||
"task: Experiment %q has an invalid value %q (allowed values: %s)",
|
"task: Experiment %q has an invalid value %q (allowed values: %s)",
|
||||||
err.Name,
|
err.Name,
|
||||||
err.Value,
|
err.Value,
|
||||||
strings.Join(err.AllowedValues, ", "),
|
strings.Join(slicesext.Convert(err.AllowedValues, strconv.Itoa), ", "),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,18 +3,24 @@ package experiments
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Experiment struct {
|
type Experiment struct {
|
||||||
Name string // The name of the experiment.
|
Name string // The name of the experiment.
|
||||||
AllowedValues []string // The values that can enable this experiment.
|
AllowedValues []int // The values that can enable this experiment.
|
||||||
Value string // The version of the experiment that is enabled.
|
Value int // The version of the experiment that is enabled.
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new experiment with the given name and sets the values that can
|
// New creates a new experiment with the given name and sets the values that can
|
||||||
// enable it.
|
// enable it.
|
||||||
func New(xName string, allowedValues ...string) Experiment {
|
func New(xName string, allowedValues ...int) Experiment {
|
||||||
value := getEnv(xName)
|
value := experimentConfig.Experiments[xName]
|
||||||
|
|
||||||
|
if value == 0 {
|
||||||
|
value, _ = strconv.Atoi(getEnv(xName))
|
||||||
|
}
|
||||||
|
|
||||||
x := Experiment{
|
x := Experiment{
|
||||||
Name: xName,
|
Name: xName,
|
||||||
AllowedValues: allowedValues,
|
AllowedValues: allowedValues,
|
||||||
@ -24,21 +30,21 @@ func New(xName string, allowedValues ...string) Experiment {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Experiment) Enabled() bool {
|
func (x Experiment) Enabled() bool {
|
||||||
return slices.Contains(x.AllowedValues, x.Value)
|
return slices.Contains(x.AllowedValues, x.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Experiment) Active() bool {
|
func (x Experiment) Active() bool {
|
||||||
return len(x.AllowedValues) > 0
|
return len(x.AllowedValues) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x Experiment) Valid() error {
|
func (x Experiment) Valid() error {
|
||||||
if !x.Active() && x.Value != "" {
|
if !x.Active() && x.Value != 0 {
|
||||||
return &InactiveError{
|
return &InactiveError{
|
||||||
Name: x.Name,
|
Name: x.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !x.Enabled() && x.Value != "" {
|
if !x.Enabled() && x.Value != 0 {
|
||||||
return &InvalidValueError{
|
return &InvalidValueError{
|
||||||
Name: x.Name,
|
Name: x.Name,
|
||||||
AllowedValues: x.AllowedValues,
|
AllowedValues: x.AllowedValues,
|
||||||
@ -50,7 +56,7 @@ func (x Experiment) Valid() error {
|
|||||||
|
|
||||||
func (x Experiment) String() string {
|
func (x Experiment) String() string {
|
||||||
if x.Enabled() {
|
if x.Enabled() {
|
||||||
return fmt.Sprintf("on (%s)", x.Value)
|
return fmt.Sprintf("on (%d)", x.Value)
|
||||||
}
|
}
|
||||||
return "off"
|
return "off"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package experiments_test
|
package experiments_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -15,8 +16,8 @@ func TestNew(t *testing.T) {
|
|||||||
)
|
)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
allowedValues []string
|
allowedValues []int
|
||||||
value string
|
value int
|
||||||
wantEnabled bool
|
wantEnabled bool
|
||||||
wantActive bool
|
wantActive bool
|
||||||
wantValid error
|
wantValid error
|
||||||
@ -28,7 +29,7 @@ func TestNew(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `[] allowed, value="1"`,
|
name: `[] allowed, value="1"`,
|
||||||
value: "1",
|
value: 1,
|
||||||
wantEnabled: false,
|
wantEnabled: false,
|
||||||
wantActive: false,
|
wantActive: false,
|
||||||
wantValid: &experiments.InactiveError{
|
wantValid: &experiments.InactiveError{
|
||||||
@ -37,33 +38,33 @@ func TestNew(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `[1] allowed, value=""`,
|
name: `[1] allowed, value=""`,
|
||||||
allowedValues: []string{"1"},
|
allowedValues: []int{1},
|
||||||
wantEnabled: false,
|
wantEnabled: false,
|
||||||
wantActive: true,
|
wantActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `[1] allowed, value="1"`,
|
name: `[1] allowed, value="1"`,
|
||||||
allowedValues: []string{"1"},
|
allowedValues: []int{1},
|
||||||
value: "1",
|
value: 1,
|
||||||
wantEnabled: true,
|
wantEnabled: true,
|
||||||
wantActive: true,
|
wantActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `[1] allowed, value="2"`,
|
name: `[1] allowed, value="2"`,
|
||||||
allowedValues: []string{"1"},
|
allowedValues: []int{1},
|
||||||
value: "2",
|
value: 2,
|
||||||
wantEnabled: false,
|
wantEnabled: false,
|
||||||
wantActive: true,
|
wantActive: true,
|
||||||
wantValid: &experiments.InvalidValueError{
|
wantValid: &experiments.InvalidValueError{
|
||||||
Name: exampleExperiment,
|
Name: exampleExperiment,
|
||||||
AllowedValues: []string{"1"},
|
AllowedValues: []int{1},
|
||||||
Value: "2",
|
Value: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Setenv(exampleExperimentEnv, tt.value)
|
t.Setenv(exampleExperimentEnv, strconv.Itoa(tt.value))
|
||||||
x := experiments.New(exampleExperiment, tt.allowedValues...)
|
x := experiments.New(exampleExperiment, tt.allowedValues...)
|
||||||
assert.Equal(t, exampleExperiment, x.Name)
|
assert.Equal(t, exampleExperiment, x.Name)
|
||||||
assert.Equal(t, tt.wantEnabled, x.Enabled())
|
assert.Equal(t, tt.wantEnabled, x.Enabled())
|
||||||
|
@ -6,13 +6,24 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const envPrefix = "TASK_X_"
|
const envPrefix = "TASK_X_"
|
||||||
|
|
||||||
// A set of experiments that can be enabled or disabled.
|
var defaultConfigFilenames = []string{
|
||||||
|
".taskrc.yml",
|
||||||
|
".taskrc.yaml",
|
||||||
|
}
|
||||||
|
|
||||||
|
type experimentConfigFile struct {
|
||||||
|
Experiments map[string]int `yaml:"experiments"`
|
||||||
|
Version *semver.Version
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
GentleForce Experiment
|
GentleForce Experiment
|
||||||
RemoteTaskfiles Experiment
|
RemoteTaskfiles Experiment
|
||||||
@ -22,15 +33,19 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// An internal list of all the initialized experiments used for iterating.
|
// An internal list of all the initialized experiments used for iterating.
|
||||||
var xList []Experiment
|
var (
|
||||||
|
xList []Experiment
|
||||||
|
experimentConfig experimentConfigFile
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
readDotEnv()
|
readDotEnv()
|
||||||
GentleForce = New("GENTLE_FORCE", "1")
|
experimentConfig = readConfig()
|
||||||
RemoteTaskfiles = New("REMOTE_TASKFILES", "1")
|
GentleForce = New("GENTLE_FORCE", 1)
|
||||||
|
RemoteTaskfiles = New("REMOTE_TASKFILES", 1)
|
||||||
AnyVariables = New("ANY_VARIABLES")
|
AnyVariables = New("ANY_VARIABLES")
|
||||||
MapVariables = New("MAP_VARIABLES", "1", "2")
|
MapVariables = New("MAP_VARIABLES", 1, 2)
|
||||||
EnvPrecedence = New("ENV_PRECEDENCE", "1")
|
EnvPrecedence = New("ENV_PRECEDENCE", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks if any experiments have been enabled while being inactive.
|
// Validate checks if any experiments have been enabled while being inactive.
|
||||||
@ -53,7 +68,7 @@ func getEnv(xName string) string {
|
|||||||
return os.Getenv(envName)
|
return os.Getenv(envName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnvFilePath() string {
|
func getFilePath(filename string) string {
|
||||||
// Parse the CLI flags again to get the directory/taskfile being run
|
// Parse the CLI flags again to get the directory/taskfile being run
|
||||||
// We use a flagset here so that we can parse a subset of flags without exiting on error.
|
// We use a flagset here so that we can parse a subset of flags without exiting on error.
|
||||||
var dir, taskfile string
|
var dir, taskfile string
|
||||||
@ -64,18 +79,18 @@ func getEnvFilePath() string {
|
|||||||
_ = fs.Parse(os.Args[1:])
|
_ = fs.Parse(os.Args[1:])
|
||||||
// If the directory is set, find a .env file in that directory.
|
// If the directory is set, find a .env file in that directory.
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
return filepath.Join(dir, ".env")
|
return filepath.Join(dir, filename)
|
||||||
}
|
}
|
||||||
// If the taskfile is set, find a .env file in the directory containing the Taskfile.
|
// If the taskfile is set, find a .env file in the directory containing the Taskfile.
|
||||||
if taskfile != "" {
|
if taskfile != "" {
|
||||||
return filepath.Join(filepath.Dir(taskfile), ".env")
|
return filepath.Join(filepath.Dir(taskfile), filename)
|
||||||
}
|
}
|
||||||
// Otherwise just use the current working directory.
|
// Otherwise just use the current working directory.
|
||||||
return ".env"
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDotEnv() {
|
func readDotEnv() {
|
||||||
env, _ := godotenv.Read(getEnvFilePath())
|
env, _ := godotenv.Read(getFilePath(".env"))
|
||||||
// If the env var is an experiment, set it.
|
// If the env var is an experiment, set it.
|
||||||
for key, value := range env {
|
for key, value := range env {
|
||||||
if strings.HasPrefix(key, envPrefix) {
|
if strings.HasPrefix(key, envPrefix) {
|
||||||
@ -83,3 +98,27 @@ func readDotEnv() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readConfig() experimentConfigFile {
|
||||||
|
var cfg experimentConfigFile
|
||||||
|
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
for _, filename := range defaultConfigFilenames {
|
||||||
|
path := getFilePath(filename)
|
||||||
|
content, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return experimentConfigFile{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(content, &cfg); err != nil {
|
||||||
|
return experimentConfigFile{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
@ -18,3 +18,15 @@ func UniqueJoin[T cmp.Ordered](ss ...[]T) []T {
|
|||||||
slices.Sort(r)
|
slices.Sort(r)
|
||||||
return slices.Compact(r)
|
return slices.Compact(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Convert[T, U any](s []T, f func(T) U) []U {
|
||||||
|
// Create a new slice with the same length as the input slice
|
||||||
|
result := make([]U, len(s))
|
||||||
|
|
||||||
|
// Convert each element using the provided function
|
||||||
|
for i, v := range s {
|
||||||
|
result[i] = f(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
86
internal/slicesext/slicesext_test.go
Normal file
86
internal/slicesext/slicesext_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package slicesext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertIntToString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
input := []int{1, 2, 3, 4, 5}
|
||||||
|
expected := []string{"1", "2", "3", "4", "5"}
|
||||||
|
result := Convert(input, strconv.Itoa)
|
||||||
|
|
||||||
|
if len(result) != len(expected) {
|
||||||
|
t.Errorf("Expected length %d, got %d", len(expected), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range expected {
|
||||||
|
if result[i] != expected[i] {
|
||||||
|
t.Errorf("At index %d: expected %v, got %v", i, expected[i], result[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertStringToInt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
input := []string{"1", "2", "3", "4", "5"}
|
||||||
|
expected := []int{1, 2, 3, 4, 5}
|
||||||
|
result := Convert(input, func(s string) int {
|
||||||
|
n, _ := strconv.Atoi(s)
|
||||||
|
return n
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(result) != len(expected) {
|
||||||
|
t.Errorf("Expected length %d, got %d", len(expected), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range expected {
|
||||||
|
if result[i] != expected[i] {
|
||||||
|
t.Errorf("At index %d: expected %v, got %v", i, expected[i], result[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertFloatToInt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
input := []float64{1.1, 2.2, 3.7, 4.5, 5.9}
|
||||||
|
expected := []int{1, 2, 4, 5, 6}
|
||||||
|
result := Convert(input, func(f float64) int {
|
||||||
|
return int(math.Round(f))
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(result) != len(expected) {
|
||||||
|
t.Errorf("Expected length %d, got %d", len(expected), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range expected {
|
||||||
|
if result[i] != expected[i] {
|
||||||
|
t.Errorf("At index %d: expected %v, got %v", i, expected[i], result[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertEmptySlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
input := []int{}
|
||||||
|
result := Convert(input, strconv.Itoa)
|
||||||
|
|
||||||
|
if len(result) != 0 {
|
||||||
|
t.Errorf("Expected empty slice, got length %d", len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertNilSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var input []int
|
||||||
|
result := Convert(input, strconv.Itoa)
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
t.Error("Expected non-nil empty slice, got nil")
|
||||||
|
}
|
||||||
|
if len(result) != 0 {
|
||||||
|
t.Errorf("Expected empty slice, got length %d", len(result))
|
||||||
|
}
|
||||||
|
}
|
12
task_test.go
12
task_test.go
@ -135,7 +135,7 @@ func TestEnv(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
enableExperimentForTest(t, &experiments.EnvPrecedence, "1")
|
enableExperimentForTest(t, &experiments.EnvPrecedence, 1)
|
||||||
ttt := fileContentTest{
|
ttt := fileContentTest{
|
||||||
Dir: "testdata/env",
|
Dir: "testdata/env",
|
||||||
Target: "overridden",
|
Target: "overridden",
|
||||||
@ -1215,7 +1215,7 @@ func TestIncludesMultiLevel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIncludesRemote(t *testing.T) {
|
func TestIncludesRemote(t *testing.T) {
|
||||||
enableExperimentForTest(t, &experiments.RemoteTaskfiles, "1")
|
enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)
|
||||||
|
|
||||||
dir := "testdata/includes_remote"
|
dir := "testdata/includes_remote"
|
||||||
|
|
||||||
@ -1373,7 +1373,7 @@ func TestIncludesEmptyMain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIncludesHttp(t *testing.T) {
|
func TestIncludesHttp(t *testing.T) {
|
||||||
enableExperimentForTest(t, &experiments.RemoteTaskfiles, "1")
|
enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)
|
||||||
|
|
||||||
dir, err := filepath.Abs("testdata/includes_http")
|
dir, err := filepath.Abs("testdata/includes_http")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -3224,7 +3224,7 @@ func TestReference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestVarInheritance(t *testing.T) {
|
func TestVarInheritance(t *testing.T) {
|
||||||
enableExperimentForTest(t, &experiments.EnvPrecedence, "1")
|
enableExperimentForTest(t, &experiments.EnvPrecedence, 1)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
want string
|
want string
|
||||||
@ -3332,12 +3332,12 @@ func TestVarInheritance(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// Typically experiments are controlled via TASK_X_ env vars, but we cannot use those in tests
|
// Typically experiments are controlled via TASK_X_ env vars, but we cannot use those in tests
|
||||||
// because the experiment settings are parsed during experiments.init(), before any tests run.
|
// because the experiment settings are parsed during experiments.init(), before any tests run.
|
||||||
func enableExperimentForTest(t *testing.T, e *experiments.Experiment, val string) {
|
func enableExperimentForTest(t *testing.T, e *experiments.Experiment, val int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
prev := *e
|
prev := *e
|
||||||
*e = experiments.Experiment{
|
*e = experiments.Experiment{
|
||||||
Name: prev.Name,
|
Name: prev.Name,
|
||||||
AllowedValues: []string{val},
|
AllowedValues: []int{val},
|
||||||
Value: val,
|
Value: val,
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { *e = prev })
|
t.Cleanup(func() { *e = prev })
|
||||||
|
@ -22,7 +22,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
if experiments.MapVariables.Enabled() {
|
if experiments.MapVariables.Enabled() {
|
||||||
|
|
||||||
// This implementation is not backwards-compatible and replaces the 'sh' key with map variables
|
// This implementation is not backwards-compatible and replaces the 'sh' key with map variables
|
||||||
if experiments.MapVariables.Value == "1" {
|
if experiments.MapVariables.Value == 1 {
|
||||||
var value any
|
var value any
|
||||||
if err := node.Decode(&value); err != nil {
|
if err := node.Decode(&value); err != nil {
|
||||||
return errors.NewTaskfileDecodeError(err, node)
|
return errors.NewTaskfileDecodeError(err, node)
|
||||||
@ -43,7 +43,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
|
// This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
|
||||||
if experiments.MapVariables.Value == "2" {
|
if experiments.MapVariables.Value == 2 {
|
||||||
switch node.Kind {
|
switch node.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
key := node.Content[0].Value
|
key := node.Content[0].Value
|
||||||
|
@ -3,6 +3,9 @@ slug: /experiments/
|
|||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
# Experiments
|
# Experiments
|
||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
@ -39,7 +42,7 @@ Which method you use depends on how you intend to use the experiment:
|
|||||||
1. Prefixing your task commands with the relevant environment variable(s). For
|
1. Prefixing your task commands with the relevant environment variable(s). For
|
||||||
example, `TASK_X_{FEATURE}=1 task {my-task}`. This is intended for one-off
|
example, `TASK_X_{FEATURE}=1 task {my-task}`. This is intended for one-off
|
||||||
invocations of Task to test out experimental features.
|
invocations of Task to test out experimental features.
|
||||||
1. Adding the relevant environment variable(s) in your "dotfiles" (e.g.
|
2. Adding the relevant environment variable(s) in your "dotfiles" (e.g.
|
||||||
`.bashrc`, `.zshrc` etc.). This will permanently enable experimental features
|
`.bashrc`, `.zshrc` etc.). This will permanently enable experimental features
|
||||||
for your personal environment.
|
for your personal environment.
|
||||||
|
|
||||||
@ -47,15 +50,33 @@ Which method you use depends on how you intend to use the experiment:
|
|||||||
export TASK_X_FEATURE=1
|
export TASK_X_FEATURE=1
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Creating a `.env` file in the same directory as your root Taskfile that
|
3. Creating a `.env` or a `.task-experiments.yml` file in the same directory as
|
||||||
contains the relevant environment variable(s). This allows you to enable an
|
your root Taskfile.\
|
||||||
experimental feature at a project level. If you commit the `.env` file to
|
The `.env` file should contain the relevant environment
|
||||||
source control then other users of your project will also have these
|
variable(s), while the `.task-experiments.yml` file should use a YAML format
|
||||||
experiments enabled.
|
where each experiment is defined as a key with a corresponding value.
|
||||||
|
|
||||||
```shell title=".env"
|
This allows you to enable an experimental feature at a project level. If you
|
||||||
TASK_X_FEATURE=1
|
commit this file to source control, then other users of your project will
|
||||||
```
|
also have these experiments enabled.
|
||||||
|
|
||||||
|
If both files are present, the values in the `.task-experiments.yml` file
|
||||||
|
will take precedence.
|
||||||
|
|
||||||
|
<Tabs values={[ {label: '.task-experiments.yml', value: 'yaml'}, {label: '.env', value: 'env'}]}>
|
||||||
|
<TabItem value="yaml">
|
||||||
|
```yaml title=".taskrc.yml"
|
||||||
|
experiments:
|
||||||
|
FEATURE: 1
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="env">
|
||||||
|
```shell title=".env"
|
||||||
|
TASK_X_FEATURE=1
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
|
15
website/static/schema-taskrc.json
Normal file
15
website/static/schema-taskrc.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"title": "Taskrc YAML Schema",
|
||||||
|
"description": "Schema for .taskrc files.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"experiments": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
Reference in New Issue
Block a user