mirror of
https://github.com/go-task/task.git
synced 2025-11-23 22:24:45 +02:00
feat: XDG taskrc config (#2380)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
This commit is contained in:
@@ -34,12 +34,7 @@ func Parse(dir string) {
|
|||||||
// Read any .env files
|
// Read any .env files
|
||||||
readDotEnv(dir)
|
readDotEnv(dir)
|
||||||
|
|
||||||
// Create a node for the Task config reader
|
config, _ := taskrc.GetConfig(dir)
|
||||||
node, _ := taskrc.NewNode("", dir)
|
|
||||||
|
|
||||||
// Read the Task config file
|
|
||||||
reader := taskrc.NewReader()
|
|
||||||
config, _ := reader.Read(node)
|
|
||||||
|
|
||||||
// Initialize the experiments
|
// Initialize the experiments
|
||||||
GentleForce = New("GENTLE_FORCE", config, 1)
|
GentleForce = New("GENTLE_FORCE", config, 1)
|
||||||
|
|||||||
@@ -37,51 +37,87 @@ func DefaultDir(entrypoint, dir string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search will look for files with the given possible filenames using the given
|
// ResolveDir returns an absolute path to the directory that the task should be
|
||||||
// entrypoint and directory. If the entrypoint is set, it will check if the
|
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not
|
||||||
|
// sit inside the directory specified by dir and we should ensure that the dir
|
||||||
|
// is absolute. Otherwise, the dir will always be the parent directory of the
|
||||||
|
// resolved entrypoint, so we should return that parent directory.
|
||||||
|
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
|
||||||
|
if entrypoint != "" && dir != "" {
|
||||||
|
return filepath.Abs(dir)
|
||||||
|
}
|
||||||
|
return filepath.Dir(resolvedEntrypoint), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search looks for files with the given possible filenames using the given
|
||||||
|
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||||
// entrypoint matches a file or if it matches a directory containing one of the
|
// entrypoint matches a file or if it matches a directory containing one of the
|
||||||
// possible filenames. Otherwise, it will walk up the file tree starting at the
|
// possible filenames. Otherwise, it walks up the file tree starting at the
|
||||||
// given directory and perform a search in each directory for the possible
|
// given directory and performs a search in each directory for the possible
|
||||||
// filenames until it finds a match or reaches the root directory. If the
|
// filenames until it finds a match or reaches the root directory. If the
|
||||||
// entrypoint and directory are both empty, it will default the directory to the
|
// entrypoint and directory are both empty, it defaults the directory to the
|
||||||
// current working directory and perform a recursive search starting there. If a
|
// current working directory and performs a recursive search starting there. If
|
||||||
// match is found, the absolute path to the file will be returned with its
|
// a match is found, the absolute path to the file is returned with its
|
||||||
// directory. If no match is found, an error will be returned.
|
// directory. If no match is found, an error is returned.
|
||||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, string, error) {
|
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
if entrypoint != "" {
|
if entrypoint != "" {
|
||||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if dir == "" {
|
return entrypoint, nil
|
||||||
dir = filepath.Dir(entrypoint)
|
|
||||||
} else {
|
|
||||||
dir, err = filepath.Abs(dir)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entrypoint, dir, nil
|
|
||||||
}
|
}
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
dir, err = os.Getwd()
|
dir, err = os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
dir = filepath.Dir(entrypoint)
|
return entrypoint, nil
|
||||||
return entrypoint, dir, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search will check if a file at the given path exists or not. If it does, it
|
// SearchAll looks for files with the given possible filenames using the given
|
||||||
// will return the path to it. If it does not, it will search for any files at
|
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||||
// the given path with any of the given possible names. If any of these match a
|
// entrypoint matches a file or if it matches a directory containing one of the
|
||||||
// file, the first matching path will be returned. If no files are found, an
|
// possible filenames and add it to a list of matches. It then walks up the file
|
||||||
|
// tree starting at the given directory and performs a search in each directory
|
||||||
|
// for the possible filenames until it finds a match or reaches the root
|
||||||
|
// directory. If the entrypoint and directory are both empty, it defaults the
|
||||||
|
// directory to the current working directory and performs a recursive search
|
||||||
|
// starting there. If matches are found, the absolute path to each file is added
|
||||||
|
// to the list and returned.
|
||||||
|
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
|
||||||
|
var err error
|
||||||
|
var entrypoints []string
|
||||||
|
if entrypoint != "" {
|
||||||
|
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entrypoints = append(entrypoints, entrypoint)
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
dir, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(entrypoints, paths...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchPath will check if a file at the given path exists or not. If it does,
|
||||||
|
// it will return the path to it. If it does not, it will search for any files
|
||||||
|
// at the given path with any of the given possible names. If any of these match
|
||||||
|
// a file, the first matching path will be returned. If no files are found, an
|
||||||
// error will be returned.
|
// error will be returned.
|
||||||
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||||
// Get file info about the path
|
// Get file info about the path
|
||||||
@@ -111,36 +147,56 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
|
|||||||
return "", os.ErrNotExist
|
return "", os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchRecursively will check if a file at the given path exists by calling
|
// SearchPathRecursively walks up the directory tree starting at the given
|
||||||
// the exists function. If a file is not found, it will walk up the directory
|
// path, calling the Search function in each directory until it finds a matching
|
||||||
// tree calling the Search function until it finds a file or reaches the root
|
// file or reaches the root directory. On supported operating systems, it will
|
||||||
// directory. On supported operating systems, it will also check if the user ID
|
// also check if the user ID of the directory changes and abort if it does.
|
||||||
// of the directory changes and abort if it does.
|
|
||||||
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
||||||
owner, err := sysinfo.Owner(path)
|
paths, err := SearchNPathRecursively(path, possibleFilenames, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
for {
|
if len(paths) == 0 {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
return paths[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchNPathRecursively walks up the directory tree starting at the given
|
||||||
|
// path, calling the Search function in each directory and adding each matching
|
||||||
|
// file that it finds to a list until it reaches the root directory or the
|
||||||
|
// length of the list exceeds n. On supported operating systems, it will also
|
||||||
|
// check if the user ID of the directory changes and abort if it does.
|
||||||
|
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
|
||||||
|
var paths []string
|
||||||
|
|
||||||
|
owner, err := sysinfo.Owner(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for n == -1 || len(paths) < n {
|
||||||
fpath, err := SearchPath(path, possibleFilenames)
|
fpath, err := SearchPath(path, possibleFilenames)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fpath, nil
|
paths = append(paths, fpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the parent path/user id
|
// Get the parent path/user id
|
||||||
parentPath := filepath.Dir(path)
|
parentPath := filepath.Dir(path)
|
||||||
parentOwner, err := sysinfo.Owner(parentPath)
|
parentOwner, err := sysinfo.Owner(parentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error if we reached the root directory and still haven't found a file
|
// Error if we reached the root directory and still haven't found a file
|
||||||
// OR if the user id of the directory changes
|
// OR if the user id of the directory changes
|
||||||
if path == parentPath || (parentOwner != owner) {
|
if path == parentPath || (parentOwner != owner) {
|
||||||
return "", os.ErrNotExist
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
owner = parentOwner
|
owner = parentOwner
|
||||||
path = parentPath
|
path = parentPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,35 +71,30 @@ func TestSearch(t *testing.T) {
|
|||||||
dir string
|
dir string
|
||||||
possibleFilenames []string
|
possibleFilenames []string
|
||||||
expectedEntrypoint string
|
expectedEntrypoint string
|
||||||
expectedDir string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "find foo.txt using relative entrypoint",
|
name: "find foo.txt using relative entrypoint",
|
||||||
entrypoint: "./testdata/foo.txt",
|
entrypoint: "./testdata/foo.txt",
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using absolute entrypoint",
|
name: "find foo.txt using absolute entrypoint",
|
||||||
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using relative dir",
|
name: "find foo.txt using relative dir",
|
||||||
dir: "./testdata",
|
dir: "./testdata",
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using absolute dir",
|
name: "find foo.txt using absolute dir",
|
||||||
dir: filepath.Join(wd, "testdata"),
|
dir: filepath.Join(wd, "testdata"),
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using relative dir and relative entrypoint",
|
name: "find foo.txt using relative dir and relative entrypoint",
|
||||||
@@ -107,7 +102,6 @@ func TestSearch(t *testing.T) {
|
|||||||
dir: "./testdata/some/other/dir",
|
dir: "./testdata/some/other/dir",
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find fs.go using no entrypoint or dir",
|
name: "find fs.go using no entrypoint or dir",
|
||||||
@@ -115,7 +109,6 @@ func TestSearch(t *testing.T) {
|
|||||||
dir: "",
|
dir: "",
|
||||||
possibleFilenames: []string{"fs.go"},
|
possibleFilenames: []string{"fs.go"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||||
expectedDir: wd,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||||
@@ -123,30 +116,109 @@ func TestSearch(t *testing.T) {
|
|||||||
dir: "",
|
dir: "",
|
||||||
possibleFilenames: []string{"Taskfile.yml"},
|
possibleFilenames: []string{"Taskfile.yml"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||||
expectedDir: filepath.Join(wd, "..", ".."),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt first if listed first in possible filenames",
|
name: "find foo.txt first if listed first in possible filenames",
|
||||||
entrypoint: "./testdata",
|
entrypoint: "./testdata",
|
||||||
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find bar.txt first if listed first in possible filenames",
|
name: "find bar.txt first if listed first in possible filenames",
|
||||||
entrypoint: "./testdata",
|
entrypoint: "./testdata",
|
||||||
possibleFilenames: []string{"bar.txt", "foo.txt"},
|
possibleFilenames: []string{"bar.txt", "foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
entrypoint, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveDir(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
entrypoint string
|
||||||
|
resolvedEntrypoint string
|
||||||
|
dir string
|
||||||
|
expectedDir string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "find foo.txt using relative entrypoint",
|
||||||
|
entrypoint: "./testdata/foo.txt",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using absolute entrypoint",
|
||||||
|
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using relative dir",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
dir: "./testdata",
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using absolute dir",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
dir: filepath.Join(wd, "testdata"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using relative dir and relative entrypoint",
|
||||||
|
entrypoint: "./testdata/foo.txt",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
dir: "./testdata/some/other/dir",
|
||||||
|
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find fs.go using no entrypoint or dir",
|
||||||
|
entrypoint: "",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||||
|
dir: "",
|
||||||
|
expectedDir: wd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||||
|
entrypoint: "",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||||
|
dir: "",
|
||||||
|
expectedDir: filepath.Join(wd, "..", ".."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt first if listed first in possible filenames",
|
||||||
|
entrypoint: "./testdata",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find bar.txt first if listed first in possible filenames",
|
||||||
|
entrypoint: "./testdata",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
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.Parallel()
|
t.Parallel()
|
||||||
entrypoint, dir, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
dir, err := ResolveDir(tt.entrypoint, tt.resolvedEntrypoint, tt.dir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
|
||||||
require.Equal(t, tt.expectedDir, dir)
|
require.Equal(t, tt.expectedDir, dir)
|
||||||
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,21 @@ type FileNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
||||||
var err error
|
// Find the entrypoint file
|
||||||
base := NewBaseNode(dir, opts...)
|
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, defaultTaskfiles)
|
||||||
entrypoint, base.dir, err = fsext.Search(entrypoint, base.dir, defaultTaskfiles)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve the directory
|
||||||
|
resolvedDir, err := fsext.ResolveDir(entrypoint, resolvedEntrypoint, dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &FileNode{
|
return &FileNode{
|
||||||
baseNode: base,
|
baseNode: NewBaseNode(resolvedDir, opts...),
|
||||||
entrypoint: entrypoint,
|
entrypoint: resolvedEntrypoint,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,27 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import "github.com/Masterminds/semver/v3"
|
import (
|
||||||
|
"maps"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
)
|
||||||
|
|
||||||
type TaskRC struct {
|
type TaskRC struct {
|
||||||
Version *semver.Version `yaml:"version"`
|
Version *semver.Version `yaml:"version"`
|
||||||
Experiments map[string]int `yaml:"experiments"`
|
Experiments map[string]int `yaml:"experiments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package taskrc
|
package taskrc
|
||||||
|
|
||||||
import "github.com/go-task/task/v3/internal/fsext"
|
import (
|
||||||
|
"github.com/go-task/task/v3/internal/fsext"
|
||||||
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
entrypoint string
|
entrypoint string
|
||||||
dir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNode(
|
func NewNode(
|
||||||
@@ -12,13 +13,11 @@ func NewNode(
|
|||||||
dir string,
|
dir string,
|
||||||
) (*Node, error) {
|
) (*Node, error) {
|
||||||
dir = fsext.DefaultDir(entrypoint, dir)
|
dir = fsext.DefaultDir(entrypoint, dir)
|
||||||
var err error
|
resolvedEntrypoint, err := fsext.SearchPath(dir, defaultTaskRCs)
|
||||||
entrypoint, dir, err = fsext.Search(entrypoint, dir, defaultTaskRCs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Node{
|
return &Node{
|
||||||
entrypoint: entrypoint,
|
entrypoint: resolvedEntrypoint,
|
||||||
dir: dir,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,63 @@
|
|||||||
package taskrc
|
package taskrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/fsext"
|
||||||
|
"github.com/go-task/task/v3/taskrc/ast"
|
||||||
|
)
|
||||||
|
|
||||||
var defaultTaskRCs = []string{
|
var defaultTaskRCs = []string{
|
||||||
".taskrc.yml",
|
".taskrc.yml",
|
||||||
".taskrc.yaml",
|
".taskrc.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfig loads and merges local and global Task configuration files
|
||||||
|
func GetConfig(dir string) (*ast.TaskRC, error) {
|
||||||
|
var config *ast.TaskRC
|
||||||
|
reader := NewReader()
|
||||||
|
|
||||||
|
// Read the XDG config file
|
||||||
|
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||||
|
xdgConfigNode, err := NewNode("", filepath.Join(xdgConfigHome, "task"))
|
||||||
|
if err == nil && xdgConfigNode != nil {
|
||||||
|
config, err = reader.Read(xdgConfigNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the nodes from the given directory up to the users home directory
|
||||||
|
entrypoints, err := fsext.SearchAll("", dir, defaultTaskRCs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the entrypoints since we want the child files to override parent ones
|
||||||
|
slices.Reverse(entrypoints)
|
||||||
|
|
||||||
|
// Loop over the nodes, and merge them into the main config
|
||||||
|
for _, entrypoint := range entrypoints {
|
||||||
|
node, err := NewNode("", entrypoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
localConfig, err := reader.Read(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if localConfig == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
config = localConfig
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config.Merge(localConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|||||||
137
taskrc/taskrc_test.go
Normal file
137
taskrc/taskrc_test.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package taskrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/taskrc/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
xdgConfigYAML = `
|
||||||
|
experiments:
|
||||||
|
FOO: 1
|
||||||
|
BAR: 1
|
||||||
|
BAZ: 1
|
||||||
|
`
|
||||||
|
|
||||||
|
homeConfigYAML = `
|
||||||
|
experiments:
|
||||||
|
FOO: 2
|
||||||
|
BAR: 2
|
||||||
|
`
|
||||||
|
|
||||||
|
localConfigYAML = `
|
||||||
|
experiments:
|
||||||
|
FOO: 3
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupDirs(t *testing.T) (string, string, string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
xdgConfigDir := t.TempDir()
|
||||||
|
xdgTaskConfigDir := filepath.Join(xdgConfigDir, "task")
|
||||||
|
require.NoError(t, os.Mkdir(xdgTaskConfigDir, 0o755))
|
||||||
|
|
||||||
|
homeDir := t.TempDir()
|
||||||
|
|
||||||
|
localDir := filepath.Join(homeDir, "local")
|
||||||
|
require.NoError(t, os.Mkdir(localDir, 0o755))
|
||||||
|
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", xdgConfigDir)
|
||||||
|
t.Setenv("HOME", homeDir)
|
||||||
|
|
||||||
|
return xdgTaskConfigDir, homeDir, localDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(t *testing.T, dir, filename, content string) {
|
||||||
|
t.Helper()
|
||||||
|
err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0o644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_NoConfigFiles(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
_, _, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_OnlyXDG(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
xdgDir, _, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
writeFile(t, xdgDir, ".taskrc.yml", xdgConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 1,
|
||||||
|
"BAR": 1,
|
||||||
|
"BAZ": 1,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_OnlyHome(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
_, homeDir, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 2,
|
||||||
|
"BAR": 2,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_OnlyLocal(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
_, _, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 3,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
xdgConfigDir, homeDir, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
// Write local config
|
||||||
|
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
|
||||||
|
|
||||||
|
// Write home config
|
||||||
|
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
|
||||||
|
|
||||||
|
// Write XDG config
|
||||||
|
writeFile(t, xdgConfigDir, ".taskrc.yml", xdgConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, cfg)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 3,
|
||||||
|
"BAR": 2,
|
||||||
|
"BAZ": 1,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
@@ -204,12 +204,20 @@ export default defineConfig({
|
|||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
text: 'CLI',
|
text: 'Taskfile Schema',
|
||||||
link: '/docs/reference/cli'
|
link: '/docs/reference/schema'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Schema',
|
text: 'Environment',
|
||||||
link: '/docs/reference/schema'
|
link: '/docs/reference/environment'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Configuration',
|
||||||
|
link: '/docs/reference/config'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'CLI',
|
||||||
|
link: '/docs/reference/cli'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Templating',
|
text: 'Templating',
|
||||||
@@ -218,10 +226,6 @@ export default defineConfig({
|
|||||||
{
|
{
|
||||||
text: 'Package API',
|
text: 'Package API',
|
||||||
link: '/docs/reference/package'
|
link: '/docs/reference/package'
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Environment',
|
|
||||||
link: '/docs/reference/environment'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
---
|
---
|
||||||
title: CLI Reference
|
title: Command Line Interface Reference
|
||||||
description: Complete reference for Task CLI commands, flags, and exit codes
|
description: Complete reference for Task CLI commands, flags, and exit codes
|
||||||
permalink: /reference/cli/
|
permalink: /reference/cli/
|
||||||
outline: deep
|
outline: deep
|
||||||
---
|
---
|
||||||
|
|
||||||
# Command Line Interface
|
# Command Line Interface Reference
|
||||||
|
|
||||||
Task CLI commands have the following syntax:
|
Task has multiple ways of being configured. These methods are parsed, in
|
||||||
|
sequence, in the following order with the highest priority last:
|
||||||
|
|
||||||
|
- [Environment variables](./environment.md)
|
||||||
|
- [Configuration files](./config.md)
|
||||||
|
- _Command-line flags_
|
||||||
|
|
||||||
|
In this document, we will look at the last of the three options, command-line
|
||||||
|
flags. All CLI commands override their configuration file and environment
|
||||||
|
variable equivalents.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
Task commands have the following syntax:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
task [options] [tasks...] [-- CLI_ARGS...]
|
task [options] [tasks...] [-- CLI_ARGS...]
|
||||||
@@ -16,7 +29,7 @@ task [options] [tasks...] [-- CLI_ARGS...]
|
|||||||
::: tip
|
::: tip
|
||||||
|
|
||||||
If `--` is given, all remaining arguments will be assigned to a special
|
If `--` is given, all remaining arguments will be assigned to a special
|
||||||
`CLI_ARGS` variable
|
`CLI_ARGS` variable.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
70
website/src/docs/reference/config.md
Normal file
70
website/src/docs/reference/config.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: Configuration Reference
|
||||||
|
description: Complete reference for the Task config files and env vars
|
||||||
|
permalink: /reference/config/
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Configuration Reference
|
||||||
|
|
||||||
|
Task has multiple ways of being configured. These methods are parsed, in
|
||||||
|
sequence, in the following order with the highest priority last:
|
||||||
|
|
||||||
|
- [Environment variables](./environment.md)
|
||||||
|
- _Configuration files_
|
||||||
|
- [Command-line flags](./cli.md)
|
||||||
|
|
||||||
|
In this document, we will look at the second of the three options, configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
## File Precedence
|
||||||
|
|
||||||
|
Task's configuration files are named `.taskrc.yml` or `.taskrc.yaml`. Task will
|
||||||
|
automatically look for directories containing files with these names in the
|
||||||
|
following order with the highest priority first:
|
||||||
|
|
||||||
|
- Current directory (or the one specified by the `--taskfile`/`--entrypoint`
|
||||||
|
flags).
|
||||||
|
- Each directory walking up the file tree from the current directory (or the one
|
||||||
|
specified by the `--taskfile`/`--entrypoint` flags) until we reach the user's
|
||||||
|
home directory or the root directory of that drive.
|
||||||
|
- `$XDG_CONFIG_HOME/task`.
|
||||||
|
|
||||||
|
All config files will be merged together into a unified config, starting with
|
||||||
|
the lowest priority file in `$XDG_CONFIG_HOME/task` with each subsequent file
|
||||||
|
overwriting the previous one if values are set.
|
||||||
|
|
||||||
|
For example, given the following files:
|
||||||
|
|
||||||
|
```yaml [$XDG_CONFIG_HOME/task/.taskrc.yml]
|
||||||
|
# lowest priority global config
|
||||||
|
option_1: foo
|
||||||
|
option_2: foo
|
||||||
|
option_3: foo
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml [$HOME/.taskrc.yml]
|
||||||
|
option_1: bar
|
||||||
|
option_2: bar
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml [$HOME/path/to/project/.taskrc.yml]
|
||||||
|
# highest priority project config
|
||||||
|
option_1: baz
|
||||||
|
```
|
||||||
|
|
||||||
|
You would end up with the following configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
option_1: baz # Taken from $HOME/path/to/project/.taskrc.yml
|
||||||
|
option_2: bar # Taken from $HOME/.taskrc.yml
|
||||||
|
option_3: foo # Taken from $XDG_CONFIG_HOME/task/.taskrc.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### `experiments`
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -6,33 +6,43 @@ outline: deep
|
|||||||
|
|
||||||
# Environment Reference
|
# Environment Reference
|
||||||
|
|
||||||
Task allows you to configure some behavior using environment variables. This
|
Task has multiple ways of being configured. These methods are parsed, in
|
||||||
page lists all the environment variables that Task supports.
|
sequence, in the following order with the highest priority last:
|
||||||
|
|
||||||
| ENV | Default | Description |
|
- _Environment variables_
|
||||||
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
- [Configuration files](./config.md)
|
||||||
| `TASK_TEMP_DIR` | `.task` | Location of the temp dir. Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
|
- [Command-line flags](./cli.md)
|
||||||
| `TASK_REMOTE_DIR` | `TASK_TEMP_DIR` | Location of the remote temp dir (used for caching). Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
|
|
||||||
| `TASK_OFFLINE` | `false` | Set the `--offline` flag through the environment variable. Only for remote experiment. CLI flag `--offline` takes precedence over the env variable |
|
|
||||||
| `FORCE_COLOR` | | Force color output usage. |
|
|
||||||
|
|
||||||
## Custom Colors
|
In this document, we will look at the first of the three options, environment
|
||||||
|
variables. All Task-specific variables are prefixed with `TASK_` and override
|
||||||
|
their configuration file equivalents.
|
||||||
|
|
||||||
| ENV | Default | Description |
|
## Variables
|
||||||
| --------------------------- | ------- | ----------------------- |
|
|
||||||
| `TASK_COLOR_RESET` | `0` | Color used for white. |
|
### `TASK_TEMP_DIR`
|
||||||
| `TASK_COLOR_RED` | `31` | Color used for red. |
|
|
||||||
| `TASK_COLOR_GREEN` | `32` | Color used for green. |
|
Defines the location of Task's temporary directory which is used for storing
|
||||||
| `TASK_COLOR_YELLOW` | `33` | Color used for yellow. |
|
checksums and temporary metadata. Can be relative like `tmp/task` or absolute
|
||||||
| `TASK_COLOR_BLUE` | `34` | Color used for blue. |
|
like `/tmp/.task` or `~/.task`. Relative paths are relative to the root
|
||||||
| `TASK_COLOR_MAGENTA` | `35` | Color used for magenta. |
|
Taskfile, not the working directory. Defaults to: `./.task`.
|
||||||
| `TASK_COLOR_CYAN` | `36` | Color used for cyan. |
|
|
||||||
| `TASK_COLOR_BRIGHT_RED` | `91` | Color used for red. |
|
### `TASK_REMOTE_DIR`
|
||||||
| `TASK_COLOR_BRIGHT_GREEN` | `92` | Color used for green. |
|
|
||||||
| `TASK_COLOR_BRIGHT_YELLOW` | `93` | Color used for yellow. |
|
Defines the location of Task's remote temporary directory which is used for
|
||||||
| `TASK_COLOR_BRIGHT_BLUE` | `94` | Color used for blue. |
|
caching remote files. Can be relative like `tmp/task` or absolute like
|
||||||
| `TASK_COLOR_BRIGHT_MAGENTA` | `95` | Color used for magenta. |
|
`/tmp/.task` or `~/.task`. Relative paths are relative to the root Taskfile, not
|
||||||
| `TASK_COLOR_BRIGHT_CYAN` | `96` | Color used for cyan. |
|
the working directory. Defaults to: `./.task`.
|
||||||
|
|
||||||
|
### `TASK_OFFLINE`
|
||||||
|
|
||||||
|
Set the `--offline` flag through the environment variable. Only for remote
|
||||||
|
experiment. CLI flag `--offline` takes precedence over the env variable.
|
||||||
|
|
||||||
|
### `FORCE_COLOR`
|
||||||
|
|
||||||
|
Force color output usage.
|
||||||
|
|
||||||
|
### Custom Colors
|
||||||
|
|
||||||
All color variables are [ANSI color codes][ansi]. You can specify multiple codes
|
All color variables are [ANSI color codes][ansi]. You can specify multiple codes
|
||||||
separated by a semicolon. For example: `31;1` will make the text bold and red.
|
separated by a semicolon. For example: `31;1` will make the text bold and red.
|
||||||
@@ -45,4 +55,22 @@ For convenience, we allow foreground colors to be specified using shorthand,
|
|||||||
comma-separated syntax: `R,G,B`. For example, `255,0,0` is equivalent to
|
comma-separated syntax: `R,G,B`. For example, `255,0,0` is equivalent to
|
||||||
`38;2;255:0:0`.
|
`38;2;255:0:0`.
|
||||||
|
|
||||||
|
A table of variables and their defaults can be found below:
|
||||||
|
|
||||||
|
| ENV | Default |
|
||||||
|
| --------------------------- | ------- |
|
||||||
|
| `TASK_COLOR_RESET` | `0` |
|
||||||
|
| `TASK_COLOR_RED` | `31` |
|
||||||
|
| `TASK_COLOR_GREEN` | `32` |
|
||||||
|
| `TASK_COLOR_YELLOW` | `33` |
|
||||||
|
| `TASK_COLOR_BLUE` | `34` |
|
||||||
|
| `TASK_COLOR_MAGENTA` | `35` |
|
||||||
|
| `TASK_COLOR_CYAN` | `36` |
|
||||||
|
| `TASK_COLOR_BRIGHT_RED` | `91` |
|
||||||
|
| `TASK_COLOR_BRIGHT_GREEN` | `92` |
|
||||||
|
| `TASK_COLOR_BRIGHT_YELLOW` | `93` |
|
||||||
|
| `TASK_COLOR_BRIGHT_BLUE` | `94` |
|
||||||
|
| `TASK_COLOR_BRIGHT_MAGENTA` | `95` |
|
||||||
|
| `TASK_COLOR_BRIGHT_CYAN` | `96` |
|
||||||
|
|
||||||
[ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
|
[ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
title: CLI Reference
|
title: Package API Reference
|
||||||
description: Complete reference for Task CLI commands, flags, and exit codes
|
description: A reference for Task's Golang package API
|
||||||
---
|
---
|
||||||
|
|
||||||
# Package API
|
# Package API Reference
|
||||||
|
|
||||||
::: warning
|
::: warning
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: Schema Reference
|
title: Taskfile Schema Reference
|
||||||
description:
|
description: A reference for the Taskfile schema
|
||||||
Complete reference for the Taskfile schema based on the official JSON schema
|
|
||||||
outline: deep
|
outline: deep
|
||||||
---
|
---
|
||||||
|
|
||||||
# Schema Reference
|
# Taskfile Schema Reference
|
||||||
|
|
||||||
This page documents all available properties and types for the Taskfile schema
|
This page documents all available properties and types for the Taskfile schema
|
||||||
version 3, based on the
|
version 3, based on the
|
||||||
|
|||||||
Reference in New Issue
Block a user