Compare commits

..

31 Commits

Author SHA1 Message Date
Pete Davison
5128a98ee2 feat: test wildcards 2025-08-11 12:00:34 +00:00
Pete Davison
8353dffc7a fix: executor and formatter tests 2025-08-11 12:00:34 +00:00
Pete Davison
6e80b401e6 feat: remove entrypoint from the executor 2025-08-11 12:00:34 +00:00
Pete Davison
1402e2baaf refactor: merge Setup into NewExecutor 2025-08-11 12:00:34 +00:00
Pete Davison
4b99f60039 refactor: split executor and reader 2025-08-11 12:00:34 +00:00
Valentin Maerten
4bdfe5ce3b fix: publish nightly draft and title (#2358) 2025-08-09 16:03:46 +02:00
Valentin Maerten
26ef693417 chore: publish nightly (#2246)
Co-authored-by: Andrey Nering <andreynering@users.noreply.github.com>
2025-08-06 20:29:36 +02:00
renovate[bot]
952f32d388 chore(deps): update all non-major dependencies (#2351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 10:51:19 -03:00
Andrey Nering
e72c35f79f fix(goreleaser): fix automatic submission of winget pr 2025-07-28 10:59:45 -03:00
Pete Davison
72991d4f04 v3.44.1 2025-07-23 20:59:38 +00:00
Pete Davison
6f965e3043 chore: changelog for #2265 2025-07-23 20:59:14 +00:00
Pete Davison
1c6d686356 chore: replace PPRemoveAbsolutePaths with generic fixture template data (#2265)
* chore: replace PPRemoveAbsolutePaths with generic fixture template data

* chore: update to goldie v2.7.1
2025-07-23 21:57:25 +01:00
renovate[bot]
dac5aa1954 chore(deps): update all non-major dependencies (#2333)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 09:46:14 -03:00
Emil
303bd6ccb2 chore(goreleaser): add section field to deb package (#2331) 2025-07-21 09:45:08 -03:00
renovate[bot]
f736cfaaf1 chore(deps): update all non-major dependencies (#2326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 09:23:53 -03:00
Pete Davison
53f97889bc chore: changelog for #2323 2025-07-09 17:38:54 +00:00
Max Mizikar
fe2da74ea3 fix: don't suggest internal tasks (#2323)
Co-authored-by: Max Mizikar <maxmzkr@gmail.com>
2025-07-09 18:36:40 +01:00
Pete Davison
64fb66895b chore: added changelogs for #2316 and #2322 2025-07-09 15:26:47 +00:00
Pete Davison
d2bd834c81 fix: spaces in path (#2322) 2025-07-09 16:21:42 +01:00
Andrey Nering
8a43ca5d8f chore: move away from deprecated func 2025-07-07 10:14:50 -03:00
Andrey Nering
a10a9faabf chore(taskfile): add go.mod as source for the lint tasks 2025-07-07 10:14:50 -03:00
renovate[bot]
3d3ed0e403 chore(deps): update all non-major dependencies 2025-07-07 10:14:50 -03:00
Pete Davison
47dc87a2c9 fix: remove extra breaking randInt function (#2316) 2025-07-03 23:08:43 +01:00
Pete Davison
3b0a746f85 feat: update go-task/template to latest version 2025-07-03 21:46:09 +00:00
renovate[bot]
281edfe5b3 chore(deps): update all non-major dependencies (#2311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 09:32:44 -03:00
Alexander Kavon
7289ffce0b docs: add macports / freebsd installation instructions (#2308) 2025-06-30 09:31:48 -03:00
dependabot[bot]
61e1af50ff chore(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /website (#2298)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 10:39:42 -03:00
renovate[bot]
715a143735 chore(deps): update all non-major dependencies (#2297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 09:26:27 -03:00
Andrey Nering
a0b1605634 chore: add changelog entry for #2291 2025-06-09 14:12:03 -03:00
Timothy Rule
69fc13bd13 fix(release): fix install script for armv5/6/7 (#2291) 2025-06-09 14:07:11 -03:00
renovate[bot]
b42a52ba77 chore(deps): update all non-major dependencies (#2289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 22:09:51 -03:00
74 changed files with 3442 additions and 3225 deletions

29
.github/workflows/release-nightly.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Realease nightly
on:
workflow_dispatch:
schedule:
- cron: 0 0 * * *
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
args: release --clean --nightly -f .goreleaser-nightly.yml
env:
GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}

View File

@@ -24,7 +24,7 @@ jobs:
with:
distribution: goreleaser-pro
version: latest
args: release --clean
args: release --clean --draft
env:
GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}

15
.goreleaser-nightly.yml Normal file
View File

@@ -0,0 +1,15 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
pro: true
release:
name_template: 'v{{.Version}}'
nightly:
publish_release: true
keep_single_release: true
version_template: "{{incminor .Version}}-nightly"
includes:
- from_file:
path: ./.goreleaser.yml

View File

@@ -30,13 +30,14 @@ builds:
flags:
- -trimpath
ldflags:
- -s -w # Don't set main.version.
- "-s -w"
- "{{if .IsNightly}}-X github.com/go-task/task/v3/internal/version.version={{.Version}}{{end}}"
gomod:
proxy: true
archives:
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
files:
- README.md
- LICENSE
@@ -45,27 +46,29 @@ archives:
- goos: windows
formats: [zip]
release:
draft: true
git:
ignore_tags:
- "{{if not .IsNightly}}nightly{{end}}"
snapshot:
version_template: "{{.Version}}"
version_template: '{{.Version}}'
checksum:
name_template: "task_checksums.txt"
name_template: 'task_checksums.txt'
nfpms:
- vendor: Task
homepage: https://taskfile.dev
maintainer: The Task authors <task@taskfile.dev>
description: Simple task runner written in Go
section: golang
license: MIT
conflicts:
- taskwarrior
formats:
- deb
- rpm
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
contents:
- src: completion/bash/task.bash
dst: /etc/bash_completion.d/task
@@ -83,8 +86,7 @@ brews:
repository:
owner: go-task
name: homebrew-tap
test:
system "#{bin}/task", "--help"
test: system "#{bin}/task", "--help"
install: |-
bin.install "task"
bash_completion.install "completion/bash/task.bash" => "task"
@@ -107,7 +109,7 @@ winget:
commit_author:
name: task-bot
email: 106601941+task-bot@users.noreply.github.com
commit_msg_template: "chore: bump {{.PackageIdentifier}} to {{.Tag}}"
commit_msg_template: 'chore: release {{.PackageIdentifier}} {{.Tag}}'
release_notes_url: https://github.com/go-task/task/releases/tag/{{.Tag}}
tags:
- build
@@ -121,13 +123,15 @@ winget:
- task-runner
- taskfile
- tool
skip_upload: true
repository:
owner: microsoft
owner: go-task
name: winget-pkgs
branch: 'chore/task-{{.Version}}'
pull_request:
enabled: true
draft: false
check_boxes: true
base:
owner: go-task
owner: microsoft
name: winget-pkgs
branch: "bump-task-to-{{.Tag}}"
branch: master

2
.nvmrc
View File

@@ -1 +1 @@
22.16.0
22.18.0

View File

@@ -1,5 +1,16 @@
# Changelog
## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be
called (#2309, #2323 by @maxmzkrcensys)
- Fixed install script for some ARM platforms (#1516, #2291 by @trulede).
- Fixed a regression where fingerprinting was not working correctly if the path
to you Taskfile contained a space (#2321, #2322 by @pd93).
- Reverted a breaking change to `randInt` (#2312, #2316 by @pd93).
- Made new variables `TEST_NAME` and `TEST_DIR` available in fixture tests
(#2265 by @pd93).
## v3.44.0 - 2025-06-08
- Added `uuid`, `randInt` and `randIntN` template functions (#1346, #2225 by

View File

@@ -53,9 +53,12 @@ tasks:
generate:fixtures:
desc: Runs tests and generates golden fixture files
aliases: [gen:fixtures, g:fixtures]
env:
GOLDIE_UPDATE: 'true'
GOLDIE_TEMPLATE: 'true'
cmds:
- find ./testdata -name '*.golden' -delete
- go test -update ./...
- go test ./...
install:mockery:
desc: Installs mockgen; a tool to generate mock files
@@ -87,6 +90,7 @@ tasks:
sources:
- './**/*.go'
- .golangci.yml
- go.mod
cmds:
- golangci-lint run
@@ -95,6 +99,7 @@ tasks:
sources:
- './**/*.go'
- .golangci.yml
- go.mod
cmds:
- golangci-lint run --fix

View File

@@ -16,6 +16,7 @@ import (
"github.com/go-task/task/v3/internal/flags"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/version"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -110,16 +111,63 @@ func run() error {
return nil
}
e := task.NewExecutor(
flags.WithFlags(),
task.WithVersionCheck(true),
if err := experiments.Validate(); err != nil {
log.Warnf("%s\n", err.Error())
}
// Create a new root node for the given entrypoint
node, err := taskfile.NewRootNode(
flags.Entrypoint,
flags.Dir,
flags.Insecure,
)
if err := e.Setup(); err != nil {
if err != nil {
return err
}
tempDir, err := task.NewTempDir(node.Dir())
if err != nil {
return err
}
reader := taskfile.NewReader(
flags.WithFlags(),
taskfile.WithTempDir(tempDir.Remote),
taskfile.WithDebugFunc(func(s string) {
log.VerboseOutf(logger.Magenta, s)
}),
taskfile.WithPromptFunc(func(s string) error {
return log.Prompt(logger.Yellow, s, "n", "y", "yes")
}),
)
ctx, cf := context.WithTimeout(context.Background(), flags.Timeout)
defer cf()
graph, err := reader.Read(ctx, node)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: flags.Timeout}
}
return err
}
executor, err := task.NewExecutor(graph,
flags.WithFlags(),
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
)
if err != nil {
return err
}
// If the download flag is specified, we should stop execution as soon as
// taskfile is downloaded
if flags.Download {
return nil
}
if flags.ClearCache {
cachePath := filepath.Join(e.TempDir.Remote, "remote")
cachePath := filepath.Join(executor.TempDir.Remote, "remote")
return os.RemoveAll(cachePath)
}
@@ -131,9 +179,9 @@ func run() error {
)
if listOptions.ShouldListTasks() {
if flags.Silent {
return e.ListTaskNames(flags.ListAll)
return executor.ListTaskNames(flags.ListAll)
}
foundTasks, err := e.ListTasks(listOptions)
foundTasks, err := executor.ListTasks(listOptions)
if err != nil {
return err
}
@@ -165,17 +213,17 @@ func run() error {
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
e.Taskfile.Vars.Merge(globals, nil)
executor.Taskfile.Vars.Merge(globals, nil)
if !flags.Watch {
e.InterceptInterruptSignals()
executor.InterceptInterruptSignals()
}
ctx := context.Background()
ctx = context.Background()
if flags.Status {
return e.Status(ctx, calls...)
return executor.Status(ctx, calls...)
}
return e.Run(ctx, calls...)
return executor.Run(ctx, calls...)
}

View File

@@ -1,6 +1,7 @@
package errors
import (
"errors"
"fmt"
"strings"
@@ -46,8 +47,9 @@ func (err *TaskRunError) Code() int {
}
func (err *TaskRunError) TaskExitCode() int {
if c, ok := interp.IsExitStatus(err.Err); ok {
return int(c)
var exit interp.ExitStatus
if errors.As(err.Err, &exit) {
return int(exit)
}
return err.Code()
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"io"
"os"
"path/filepath"
"sync"
"time"
@@ -26,27 +27,21 @@ type (
// within them.
Executor struct {
// Flags
Dir string
Entrypoint string
TempDir TempDir
Force bool
ForceAll bool
Insecure bool
Download bool
Offline bool
Timeout time.Duration
CacheExpiryDuration time.Duration
Watch bool
Verbose bool
Silent bool
AssumeYes bool
AssumeTerm bool // Used for testing
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration
Dir string
TempDir *TempDir
Force bool
ForceAll bool
Watch bool
Verbose bool
Silent bool
AssumeYes bool
AssumeTerm bool // Used for testing
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration
// I/O
Stdin io.Reader
@@ -72,17 +67,17 @@ type (
executionHashesMutex sync.Mutex
watchedDirs *xsync.MapOf[string, bool]
}
TempDir struct {
Remote string
Fingerprint string
}
)
// NewExecutor creates a new [Executor] and applies the given functional options
// to it.
func NewExecutor(opts ...ExecutorOption) *Executor {
func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, error) {
tf, err := graph.Merge()
if err != nil {
return nil, err
}
e := &Executor{
Timeout: time.Second * 10,
Taskfile: tf,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
@@ -100,7 +95,10 @@ func NewExecutor(opts ...ExecutorOption) *Executor {
executionHashesMutex: sync.Mutex{},
}
e.Options(opts...)
return e
if err := e.setup(); err != nil {
return nil, err
}
return e, nil
}
// Options loops through the given [ExecutorOption] functions and applies them
@@ -122,33 +120,23 @@ type dirOption struct {
}
func (o *dirOption) ApplyToExecutor(e *Executor) {
e.Dir = o.dir
}
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By
// default, Task will search for one of the default Taskfiles in the given
// directory.
func WithEntrypoint(entrypoint string) ExecutorOption {
return &entrypointOption{entrypoint}
}
type entrypointOption struct {
entrypoint string
}
func (o *entrypointOption) ApplyToExecutor(e *Executor) {
e.Entrypoint = o.entrypoint
absDir, err := filepath.Abs(o.dir)
if err != nil {
e.Dir = o.dir
return
}
e.Dir = absDir
}
// WithTempDir sets the temporary directory that will be used by [Executor] for
// storing temporary files like checksums and cached remote files. By default,
// the temporary directory is set to the user's temporary directory.
func WithTempDir(tempDir TempDir) ExecutorOption {
func WithTempDir(tempDir *TempDir) ExecutorOption {
return &tempDirOption{tempDir}
}
type tempDirOption struct {
tempDir TempDir
tempDir *TempDir
}
func (o *tempDirOption) ApplyToExecutor(e *Executor) {
@@ -183,76 +171,6 @@ func (o *forceAllOption) ApplyToExecutor(e *Executor) {
e.ForceAll = o.forceAll
}
// WithInsecure allows the [Executor] to make insecure connections when reading
// remote taskfiles. By default, insecure connections are rejected.
func WithInsecure(insecure bool) ExecutorOption {
return &insecureOption{insecure}
}
type insecureOption struct {
insecure bool
}
func (o *insecureOption) ApplyToExecutor(e *Executor) {
e.Insecure = o.insecure
}
// WithDownload forces the [Executor] to download a fresh copy of the taskfile
// from the remote source.
func WithDownload(download bool) ExecutorOption {
return &downloadOption{download}
}
type downloadOption struct {
download bool
}
func (o *downloadOption) ApplyToExecutor(e *Executor) {
e.Download = o.download
}
// WithOffline stops the [Executor] from being able to make network connections.
// It will still be able to read local files and cached copies of remote files.
func WithOffline(offline bool) ExecutorOption {
return &offlineOption{offline}
}
type offlineOption struct {
offline bool
}
func (o *offlineOption) ApplyToExecutor(e *Executor) {
e.Offline = o.offline
}
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
// default, the timeout is set to 10 seconds.
func WithTimeout(timeout time.Duration) ExecutorOption {
return &timeoutOption{timeout}
}
type timeoutOption struct {
timeout time.Duration
}
func (o *timeoutOption) ApplyToExecutor(e *Executor) {
e.Timeout = o.timeout
}
// WithCacheExpiryDuration sets the duration after which the cache is considered
// expired. By default, the cache is considered expired after 24 hours.
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
return &cacheExpiryDurationOption{duration: duration}
}
type cacheExpiryDurationOption struct {
duration time.Duration
}
func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
r.CacheExpiryDuration = o.duration
}
// WithWatch tells the [Executor] to keep running in the background and watch
// for changes to the fingerprint of the tasks that are run. When changes are
// detected, a new task run is triggered.

View File

@@ -4,18 +4,13 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"github.com/Masterminds/semver/v3"
"github.com/sajari/fuzzy"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/env"
"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/internal/output"
"github.com/go-task/task/v3/internal/version"
@@ -23,18 +18,8 @@ import (
"github.com/go-task/task/v3/taskfile/ast"
)
func (e *Executor) Setup() error {
func (e *Executor) setup() error {
e.setupLogger()
node, err := e.getRootNode()
if err != nil {
return err
}
if err := e.setupTempDir(); err != nil {
return err
}
if err := e.readTaskfile(node); err != nil {
return err
}
e.setupFuzzyModel()
e.setupStdFiles()
if err := e.setupOutput(); err != nil {
@@ -54,46 +39,6 @@ func (e *Executor) Setup() error {
return nil
}
func (e *Executor) getRootNode() (taskfile.Node, error) {
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
if err != nil {
return nil, err
}
e.Dir = node.Dir()
return node, err
}
func (e *Executor) readTaskfile(node taskfile.Node) error {
ctx, cf := context.WithTimeout(context.Background(), e.Timeout)
defer cf()
debugFunc := func(s string) {
e.Logger.VerboseOutf(logger.Magenta, s)
}
promptFunc := func(s string) error {
return e.Logger.Prompt(logger.Yellow, s, "n", "y", "yes")
}
reader := taskfile.NewReader(
taskfile.WithInsecure(e.Insecure),
taskfile.WithDownload(e.Download),
taskfile.WithOffline(e.Offline),
taskfile.WithTempDir(e.TempDir.Remote),
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
taskfile.WithDebugFunc(debugFunc),
taskfile.WithPromptFunc(promptFunc),
)
graph, err := reader.Read(ctx, node)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: e.Timeout}
}
return err
}
if e.Taskfile, err = graph.Merge(); err != nil {
return err
}
return nil
}
func (e *Executor) setupFuzzyModel() {
if e.Taskfile == nil {
return
@@ -104,6 +49,9 @@ func (e *Executor) setupFuzzyModel() {
var words []string
for name, task := range e.Taskfile.Tasks.All(nil) {
if task.Internal {
continue
}
words = append(words, name)
words = slices.Concat(words, task.Aliases)
}
@@ -112,52 +60,6 @@ func (e *Executor) setupFuzzyModel() {
e.fuzzyModel = model
}
func (e *Executor) setupTempDir() error {
if e.TempDir != (TempDir{}) {
return nil
}
tempDir := env.GetTaskEnv("TEMP_DIR")
if tempDir == "" {
e.TempDir = TempDir{
Remote: filepathext.SmartJoin(e.Dir, ".task"),
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
}
} else if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
tempDir, err := execext.ExpandLiteral(tempDir)
if err != nil {
return err
}
projectDir, _ := filepath.Abs(e.Dir)
projectName := filepath.Base(projectDir)
e.TempDir = TempDir{
Remote: tempDir,
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
}
} else {
e.TempDir = TempDir{
Remote: filepathext.SmartJoin(e.Dir, tempDir),
Fingerprint: filepathext.SmartJoin(e.Dir, tempDir),
}
}
remoteDir := env.GetTaskEnv("REMOTE_DIR")
if remoteDir != "" {
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
if err != nil {
return err
}
e.TempDir.Remote = remoteTempDir
} else {
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
}
}
return nil
}
func (e *Executor) setupStdFiles() {
if e.Stdin == nil {
e.Stdin = os.Stdin
@@ -203,7 +105,7 @@ func (e *Executor) setupCompiler() error {
e.Compiler = &Compiler{
Dir: e.Dir,
Entrypoint: e.Entrypoint,
Entrypoint: e.Taskfile.Location,
UserWorkingDir: e.UserWorkingDir,
TaskfileEnv: e.Taskfile.Env,
TaskfileVars: e.Taskfile.Vars,

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"testing"
"github.com/sebdah/goldie/v2"
@@ -15,6 +16,7 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -34,7 +36,12 @@ type (
task string
vars map[string]any
input string
nodeDir string
nodeEntrypoint string
nodeInsecure bool
readerOpts []taskfile.ReaderOption
executorOpts []task.ExecutorOption
wantReaderError bool
wantSetupError bool
wantRunError bool
wantStatusError bool
@@ -47,10 +54,12 @@ type (
func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
t.Helper()
tt := &ExecutorTest{
task: "default",
vars: map[string]any{},
task: "default",
vars: map[string]any{},
nodeDir: ".",
TaskTest: TaskTest{
experiments: map[*experiments.Experiment]int{},
experiments: map[*experiments.Experiment]int{},
fixtureTemplateData: map[string]any{},
},
}
// Apply the functional options
@@ -144,11 +153,52 @@ func (tt *ExecutorTest) run(t *testing.T) {
f := func(t *testing.T) {
t.Helper()
var buf bytes.Buffer
ctx := context.Background()
opts := append(
// Create a new root node for the given entrypoint
node, err := taskfile.NewRootNode(
tt.nodeEntrypoint,
tt.nodeDir,
tt.nodeInsecure,
)
require.NoError(t, err)
// Create a golden fixture file for the output
g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
)
// Set up a temporary directory for the taskfile reader and task executor
tempDir, err := task.NewTempDir(node.Dir())
require.NoError(t, err)
tt.readerOpts = append(tt.readerOpts, taskfile.WithTempDir(tempDir.Remote))
// Set up the taskfile reader
reader := taskfile.NewReader(tt.readerOpts...)
graph, err := reader.Read(ctx, node)
if tt.wantReaderError {
require.Error(t, err)
tt.writeFixtureErrReader(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
return
} else {
require.NoError(t, err)
}
executorOpts := slices.Concat(
// Apply the node directory and temp directory to the executor options
// by default, but allow them to by overridden by the test options
[]task.ExecutorOption{
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
},
// Apply the executor options from the test
tt.executorOpts,
task.WithStdout(&buf),
task.WithStderr(&buf),
// Force the input/output streams to be set to the test buffer
[]task.ExecutorOption{
task.WithStdout(&buf),
task.WithStderr(&buf),
},
)
// If the test has input, create a reader for it and add it to the
@@ -156,19 +206,12 @@ func (tt *ExecutorTest) run(t *testing.T) {
if tt.input != "" {
var reader bytes.Buffer
reader.WriteString(tt.input)
opts = append(opts, task.WithStdin(&reader))
executorOpts = append(executorOpts, task.WithStdin(&reader))
}
// Set up the task executor
e := task.NewExecutor(opts...)
// Create a golden fixture file for the output
g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
)
// Call setup and check for errors
if err := e.Setup(); tt.wantSetupError {
executor, err := task.NewExecutor(graph, executorOpts...)
if tt.wantSetupError {
require.Error(t, err)
tt.writeFixtureErrSetup(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
@@ -188,8 +231,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
}
// Run the task and check for errors
ctx := context.Background()
if err := e.Run(ctx, call); tt.wantRunError {
if err := executor.Run(ctx, call); tt.wantRunError {
require.Error(t, err)
tt.writeFixtureErrRun(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
@@ -200,7 +242,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
// If the status flag is set, run the status check
if tt.wantStatusError {
if err := e.Status(ctx, call); err != nil {
if err := executor.Status(ctx, call); err != nil {
tt.writeFixtureStatus(t, g, err.Error())
}
}
@@ -219,20 +261,17 @@ func (tt *ExecutorTest) run(t *testing.T) {
func TestEmptyTask(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithExecutorOptions(
task.WithDir("testdata/empty_task"),
),
WithNodeDir("testdata/empty_task"),
WithExecutorOptions(),
)
}
func TestEmptyTaskfile(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithExecutorOptions(
task.WithDir("testdata/empty_taskfile"),
),
WithSetupError(),
WithPostProcessFn(PPRemoveAbsolutePaths),
WithNodeDir("testdata/empty_taskfile"),
WithReaderError(),
WithFixtureTemplating(),
)
}
@@ -240,15 +279,15 @@ func TestEnv(t *testing.T) {
t.Setenv("QUX", "from_os")
NewExecutorTest(t,
WithName("env precedence disabled"),
WithNodeDir("testdata/env"),
WithExecutorOptions(
task.WithDir("testdata/env"),
task.WithSilent(true),
),
)
NewExecutorTest(t,
WithName("env precedence enabled"),
WithNodeDir("testdata/env"),
WithExecutorOptions(
task.WithDir("testdata/env"),
task.WithSilent(true),
),
WithExperiment(&experiments.EnvPrecedence, 1),
@@ -258,8 +297,8 @@ func TestEnv(t *testing.T) {
func TestVars(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithNodeDir("testdata/vars"),
WithExecutorOptions(
task.WithDir("testdata/vars"),
task.WithSilent(true),
),
)
@@ -269,25 +308,19 @@ func TestRequires(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithName("required var missing"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("missing-var"),
WithRunError(),
)
NewExecutorTest(t,
WithName("required var ok"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("missing-var"),
WithVar("FOO", "bar"),
)
NewExecutorTest(t,
WithName("fails validation"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("validation-var"),
WithVar("ENV", "dev"),
WithVar("FOO", "bar"),
@@ -295,48 +328,37 @@ func TestRequires(t *testing.T) {
)
NewExecutorTest(t,
WithName("passes validation"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("validation-var"),
WithVar("FOO", "one"),
WithVar("ENV", "dev"),
)
NewExecutorTest(t,
WithName("required var missing + fails validation"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("validation-var"),
WithRunError(),
)
NewExecutorTest(t,
WithName("required var missing + fails validation"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("validation-var-dynamic"),
WithVar("FOO", "one"),
WithVar("ENV", "dev"),
)
NewExecutorTest(t,
WithName("require before compile"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("require-before-compile"),
WithRunError(),
)
NewExecutorTest(t,
WithName("var defined in task"),
WithExecutorOptions(
task.WithDir("testdata/requires"),
),
WithNodeDir("testdata/requires"),
WithTask("var-defined-in-task"),
)
}
// TODO: mock fs
func TestSpecialVars(t *testing.T) {
t.Parallel()
@@ -357,17 +379,18 @@ func TestSpecialVars(t *testing.T) {
"included:print-taskfile-dir",
}
for _, dir := range []string{dir, subdir} {
for _, executorDir := range []string{dir, subdir} {
for _, test := range tests {
name := fmt.Sprintf("%s-%s", executorDir, test)
NewExecutorTest(t,
WithName(fmt.Sprintf("%s-%s", dir, test)),
WithName(name),
WithNodeDir(executorDir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
task.WithVersionCheck(true),
),
WithTask(test),
WithPostProcessFn(PPRemoveAbsolutePaths),
WithFixtureTemplating(),
)
}
}
@@ -376,8 +399,8 @@ func TestSpecialVars(t *testing.T) {
func TestConcurrency(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithNodeDir("testdata/concurrency"),
WithExecutorOptions(
task.WithDir("testdata/concurrency"),
task.WithConcurrency(1),
),
WithPostProcessFn(PPSortedLines),
@@ -387,8 +410,8 @@ func TestConcurrency(t *testing.T) {
func TestParams(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithNodeDir("testdata/params"),
WithExecutorOptions(
task.WithDir("testdata/params"),
task.WithSilent(true),
),
WithPostProcessFn(PPSortedLines),
@@ -398,15 +421,14 @@ func TestParams(t *testing.T) {
func TestDeps(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithNodeDir("testdata/deps"),
WithExecutorOptions(
task.WithDir("testdata/deps"),
task.WithSilent(true),
),
WithPostProcessFn(PPSortedLines),
)
}
// TODO: mock fs
func TestStatus(t *testing.T) {
t.Parallel()
@@ -429,8 +451,8 @@ func TestStatus(t *testing.T) {
// gen-foo creates foo.txt, and will always fail it's status check.
NewExecutorTest(t,
WithName("run gen-foo 1 silent"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
),
WithTask("gen-foo"),
@@ -441,8 +463,8 @@ func TestStatus(t *testing.T) {
// only exists after the first run.
NewExecutorTest(t,
WithName("run gen-bar 1 silent"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
),
WithTask("gen-bar"),
@@ -451,8 +473,8 @@ func TestStatus(t *testing.T) {
// if e.Verbose is set to true.
NewExecutorTest(t,
WithName("run gen-baz silent"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
),
WithTask("gen-silent-baz"),
@@ -467,8 +489,8 @@ func TestStatus(t *testing.T) {
// Run gen-bar a second time to produce a checksum file that matches bar.txt
NewExecutorTest(t,
WithName("run gen-bar 2 silent"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
),
WithTask("gen-bar"),
@@ -476,8 +498,8 @@ func TestStatus(t *testing.T) {
// Run gen-bar a third time, to make sure we've triggered the status check.
NewExecutorTest(t,
WithName("run gen-bar 3 silent"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
),
WithTask("gen-bar"),
@@ -489,8 +511,8 @@ func TestStatus(t *testing.T) {
require.NoError(t, err)
NewExecutorTest(t,
WithName("run gen-bar 4 silent"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true),
),
WithTask("gen-bar"),
@@ -498,60 +520,48 @@ func TestStatus(t *testing.T) {
// all: not up-to-date
NewExecutorTest(t,
WithName("run gen-foo 2"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("gen-foo"),
)
// status: not up-to-date
NewExecutorTest(t,
WithName("run gen-foo 3"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("gen-foo"),
)
// sources: not up-to-date
NewExecutorTest(t,
WithName("run gen-bar 5"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("gen-bar"),
)
// all: up-to-date
NewExecutorTest(t,
WithName("run gen-bar 6"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("gen-bar"),
)
// sources: not up-to-date, no output produced.
NewExecutorTest(t,
WithName("run gen-baz 2"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("gen-silent-baz"),
)
// up-to-date, no output produced
NewExecutorTest(t,
WithName("run gen-baz 3"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("gen-silent-baz"),
)
// up-to-date, output produced due to Verbose mode.
NewExecutorTest(t,
WithName("run gen-baz 4 verbose"),
WithNodeDir(dir),
WithExecutorOptions(
task.WithDir(dir),
task.WithVerbose(true),
),
WithTask("gen-silent-baz"),
WithPostProcessFn(PPRemoveAbsolutePaths),
WithFixtureTemplating(),
)
}
@@ -560,32 +570,24 @@ func TestPrecondition(t *testing.T) {
const dir = "testdata/precondition"
NewExecutorTest(t,
WithName("a precondition has been met"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("foo"),
)
NewExecutorTest(t,
WithName("a precondition was not met"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("impossible"),
WithRunError(),
)
NewExecutorTest(t,
WithName("precondition in dependency fails the task"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("depends_on_impossible"),
WithRunError(),
)
NewExecutorTest(t,
WithName("precondition in cmd fails the task"),
WithExecutorOptions(
task.WithDir(dir),
),
WithNodeDir(dir),
WithTask("executes_failing_task_as_cmd"),
WithRunError(),
)
@@ -596,25 +598,21 @@ func TestAlias(t *testing.T) {
NewExecutorTest(t,
WithName("alias"),
WithExecutorOptions(
task.WithDir("testdata/alias"),
),
WithNodeDir("testdata/alias"),
WithTask("f"),
)
NewExecutorTest(t,
WithName("duplicate alias"),
WithExecutorOptions(
task.WithDir("testdata/alias"),
),
WithNodeDir("testdata/alias"),
WithTask("x"),
WithRunError(),
)
NewExecutorTest(t,
WithName("alias summary"),
WithNodeDir("testdata/alias"),
WithExecutorOptions(
task.WithDir("testdata/alias"),
task.WithSummary(true),
),
WithTask("f"),
@@ -626,16 +624,14 @@ func TestLabel(t *testing.T) {
NewExecutorTest(t,
WithName("up to date"),
WithExecutorOptions(
task.WithDir("testdata/label_uptodate"),
),
WithNodeDir("testdata/label_uptodate"),
WithTask("foo"),
)
NewExecutorTest(t,
WithName("summary"),
WithNodeDir("testdata/label_summary"),
WithExecutorOptions(
task.WithDir("testdata/label_summary"),
task.WithSummary(true),
),
WithTask("foo"),
@@ -643,26 +639,20 @@ func TestLabel(t *testing.T) {
NewExecutorTest(t,
WithName("status"),
WithExecutorOptions(
task.WithDir("testdata/label_status"),
),
WithNodeDir("testdata/label_status"),
WithTask("foo"),
WithStatusError(),
)
NewExecutorTest(t,
WithName("var"),
WithExecutorOptions(
task.WithDir("testdata/label_var"),
),
WithNodeDir("testdata/label_var"),
WithTask("foo"),
)
NewExecutorTest(t,
WithName("label in summary"),
WithExecutorOptions(
task.WithDir("testdata/label_summary"),
),
WithNodeDir("testdata/label_summary"),
WithTask("foo"),
)
}
@@ -689,8 +679,8 @@ func TestPromptInSummary(t *testing.T) {
opts := []ExecutorTestOption{
WithName(test.name),
WithNodeDir("testdata/prompt"),
WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true),
),
WithTask("foo"),
@@ -708,8 +698,8 @@ func TestPromptWithIndirectTask(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithNodeDir("testdata/prompt"),
WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true),
),
WithTask("bar"),
@@ -722,8 +712,8 @@ func TestPromptAssumeYes(t *testing.T) {
NewExecutorTest(t,
WithName("--yes flag should skip prompt"),
WithNodeDir("testdata/prompt"),
WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true),
task.WithAssumeYes(true),
),
@@ -733,8 +723,8 @@ func TestPromptAssumeYes(t *testing.T) {
NewExecutorTest(t,
WithName("task should raise errors.TaskCancelledError"),
WithNodeDir("testdata/prompt"),
WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true),
),
WithTask("foo"),
@@ -771,13 +761,13 @@ func TestForCmds(t *testing.T) {
for _, test := range tests {
opts := []ExecutorTestOption{
WithName(test.name),
WithNodeDir("testdata/for/cmds"),
WithExecutorOptions(
task.WithDir("testdata/for/cmds"),
task.WithSilent(true),
task.WithForce(true),
),
WithTask(test.name),
WithPostProcessFn(PPRemoveAbsolutePaths),
WithFixtureTemplating(),
}
if test.wantErr {
opts = append(opts, WithRunError())
@@ -814,15 +804,15 @@ func TestForDeps(t *testing.T) {
for _, test := range tests {
opts := []ExecutorTestOption{
WithName(test.name),
WithNodeDir("testdata/for/deps"),
WithExecutorOptions(
task.WithDir("testdata/for/deps"),
task.WithSilent(true),
task.WithForce(true),
// Force output of each dep to be grouped together to prevent interleaving
task.WithOutputStyle(ast.Output{Name: "group"}),
),
WithTask(test.name),
WithPostProcessFn(PPRemoveAbsolutePaths),
WithFixtureTemplating(),
WithPostProcessFn(PPSortedLines),
}
if test.wantErr {
@@ -860,8 +850,8 @@ func TestReference(t *testing.T) {
for _, test := range tests {
NewExecutorTest(t,
WithName(test.name),
WithNodeDir("testdata/var_references"),
WithExecutorOptions(
task.WithDir("testdata/var_references"),
task.WithSilent(true),
task.WithForce(true),
),
@@ -928,8 +918,8 @@ func TestVarInheritance(t *testing.T) {
for _, test := range tests {
NewExecutorTest(t,
WithName(test.name),
WithNodeDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
WithExecutorOptions(
task.WithDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
task.WithSilent(true),
task.WithForce(true),
),
@@ -943,20 +933,23 @@ func TestFuzzyModel(t *testing.T) {
NewExecutorTest(t,
WithName("fuzzy"),
WithExecutorOptions(
task.WithDir("testdata/fuzzy"),
),
WithNodeDir("testdata/fuzzy"),
WithTask("instal"),
WithRunError(),
)
NewExecutorTest(t,
WithName("not-fuzzy"),
WithExecutorOptions(
task.WithDir("testdata/fuzzy"),
),
WithNodeDir("testdata/fuzzy"),
WithTask("install"),
)
NewExecutorTest(t,
WithName("intern"),
WithNodeDir("testdata/fuzzy"),
WithTask("intern"),
WithRunError(),
)
}
func TestIncludeChecksum(t *testing.T) {
@@ -964,17 +957,65 @@ func TestIncludeChecksum(t *testing.T) {
NewExecutorTest(t,
WithName("correct"),
WithExecutorOptions(
task.WithDir("testdata/includes_checksum/correct"),
),
WithNodeDir("testdata/includes_checksum/correct"),
)
NewExecutorTest(t,
WithName("incorrect"),
WithExecutorOptions(
task.WithDir("testdata/includes_checksum/incorrect"),
),
WithSetupError(),
WithPostProcessFn(PPRemoveAbsolutePaths),
WithNodeDir("testdata/includes_checksum/incorrect"),
WithReaderError(),
WithFixtureTemplating(),
)
}
func TestWildcard(t *testing.T) {
t.Parallel()
tests := []struct {
name string
call string
wantErr bool
}{
{
name: "basic wildcard",
call: "wildcard-foo",
},
{
name: "double wildcard",
call: "foo-wildcard-bar",
},
{
name: "store wildcard",
call: "start-foo",
},
{
name: "matches exactly",
call: "matches-exactly-*",
},
{
name: "no matches",
call: "no-match",
wantErr: true,
},
{
name: "multiple matches",
call: "wildcard-foo-bar",
},
}
for _, test := range tests {
opts := []ExecutorTestOption{
WithName(test.name),
WithNodeDir("testdata/wildcards"),
WithExecutorOptions(
task.WithSilent(true),
task.WithForce(true),
),
WithTask(test.call),
}
if test.wantErr {
opts = append(opts, WithRunError())
}
NewExecutorTest(t, opts...)
}
}

View File

@@ -2,7 +2,9 @@ package task_test
import (
"bytes"
"context"
"path/filepath"
"slices"
"testing"
"github.com/sebdah/goldie/v2"
@@ -10,6 +12,7 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -26,12 +29,17 @@ type (
// running `task gen:fixtures`.
FormatterTest struct {
TaskTest
task string
vars map[string]any
executorOpts []task.ExecutorOption
listOptions task.ListOptions
wantSetupError bool
wantListError bool
task string
vars map[string]any
nodeDir string
nodeEntrypoint string
nodeInsecure bool
readerOpts []taskfile.ReaderOption
executorOpts []task.ExecutorOption
listOptions task.ListOptions
wantReaderError bool
wantSetupError bool
wantListError bool
}
)
@@ -41,10 +49,12 @@ type (
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
t.Helper()
tt := &FormatterTest{
task: "default",
vars: map[string]any{},
task: "default",
vars: map[string]any{},
nodeDir: ".",
TaskTest: TaskTest{
experiments: map[*experiments.Experiment]int{},
experiments: map[*experiments.Experiment]int{},
fixtureTemplateData: map[string]any{},
},
}
// Apply the functional options
@@ -113,23 +123,57 @@ func (tt *FormatterTest) run(t *testing.T) {
f := func(t *testing.T) {
t.Helper()
var buf bytes.Buffer
ctx := context.Background()
opts := append(
tt.executorOpts,
task.WithStdout(&buf),
task.WithStderr(&buf),
// Create a new root node for the given entrypoint
node, err := taskfile.NewRootNode(
tt.nodeEntrypoint,
tt.nodeDir,
tt.nodeInsecure,
)
// Set up the task executor
e := task.NewExecutor(opts...)
require.NoError(t, err)
// Create a golden fixture file for the output
g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
)
// Call setup and check for errors
if err := e.Setup(); tt.wantSetupError {
// Set up a temporary directory for the taskfile reader and task executor
tempDir, err := task.NewTempDir(node.Dir())
require.NoError(t, err)
tt.readerOpts = append(tt.readerOpts, taskfile.WithTempDir(tempDir.Remote))
// Set up the taskfile reader
reader := taskfile.NewReader(tt.readerOpts...)
graph, err := reader.Read(ctx, node)
if tt.wantReaderError {
require.Error(t, err)
tt.writeFixtureErrReader(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
return
} else {
require.NoError(t, err)
}
executorOpts := slices.Concat(
// Apply the node directory and temp directory to the executor options
// by default, but allow them to by overridden by the test options
[]task.ExecutorOption{
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
},
// Apply the executor options from the test
tt.executorOpts,
// Force the input/output streams to be set to the test buffer
[]task.ExecutorOption{
task.WithStdout(&buf),
task.WithStderr(&buf),
},
)
// Set up the task executor
executor, err := task.NewExecutor(graph, executorOpts...)
if tt.wantSetupError {
require.Error(t, err)
tt.writeFixtureErrSetup(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
@@ -145,7 +189,7 @@ func (tt *FormatterTest) run(t *testing.T) {
}
// Run the formatter and check for errors
if _, err := e.ListTasks(tt.listOptions); tt.wantListError {
if _, err := executor.ListTasks(tt.listOptions); tt.wantListError {
require.Error(t, err)
tt.writeFixtureErrList(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
@@ -169,9 +213,7 @@ func TestNoLabelInList(t *testing.T) {
t.Parallel()
NewFormatterTest(t,
WithExecutorOptions(
task.WithDir("testdata/label_list"),
),
WithNodeDir("testdata/label_list"),
WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true,
}),
@@ -183,9 +225,7 @@ func TestListAllShowsNoDesc(t *testing.T) {
t.Parallel()
NewFormatterTest(t,
WithExecutorOptions(
task.WithDir("testdata/list_mixed_desc"),
),
WithNodeDir("testdata/list_mixed_desc"),
WithListOptions(task.ListOptions{
ListAllTasks: true,
}),
@@ -197,9 +237,7 @@ func TestListCanListDescOnly(t *testing.T) {
t.Parallel()
NewFormatterTest(t,
WithExecutorOptions(
task.WithDir("testdata/list_mixed_desc"),
),
WithNodeDir("testdata/list_mixed_desc"),
WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true,
}),
@@ -210,9 +248,7 @@ func TestListDescInterpolation(t *testing.T) {
t.Parallel()
NewFormatterTest(t,
WithExecutorOptions(
task.WithDir("testdata/list_desc_interpolation"),
),
WithNodeDir("testdata/list_desc_interpolation"),
WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true,
}),
@@ -222,19 +258,11 @@ func TestListDescInterpolation(t *testing.T) {
func TestJsonListFormat(t *testing.T) {
t.Parallel()
fp, err := filepath.Abs("testdata/json_list_format/Taskfile.yml")
require.NoError(t, err)
NewFormatterTest(t,
WithExecutorOptions(
task.WithDir("testdata/json_list_format"),
),
WithNodeDir("testdata/json_list_format"),
WithListOptions(task.ListOptions{
FormatTaskListAsJSON: true,
}),
WithFixtureTemplateData(struct {
TaskfileLocation string
}{
TaskfileLocation: fp,
}),
WithFixtureTemplating(),
)
}

20
go.mod
View File

@@ -4,8 +4,8 @@ go 1.23.0
require (
github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.3.1
github.com/alecthomas/chroma/v2 v2.18.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/alecthomas/chroma/v2 v2.20.0
github.com/chainguard-dev/git-urls v1.0.2
github.com/davecgh/go-spew v1.1.1
github.com/dominikbraun/graph v0.23.0
@@ -13,23 +13,23 @@ require (
github.com/fatih/color v1.18.0
github.com/fsnotify/fsnotify v1.9.0
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.0
github.com/go-git/go-git/v5 v5.16.2
github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.1.0
github.com/go-task/template v0.2.0
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/otiai10/copy v1.14.1
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.5.5
github.com/spf13/pflag v1.0.6
github.com/sebdah/goldie/v2 v2.7.1
github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.10.0
github.com/zeebo/xxh3 v1.0.2
golang.org/x/sync v0.14.0
golang.org/x/term v0.32.0
golang.org/x/sync v0.16.0
golang.org/x/term v0.33.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.11.0
mvdan.cc/sh/v3 v3.12.0
)
require (
@@ -56,6 +56,6 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/sys v0.34.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

42
go.sum
View File

@@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -11,8 +11,10 @@ github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNx
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.18.0 h1:6h53Q4hW83SuF+jcsp7CVhLsMozzvQvO8HBbKQW+gn4=
github.com/alecthomas/chroma/v2 v2.18.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -52,14 +54,14 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -109,16 +111,16 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
@@ -141,8 +143,8 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbR
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -152,11 +154,11 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
@@ -171,5 +173,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=

View File

@@ -64,21 +64,15 @@ get_binaries() {
case "$PLATFORM" in
darwin/amd64) BINARIES="task" ;;
darwin/arm64) BINARIES="task" ;;
darwin/armv5) BINARIES="task" ;;
darwin/armv6) BINARIES="task" ;;
darwin/armv7) BINARIES="task" ;;
darwin/arm) BINARIES="task" ;;
linux/386) BINARIES="task" ;;
linux/amd64) BINARIES="task" ;;
linux/arm64) BINARIES="task" ;;
linux/armv5) BINARIES="task" ;;
linux/armv6) BINARIES="task" ;;
linux/armv7) BINARIES="task" ;;
linux/arm) BINARIES="task" ;;
windows/386) BINARIES="task" ;;
windows/amd64) BINARIES="task" ;;
windows/arm64) BINARIES="task" ;;
windows/armv5) BINARIES="task" ;;
windows/armv6) BINARIES="task" ;;
windows/armv7) BINARIES="task" ;;
windows/arm) BINARIES="task" ;;
*)
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
exit 1

View File

@@ -90,6 +90,15 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
return r.Run(ctx, p)
}
func escape(s string) string {
s = filepath.ToSlash(s)
s = strings.ReplaceAll(s, " ", `\ `)
s = strings.ReplaceAll(s, "&", `\&`)
s = strings.ReplaceAll(s, "(", `\(`)
s = strings.ReplaceAll(s, ")", `\)`)
return s
}
// ExpandLiteral is a wrapper around [expand.Literal]. It will escape the input
// string, expand any shell symbols (such as '~') and resolve any environment
// variables.
@@ -115,6 +124,7 @@ func ExpandLiteral(s string) (string, error) {
// variables. It also expands brace expressions ({a.b}) and globs (*/**) and
// returns the results as a list of strings.
func ExpandFields(s string) ([]string, error) {
s = escape(s)
p := syntax.NewParser()
var words []*syntax.Word
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {

View File

@@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/sort"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -201,7 +202,7 @@ func Validate() error {
// WithFlags is a special internal functional option that is used to pass flags
// from the CLI into any constructor that accepts functional options.
func WithFlags() task.ExecutorOption {
func WithFlags() *flagsOption {
return &flagsOption{}
}
@@ -228,14 +229,8 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
e.Options(
task.WithDir(dir),
task.WithEntrypoint(Entrypoint),
task.WithForce(Force),
task.WithForceAll(ForceAll),
task.WithInsecure(Insecure),
task.WithDownload(Download),
task.WithOffline(Offline),
task.WithTimeout(Timeout),
task.WithCacheExpiryDuration(CacheExpiryDuration),
task.WithWatch(Watch),
task.WithVerbose(Verbose),
task.WithSilent(Silent),
@@ -251,3 +246,12 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithVersionCheck(true),
)
}
func (o *flagsOption) ApplyToReader(r *taskfile.Reader) {
r.Options(
taskfile.WithInsecure(Insecure),
taskfile.WithDownload(Download),
taskfile.WithOffline(Offline),
taskfile.WithCacheExpiryDuration(CacheExpiryDuration),
)
}

View File

@@ -41,7 +41,6 @@ func init() {
"toYaml": toYaml,
"mustToYaml": mustToYaml,
"uuid": uuid.New,
"randInt": rand.Int,
"randIntN": rand.IntN,
}

View File

@@ -1 +1 @@
3.44.0
3.44.1

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.44.0",
"version": "3.44.1",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.44.0",
"version": "3.44.1",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",

View File

@@ -220,13 +220,13 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
}
exitCode, isExitError := interp.IsExitStatus(err)
if isExitError {
var exitCode interp.ExitStatus
if errors.As(err, &exitCode) {
if t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
}
deferredExitCode = exitCode
deferredExitCode = uint8(exitCode)
}
if call.Indirect {
@@ -356,7 +356,8 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
if closeErr := closer(err); closeErr != nil {
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
}
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
var exitCode interp.ExitStatus
if errors.As(err, &exitCode) && cmd.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ package taskfile
import (
"context"
"strings"
"time"
giturls "github.com/chainguard-dev/git-urls"
@@ -33,7 +32,6 @@ func NewRootNode(
entrypoint string,
dir string,
insecure bool,
timeout time.Duration,
) (Node, error) {
dir = fsext.DefaultDir(entrypoint, dir)
// If the entrypoint is "-", we read from stdin

78
temp_dir.go Normal file
View File

@@ -0,0 +1,78 @@
package task
import (
"path/filepath"
"strings"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
)
type TempDir struct {
Remote string
Fingerprint string
}
func NewTempDir(dir string) (*TempDir, error) {
tempDir, err := setupTempDirFingerprint(dir)
if err != nil {
return nil, err
}
err = setupTempDirRemote(dir, tempDir)
if err != nil {
return nil, err
}
return tempDir, nil
}
func setupTempDirFingerprint(dir string) (*TempDir, error) {
tempDir := env.GetTaskEnv("TEMP_DIR")
if tempDir == "" {
return &TempDir{
Remote: filepathext.SmartJoin(dir, ".task"),
Fingerprint: filepathext.SmartJoin(dir, ".task"),
}, nil
}
if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
tempDir, err := execext.ExpandLiteral(tempDir)
if err != nil {
return nil, err
}
projectDir, _ := filepath.Abs(dir)
projectName := filepath.Base(projectDir)
return &TempDir{
Remote: tempDir,
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
}, nil
}
return &TempDir{
Remote: filepathext.SmartJoin(dir, tempDir),
Fingerprint: filepathext.SmartJoin(dir, tempDir),
}, nil
}
func setupTempDirRemote(dir string, tempDir *TempDir) error {
remoteDir := env.GetTaskEnv("REMOTE_DIR")
if remoteDir == "" {
return nil
}
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
if err != nil {
return err
}
tempDir.Remote = remoteTempDir
return nil
}
tempDir.Remote = filepathext.SmartJoin(dir, ".task")
return nil
}

View File

@@ -0,0 +1 @@
task: Missing schema version in Taskfile "{{.TEST_DIR}}/testdata/empty_taskfile/Taskfile.yml"

View File

@@ -1 +0,0 @@
task: Missing schema version in Taskfile "/testdata/empty_taskfile/Taskfile.yml"

View File

@@ -1,2 +1,2 @@
task: Failed to parse /testdata/for/cmds/Taskfile.yml:
task: Failed to parse {{.TEST_DIR}}/testdata/for/cmds/Taskfile.yml:
matrix reference ".NOT_A_LIST" must resolve to a list

View File

@@ -1,2 +1,2 @@
matrix reference ".NOT_A_LIST" must resolve to a list
task: Failed to parse /testdata/for/deps/Taskfile.yml:
task: Failed to parse {{.TEST_DIR}}/testdata/for/deps/Taskfile.yml:

View File

@@ -2,3 +2,8 @@ version: 3
tasks:
install: echo 'install'
internal:
internal: true
cmds:
- echo "internal"

View File

@@ -0,0 +1 @@
task: Task "intern" does not exist

View File

@@ -0,0 +1 @@
task: No tasks with description available. Try --list-all to list all tasks

View File

@@ -0,0 +1,3 @@
task: The checksum of the Taskfile at "{{.TEST_DIR}}/testdata/includes_checksum/included.yml" does not match!
got: "c97f39eb96fe3fa5fe2a610d244b8449897b06f0c93821484af02e0999781bf5"
want: "foo"

View File

@@ -1,3 +0,0 @@
task: The checksum of the Taskfile at "/testdata/includes_checksum/included.yml" does not match!
got: "c97f39eb96fe3fa5fe2a610d244b8449897b06f0c93821484af02e0999781bf5"
want: "foo"

View File

@@ -10,9 +10,9 @@
"location": {
"line": 4,
"column": 3,
"taskfile": "{{ .TaskfileLocation }}"
"taskfile": "{{.TEST_DIR}}/testdata/json_list_format/Taskfile.yml"
}
}
],
"location": "{{ .TaskfileLocation }}"
"location": "{{.TEST_DIR}}/testdata/json_list_format/Taskfile.yml"
}

View File

@@ -1 +0,0 @@
/testdata/special_vars/included/Taskfile.yml

View File

@@ -1 +0,0 @@
/testdata/special_vars/Taskfile.yml

View File

@@ -1 +1 @@
/testdata/special_vars
{{.TEST_DIR}}/testdata/special_vars

View File

@@ -1 +1 @@
/testdata/special_vars/included
{{.TEST_DIR}}/testdata/special_vars/included

View File

@@ -1 +1 @@
/testdata/special_vars/included/Taskfile.yml
{{.TEST_DIR}}/testdata/special_vars/included/Taskfile.yml

View File

@@ -1 +1 @@
/testdata/special_vars
{{.TEST_DIR}}/testdata/special_vars

View File

@@ -1 +1 @@
/testdata/special_vars/foo
{{.TEST_DIR}}/testdata/special_vars/foo

View File

@@ -1 +1 @@
/testdata/special_vars
{{.TEST_DIR}}/testdata/special_vars

View File

@@ -1 +1 @@
/testdata/special_vars/Taskfile.yml
{{.TEST_DIR}}/testdata/special_vars/Taskfile.yml

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars/included

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars/included/Taskfile.yml

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars/foo

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars

View File

@@ -0,0 +1 @@
{{.TEST_DIR}}/testdata/special_vars/Taskfile.yml

View File

@@ -0,0 +1 @@
Hello foo

View File

@@ -0,0 +1 @@
Hello foo bar

View File

@@ -0,0 +1 @@
I don't consume matches: []

View File

@@ -0,0 +1 @@
Hello foo-bar

View File

@@ -0,0 +1 @@
task: Task "no-match" does not exist

View File

@@ -0,0 +1 @@
task: No tasks with description available. Try --list-all to list all tasks

View File

@@ -0,0 +1 @@
Starting foo

View File

@@ -5,6 +5,17 @@ sidebar_position: 14
# Changelog
## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be
called (#2309, #2323 by @maxmzkrcensys)
- Fixed install script for some ARM platforms (#1516, #2291 by @trulede).
- Fixed a regression where fingerprinting was not working correctly if the path
to you Taskfile contained a space (#2321, #2322 by @pd93).
- Reverted a breaking change to `randInt` (#2312, #2316 by @pd93).
- Made new variables `TEST_NAME` and `TEST_DIR` available in fixture tests
(#2265 by @pd93).
## v3.44.0 - 2025-06-08
- Added `uuid`, `randInt` and `randIntN` template functions (#1346, #2225 by

View File

@@ -36,6 +36,14 @@ repository [[package](https://formulae.brew.sh/formula/go-task)]
brew install go-task
```
### [Macports][macports] ![][macos] ![][community] \{#macports}
Task repository is tracked by Macports [[package](https://ports.macports.org/port/go-task/details/)] [[source](https://github.com/macports/macports-ports/blob/master/devel/go-task/Portfile)]:
```shell
port install go-task
```
### [Snap][snapcraft] ![][macos] ![][linux] \{#snap}
Task is available on [Snapcraft][snapcraft] [[source](https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml)], but keep in mind that your Linux
@@ -104,6 +112,14 @@ pacman -S go-task
dnf install go-task
```
### FreeBSD ([Ports][freebsdports]) ![][freebsd] ![][community] \{#freebsd}
[[package](https://cgit.freebsd.org/ports/tree/devel/task)] [[source](https://cgit.freebsd.org/ports/tree/devel/task/Makefile)]
```shell
pkg install task
```
### NixOS ([nix][nix]) ![][nixos] ![][linux] ![][community] \{#nix}
[[source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/go/go-task/package.nix)]
@@ -304,6 +320,7 @@ task --completion fish > ~/.config/fish/completions/task.fish
{/* prettier-ignore-start */}
[homebrew]: https://brew.sh
[macports]: https://macports.org
[snapcraft]: https://snapcraft.io/task
[winget]: https://github.com/microsoft/winget-cli
[choco]: https://chocolatey.org
@@ -317,6 +334,7 @@ task --completion fish > ~/.config/fish/completions/task.fish
[aqua]: https://aquaproj.github.io
[pacstall]: https://github.com/pacstall/pacstall
[pkgx]: https://pkgx.sh
[freebsdports]: https://ports.freebsd.org/cgi/ports.cgi
[go]: https://golang.org
[godownloader]: https://github.com/goreleaser/godownloader
@@ -332,4 +350,5 @@ task --completion fish > ~/.config/fish/completions/task.fish
[nixos]: https://img.shields.io/badge/NixOS-5277C3?logo=nixos&logoColor=fff
[debian]: https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=fff
[ubuntu]: https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=fff
[freebsd]: https://img.shields.io/badge/FreeBSD-990000?logo=freebsd&logoColor=fff
{/* prettier-ignore-end */}

View File

@@ -400,7 +400,6 @@ Lastly, Task itself provides a few functions:
| `fromYaml`\* | Decodes a YAML string into an object. |
| `toYaml`\* | Encodes an object as a YAML string. |
| `uuid` | Generates a new pseudo-random UUIDv4 string. |
| `randInt` | Generates a new pseudo-random, non-negative, 32bit integer. Generated numbers are not suitable for security-sensitive work. |
| `randIntN` | Generates a new pseudo-random, non-negative, 32bit integer in the half-open interval `[0,n)`. Generated numbers are not suitable for security-sensitive work. |
{/* prettier-ignore-start */}

View File

@@ -64,21 +64,15 @@ get_binaries() {
case "$PLATFORM" in
darwin/amd64) BINARIES="task" ;;
darwin/arm64) BINARIES="task" ;;
darwin/armv5) BINARIES="task" ;;
darwin/armv6) BINARIES="task" ;;
darwin/armv7) BINARIES="task" ;;
darwin/arm) BINARIES="task" ;;
linux/386) BINARIES="task" ;;
linux/amd64) BINARIES="task" ;;
linux/arm64) BINARIES="task" ;;
linux/armv5) BINARIES="task" ;;
linux/armv6) BINARIES="task" ;;
linux/armv7) BINARIES="task" ;;
linux/arm) BINARIES="task" ;;
windows/386) BINARIES="task" ;;
windows/amd64) BINARIES="task" ;;
windows/arm64) BINARIES="task" ;;
windows/armv5) BINARIES="task" ;;
windows/armv6) BINARIES="task" ;;
windows/armv7) BINARIES="task" ;;
windows/arm) BINARIES="task" ;;
*)
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
exit 1

View File

@@ -5,6 +5,17 @@ sidebar_position: 14
# Changelog
## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be
called (#2309, #2323 by @maxmzkrcensys)
- Fixed install script for some ARM platforms (#1516, #2291 by @trulede).
- Fixed a regression where fingerprinting was not working correctly if the path
to you Taskfile contained a space (#2321, #2322 by @pd93).
- Reverted a breaking change to `randInt` (#2312, #2316 by @pd93).
- Made new variables `TEST_NAME` and `TEST_DIR` available in fixture tests
(#2265 by @pd93).
## v3.44.0 - 2025-06-08
- Added `uuid`, `randInt` and `randIntN` template functions (#1346, #2225 by

View File

@@ -36,6 +36,14 @@ repository [[package](https://formulae.brew.sh/formula/go-task)]
brew install go-task
```
### [Macports][macports] ![][macos] ![][community] \{#macports}
Task repository is tracked by Macports [[package](https://ports.macports.org/port/go-task/details/)] [[source](https://github.com/macports/macports-ports/blob/master/devel/go-task/Portfile)]:
```shell
port install go-task
```
### [Snap][snapcraft] ![][macos] ![][linux] \{#snap}
Task is available on [Snapcraft][snapcraft] [[source](https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml)], but keep in mind that your Linux
@@ -104,6 +112,14 @@ pacman -S go-task
dnf install go-task
```
### FreeBSD ([Ports][freebsdports]) ![][freebsd] ![][community] \{#freebsd}
[[package](https://cgit.freebsd.org/ports/tree/devel/task)] [[source](https://cgit.freebsd.org/ports/tree/devel/task/Makefile)]
```shell
pkg install task
```
### NixOS ([nix][nix]) ![][nixos] ![][linux] ![][community] \{#nix}
[[source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/go/go-task/package.nix)]
@@ -304,6 +320,7 @@ task --completion fish > ~/.config/fish/completions/task.fish
{/* prettier-ignore-start */}
[homebrew]: https://brew.sh
[macports]: https://macports.org
[snapcraft]: https://snapcraft.io/task
[winget]: https://github.com/microsoft/winget-cli
[choco]: https://chocolatey.org
@@ -317,6 +334,7 @@ task --completion fish > ~/.config/fish/completions/task.fish
[aqua]: https://aquaproj.github.io
[pacstall]: https://github.com/pacstall/pacstall
[pkgx]: https://pkgx.sh
[freebsdports]: https://ports.freebsd.org/cgi/ports.cgi
[go]: https://golang.org
[godownloader]: https://github.com/goreleaser/godownloader
@@ -332,4 +350,5 @@ task --completion fish > ~/.config/fish/completions/task.fish
[nixos]: https://img.shields.io/badge/NixOS-5277C3?logo=nixos&logoColor=fff
[debian]: https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=fff
[ubuntu]: https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=fff
[freebsd]: https://img.shields.io/badge/FreeBSD-990000?logo=freebsd&logoColor=fff
{/* prettier-ignore-end */}

View File

@@ -400,7 +400,6 @@ Lastly, Task itself provides a few functions:
| `fromYaml`\* | Decodes a YAML string into an object. |
| `toYaml`\* | Encodes an object as a YAML string. |
| `uuid` | Generates a new pseudo-random UUIDv4 string. |
| `randInt` | Generates a new pseudo-random, non-negative, 32bit integer. Generated numbers are not suitable for security-sensitive work. |
| `randIntN` | Generates a new pseudo-random, non-negative, 32bit integer in the half-open interval `[0,n)`. Generated numbers are not suitable for security-sensitive work. |
{/* prettier-ignore-start */}

File diff suppressed because it is too large Load Diff