mirror of
https://github.com/go-task/task.git
synced 2025-06-08 23:56:21 +02:00
feat: root remote taskfiles
This commit is contained in:
parent
f00693052a
commit
cbc19d35ea
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@ -94,11 +93,6 @@ func run() error {
|
|||||||
dir = home
|
dir = home
|
||||||
}
|
}
|
||||||
|
|
||||||
if entrypoint != "" {
|
|
||||||
dir = filepath.Dir(entrypoint)
|
|
||||||
entrypoint = filepath.Base(entrypoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
var taskSorter sort.TaskSorter
|
var taskSorter sort.TaskSorter
|
||||||
switch flags.TaskSort {
|
switch flags.TaskSort {
|
||||||
case "none":
|
case "none":
|
||||||
|
@ -131,10 +131,6 @@ func Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if Dir != "" && Entrypoint != "" {
|
|
||||||
return errors.New("task: You can't set both --dir and --taskfile")
|
|
||||||
}
|
|
||||||
|
|
||||||
if Output.Name != "group" {
|
if Output.Name != "group" {
|
||||||
if Output.Group.Begin != "" {
|
if Output.Group.Begin != "" {
|
||||||
return errors.New("task: You can't set --output-group-begin without --output=group")
|
return errors.New("task: You can't set --output-group-begin without --output=group")
|
||||||
|
4
setup.go
4
setup.go
@ -54,11 +54,11 @@ func (e *Executor) Setup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||||
node, err := taskfile.NewRootNode(e.Dir, e.Entrypoint, e.Insecure)
|
node, err := taskfile.NewRootNode(e.Logger, e.Entrypoint, e.Dir, e.Insecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
e.Dir = node.BaseDir()
|
e.Dir = node.Dir()
|
||||||
return node, err
|
return node, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
task_test.go
10
task_test.go
@ -73,7 +73,7 @@ func (fct fileContentTest) Run(t *testing.T) {
|
|||||||
|
|
||||||
for name, expectContent := range fct.Files {
|
for name, expectContent := range fct.Files {
|
||||||
t.Run(fct.name(name), func(t *testing.T) {
|
t.Run(fct.name(name), func(t *testing.T) {
|
||||||
path := filepathext.SmartJoin(fct.Dir, name)
|
path := filepathext.SmartJoin(e.Dir, name)
|
||||||
b, err := os.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
require.NoError(t, err, "Error reading file")
|
require.NoError(t, err, "Error reading file")
|
||||||
s := string(b)
|
s := string(b)
|
||||||
@ -1123,8 +1123,8 @@ func TestIncludesOptionalExplicitFalse(t *testing.T) {
|
|||||||
|
|
||||||
func TestIncludesFromCustomTaskfile(t *testing.T) {
|
func TestIncludesFromCustomTaskfile(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
|
Entrypoint: "testdata/includes_yaml/Custom.ext",
|
||||||
Dir: "testdata/includes_yaml",
|
Dir: "testdata/includes_yaml",
|
||||||
Entrypoint: "Custom.ext",
|
|
||||||
Target: "default",
|
Target: "default",
|
||||||
TrimSpace: true,
|
TrimSpace: true,
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
@ -1486,13 +1486,9 @@ func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
|
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
|
||||||
const dir = "testdata/dotenv/error_included_envs"
|
|
||||||
const entry = "Taskfile.yml"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Dir: dir,
|
Dir: "testdata/dotenv/error_included_envs",
|
||||||
Entrypoint: entry,
|
|
||||||
Summary: true,
|
Summary: true,
|
||||||
Stdout: &buff,
|
Stdout: &buff,
|
||||||
Stderr: &buff,
|
Stderr: &buff,
|
||||||
|
@ -2,13 +2,9 @@ package ast
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
|
||||||
omap "github.com/go-task/task/v3/internal/omap"
|
omap "github.com/go-task/task/v3/internal/omap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +18,6 @@ type Include struct {
|
|||||||
Aliases []string
|
Aliases []string
|
||||||
AdvancedImport bool
|
AdvancedImport bool
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
BaseDir string // The directory from which the including taskfile was loaded; used to resolve relative paths
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Includes represents information about included tasksfiles
|
// Includes represents information about included tasksfiles
|
||||||
@ -120,39 +115,5 @@ func (include *Include) DeepCopy() *Include {
|
|||||||
Internal: include.Internal,
|
Internal: include.Internal,
|
||||||
AdvancedImport: include.AdvancedImport,
|
AdvancedImport: include.AdvancedImport,
|
||||||
Vars: include.Vars.DeepCopy(),
|
Vars: include.Vars.DeepCopy(),
|
||||||
BaseDir: include.BaseDir,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullTaskfilePath returns the fully qualified path to the included taskfile
|
|
||||||
func (include *Include) FullTaskfilePath() (string, error) {
|
|
||||||
return include.resolvePath(include.Taskfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullDirPath returns the fully qualified path to the included taskfile's working directory
|
|
||||||
func (include *Include) FullDirPath() (string, error) {
|
|
||||||
return include.resolvePath(include.Dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (include *Include) resolvePath(path string) (string, error) {
|
|
||||||
// If the file is remote, we don't need to resolve the path
|
|
||||||
if strings.Contains(include.Taskfile, "://") {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := execext.Expand(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepathext.IsAbs(path) {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := filepath.Abs(filepathext.SmartJoin(include.BaseDir, path))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("task: error resolving path %s relative to %s: %w", path, include.BaseDir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
@ -8,20 +8,25 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/experiments"
|
"github.com/go-task/task/v3/internal/experiments"
|
||||||
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node interface {
|
type Node interface {
|
||||||
Read(ctx context.Context) ([]byte, error)
|
Read(ctx context.Context) ([]byte, error)
|
||||||
Parent() Node
|
Parent() Node
|
||||||
Location() string
|
Location() string
|
||||||
|
Dir() string
|
||||||
Optional() bool
|
Optional() bool
|
||||||
Remote() bool
|
Remote() bool
|
||||||
BaseDir() string
|
ResolveIncludeEntrypoint(include ast.Include) (string, error)
|
||||||
|
ResolveIncludeDir(include ast.Include) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRootNode(
|
func NewRootNode(
|
||||||
dir string,
|
l *logger.Logger,
|
||||||
entrypoint string,
|
entrypoint string,
|
||||||
|
dir string,
|
||||||
insecure bool,
|
insecure bool,
|
||||||
) (Node, error) {
|
) (Node, error) {
|
||||||
dir = getDefaultDir(entrypoint, dir)
|
dir = getDefaultDir(entrypoint, dir)
|
||||||
@ -30,32 +35,24 @@ func NewRootNode(
|
|||||||
if (stat.Mode()&os.ModeCharDevice) == 0 && stat.Size() > 0 {
|
if (stat.Mode()&os.ModeCharDevice) == 0 && stat.Size() > 0 {
|
||||||
return NewStdinNode(dir)
|
return NewStdinNode(dir)
|
||||||
}
|
}
|
||||||
// If no entrypoint is specified, search for a taskfile
|
return NewNode(l, entrypoint, dir, insecure)
|
||||||
if entrypoint == "" {
|
|
||||||
root, err := ExistsWalk(dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewNode(root, insecure)
|
|
||||||
}
|
|
||||||
// Use the specified entrypoint
|
|
||||||
uri := filepath.Join(dir, entrypoint)
|
|
||||||
return NewNode(uri, insecure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNode(
|
func NewNode(
|
||||||
uri string,
|
l *logger.Logger,
|
||||||
|
entrypoint string,
|
||||||
|
dir string,
|
||||||
insecure bool,
|
insecure bool,
|
||||||
opts ...NodeOption,
|
opts ...NodeOption,
|
||||||
) (Node, error) {
|
) (Node, error) {
|
||||||
var node Node
|
var node Node
|
||||||
var err error
|
var err error
|
||||||
switch getScheme(uri) {
|
switch getScheme(entrypoint) {
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
node, err = NewHTTPNode(uri, insecure, opts...)
|
node, err = NewHTTPNode(l, entrypoint, dir, insecure, opts...)
|
||||||
default:
|
default:
|
||||||
// If no other scheme matches, we assume it's a file
|
// If no other scheme matches, we assume it's a file
|
||||||
node, err = NewFileNode(uri, opts...)
|
node, err = NewFileNode(l, entrypoint, dir, opts...)
|
||||||
}
|
}
|
||||||
if node.Remote() && !experiments.RemoteTaskfiles.Enabled {
|
if node.Remote() && !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 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")
|
||||||
|
@ -9,6 +9,7 @@ type (
|
|||||||
BaseNode struct {
|
BaseNode struct {
|
||||||
parent Node
|
parent Node
|
||||||
optional bool
|
optional bool
|
||||||
|
dir string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ func NewBaseNode(opts ...NodeOption) *BaseNode {
|
|||||||
node := &BaseNode{
|
node := &BaseNode{
|
||||||
parent: nil,
|
parent: nil,
|
||||||
optional: false,
|
optional: false,
|
||||||
|
dir: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply options
|
// Apply options
|
||||||
@ -45,3 +47,7 @@ func WithOptional(optional bool) NodeOption {
|
|||||||
func (node *BaseNode) Optional() bool {
|
func (node *BaseNode) Optional() bool {
|
||||||
return node.optional
|
return node.optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (node *BaseNode) Dir() string {
|
||||||
|
return node.dir
|
||||||
|
}
|
||||||
|
@ -5,39 +5,36 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A FileNode is a node that reads a taskfile from the local filesystem.
|
// A FileNode is a node that reads a taskfile from the local filesystem.
|
||||||
type FileNode struct {
|
type FileNode struct {
|
||||||
*BaseNode
|
*BaseNode
|
||||||
Dir string
|
|
||||||
Entrypoint string
|
Entrypoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileNode(uri string, opts ...NodeOption) (*FileNode, error) {
|
func NewFileNode(l *logger.Logger, entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
||||||
|
var err error
|
||||||
base := NewBaseNode(opts...)
|
base := NewBaseNode(opts...)
|
||||||
if uri == "" {
|
entrypoint, dir, err = resolveFileNodeEntrypointAndDir(l, entrypoint, dir)
|
||||||
d, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
uri = d
|
|
||||||
}
|
|
||||||
path, err := Exists(uri)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
base.dir = dir
|
||||||
return &FileNode{
|
return &FileNode{
|
||||||
BaseNode: base,
|
BaseNode: base,
|
||||||
Dir: filepath.Dir(path),
|
Entrypoint: entrypoint,
|
||||||
Entrypoint: filepath.Base(path),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *FileNode) Location() string {
|
func (node *FileNode) Location() string {
|
||||||
return filepathext.SmartJoin(node.Dir, node.Entrypoint)
|
return node.Entrypoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *FileNode) Remote() bool {
|
func (node *FileNode) Remote() bool {
|
||||||
@ -53,6 +50,67 @@ func (node *FileNode) Read(ctx context.Context) ([]byte, error) {
|
|||||||
return io.ReadAll(f)
|
return io.ReadAll(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *FileNode) BaseDir() string {
|
// resolveFileNodeEntrypointAndDir resolves checks the values of entrypoint and dir and
|
||||||
return node.Dir
|
// populates them with default values if necessary.
|
||||||
|
func resolveFileNodeEntrypointAndDir(l *logger.Logger, entrypoint, dir string) (string, string, error) {
|
||||||
|
var err error
|
||||||
|
if entrypoint != "" {
|
||||||
|
entrypoint, err = Exists(l, entrypoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
dir = filepath.Dir(entrypoint)
|
||||||
|
}
|
||||||
|
return entrypoint, dir, nil
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
dir, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entrypoint, err = ExistsWalk(l, dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
dir = filepath.Dir(entrypoint)
|
||||||
|
return entrypoint, dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *FileNode) ResolveIncludeEntrypoint(include ast.Include) (string, error) {
|
||||||
|
// If the file is remote, we don't need to resolve the path
|
||||||
|
if strings.Contains(include.Taskfile, "://") {
|
||||||
|
return include.Taskfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := execext.Expand(include.Taskfile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepathext.IsAbs(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||||
|
// This means that files are included relative to one another
|
||||||
|
entrypointDir := filepath.Dir(node.Entrypoint)
|
||||||
|
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *FileNode) ResolveIncludeDir(include ast.Include) (string, error) {
|
||||||
|
path, err := execext.Expand(include.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepathext.IsAbs(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||||
|
// This means that files are included relative to one another
|
||||||
|
entrypointDir := filepath.Dir(node.Entrypoint)
|
||||||
|
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
|
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
|
||||||
@ -15,14 +20,19 @@ type HTTPNode struct {
|
|||||||
URL *url.URL
|
URL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPNode(uri string, insecure bool, opts ...NodeOption) (*HTTPNode, error) {
|
func NewHTTPNode(l *logger.Logger, entrypoint, dir string, insecure bool, opts ...NodeOption) (*HTTPNode, error) {
|
||||||
base := NewBaseNode(opts...)
|
base := NewBaseNode(opts...)
|
||||||
url, err := url.Parse(uri)
|
base.dir = dir
|
||||||
|
url, err := url.Parse(entrypoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if url.Scheme == "http" && !insecure {
|
if url.Scheme == "http" && !insecure {
|
||||||
return nil, &errors.TaskfileNotSecureError{URI: uri}
|
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
|
||||||
|
}
|
||||||
|
url, err = RemoteExists(l, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return &HTTPNode{
|
return &HTTPNode{
|
||||||
BaseNode: base,
|
BaseNode: base,
|
||||||
@ -66,6 +76,26 @@ func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *HTTPNode) BaseDir() string {
|
func (node *HTTPNode) ResolveIncludeEntrypoint(include ast.Include) (string, error) {
|
||||||
return ""
|
ref, err := url.Parse(include.Taskfile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return node.URL.ResolveReference(ref).String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *HTTPNode) ResolveIncludeDir(include ast.Include) (string, error) {
|
||||||
|
path, err := execext.Expand(include.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepathext.IsAbs(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||||
|
// This means that files are included relative to one another
|
||||||
|
entrypointDir := filepath.Dir(node.Dir())
|
||||||
|
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||||
}
|
}
|
||||||
|
@ -5,19 +5,23 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A StdinNode is a node that reads a taskfile from the standard input stream.
|
// A StdinNode is a node that reads a taskfile from the standard input stream.
|
||||||
type StdinNode struct {
|
type StdinNode struct {
|
||||||
*BaseNode
|
*BaseNode
|
||||||
Dir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStdinNode(dir string) (*StdinNode, error) {
|
func NewStdinNode(dir string) (*StdinNode, error) {
|
||||||
base := NewBaseNode()
|
base := NewBaseNode()
|
||||||
|
base.dir = dir
|
||||||
return &StdinNode{
|
return &StdinNode{
|
||||||
BaseNode: base,
|
BaseNode: base,
|
||||||
Dir: dir,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +45,33 @@ func (node *StdinNode) Read(ctx context.Context) ([]byte, error) {
|
|||||||
return stdin, nil
|
return stdin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *StdinNode) BaseDir() string {
|
func (node *StdinNode) ResolveIncludeEntrypoint(include ast.Include) (string, error) {
|
||||||
return node.Dir
|
// If the file is remote, we don't need to resolve the path
|
||||||
|
if strings.Contains(include.Taskfile, "://") {
|
||||||
|
return include.Taskfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := execext.Expand(include.Taskfile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepathext.IsAbs(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepathext.SmartJoin(node.Dir(), path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *StdinNode) ResolveIncludeDir(include ast.Include) (string, error) {
|
||||||
|
path, err := execext.Expand(include.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepathext.IsAbs(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepathext.SmartJoin(node.Dir(), path), nil
|
||||||
}
|
}
|
||||||
|
@ -48,17 +48,6 @@ func Read(
|
|||||||
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
|
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir := node.BaseDir(); dir != "" {
|
|
||||||
_ = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
|
||||||
// Set the base directory for resolving relative paths, but only if not already set
|
|
||||||
if include.BaseDir == "" {
|
|
||||||
include.BaseDir = dir
|
|
||||||
tf.Includes.Set(namespace, include)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
err = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
||||||
cache := &templater.Cache{Vars: tf.Vars}
|
cache := &templater.Cache{Vars: tf.Vars}
|
||||||
include = ast.Include{
|
include = ast.Include{
|
||||||
@ -70,18 +59,22 @@ func Read(
|
|||||||
Aliases: include.Aliases,
|
Aliases: include.Aliases,
|
||||||
AdvancedImport: include.AdvancedImport,
|
AdvancedImport: include.AdvancedImport,
|
||||||
Vars: include.Vars,
|
Vars: include.Vars,
|
||||||
BaseDir: include.BaseDir,
|
|
||||||
}
|
}
|
||||||
if err := cache.Err(); err != nil {
|
if err := cache.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
uri, err := include.FullTaskfilePath()
|
entrypoint, err := node.ResolveIncludeEntrypoint(include)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
includeReaderNode, err := NewNode(uri, insecure,
|
dir, err := node.ResolveIncludeDir(include)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
includeReaderNode, err := NewNode(l, entrypoint, dir, insecure,
|
||||||
WithParent(node),
|
WithParent(node),
|
||||||
WithOptional(include.Optional),
|
WithOptional(include.Optional),
|
||||||
)
|
)
|
||||||
@ -109,11 +102,6 @@ func Read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if include.AdvancedImport {
|
if include.AdvancedImport {
|
||||||
dir, err := include.FullDirPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: errcheck
|
// nolint: errcheck
|
||||||
includedTaskfile.Vars.Range(func(k string, v ast.Var) error {
|
includedTaskfile.Vars.Range(func(k string, v ast.Var) error {
|
||||||
o := v
|
o := v
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/sysinfo"
|
"github.com/go-task/task/v3/internal/sysinfo"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,14 +28,80 @@ var (
|
|||||||
"Taskfile.dist.yaml",
|
"Taskfile.dist.yaml",
|
||||||
"taskfile.dist.yaml",
|
"taskfile.dist.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowedContentTypes = []string{
|
||||||
|
"text/plain",
|
||||||
|
"text/yaml",
|
||||||
|
"text/x-yaml",
|
||||||
|
"application/yaml",
|
||||||
|
"application/x-yaml",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RemoteExists will check if a file at the given URL Exists. If it does, it
|
||||||
|
// will return its URL. If it does not, it will search the search for any files
|
||||||
|
// at the given URL with any of the default Taskfile files 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.
|
||||||
|
func RemoteExists(l *logger.Logger, u *url.URL) (*url.URL, error) {
|
||||||
|
// Create a new HEAD request for the given URL to check if the resource exists
|
||||||
|
req, err := http.NewRequest("HEAD", u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request the given URL
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// If the request was successful and the content type is allowed, return the
|
||||||
|
// URL The content type check is to avoid downloading files that are not
|
||||||
|
// Taskfiles It means we can try other files instead of downloading
|
||||||
|
// something that is definitely not a Taskfile
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
if resp.StatusCode == http.StatusOK && slices.ContainsFunc(allowedContentTypes, func(s string) bool {
|
||||||
|
return strings.Contains(contentType, s)
|
||||||
|
}) {
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request was not successful, append the default Taskfile names to
|
||||||
|
// the URL and return the URL of the first successful request
|
||||||
|
for _, taskfile := range defaultTaskfiles {
|
||||||
|
// Fixes a bug with JoinPath where a leading slash is not added to the
|
||||||
|
// path if it is empty
|
||||||
|
if u.Path == "" {
|
||||||
|
u.Path = "/"
|
||||||
|
}
|
||||||
|
alt := u.JoinPath(taskfile)
|
||||||
|
req.URL = alt
|
||||||
|
|
||||||
|
// Try the alternative URL
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// If the request was successful, return the URL
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
l.VerboseOutf(logger.Magenta, "task: [%s] Not found - Using alternative (%s)\n", alt.String(), taskfile)
|
||||||
|
return alt, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.TaskfileNotFoundError{URI: u.String(), Walk: false}
|
||||||
|
}
|
||||||
|
|
||||||
// Exists will check if a file at the given path Exists. If it does, it will
|
// Exists will check if a file at the given path Exists. If it does, it will
|
||||||
// return the path to it. If it does not, it will search the search for any
|
// return the path to it. If it does not, it will search for any files at the
|
||||||
// files at the given path with any of the default Taskfile files names. If any
|
// given path with any of the default Taskfile files names. If any of these
|
||||||
// of these match a file, the first matching path will be returned. If no files
|
// match a file, the first matching path will be returned. If no files are
|
||||||
// are found, an error will be returned.
|
// found, an error will be returned.
|
||||||
func Exists(path string) (string, error) {
|
func Exists(l *logger.Logger, path string) (string, error) {
|
||||||
fi, err := os.Stat(path)
|
fi, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -42,10 +113,11 @@ func Exists(path string) (string, error) {
|
|||||||
return filepath.Abs(path)
|
return filepath.Abs(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, n := range defaultTaskfiles {
|
for _, taskfile := range defaultTaskfiles {
|
||||||
fpath := filepathext.SmartJoin(path, n)
|
alt := filepathext.SmartJoin(path, taskfile)
|
||||||
if _, err := os.Stat(fpath); err == nil {
|
if _, err := os.Stat(alt); err == nil {
|
||||||
return filepath.Abs(fpath)
|
l.VerboseOutf(logger.Magenta, "task: [%s] Not found - Using alternative (%s)\n", path, taskfile)
|
||||||
|
return filepath.Abs(alt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,14 +129,14 @@ func Exists(path string) (string, error) {
|
|||||||
// calling the exists function until it finds a file or reaches the root
|
// calling the exists function until it finds a file or reaches the root
|
||||||
// directory. On supported operating systems, it will also check if the user ID
|
// directory. On supported operating systems, it will 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 ExistsWalk(path string) (string, error) {
|
func ExistsWalk(l *logger.Logger, path string) (string, error) {
|
||||||
origPath := path
|
origPath := path
|
||||||
owner, err := sysinfo.Owner(path)
|
owner, err := sysinfo.Owner(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
fpath, err := Exists(path)
|
fpath, err := Exists(l, path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fpath, nil
|
return fpath, nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user