Compare commits

..

5 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
202 changed files with 22045 additions and 17874 deletions

View File

@@ -8,6 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
indent_style = tab indent_style = tab
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,vue,css,svg,sh,bash,fish}] [*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,css,svg,sh,bash,fish}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

View File

@@ -13,14 +13,14 @@ jobs:
name: Lint name: Lint
strategy: strategy:
matrix: matrix:
go-version: [1.24.x, 1.25.x] go-version: [1.23.x, 1.24.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: ${{matrix.go-version}} go-version: ${{matrix.go-version}}
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v8 uses: golangci/golangci-lint-action@v8
@@ -32,12 +32,43 @@ jobs:
steps: steps:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: 3.13 python-version: 3.12
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: install check-jsonschema - name: install check-jsonschema
run: python -m pip install 'check-jsonschema==0.27.3' run: python -m pip install 'check-jsonschema==0.27.3'
- name: check-jsonschema (metaschema) - name: check-jsonschema (metaschema)
run: check-jsonschema --check-metaschema website/src/public/schema.json run: check-jsonschema --check-metaschema website/static/schema.json
check_doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files in the docs folder
id: changed-files-specific
uses: tj-actions/changed-files@v46
with:
files: website/versioned_docs/**
- uses: actions/github-script@v7
if: steps.changed-files-specific.outputs.any_changed == 'true'
with:
script: |
core.setFailed('website/versioned_docs has changed. Instead you need to update the docs in the website/docs folder.')
check_schema:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files in the docs folder
id: changed-files-specific
uses: tj-actions/changed-files@v46
with:
files: |
website/static/schema.json
website/static/schema-taskrc.json
- uses: actions/github-script@v7
if: steps.changed-files-specific.outputs.any_changed == 'true'
with:
script: |
core.setFailed('schema.json or schema-taskrc.json has changed. Instead you need to update next-schema.json or next-schema-taskrc.json.')

View File

@@ -1,4 +1,4 @@
name: Release nightly name: Realease nightly
on: on:
workflow_dispatch: workflow_dispatch:
@@ -9,14 +9,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.25.x go-version: 1.24.x
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
@@ -27,4 +27,3 @@ jobs:
env: env:
GITHUB_TOKEN: ${{secrets.GH_PAT}} GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}} GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}

View File

@@ -10,31 +10,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.25.x go-version: 1.24.x
- name: npm-login
run: |
npm config set '//registry.npmjs.org/:_authToken'=${{ secrets.NPM_TOKEN }}
- name: Install Task
uses: go-task/setup-task@v1
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'pnpm'
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
@@ -45,9 +28,3 @@ jobs:
env: env:
GITHUB_TOKEN: ${{secrets.GH_PAT}} GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}} GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
- name: Deploy Website
shell: bash
run: |
task website:deploy:prod

View File

@@ -13,7 +13,7 @@ jobs:
name: Test name: Test
strategy: strategy:
matrix: matrix:
go-version: [1.24.x, 1.25.x] go-version: [1.23.x, 1.24.x]
platform: [ubuntu-latest, macos-latest, windows-latest] platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}} runs-on: ${{matrix.platform}}
steps: steps:
@@ -24,7 +24,7 @@ jobs:
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Download Go modules - name: Download Go modules
run: go mod download run: go mod download

View File

@@ -68,7 +68,6 @@ nfpms:
formats: formats:
- deb - deb
- rpm - rpm
- apk
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}' file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
contents: contents:
- src: completion/bash/task.bash - src: completion/bash/task.bash
@@ -136,37 +135,3 @@ winget:
owner: microsoft owner: microsoft
name: winget-pkgs name: winget-pkgs
branch: master branch: master
npms:
- name: "@go-task/cli"
repository: "git+https://github.com/go-task/task.git"
bugs: https://github.com/go-task/task/issues
description: A task runner / simpler Make alternative written in Go
homepage: https://taskfile.dev
license: MIT
author: "The Task authors"
access: public
keywords:
- "task"
- "taskfile"
- "build-tool"
- "task-runner"
cloudsmiths:
- organization: "task"
repository: "{{if not .IsNightly}}task{{end}}"
formats:
- deb
- rpm
- apk
distributions:
deb:
- "any-distro/any-version"
rpm:
- "any-distro/any-version"
alpine:
- "alpine/any-version"
component: main
republish: true

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
22.18.0

View File

@@ -1,58 +1,5 @@
# Changelog # Changelog
## v3.45.2 - 2025-09-15
- Task now includes built-in core utilities to greatly improve compatibility on
Windows. This means that your commands that uses `cp`, `mv`, `mkdir` or any
other common core utility will now work by default on Windows, without extra
setup. This is something we wanted to address for many many years, and it's
finally being shipped!
[Read our blog post this the topic](https://taskfile.dev/blog/windows-core-utils).
(#197, #2360 by @andreynering).
- :sparkles: Built and deployed a [brand new website](https://taskfile.dev)
using [VitePress](https://vitepress.dev) (#2359, #2369, #2371, #2375, #2378 by
@vmaerten, @andreynering, @pd93).
- Began releasing
[nightly builds](https://github.com/go-task/task/releases/tag/nightly). This
will allow people to test our changes before they are fully released and
without having to install Go to build them (#2358 by @vmaerten).
- Added support for global config files in `$XDG_CONFIG_HOME/task/taskrc.yml` or
`$HOME/.taskrc.yml`. Check out our new
[configuration guide](https://taskfile.dev/docs/reference/config) for more
details (#2247, #2380, #2390, #2391 by @vmaerten, @pd93).
- Added experiments to the taskrc schema to clarify the expected keys and values
(#2235 by @vmaerten).
- Added support for new properties in `.taskrc.yml`: insecure, verbose,
concurrency, remote offline, remote timeout, and remote expiry. :warning:
Note: setting offline via environment variable is no longer supported. (#2389
by @vmaerten)
- Added a `--nested` flag when outputting tasks using `--list --json`. This will
output tasks in a nested structure when tasks are namespaced (#2415 by @pd93).
- Enhanced support for tasks with wildcards: they are now logged correctly, and
wildcard parameters are fully considered during fingerprinting (#1808, #1795
by @vmaerten).
- Fixed panic when a variable was declared as an empty hash (`{}`) (#2416, #2417
by @trulede).
#### Package API
- Bumped the minimum version of Go to 1.24 (#2358 by @vmaerten).
#### Other news
We recently released our
[official GitHub Action](https://github.com/go-task/setup-task). This is based
on the fantastic work by the Arduino team who created and maintained the
community version. Now that this is officially adopted, fixes/updates should be
more timely. We have already merged a couple of longstanding PRs in our
[first release](https://github.com/go-task/setup-task/releases/tag/v1.0.0) (by
@pd93, @shrink, @trim21 and all the previous contributors to
[arduino/setup-task](https://github.com/arduino/setup-task/)).
## v3.45.0-v3.45.1 - 2025-09-15
Failed due to an issue with our release process.
## v3.44.1 - 2025-07-23 ## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be - Internal tasks will no longer be shown as suggestions since they cannot be

View File

@@ -1,6 +1,6 @@
<div align="center"> <div align="center">
<a href="https://taskfile.dev"> <a href="https://taskfile.dev">
<img src="website/src/public/img/logo.svg" width="200px" height="200px" /> <img src="website/static/img/logo.svg" width="200px" height="200px" />
</a> </a>
<h1>Task</h1> <h1>Task</h1>
@@ -19,7 +19,7 @@
<tr> <tr>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a target="_blank" href="https://devowl.io"> <a target="_blank" href="https://devowl.io">
<img src="https://devowl.io/wp-content/uploads/meta/favicon.webp" height="100px" title="devowl.io" /> <img src="/website/static/img/devowl.io.svg" height="100px" title="devowl.io" />
</a> </a>
</td> </td>
</tr> </tr>

View File

@@ -8,7 +8,6 @@ includes:
vars: vars:
BIN: "{{.ROOT_DIR}}/bin" BIN: "{{.ROOT_DIR}}/bin"
GOTESTSUM_FORMAT: '{{if .CI}}github-actions{{else}}pkgname{{end}}'
env: env:
CGO_ENABLED: '0' CGO_ENABLED: '0'
@@ -132,37 +131,29 @@ tasks:
test: test:
desc: Runs test suite desc: Runs test suite
aliases: [t] aliases: [t]
deps: [gotestsum:install]
sources: sources:
- "**/*.go" - "**/*.go"
- "testdata/**/*" - "testdata/**/*"
cmds: cmds:
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... - go test ./...
test:watch: test:watch:
desc: Runs test suite with watch tests included desc: Runs test suite with watch tests included
deps: [sleepit:build, gotestsum:install] deps: [sleepit:build]
cmds: cmds:
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... -tags 'watch' - go test ./... -tags 'watch'
test:all: test:all:
desc: Runs test suite with signals and watch tests included desc: Runs test suite with signals and watch tests included
deps: [sleepit:build, gotestsum:install] deps: [sleepit:build]
cmds: cmds:
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' -tags 'signals watch' ./... - go test -tags 'signals watch' ./...
goreleaser:test: goreleaser:test:
desc: Tests release process without publishing desc: Tests release process without publishing
cmds: cmds:
- goreleaser --snapshot --clean - goreleaser --snapshot --clean
gotestsum:install:
desc: Installs gotestsum
status:
- command -v gotestsum
cmds:
- go install gotest.tools/gotestsum@latest
goreleaser:install: goreleaser:install:
desc: Installs goreleaser desc: Installs goreleaser
cmds: cmds:
@@ -212,6 +203,7 @@ tasks:
Please wait for the CI to finish and then do the following: Please wait for the CI to finish and then do the following:
- Copy the changelog for v{{.VERSION}} to the GitHub release - Copy the changelog for v{{.VERSION}} to the GitHub release
- Publish the package to NPM with `task npm:publish`
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml - Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
preconditions: preconditions:
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main" - sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
@@ -230,3 +222,8 @@ tasks:
- "git push origin tag v{{.VERSION}}" - "git push origin tag v{{.VERSION}}"
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}' - cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
silent: true silent: true
npm:publish:
desc: Publish release to npm
cmds:
- npm publish --access=public

View File

@@ -3,23 +3,33 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/otiai10/copy"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )
const ( const (
changelogSource = "CHANGELOG.md" changelogSource = "CHANGELOG.md"
changelogTarget = "website/src/docs/changelog.md" changelogTarget = "website/docs/changelog.mdx"
versionFile = "internal/version/version.txt" docsSource = "website/docs"
docsTarget = "website/versioned_docs/version-latest"
schemaSource = "website/static/next-schema.json"
schemaTarget = "website/static/schema.json"
schemaTaskrcSource = "website/static/next-schema-taskrc.json"
schemaTaskrcTarget = "website/static/schema-taskrc.json"
) )
var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`) var (
changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
)
// Flags // Flags
var ( var (
@@ -43,7 +53,7 @@ func release() error {
return errors.New("error: expected version number") return errors.New("error: expected version number")
} }
version, err := getVersion(versionFile) version, err := getVersion()
if err != nil { if err != nil {
return err return err
} }
@@ -61,18 +71,36 @@ func release() error {
return err return err
} }
if err := setVersionFile(versionFile, version); err != nil { if err := setVersionFile("internal/version/version.txt", version); err != nil {
return err
}
if err := setJSONVersion("package.json", version); err != nil {
return err
}
if err := setJSONVersion("package-lock.json", version); err != nil {
return err
}
if err := docs(); err != nil {
return err
}
if err := schema(); err != nil {
return err return err
} }
return nil return nil
} }
func getVersion(filename string) (*semver.Version, error) { func getVersion() (*semver.Version, error) {
b, err := os.ReadFile(filename) cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
b, err := cmd.Output()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return semver.NewVersion(strings.TrimSpace(string(b))) return semver.NewVersion(strings.TrimSpace(string(b)))
} }
@@ -131,3 +159,37 @@ func changelog(version *semver.Version) error {
func setVersionFile(fileName string, version *semver.Version) error { func setVersionFile(fileName string, version *semver.Version) error {
return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644) return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644)
} }
func setJSONVersion(fileName string, version *semver.Version) error {
// Read the JSON file
b, err := os.ReadFile(fileName)
if err != nil {
return err
}
// Replace the version
new := versionRegex.ReplaceAllString(string(b), fmt.Sprintf(` "version": "%s",`, version.String()))
// Write the JSON file
return os.WriteFile(fileName, []byte(new), 0o644)
}
func docs() error {
if err := os.RemoveAll(docsTarget); err != nil {
return err
}
if err := copy.Copy(docsSource, docsTarget); err != nil {
return err
}
return nil
}
func schema() error {
if err := copy.Copy(schemaSource, schemaTarget); err != nil {
return err
}
if err := copy.Copy(schemaTaskrcSource, schemaTaskrcTarget); err != nil {
return err
}
return nil
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/go-task/task/v3/internal/flags" "github.com/go-task/task/v3/internal/flags"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/version" "github.com/go-task/task/v3/internal/version"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -110,16 +111,63 @@ func run() error {
return nil return nil
} }
e := task.NewExecutor( if err := experiments.Validate(); err != nil {
flags.WithFlags(), log.Warnf("%s\n", err.Error())
task.WithVersionCheck(true), }
// 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 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 { if flags.ClearCache {
cachePath := filepath.Join(e.TempDir.Remote, "remote") cachePath := filepath.Join(executor.TempDir.Remote, "remote")
return os.RemoveAll(cachePath) return os.RemoveAll(cachePath)
} }
@@ -128,13 +176,12 @@ func run() error {
flags.ListAll, flags.ListAll,
flags.ListJson, flags.ListJson,
flags.NoStatus, flags.NoStatus,
flags.Nested,
) )
if listOptions.ShouldListTasks() { if listOptions.ShouldListTasks() {
if flags.Silent { 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 { if err != nil {
return err return err
} }
@@ -166,17 +213,17 @@ func run() error {
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent}) globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose}) globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline}) globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
e.Taskfile.Vars.Merge(globals, nil) executor.Taskfile.Vars.Merge(globals, nil)
if !flags.Watch { if !flags.Watch {
e.InterceptInterruptSignals() executor.InterceptInterruptSignals()
} }
ctx := context.Background() ctx = context.Background()
if flags.Status { 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

@@ -162,7 +162,7 @@ func (v MissingVar) String() string {
} }
func (err *TaskMissingRequiredVarsError) Error() string { func (err *TaskMissingRequiredVarsError) Error() string {
vars := make([]string, 0, len(err.MissingVars)) var vars []string
for _, v := range err.MissingVars { for _, v := range err.MissingVars {
vars = append(vars, v.String()) vars = append(vars, v.String())
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
@@ -26,27 +27,21 @@ type (
// within them. // within them.
Executor struct { Executor struct {
// Flags // Flags
Dir string Dir string
Entrypoint string TempDir *TempDir
TempDir TempDir Force bool
Force bool ForceAll bool
ForceAll bool Watch bool
Insecure bool Verbose bool
Download bool Silent bool
Offline bool AssumeYes bool
Timeout time.Duration AssumeTerm bool // Used for testing
CacheExpiryDuration time.Duration Dry bool
Watch bool Summary bool
Verbose bool Parallel bool
Silent bool Color bool
AssumeYes bool Concurrency int
AssumeTerm bool // Used for testing Interval time.Duration
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration
// I/O // I/O
Stdin io.Reader Stdin io.Reader
@@ -72,17 +67,17 @@ type (
executionHashesMutex sync.Mutex executionHashesMutex sync.Mutex
watchedDirs *xsync.MapOf[string, bool] watchedDirs *xsync.MapOf[string, bool]
} }
TempDir struct {
Remote string
Fingerprint string
}
) )
// NewExecutor creates a new [Executor] and applies the given functional options // NewExecutor creates a new [Executor] and applies the given functional options
// to it. // 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{ e := &Executor{
Timeout: time.Second * 10, Taskfile: tf,
Stdin: os.Stdin, Stdin: os.Stdin,
Stdout: os.Stdout, Stdout: os.Stdout,
Stderr: os.Stderr, Stderr: os.Stderr,
@@ -100,7 +95,10 @@ func NewExecutor(opts ...ExecutorOption) *Executor {
executionHashesMutex: sync.Mutex{}, executionHashesMutex: sync.Mutex{},
} }
e.Options(opts...) 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 // Options loops through the given [ExecutorOption] functions and applies them
@@ -122,33 +120,23 @@ type dirOption struct {
} }
func (o *dirOption) ApplyToExecutor(e *Executor) { func (o *dirOption) ApplyToExecutor(e *Executor) {
e.Dir = o.dir absDir, err := filepath.Abs(o.dir)
} if err != nil {
e.Dir = o.dir
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By return
// default, Task will search for one of the default Taskfiles in the given }
// directory. e.Dir = absDir
func WithEntrypoint(entrypoint string) ExecutorOption {
return &entrypointOption{entrypoint}
}
type entrypointOption struct {
entrypoint string
}
func (o *entrypointOption) ApplyToExecutor(e *Executor) {
e.Entrypoint = o.entrypoint
} }
// WithTempDir sets the temporary directory that will be used by [Executor] for // WithTempDir sets the temporary directory that will be used by [Executor] for
// storing temporary files like checksums and cached remote files. By default, // storing temporary files like checksums and cached remote files. By default,
// the temporary directory is set to the user's temporary directory. // the temporary directory is set to the user's temporary directory.
func WithTempDir(tempDir TempDir) ExecutorOption { func WithTempDir(tempDir *TempDir) ExecutorOption {
return &tempDirOption{tempDir} return &tempDirOption{tempDir}
} }
type tempDirOption struct { type tempDirOption struct {
tempDir TempDir tempDir *TempDir
} }
func (o *tempDirOption) ApplyToExecutor(e *Executor) { func (o *tempDirOption) ApplyToExecutor(e *Executor) {
@@ -183,76 +171,6 @@ func (o *forceAllOption) ApplyToExecutor(e *Executor) {
e.ForceAll = o.forceAll 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 // 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 // for changes to the fingerprint of the tasks that are run. When changes are
// detected, a new task run is triggered. // detected, a new task run is triggered.

View File

@@ -4,18 +4,13 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath"
"slices" "slices"
"strings"
"sync" "sync"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/sajari/fuzzy" "github.com/sajari/fuzzy"
"github.com/go-task/task/v3/errors" "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/logger"
"github.com/go-task/task/v3/internal/output" "github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/version" "github.com/go-task/task/v3/internal/version"
@@ -23,18 +18,8 @@ import (
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
func (e *Executor) Setup() error { func (e *Executor) setup() error {
e.setupLogger() 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.setupFuzzyModel()
e.setupStdFiles() e.setupStdFiles()
if err := e.setupOutput(); err != nil { if err := e.setupOutput(); err != nil {
@@ -54,46 +39,6 @@ func (e *Executor) Setup() error {
return nil 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() { func (e *Executor) setupFuzzyModel() {
if e.Taskfile == nil { if e.Taskfile == nil {
return return
@@ -115,52 +60,6 @@ func (e *Executor) setupFuzzyModel() {
e.fuzzyModel = model 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() { func (e *Executor) setupStdFiles() {
if e.Stdin == nil { if e.Stdin == nil {
e.Stdin = os.Stdin e.Stdin = os.Stdin
@@ -206,7 +105,7 @@ func (e *Executor) setupCompiler() error {
e.Compiler = &Compiler{ e.Compiler = &Compiler{
Dir: e.Dir, Dir: e.Dir,
Entrypoint: e.Entrypoint, Entrypoint: e.Taskfile.Location,
UserWorkingDir: e.UserWorkingDir, UserWorkingDir: e.UserWorkingDir,
TaskfileEnv: e.Taskfile.Env, TaskfileEnv: e.Taskfile.Env,
TaskfileVars: e.Taskfile.Vars, TaskfileVars: e.Taskfile.Vars,

View File

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

@@ -9,7 +9,6 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/go-task/task/v3/taskrc" "github.com/go-task/task/v3/taskrc"
"github.com/go-task/task/v3/taskrc/ast"
) )
const envPrefix = "TASK_X_" const envPrefix = "TASK_X_"
@@ -32,15 +31,16 @@ var (
var xList []Experiment var xList []Experiment
func Parse(dir string) { func Parse(dir string) {
config, _ := taskrc.GetConfig(dir)
ParseWithConfig(dir, config)
}
func ParseWithConfig(dir string, config *ast.TaskRC) {
// Read any .env files // Read any .env files
readDotEnv(dir) readDotEnv(dir)
// Create a node for the Task config reader
node, _ := taskrc.NewNode("", dir)
// Read the Task config file
reader := taskrc.NewReader()
config, _ := reader.Read(node)
// Initialize the experiments // Initialize the experiments
GentleForce = New("GENTLE_FORCE", config, 1) GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1) RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)

View File

@@ -2,7 +2,9 @@ package task_test
import ( import (
"bytes" "bytes"
"context"
"path/filepath" "path/filepath"
"slices"
"testing" "testing"
"github.com/sebdah/goldie/v2" "github.com/sebdah/goldie/v2"
@@ -10,6 +12,7 @@ import (
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
"github.com/go-task/task/v3/experiments" "github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -26,12 +29,17 @@ type (
// running `task gen:fixtures`. // running `task gen:fixtures`.
FormatterTest struct { FormatterTest struct {
TaskTest TaskTest
task string task string
vars map[string]any vars map[string]any
executorOpts []task.ExecutorOption nodeDir string
listOptions task.ListOptions nodeEntrypoint string
wantSetupError bool nodeInsecure bool
wantListError bool readerOpts []taskfile.ReaderOption
executorOpts []task.ExecutorOption
listOptions task.ListOptions
wantReaderError bool
wantSetupError bool
wantListError bool
} }
) )
@@ -41,8 +49,9 @@ type (
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) { func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
t.Helper() t.Helper()
tt := &FormatterTest{ tt := &FormatterTest{
task: "default", task: "default",
vars: map[string]any{}, vars: map[string]any{},
nodeDir: ".",
TaskTest: TaskTest{ TaskTest: TaskTest{
experiments: map[*experiments.Experiment]int{}, experiments: map[*experiments.Experiment]int{},
fixtureTemplateData: map[string]any{}, fixtureTemplateData: map[string]any{},
@@ -114,23 +123,57 @@ func (tt *FormatterTest) run(t *testing.T) {
f := func(t *testing.T) { f := func(t *testing.T) {
t.Helper() t.Helper()
var buf bytes.Buffer var buf bytes.Buffer
ctx := context.Background()
opts := append( // Create a new root node for the given entrypoint
tt.executorOpts, node, err := taskfile.NewRootNode(
task.WithStdout(&buf), tt.nodeEntrypoint,
task.WithStderr(&buf), tt.nodeDir,
tt.nodeInsecure,
) )
require.NoError(t, err)
// Set up the task executor
e := task.NewExecutor(opts...)
// Create a golden fixture file for the output // Create a golden fixture file for the output
g := goldie.New(t, g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")), goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
) )
// Call setup and check for errors // Set up a temporary directory for the taskfile reader and task executor
if err := e.Setup(); tt.wantSetupError { 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) require.Error(t, err)
tt.writeFixtureErrSetup(t, g, err) tt.writeFixtureErrSetup(t, g, err)
tt.writeFixtureBuffer(t, g, buf) tt.writeFixtureBuffer(t, g, buf)
@@ -146,7 +189,7 @@ func (tt *FormatterTest) run(t *testing.T) {
} }
// Run the formatter and check for errors // 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) require.Error(t, err)
tt.writeFixtureErrList(t, g, err) tt.writeFixtureErrList(t, g, err)
tt.writeFixtureBuffer(t, g, buf) tt.writeFixtureBuffer(t, g, buf)
@@ -170,9 +213,7 @@ func TestNoLabelInList(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/label_list"),
task.WithDir("testdata/label_list"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true, ListOnlyTasksWithDescriptions: true,
}), }),
@@ -184,9 +225,7 @@ func TestListAllShowsNoDesc(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/list_mixed_desc"),
task.WithDir("testdata/list_mixed_desc"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListAllTasks: true, ListAllTasks: true,
}), }),
@@ -198,9 +237,7 @@ func TestListCanListDescOnly(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/list_mixed_desc"),
task.WithDir("testdata/list_mixed_desc"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true, ListOnlyTasksWithDescriptions: true,
}), }),
@@ -211,9 +248,7 @@ func TestListDescInterpolation(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/list_desc_interpolation"),
task.WithDir("testdata/list_desc_interpolation"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true, ListOnlyTasksWithDescriptions: true,
}), }),
@@ -224,9 +259,7 @@ func TestJsonListFormat(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/json_list_format"),
task.WithDir("testdata/json_list_format"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
FormatTaskListAsJSON: true, FormatTaskListAsJSON: true,
}), }),

21
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/go-task/task/v3 module github.com/go-task/task/v3
go 1.24.0 go 1.23.0
require ( require (
github.com/Ladicle/tabwriter v1.0.0 github.com/Ladicle/tabwriter v1.0.0
@@ -19,16 +19,16 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mitchellh/hashstructure/v2 v2.0.2 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/puzpuzpuz/xsync/v3 v3.5.1
github.com/sajari/fuzzy v1.0.0 github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.7.1 github.com/sebdah/goldie/v2 v2.7.1
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.10.0
github.com/zeebo/xxh3 v1.0.2 github.com/zeebo/xxh3 v1.0.2
golang.org/x/sync v0.17.0 golang.org/x/sync v0.16.0
golang.org/x/term v0.35.0 golang.org/x/term v0.33.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa
mvdan.cc/sh/v3 v3.12.0 mvdan.cc/sh/v3 v3.12.0
) )
@@ -39,28 +39,23 @@ require (
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/otiai10/mint v1.6.3 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.36.0 // indirect golang.org/x/sys v0.34.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
) )

50
go.sum
View File

@@ -11,10 +11,12 @@ 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/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 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
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 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 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= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -34,8 +36,6 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
@@ -76,12 +76,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -98,8 +94,10 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -123,22 +121,14 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7 h1:ax+jBy7xFhh+Ka0IGLmH5mft+YDuqvzEjSgWuAP0nsM=
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
@@ -148,15 +138,13 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -166,15 +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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
@@ -189,7 +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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa h1:sRmA9AmA5+9CbK6a7N52q9W9jAeoBy1EJ7cncm+SLxw=
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa/go.mod h1:Of9PCedbLDYT8b3EyiYG64rNnx5nOp27OLCVdDrjJyo=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI= 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= mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=

65
help.go
View File

@@ -24,17 +24,15 @@ type ListOptions struct {
ListAllTasks bool ListAllTasks bool
FormatTaskListAsJSON bool FormatTaskListAsJSON bool
NoStatus bool NoStatus bool
Nested bool
} }
// NewListOptions creates a new ListOptions instance // NewListOptions creates a new ListOptions instance
func NewListOptions(list, listAll, listAsJson, noStatus, nested bool) ListOptions { func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
return ListOptions{ return ListOptions{
ListOnlyTasksWithDescriptions: list, ListOnlyTasksWithDescriptions: list,
ListAllTasks: listAll, ListAllTasks: listAll,
FormatTaskListAsJSON: listAsJson, FormatTaskListAsJSON: listAsJson,
NoStatus: noStatus, NoStatus: noStatus,
Nested: nested,
} }
} }
@@ -65,7 +63,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
return false, err return false, err
} }
if o.FormatTaskListAsJSON { if o.FormatTaskListAsJSON {
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested) output, err := e.ToEditorOutput(tasks, o.NoStatus)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -137,17 +135,33 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
return nil return nil
} }
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) { func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
o := &editors.Taskfile{
Tasks: make([]editors.Task, len(tasks)),
Location: e.Taskfile.Location,
}
var g errgroup.Group var g errgroup.Group
editorTasks := make([]editors.Task, len(tasks))
// Look over each task in parallel and turn it into an editor task
for i := range tasks { for i := range tasks {
aliases := []string{}
if len(tasks[i].Aliases) > 0 {
aliases = tasks[i].Aliases
}
g.Go(func() error { g.Go(func() error {
editorTask := editors.NewTask(tasks[i]) o.Tasks[i] = editors.Task{
Name: tasks[i].Name(),
Task: tasks[i].Task,
Desc: tasks[i].Desc,
Summary: tasks[i].Summary,
Aliases: aliases,
UpToDate: false,
Location: &editors.Location{
Line: tasks[i].Location.Line,
Column: tasks[i].Location.Column,
Taskfile: tasks[i].Location.Taskfile,
},
}
if noStatus { if noStatus {
editorTasks[i] = editorTask
return nil return nil
} }
@@ -166,35 +180,10 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool)
return err return err
} }
editorTask.UpToDate = &upToDate o.Tasks[i].UpToDate = upToDate
editorTasks[i] = editorTask
return nil return nil
}) })
} }
if err := g.Wait(); err != nil { return o, g.Wait()
return nil, err
}
// Create the root namespace
var tasksLen int
if !nested {
tasksLen = len(editorTasks)
}
rootNamespace := &editors.Namespace{
Tasks: make([]editors.Task, tasksLen),
Location: e.Taskfile.Location,
}
// Recursively add namespaces to the root namespace or if nesting is
// disabled add them all to the root namespace
for i, task := range editorTasks {
taskNamespacePath := strings.Split(task.Task, ast.NamespaceSeparator)
if nested {
rootNamespace.AddNamespace(taskNamespacePath, task)
} else {
rootNamespace.Tasks[i] = task
}
}
return rootNamespace, g.Wait()
} }

View File

@@ -1,15 +1,10 @@
package editors package editors
import (
"github.com/go-task/task/v3/taskfile/ast"
)
type ( type (
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc) // Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
Namespace struct { Taskfile struct {
Tasks []Task `json:"tasks"` Tasks []Task `json:"tasks"`
Namespaces map[string]*Namespace `json:"namespaces,omitempty"` Location string `json:"location"`
Location string `json:"location,omitempty"`
} }
// Task describes a single task // Task describes a single task
Task struct { Task struct {
@@ -18,7 +13,7 @@ type (
Desc string `json:"desc"` Desc string `json:"desc"`
Summary string `json:"summary"` Summary string `json:"summary"`
Aliases []string `json:"aliases"` Aliases []string `json:"aliases"`
UpToDate *bool `json:"up_to_date,omitempty"` UpToDate bool `json:"up_to_date"`
Location *Location `json:"location"` Location *Location `json:"location"`
} }
// Location describes a task's location in a taskfile // Location describes a task's location in a taskfile
@@ -28,59 +23,3 @@ type (
Taskfile string `json:"taskfile"` Taskfile string `json:"taskfile"`
} }
) )
func NewTask(task *ast.Task) Task {
aliases := []string{}
if len(task.Aliases) > 0 {
aliases = task.Aliases
}
return Task{
Name: task.Name(),
Task: task.Task,
Desc: task.Desc,
Summary: task.Summary,
Aliases: aliases,
Location: &Location{
Line: task.Location.Line,
Column: task.Location.Column,
Taskfile: task.Location.Taskfile,
},
}
}
func (parent *Namespace) AddNamespace(namespacePath []string, task Task) {
if len(namespacePath) == 0 {
return
}
// If there are no child namespaces, then we have found a task and we can
// simply add it to the current namespace
if len(namespacePath) == 1 {
parent.Tasks = append(parent.Tasks, task)
return
}
// Get the key of the current namespace in the path
namespaceKey := namespacePath[0]
// Add the namespace to the parent namespaces map using the namespace key
if parent.Namespaces == nil {
parent.Namespaces = make(map[string]*Namespace, 0)
}
// Search for the current namespace in the parent namespaces map
// If it doesn't exist, create it
namespace, ok := parent.Namespaces[namespaceKey]
if !ok {
namespace = &Namespace{}
parent.Namespaces[namespaceKey] = namespace
}
// Remove the current namespace key from the namespace path.
childNamespacePath := namespacePath[1:]
// If there are no child namespaces in the task name, then we have found the
// namespace of the task and we can add it to the current namespace.
// Otherwise, we need to go deeper
namespace.AddNamespace(childNamespacePath, task)
}

View File

@@ -1,20 +0,0 @@
package execext
import (
"runtime"
"strconv"
"github.com/go-task/task/v3/internal/env"
)
var useGoCoreUtils bool
func init() {
// If TASK_CORE_UTILS is set to either true or false, respect that.
// By default, enable on Windows only.
if v, err := strconv.ParseBool(env.GetTaskEnv("CORE_UTILS")); err == nil {
useGoCoreUtils = v
} else {
useGoCoreUtils = runtime.GOOS == "windows"
}
}

View File

@@ -7,8 +7,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"mvdan.cc/sh/moreinterp/coreutils"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
@@ -59,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
r, err := interp.New( r, err := interp.New(
interp.Params(params...), interp.Params(params...),
interp.Env(expand.ListEnviron(environ...)), interp.Env(expand.ListEnviron(environ...)),
interp.ExecHandlers(execHandlers()...), interp.ExecHandlers(execHandler),
interp.OpenHandler(openHandler), interp.OpenHandler(openHandler),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr), interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
dirOption(opts.Dir), dirOption(opts.Dir),
@@ -143,11 +143,8 @@ func ExpandFields(s string) ([]string, error) {
return expand.Fields(cfg, words...) return expand.Fields(cfg, words...)
} }
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) { func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
if useGoCoreUtils { return interp.DefaultExecHandler(15 * time.Second)
handlers = append(handlers, coreutils.ExecHandler)
}
return
} }
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {

View File

@@ -1,6 +1,7 @@
package fingerprint package fingerprint
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -163,7 +164,7 @@ func TestIsTaskUpToDate(t *testing.T) {
} }
result, err := IsTaskUpToDate( result, err := IsTaskUpToDate(
t.Context(), context.Background(),
tt.task, tt.task,
WithStatusChecker(mockStatusChecker), WithStatusChecker(mockStatusChecker),
WithSourcesChecker(mockSourcesChecker), WithSourcesChecker(mockSourcesChecker),

View File

@@ -5,6 +5,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"time" "time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -12,10 +13,10 @@ import (
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/experiments" "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/internal/sort"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
"github.com/go-task/task/v3/taskrc"
taskrcast "github.com/go-task/task/v3/taskrc/ast"
) )
const usage = `Usage: task [flags...] [task...] const usage = `Usage: task [flags...] [task...]
@@ -51,7 +52,6 @@ var (
TaskSort string TaskSort string
Status bool Status bool
NoStatus bool NoStatus bool
Nested bool
Insecure bool Insecure bool
Force bool Force bool
ForceAll bool ForceAll bool
@@ -96,9 +96,7 @@ func init() {
// Parse the experiments // Parse the experiments
dir = cmp.Or(dir, filepath.Dir(entrypoint)) dir = cmp.Or(dir, filepath.Dir(entrypoint))
experiments.Parse(dir)
config, _ := taskrc.GetConfig(dir)
experiments.ParseWithConfig(dir, config)
// Parse the rest of the flags // Parse the rest of the flags
log.SetFlags(0) log.SetFlags(0)
@@ -107,7 +105,10 @@ func init() {
log.Print(usage) log.Print(usage)
pflag.PrintDefaults() pflag.PrintDefaults()
} }
offline, err := strconv.ParseBool(cmp.Or(env.GetTaskEnv("OFFLINE"), "false"))
if err != nil {
offline = false
}
pflag.BoolVar(&Version, "version", false, "Show Task version.") pflag.BoolVar(&Version, "version", false, "Show Task version.")
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.") pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.") pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
@@ -118,10 +119,9 @@ func init() {
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].") pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.") pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON") pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON") pflag.BoolVar(&Insecure, "insecure", false, "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVar(&Insecure, "insecure", getConfig(config, func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.") pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.") pflag.BoolVarP(&Verbose, "verbose", "v", false, "Enables verbose mode.")
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.") pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.") pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.") pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
@@ -135,7 +135,7 @@ func init() {
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.") pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.") pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.") pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.") pflag.IntVarP(&Concurrency, "concurrency", "C", 0, "Limit number of tasks to run concurrently.")
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.") pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.") pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.") pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
@@ -151,11 +151,12 @@ func init() {
// Remote Taskfiles experiment will adds the "download" and "offline" flags // Remote Taskfiles experiment will adds the "download" and "offline" flags
if experiments.RemoteTaskfiles.Enabled() { if experiments.RemoteTaskfiles.Enabled() {
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.") pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&Offline, "offline", getConfig(config, func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.") pflag.BoolVar(&Offline, "offline", offline, "Forces Task to only use local or cached Taskfiles.")
pflag.DurationVar(&Timeout, "timeout", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.") pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.") pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, 0), "Expiry duration for cached remote Taskfiles.") pflag.DurationVar(&CacheExpiryDuration, "expiry", 0, "Expiry duration for cached remote Taskfiles.")
} }
pflag.Parse() pflag.Parse()
} }
@@ -196,16 +197,12 @@ func Validate() error {
return errors.New("task: --no-status only applies to --json with --list or --list-all") return errors.New("task: --no-status only applies to --json with --list or --list-all")
} }
if Nested && !ListJson {
return errors.New("task: --nested only applies to --json with --list or --list-all")
}
return nil return nil
} }
// WithFlags is a special internal functional option that is used to pass flags // WithFlags is a special internal functional option that is used to pass flags
// from the CLI into any constructor that accepts functional options. // from the CLI into any constructor that accepts functional options.
func WithFlags() task.ExecutorOption { func WithFlags() *flagsOption {
return &flagsOption{} return &flagsOption{}
} }
@@ -232,14 +229,8 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
e.Options( e.Options(
task.WithDir(dir), task.WithDir(dir),
task.WithEntrypoint(Entrypoint),
task.WithForce(Force), task.WithForce(Force),
task.WithForceAll(ForceAll), task.WithForceAll(ForceAll),
task.WithInsecure(Insecure),
task.WithDownload(Download),
task.WithOffline(Offline),
task.WithTimeout(Timeout),
task.WithCacheExpiryDuration(CacheExpiryDuration),
task.WithWatch(Watch), task.WithWatch(Watch),
task.WithVerbose(Verbose), task.WithVerbose(Verbose),
task.WithSilent(Silent), task.WithSilent(Silent),
@@ -256,15 +247,11 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
) )
} }
// getConfig extracts a config value directly from a pointer field with a fallback default func (o *flagsOption) ApplyToReader(r *taskfile.Reader) {
func getConfig[T any](config *taskrcast.TaskRC, fieldFunc func() *T, fallback T) T { r.Options(
if config == nil { taskfile.WithInsecure(Insecure),
return fallback taskfile.WithDownload(Download),
} taskfile.WithOffline(Offline),
taskfile.WithCacheExpiryDuration(CacheExpiryDuration),
field := fieldFunc() )
if field != nil {
return *field
}
return fallback
} }

View File

@@ -37,87 +37,51 @@ func DefaultDir(entrypoint, dir string) string {
return "" return ""
} }
// ResolveDir returns an absolute path to the directory that the task should be // Search will look for files with the given possible filenames using the given
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not // entrypoint and directory. If the entrypoint is set, it will check if the
// sit inside the directory specified by dir and we should ensure that the dir
// is absolute. Otherwise, the dir will always be the parent directory of the
// resolved entrypoint, so we should return that parent directory.
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
if entrypoint != "" && dir != "" {
return filepath.Abs(dir)
}
return filepath.Dir(resolvedEntrypoint), nil
}
// Search looks for files with the given possible filenames using the given
// entrypoint and directory. If the entrypoint is set, it checks if the
// entrypoint matches a file or if it matches a directory containing one of the // entrypoint matches a file or if it matches a directory containing one of the
// possible filenames. Otherwise, it walks up the file tree starting at the // possible filenames. Otherwise, it will walk up the file tree starting at the
// given directory and performs a search in each directory for the possible // given directory and perform a search in each directory for the possible
// filenames until it finds a match or reaches the root directory. If the // filenames until it finds a match or reaches the root directory. If the
// entrypoint and directory are both empty, it defaults the directory to the // entrypoint and directory are both empty, it will default the directory to the
// current working directory and performs a recursive search starting there. If // current working directory and perform a recursive search starting there. If a
// a match is found, the absolute path to the file is returned with its // match is found, the absolute path to the file will be returned with its
// directory. If no match is found, an error is returned. // directory. If no match is found, an error will be returned.
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) { func Search(entrypoint, dir string, possibleFilenames []string) (string, string, error) {
var err error var err error
if entrypoint != "" { if entrypoint != "" {
entrypoint, err = SearchPath(entrypoint, possibleFilenames) entrypoint, err = SearchPath(entrypoint, possibleFilenames)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return entrypoint, nil if dir == "" {
dir = filepath.Dir(entrypoint)
} else {
dir, err = filepath.Abs(dir)
if err != nil {
return "", "", err
}
}
return entrypoint, dir, nil
} }
if dir == "" { if dir == "" {
dir, err = os.Getwd() dir, err = os.Getwd()
if err != nil { if err != nil {
return "", err return "", "", err
} }
} }
entrypoint, err = SearchPathRecursively(dir, possibleFilenames) entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return entrypoint, nil dir = filepath.Dir(entrypoint)
return entrypoint, dir, nil
} }
// SearchAll looks for files with the given possible filenames using the given // Search will check if a file at the given path exists or not. If it does, it
// entrypoint and directory. If the entrypoint is set, it checks if the // will return the path to it. If it does not, it will search for any files at
// entrypoint matches a file or if it matches a directory containing one of the // the given path with any of the given possible names. If any of these match a
// possible filenames and add it to a list of matches. It then walks up the file // file, the first matching path will be returned. If no files are found, an
// tree starting at the given directory and performs a search in each directory
// for the possible filenames until it finds a match or reaches the root
// directory. If the entrypoint and directory are both empty, it defaults the
// directory to the current working directory and performs a recursive search
// starting there. If matches are found, the absolute path to each file is added
// to the list and returned.
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
var err error
var entrypoints []string
if entrypoint != "" {
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
if err != nil {
return nil, err
}
entrypoints = append(entrypoints, entrypoint)
}
if dir == "" {
dir, err = os.Getwd()
if err != nil {
return nil, err
}
}
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
if err != nil {
return nil, err
}
return append(entrypoints, paths...), nil
}
// SearchPath will check if a file at the given path exists or not. If it does,
// it will return the path to it. If it does not, it will search for any files
// at the given path with any of the given possible names. If any of these match
// a file, the first matching path will be returned. If no files are found, an
// error will be returned. // error will be returned.
func SearchPath(path string, possibleFilenames []string) (string, error) { func SearchPath(path string, possibleFilenames []string) (string, error) {
// Get file info about the path // Get file info about the path
@@ -147,56 +111,36 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
return "", os.ErrNotExist return "", os.ErrNotExist
} }
// SearchPathRecursively walks up the directory tree starting at the given // SearchRecursively will check if a file at the given path exists by calling
// path, calling the Search function in each directory until it finds a matching // the exists function. If a file is not found, it will walk up the directory
// file or reaches the root directory. On supported operating systems, it will // tree calling the Search function until it finds a file or reaches the root
// also check if the user ID of the directory changes and abort if it does. // directory. On supported operating systems, it will also check if the user ID
// of the directory changes and abort if it does.
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) { func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
paths, err := SearchNPathRecursively(path, possibleFilenames, 1) owner, err := sysinfo.Owner(path)
if err != nil { if err != nil {
return "", err return "", err
} }
if len(paths) == 0 { for {
return "", os.ErrNotExist
}
return paths[0], nil
}
// SearchNPathRecursively walks up the directory tree starting at the given
// path, calling the Search function in each directory and adding each matching
// file that it finds to a list until it reaches the root directory or the
// length of the list exceeds n. On supported operating systems, it will also
// check if the user ID of the directory changes and abort if it does.
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
var paths []string
owner, err := sysinfo.Owner(path)
if err != nil {
return nil, err
}
for n == -1 || len(paths) < n {
fpath, err := SearchPath(path, possibleFilenames) fpath, err := SearchPath(path, possibleFilenames)
if err == nil { if err == nil {
paths = append(paths, fpath) return fpath, nil
} }
// Get the parent path/user id // Get the parent path/user id
parentPath := filepath.Dir(path) parentPath := filepath.Dir(path)
parentOwner, err := sysinfo.Owner(parentPath) parentOwner, err := sysinfo.Owner(parentPath)
if err != nil { if err != nil {
return nil, err return "", err
} }
// Error if we reached the root directory and still haven't found a file // Error if we reached the root directory and still haven't found a file
// OR if the user id of the directory changes // OR if the user id of the directory changes
if path == parentPath || (parentOwner != owner) { if path == parentPath || (parentOwner != owner) {
return paths, nil return "", os.ErrNotExist
} }
owner = parentOwner owner = parentOwner
path = parentPath path = parentPath
} }
return paths, nil
} }

View File

@@ -71,30 +71,35 @@ func TestSearch(t *testing.T) {
dir string dir string
possibleFilenames []string possibleFilenames []string
expectedEntrypoint string expectedEntrypoint string
expectedDir string
}{ }{
{ {
name: "find foo.txt using relative entrypoint", name: "find foo.txt using relative entrypoint",
entrypoint: "./testdata/foo.txt", entrypoint: "./testdata/foo.txt",
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using absolute entrypoint", name: "find foo.txt using absolute entrypoint",
entrypoint: filepath.Join(wd, "testdata", "foo.txt"), entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using relative dir", name: "find foo.txt using relative dir",
dir: "./testdata", dir: "./testdata",
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using absolute dir", name: "find foo.txt using absolute dir",
dir: filepath.Join(wd, "testdata"), dir: filepath.Join(wd, "testdata"),
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using relative dir and relative entrypoint", name: "find foo.txt using relative dir and relative entrypoint",
@@ -102,6 +107,7 @@ func TestSearch(t *testing.T) {
dir: "./testdata/some/other/dir", dir: "./testdata/some/other/dir",
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
}, },
{ {
name: "find fs.go using no entrypoint or dir", name: "find fs.go using no entrypoint or dir",
@@ -109,6 +115,7 @@ func TestSearch(t *testing.T) {
dir: "", dir: "",
possibleFilenames: []string{"fs.go"}, possibleFilenames: []string{"fs.go"},
expectedEntrypoint: filepath.Join(wd, "fs.go"), expectedEntrypoint: filepath.Join(wd, "fs.go"),
expectedDir: wd,
}, },
{ {
name: "find ../../Taskfile.yml using no entrypoint or dir by walking", name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
@@ -116,109 +123,30 @@ func TestSearch(t *testing.T) {
dir: "", dir: "",
possibleFilenames: []string{"Taskfile.yml"}, possibleFilenames: []string{"Taskfile.yml"},
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"), expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
expectedDir: filepath.Join(wd, "..", ".."),
}, },
{ {
name: "find foo.txt first if listed first in possible filenames", name: "find foo.txt first if listed first in possible filenames",
entrypoint: "./testdata", entrypoint: "./testdata",
possibleFilenames: []string{"foo.txt", "bar.txt"}, possibleFilenames: []string{"foo.txt", "bar.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find bar.txt first if listed first in possible filenames", name: "find bar.txt first if listed first in possible filenames",
entrypoint: "./testdata", entrypoint: "./testdata",
possibleFilenames: []string{"bar.txt", "foo.txt"}, possibleFilenames: []string{"bar.txt", "foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
entrypoint, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames) entrypoint, dir, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tt.expectedEntrypoint, entrypoint) require.Equal(t, tt.expectedEntrypoint, entrypoint)
require.NoError(t, err)
})
}
}
func TestResolveDir(t *testing.T) {
t.Parallel()
wd, err := os.Getwd()
require.NoError(t, err)
tests := []struct {
name string
entrypoint string
resolvedEntrypoint string
dir string
expectedDir string
}{
{
name: "find foo.txt using relative entrypoint",
entrypoint: "./testdata/foo.txt",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using absolute entrypoint",
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using relative dir",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
dir: "./testdata",
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using absolute dir",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
dir: filepath.Join(wd, "testdata"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using relative dir and relative entrypoint",
entrypoint: "./testdata/foo.txt",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
dir: "./testdata/some/other/dir",
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
},
{
name: "find fs.go using no entrypoint or dir",
entrypoint: "",
resolvedEntrypoint: filepath.Join(wd, "fs.go"),
dir: "",
expectedDir: wd,
},
{
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
entrypoint: "",
resolvedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
dir: "",
expectedDir: filepath.Join(wd, "..", ".."),
},
{
name: "find foo.txt first if listed first in possible filenames",
entrypoint: "./testdata",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find bar.txt first if listed first in possible filenames",
entrypoint: "./testdata",
resolvedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
dir, err := ResolveDir(tt.entrypoint, tt.resolvedEntrypoint, tt.dir)
require.NoError(t, err)
require.Equal(t, tt.expectedDir, dir) require.Equal(t, tt.expectedDir, dir)
require.NoError(t, err)
}) })
} }
} }

View File

@@ -1 +1 @@
3.45.2 3.44.1

32
package-lock.json generated Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@go-task/cli",
"version": "3.44.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@go-task/cli",
"version": "3.26.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@go-task/go-npm": "^0.2.0"
}
},
"node_modules/@go-task/go-npm": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw==",
"bin": {
"go-npm": "bin/index.js"
}
}
},
"dependencies": {
"@go-task/go-npm": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw=="
}
}
}

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "@go-task/cli",
"version": "3.44.1",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",
"preuninstall": "go-npm uninstall"
},
"goBinary": {
"name": "task",
"path": "./bin",
"url": "https://github.com/go-task/task/releases/download/v{{version}}/task_{{platform}}_{{arch}}{{archive_ext}}"
},
"files": [],
"repository": {
"type": "git",
"url": "https://github.com/go-task/task.git"
},
"keywords": [
"task",
"taskfile",
"build-tool",
"task-runner"
],
"author": "The Task authors",
"license": "MIT",
"bugs": {
"url": "https://github.com/go-task/task/issues"
},
"homepage": "https://taskfile.dev",
"dependencies": {
"@go-task/go-npm": "^0.2.0"
}
}

View File

@@ -172,6 +172,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
if t.Method != "" { if t.Method != "" {
method = t.Method method = t.Method
} }
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t, upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
fingerprint.WithMethod(method), fingerprint.WithMethod(method),
fingerprint.WithTempDir(e.TempDir.Fingerprint), fingerprint.WithTempDir(e.TempDir.Fingerprint),
@@ -466,6 +467,7 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
DidYouMean: didYouMean, DidYouMean: didYouMean,
} }
} }
return matchingTask, nil return matchingTask, nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -46,22 +46,17 @@ type Task struct {
Namespace string Namespace string
IncludeVars *Vars IncludeVars *Vars
IncludedTaskfileVars *Vars IncludedTaskfileVars *Vars
FullName string
} }
func (t *Task) Name() string { func (t *Task) Name() string {
if t.Label != "" { if t.Label != "" {
return t.Label return t.Label
} }
if t.FullName != "" {
return t.FullName
}
return t.Task return t.Task
} }
func (t *Task) LocalName() string { func (t *Task) LocalName() string {
name := t.FullName name := t.Task
name = strings.TrimPrefix(name, t.Namespace) name = strings.TrimPrefix(name, t.Namespace)
name = strings.TrimPrefix(name, ":") name = strings.TrimPrefix(name, ":")
return name return name
@@ -225,7 +220,6 @@ func (t *Task) DeepCopy() *Task {
Location: t.Location.DeepCopy(), Location: t.Location.DeepCopy(),
Requires: t.Requires.DeepCopy(), Requires: t.Requires.DeepCopy(),
Namespace: t.Namespace, Namespace: t.Namespace,
FullName: t.FullName,
} }
return c return c
} }

View File

@@ -18,10 +18,7 @@ type Var struct {
func (v *Var) UnmarshalYAML(node *yaml.Node) error { func (v *Var) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind { switch node.Kind {
case yaml.MappingNode: case yaml.MappingNode:
key := "<none>" key := node.Content[0].Value
if len(node.Content) > 0 {
key = node.Content[0].Value
}
switch key { switch key {
case "sh", "ref", "map": case "sh", "ref", "map":
var m struct { var m struct {

View File

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

View File

@@ -18,21 +18,15 @@ type FileNode struct {
} }
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) { func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
// Find the entrypoint file var err error
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, defaultTaskfiles) base := NewBaseNode(dir, opts...)
entrypoint, base.dir, err = fsext.Search(entrypoint, base.dir, defaultTaskfiles)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Resolve the directory
resolvedDir, err := fsext.ResolveDir(entrypoint, resolvedEntrypoint, dir)
if err != nil {
return nil, err
}
return &FileNode{ return &FileNode{
baseNode: NewBaseNode(resolvedDir, opts...), baseNode: base,
entrypoint: resolvedEntrypoint, entrypoint: entrypoint,
}, nil }, nil
} }

View File

@@ -1,48 +1,8 @@
package ast package ast
import ( import "github.com/Masterminds/semver/v3"
"cmp"
"maps"
"time"
"github.com/Masterminds/semver/v3"
)
type TaskRC struct { type TaskRC struct {
Version *semver.Version `yaml:"version"` Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"`
Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"`
Experiments map[string]int `yaml:"experiments"` Experiments map[string]int `yaml:"experiments"`
} }
type Remote struct {
Insecure *bool `yaml:"insecure"`
Offline *bool `yaml:"offline"`
Timeout *time.Duration `yaml:"timeout"`
CacheExpiry *time.Duration `yaml:"cache-expiry"`
}
// Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC.
func (t *TaskRC) Merge(other *TaskRC) {
if other == nil {
return
}
t.Version = cmp.Or(other.Version, t.Version)
if t.Experiments == nil && other.Experiments != nil {
t.Experiments = other.Experiments
} else if t.Experiments != nil && other.Experiments != nil {
maps.Copy(t.Experiments, other.Experiments)
}
// Merge Remote fields
t.Remote.Insecure = cmp.Or(other.Remote.Insecure, t.Remote.Insecure)
t.Remote.Offline = cmp.Or(other.Remote.Offline, t.Remote.Offline)
t.Remote.Timeout = cmp.Or(other.Remote.Timeout, t.Remote.Timeout)
t.Remote.CacheExpiry = cmp.Or(other.Remote.CacheExpiry, t.Remote.CacheExpiry)
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
}

View File

@@ -1,24 +1,24 @@
package taskrc package taskrc
import ( import "github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/internal/fsext"
)
type Node struct { type Node struct {
entrypoint string entrypoint string
dir string
} }
func NewNode( func NewNode(
entrypoint string, entrypoint string,
dir string, dir string,
possibleFileNames []string,
) (*Node, error) { ) (*Node, error) {
dir = fsext.DefaultDir(entrypoint, dir) dir = fsext.DefaultDir(entrypoint, dir)
resolvedEntrypoint, err := fsext.SearchPath(dir, possibleFileNames) var err error
entrypoint, dir, err = fsext.Search(entrypoint, dir, defaultTaskRCs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Node{ return &Node{
entrypoint: resolvedEntrypoint, entrypoint: entrypoint,
dir: dir,
}, nil }, nil
} }

View File

@@ -1,89 +1,6 @@
package taskrc package taskrc
import ( var defaultTaskRCs = []string{
"os" ".taskrc.yml",
"path/filepath" ".taskrc.yaml",
"slices"
"strings"
"github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/taskrc/ast"
)
var (
defaultXDGTaskRCs = []string{
"taskrc.yml",
"taskrc.yaml",
}
defaultTaskRCs = []string{
".taskrc.yml",
".taskrc.yaml",
}
)
// GetConfig loads and merges local and global Task configuration files
func GetConfig(dir string) (*ast.TaskRC, error) {
var config *ast.TaskRC
reader := NewReader()
// Read the XDG config file
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
xdgConfigNode, err := NewNode("", filepath.Join(xdgConfigHome, "task"), defaultXDGTaskRCs)
if err == nil && xdgConfigNode != nil {
xdgConfig, err := reader.Read(xdgConfigNode)
if err != nil {
return nil, err
}
config = xdgConfig
}
}
// If the current path does not contain $HOME
// If it does contain $HOME, then we will find this config later anyway
home, err := os.UserHomeDir()
if err == nil && !strings.Contains(home, dir) {
homeNode, err := NewNode("", home, defaultTaskRCs)
if err == nil && homeNode != nil {
homeConfig, err := reader.Read(homeNode)
if err != nil {
return nil, err
}
if config == nil {
config = homeConfig
} else {
config.Merge(homeConfig)
}
}
}
// Find all the nodes from the given directory up to the users home directory
entrypoints, err := fsext.SearchAll("", dir, defaultTaskRCs)
if err != nil {
return nil, err
}
// Reverse the entrypoints since we want the child files to override parent ones
slices.Reverse(entrypoints)
// Loop over the nodes, and merge them into the main config
for _, entrypoint := range entrypoints {
node, err := NewNode("", entrypoint, defaultTaskRCs)
if err != nil {
return nil, err
}
localConfig, err := reader.Read(node)
if err != nil {
return nil, err
}
if localConfig == nil {
continue
}
if config == nil {
config = localConfig
continue
}
config.Merge(localConfig)
}
return config, nil
} }

View File

@@ -1,137 +0,0 @@
package taskrc
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/taskrc/ast"
)
const (
xdgConfigYAML = `
experiments:
FOO: 1
BAR: 1
BAZ: 1
`
homeConfigYAML = `
experiments:
FOO: 2
BAR: 2
`
localConfigYAML = `
experiments:
FOO: 3
`
)
func setupDirs(t *testing.T) (string, string, string) {
t.Helper()
xdgConfigDir := t.TempDir()
xdgTaskConfigDir := filepath.Join(xdgConfigDir, "task")
require.NoError(t, os.Mkdir(xdgTaskConfigDir, 0o755))
homeDir := t.TempDir()
localDir := filepath.Join(homeDir, "local")
require.NoError(t, os.Mkdir(localDir, 0o755))
t.Setenv("XDG_CONFIG_HOME", xdgConfigDir)
t.Setenv("HOME", homeDir)
return xdgTaskConfigDir, homeDir, localDir
}
func writeFile(t *testing.T, dir, filename, content string) {
t.Helper()
err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0o644)
assert.NoError(t, err)
}
func TestGetConfig_NoConfigFiles(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, _, localDir := setupDirs(t)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Nil(t, cfg)
}
func TestGetConfig_OnlyXDG(t *testing.T) { //nolint:paralleltest // cannot run in parallel
xdgDir, _, localDir := setupDirs(t)
writeFile(t, xdgDir, "taskrc.yml", xdgConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 1,
"BAR": 1,
"BAZ": 1,
},
}, cfg)
}
func TestGetConfig_OnlyHome(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, homeDir, localDir := setupDirs(t)
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 2,
"BAR": 2,
},
}, cfg)
}
func TestGetConfig_OnlyLocal(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, _, localDir := setupDirs(t)
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 3,
},
}, cfg)
}
func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in parallel
xdgConfigDir, homeDir, localDir := setupDirs(t)
// Write local config
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
// Write home config
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
// Write XDG config
writeFile(t, xdgConfigDir, "taskrc.yml", xdgConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 3,
"BAR": 2,
"BAZ": 1,
},
}, cfg)
}

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

@@ -12,14 +12,6 @@ tasks:
generates: generates:
- ./generated.txt - ./generated.txt
method: checksum method: checksum
build-*:
cmds:
- cp ./source.txt ./generated-{{index .MATCH 0}}.txt
sources:
- ./source.txt
generates:
- ./generated-{{index .MATCH 0}}.txt
method: checksum
build-with-status: build-with-status:
cmds: cmds:

View File

@@ -1 +0,0 @@
Hello, World!

View File

@@ -22,14 +22,3 @@ tasks:
run: once run: once
cmds: cmds:
- echo starting {{.CONTENT}} >> hash.txt - echo starting {{.CONTENT}} >> hash.txt
deploy:
cmds:
- rm -rf wildcard.txt
- task: deploy:infra
- task: deploy:js
- task: deploy:go
deploy:*:
run: once
cmd: echo "Deploy {{index .MATCH 0}}" >> wildcard.txt

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

@@ -44,14 +44,9 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
fullName := origTask.Task
if matches, exists := vars.Get("MATCH"); exists {
for _, match := range matches.Value.([]string) {
fullName = strings.Replace(fullName, "*", match, 1)
}
}
cache := &templater.Cache{Vars: vars} cache := &templater.Cache{Vars: vars}
new := ast.Task{ new := ast.Task{
Task: origTask.Task, Task: origTask.Task,
Label: templater.Replace(origTask.Label, cache), Label: templater.Replace(origTask.Label, cache),
@@ -81,7 +76,6 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
Requires: origTask.Requires, Requires: origTask.Requires,
Watch: origTask.Watch, Watch: origTask.Watch,
Namespace: origTask.Namespace, Namespace: origTask.Namespace,
FullName: fullName,
} }
new.Dir, err = execext.ExpandLiteral(new.Dir) new.Dir, err = execext.ExpandLiteral(new.Dir)
if err != nil { if err != nil {

18
website/.gitignore vendored
View File

@@ -1,9 +1,21 @@
# Dependencies # Dependencies
/node_modules /node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
i18n
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.vitepress/cache
.vitepress/dist
.task/

0
website/.nojekyll Normal file
View File

View File

@@ -1 +0,0 @@
pnpm-lock.yaml

View File

@@ -1,5 +0,0 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@@ -1,97 +0,0 @@
<template>
<div class="author-compact" v-if="author">
<img :src="author.avatar" :alt="author.name" class="author-avatar" />
<div class="author-info">
<div class="author-name-line">
<span class="author-name">{{ author.name }}</span>
<div class="author-socials">
<a
v-for="{ link, icon } in author.links"
:key="link"
:href="link"
target="_blank"
class="social-link"
>
<span :class="`vpi-social-${icon}`"></span>
</a>
</div>
</div>
<span class="author-bio">{{ author.title }}</span>
</div>
</div>
</template>
<script setup>
import { team } from '../team.ts';
import { computed } from 'vue';
const props = defineProps({
author: String
});
const author = computed(() => {
return team.find((m) => m.slug === props.author) || null;
});
</script>
<style scoped>
.author-compact {
display: flex;
align-items: center;
gap: 0.75rem;
margin: 1.5rem 0;
}
.author-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.author-info {
display: flex;
flex-direction: column;
gap: 0.1rem;
flex: 1;
}
.author-name-line {
display: flex;
align-items: center;
gap: 0.75rem;
}
.author-name {
font-weight: 600;
color: var(--vp-c-text-1);
font-size: 0.95rem;
}
.author-bio {
color: var(--vp-c-text-2);
font-size: 0.85rem;
}
.author-socials {
display: flex;
gap: 0.5rem;
}
.social-link {
color: var(--vp-c-text-2);
transition: color 0.2s;
display: flex;
align-items: center;
}
.social-link:hover {
color: var(--vp-c-brand-1);
}
@media (max-width: 768px) {
.author-compact {
margin-bottom: 1rem;
}
}
</style>

View File

@@ -1,182 +0,0 @@
<template>
<article class="blog-post">
<div class="post-header">
<h3 class="post-title">
<a :href="url">{{ title }}</a>
</h3>
<div class="post-meta">
<time :datetime="date" class="post-date">
{{ formatDate(date) }}
</time>
</div>
</div>
<div class="post-content">
<div class="post-image" v-if="image">
<img :src="image" :alt="title" />
</div>
<div class="post-text">
<AuthorCard :author="author" />
<p class="post-description">{{ description }}</p>
<div class="post-footer">
<div class="post-tags" v-if="tags?.length">
<strong>Tags:</strong>
<code v-for="tag in tags" :key="tag" class="post-tag">{{
tag
}}</code>
</div>
<a :href="url" class="read-more"> Read more </a>
</div>
</div>
</div>
</article>
</template>
<script setup>
import AuthorCard from './AuthorCard.vue';
const props = defineProps({
title: String,
url: String,
date: String,
author: String,
description: String,
tags: Array,
image: String
});
function formatDate(date) {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
</script>
<style scoped>
.blog-post {
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 2rem;
margin-bottom: 2rem;
}
.blog-post:last-child {
border-bottom: none;
margin-bottom: 0;
}
.post-title {
margin: 0 0 0.5rem 0;
font-size: 1.8rem;
font-weight: 600;
}
.post-title a {
transition: color 0.2s;
}
.post-title a:hover {
color: var(--vp-c-brand-1);
}
.post-date {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.post-content {
display: flex;
gap: 2rem;
align-items: flex-start;
}
.post-image {
flex-shrink: 0;
width: 300px;
}
.post-image img {
width: 100%;
height: auto;
border-radius: 8px;
object-fit: cover;
aspect-ratio: 16 / 9;
}
.post-text {
flex: 1;
}
.post-description {
color: var(--vp-c-text-2);
line-height: 1.6;
margin: 1.5rem 0;
font-size: 1.05rem;
}
.post-footer {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.post-tags {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.post-tag {
background: var(--vp-c-default-soft);
color: var(--vp-c-text-2);
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
margin-left: 0.5rem;
font-family: var(--vp-font-family-mono);
}
.read-more {
color: var(--vp-c-brand-1);
text-decoration: none;
font-weight: 500;
transition: all 0.2s;
padding: 0.5rem 1rem;
border: 1px solid var(--vp-c-brand-1);
border-radius: 6px;
font-size: 0.9rem;
}
.read-more:hover {
background: var(--vp-c-brand-1);
color: white;
}
/* Responsive */
@media (max-width: 768px) {
.post-content {
flex-direction: column;
gap: 1rem;
}
.post-image {
width: 100%;
}
.post-title {
font-size: 1.5rem;
}
.post-footer {
flex-direction: column;
align-items: flex-start;
}
}
</style>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import { VPHomeSponsors } from 'vitepress/theme';
import { sponsors } from '../sponsors';
</script>
<template>
<div class="content">
<div class="content-container">
<main class="main">
<VPHomeSponsors
v-if="sponsors"
message="Task is free and open source, made possible by wonderful sponsors."
:data="sponsors"
/>
</main>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -1,245 +0,0 @@
<script setup lang="ts">
import type { DefaultTheme } from 'vitepress/theme';
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue';
interface Props {
size?: 'small' | 'medium';
member: TeamMember;
}
interface TeamMember extends DefaultTheme.TeamMember {
icon?: string;
}
withDefaults(defineProps<Props>(), {
size: 'medium'
});
</script>
<template>
<article class="VPTeamMembersItem" :class="[size]">
<div class="profile">
<figure class="avatar">
<img class="avatar-img" :src="member.avatar" :alt="member.name" />
</figure>
<div class="data">
<h1 class="name">
<img :src="member.icon" alt="profile-icon" />
{{ member.name }}
</h1>
<p v-if="member.title || member.org" class="affiliation">
<span v-if="member.title" class="title">
{{ member.title }}
</span>
<span v-if="member.title && member.org" class="at"> @ </span>
<VPLink
v-if="member.org"
class="org"
:class="{ link: member.orgLink }"
:href="member.orgLink"
no-icon
>
{{ member.org }}
</VPLink>
</p>
<p v-if="member.desc" class="desc" v-html="member.desc" />
<div v-if="member.links" class="links">
<VPSocialLinks :links="member.links" :me="false" />
</div>
</div>
</div>
<div v-if="member.sponsor" class="sp">
<VPLink class="sp-link" :href="member.sponsor" no-icon>
<span class="vpi-heart sp-icon" /> {{ member.actionText || 'Sponsor' }}
</VPLink>
</div>
</article>
</template>
<style scoped>
.VPTeamMembersItem {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 12px;
width: 100%;
height: 100%;
overflow: hidden;
}
.VPTeamMembersItem.small .profile {
padding: 32px;
}
.VPTeamMembersItem.small .data {
padding-top: 20px;
}
.VPTeamMembersItem.small .avatar {
width: 64px;
height: 64px;
}
.VPTeamMembersItem.small .name {
line-height: 24px;
font-size: 16px;
}
.VPTeamMembersItem.small .affiliation {
padding-top: 4px;
line-height: 20px;
font-size: 14px;
}
.VPTeamMembersItem.small .desc {
padding-top: 12px;
line-height: 20px;
font-size: 14px;
}
.VPTeamMembersItem.small .links {
margin: 0 -16px -20px;
padding: 10px 0 0;
}
.VPTeamMembersItem.medium .profile {
padding: 48px 32px;
}
.VPTeamMembersItem.medium .data {
padding-top: 24px;
text-align: center;
}
.VPTeamMembersItem.medium .avatar {
width: 96px;
height: 96px;
}
.VPTeamMembersItem.medium .name {
letter-spacing: 0.15px;
line-height: 28px;
font-size: 20px;
}
.VPTeamMembersItem.medium .affiliation {
padding-top: 4px;
font-size: 16px;
}
.VPTeamMembersItem.medium .desc {
padding-top: 16px;
max-width: 288px;
font-size: 16px;
}
.VPTeamMembersItem.medium .links {
margin: 0 -16px -12px;
padding: 16px 12px 0;
}
.VPTeamMembersItem .profile .name {
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
}
.VPTeamMembersItem .profile .name img {
display: inline-block;
height: 22px;
background-repeat: no-repeat;
}
.profile {
flex-grow: 1;
background-color: var(--vp-c-bg-soft);
}
.data {
text-align: center;
}
.avatar {
position: relative;
flex-shrink: 0;
margin: 0 auto;
border-radius: 50%;
box-shadow: var(--vp-shadow-3);
}
.avatar-img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
object-fit: cover;
}
.name {
margin: 0;
font-weight: 600;
}
.affiliation {
margin: 0;
font-weight: 500;
color: var(--vp-c-text-2);
}
.org.link {
color: var(--vp-c-text-2);
transition: color 0.25s;
}
.org.link:hover {
color: var(--vp-c-brand-1);
}
.desc {
margin: 0 auto;
}
.desc :deep(a) {
font-weight: 500;
color: var(--vp-c-brand-1);
text-decoration-style: dotted;
transition: color 0.25s;
}
.links {
display: flex;
justify-content: center;
height: 56px;
}
.sp-link {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-sponsor);
background-color: var(--vp-c-bg-soft);
transition:
color 0.25s,
background-color 0.25s;
}
.sp .sp-link.link:hover,
.sp .sp-link.link:focus {
outline: none;
color: var(--vp-c-white);
background-color: var(--vp-c-sponsor);
}
.sp-icon {
margin-right: 8px;
font-size: 16px;
}
</style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts">
import { VPBadge } from 'vitepress/theme';
</script>
<template>
<VPBadge type="info"> <slot />+ </VPBadge>
</template>

View File

@@ -1,348 +0,0 @@
import { defineConfig } from 'vitepress';
import githubLinksPlugin from './plugins/github-links';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader
} from 'vitepress-plugin-group-icons';
import { team } from './team.ts';
import { taskDescription, taskName } from './meta.ts';
import { fileURLToPath, URL } from 'node:url';
const version = readFileSync(
resolve(__dirname, '../../internal/version/version.txt'),
'utf8'
).trim();
const urlVersion =
process.env.NODE_ENV === 'development'
? {
current: 'https://taskfile.dev/',
next: 'http://localhost:3002/'
}
: {
current: 'https://taskfile.dev/',
next: 'https://next.taskfile.dev/'
};
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: taskName,
description: taskDescription,
lang: 'en-US',
head: [
[
'link',
{
rel: 'icon',
type: 'image/x-icon',
href: '/img/favicon.icon',
sizes: '48x48'
}
],
[
'link',
{
rel: 'icon',
sizes: 'any',
type: 'image/svg+xml',
href: '/img/logo.svg'
}
],
[
'link',
{
rel: 'canonical',
href: 'https://taskfile.dev/'
}
],
[
'meta',
{ name: 'author', content: `${team.map((c) => c.name).join(', ')}` }
],
[
'meta',
{
name: 'keywords',
content:
'task runner, build tool, taskfile, yaml build tool, go task runner, make alternative, cross-platform build tool, makefile alternative, automation tool, ci cd pipeline, developer productivity, build automation, command line tool, go binary, yaml configuration'
}
],
[
'script',
{
async: '',
src: 'https://www.googletagmanager.com/gtag/js?id=G-4RT25NXQ7N'
}
],
[
'script',
{},
`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag("js", new Date());
gtag("config", "G-4RT25NXQ7N");`
],
[
"script",
{
defer: "",
src: "https://umami.taskfile.dev/script.js",
"data-website-id": "084030b0-0e3f-4891-8d2a-0c12c40f5933"
}
]
],
srcDir: 'src',
cleanUrls: true,
markdown: {
config: (md) => {
md.use(githubLinksPlugin, {
baseUrl: 'https://github.com',
repo: 'go-task/task'
});
md.use(tabsMarkdownPlugin);
md.use(groupIconMdPlugin);
}
},
vite: {
plugins: [
groupIconVitePlugin({
customIcon: {
'.taskrc.yml': localIconLoader(
import.meta.url,
'./theme/icons/task.svg'
),
'Taskfile.yml': localIconLoader(
import.meta.url,
'./theme/icons/task.svg'
)
}
})
],
resolve: {
alias: [
{
find: /^.*\/VPTeamMembersItem\.vue$/,
replacement: fileURLToPath(
new URL('./components/VPTeamMembersItem.vue', import.meta.url)
)
}
]
}
},
themeConfig: {
logo: '/img/logo.svg',
carbonAds: {
code: 'CESI65QJ',
placement: 'taskfiledev'
},
search: {
provider: 'algolia',
options: {
appId: '7IZIJ13AI7',
apiKey: '34b64ae4fc8d9da43d9a13d9710aaddc',
indexName: 'taskfile'
}
},
nav: [
{ text: 'Home', link: '/' },
{
text: 'Docs',
link: '/docs/guide',
activeMatch: '^/docs'
},
{ text: 'Blog', link: '/blog', activeMatch: '^/blog' },
{ text: 'Donate', link: '/donate' },
{ text: 'Team', link: '/team' },
{
text: process.env.NODE_ENV === 'development' ? 'Next' : `v${version}`,
items: [
{
items: [
{
text: `v${version}`,
link: urlVersion.current
},
{
text: 'Next',
link: urlVersion.next
}
]
}
]
}
],
sidebar: {
'/blog/': [
{
text: '2025',
collapsed: false,
items: [
{
text: 'Built-in Core Utilities',
link: '/blog/windows-core-utils'
}
]
},
{
text: '2024',
collapsed: false,
items: [
{
text: 'Any Variables',
link: '/blog/any-variables'
}
]
},
{
text: '2023',
collapsed: false,
items: [
{
text: 'Introducing Experiments',
link: '/blog/task-in-2023'
}
]
}
],
'/': [
{
text: 'Installation',
link: '/docs/installation'
},
{
text: 'Getting Started',
link: '/docs/getting-started'
},
{
text: 'Guide',
link: '/docs/guide'
},
{
text: 'Reference',
collapsed: true,
items: [
{
text: 'Taskfile Schema',
link: '/docs/reference/schema'
},
{
text: 'Environment',
link: '/docs/reference/environment'
},
{
text: 'Configuration',
link: '/docs/reference/config'
},
{
text: 'CLI',
link: '/docs/reference/cli'
},
{
text: 'Templating',
link: '/docs/reference/templating'
},
{
text: 'Package API',
link: '/docs/reference/package'
}
]
},
{
text: 'Experiments',
collapsed: true,
link: '/docs/experiments/',
items: [
{
text: 'Env Precedence (#1038)',
link: '/docs/experiments/env-precedence'
},
{
text: 'Gentle Force (#1200)',
link: '/docs/experiments/gentle-force'
},
{
text: 'Remote Taskfiles (#1317)',
link: '/docs/experiments/remote-taskfiles'
}
]
},
{
text: 'Deprecations',
collapsed: true,
link: '/docs/deprecations/',
items: [
{
text: 'Completion Scripts',
link: '/docs/deprecations/completion-scripts'
},
{
text: 'Template Functions',
link: '/docs/deprecations/template-functions'
},
{
text: 'Version 2 Schema (#1197)',
link: '/docs/deprecations/version-2-schema'
}
]
},
{
text: 'Taskfile Versions',
link: '/docs/taskfile-versions'
},
{
text: 'Integrations',
link: '/docs/integrations'
},
{
text: 'Community',
link: '/docs/community'
},
{
text: 'Style Guide',
link: '/docs/styleguide'
},
{
text: 'Contributing',
link: '/docs/contributing'
},
{
text: 'Releasing',
link: '/docs/releasing'
},
{
text: 'Changelog',
link: '/docs/changelog'
},
{
text: 'FAQ',
link: '/docs/faq'
}
],
// Hacky to disable sidebar for these pages
'/donate': [],
'/team': []
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/go-task/task' },
{ icon: 'discord', link: 'https://discord.gg/6TY36E39UK' },
{ icon: 'x', link: 'https://twitter.com/taskfiledev' },
{ icon: 'bluesky', link: 'https://bsky.app/profile/taskfile.dev' },
{ icon: 'mastodon', link: 'https://fosstodon.org/@task' }
],
footer: {
message:
'Built with <a target="_blank" href="https://www.netlify.com">Netlify</a>'
}
},
sitemap: {
hostname: 'https://taskfile.dev'
}
});

View File

@@ -1,5 +0,0 @@
export const taskName = 'Task';
export const taskDescription =
'A fast, cross-platform build tool inspired by Make, designed for modern workflows.';
export const ogUrl = 'https://taskfile.dev/';

View File

@@ -1,63 +0,0 @@
import type MarkdownIt from 'markdown-it';
interface PluginOptions {
repo: string;
}
function githubLinksPlugin(
md: MarkdownIt,
options: PluginOptions = {} as PluginOptions
): void {
const baseUrl = 'https://github.com';
const { repo } = options;
md.core.ruler.after('inline', 'github-links', (state): void => {
const tokens = state.tokens;
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].type === 'inline' && tokens[i].children) {
const inlineTokens = tokens[i].children!;
for (let j = 0; j < inlineTokens.length; j++) {
if (inlineTokens[j].type === 'text') {
let text: string = inlineTokens[j].content!;
const protectedRefs: string[] = [];
let protectIndex: number = 0;
text = text.replace(
/[\w.-]+\/[\w.-]+#(\d+)/g,
(match: string): string => {
const placeholder: string = `__PROTECTED_${protectIndex}__`;
protectedRefs[protectIndex] = match;
protectIndex++;
return placeholder;
}
);
text = text.replace(
/#(\d+)/g,
`<a href="${baseUrl}/${repo}/issues/$1" target="_blank" class="github-pr-link">#$1</a>`
);
text = text.replace(
/@([a-zA-Z0-9_-]+)(?![\w@.])/g,
`<a href="${baseUrl}/$1" target="_blank" class="github-user-mention">@$1</a>`
);
protectedRefs.forEach((ref: string, index: number): void => {
text = text.replace(`__PROTECTED_${index}__`, ref);
});
if (text !== inlineTokens[j].content) {
inlineTokens[j].content = text;
inlineTokens[j].type = 'html_inline';
}
}
}
}
}
});
}
export default githubLinksPlugin;

View File

@@ -1,13 +0,0 @@
export const sponsors = [
{
tier: 'Gold Sponsors',
size: 'big',
items: [
{
name: 'devowl',
url: 'https://devowl.io/',
img: '/img/devowl.io.svg'
}
]
}
];

View File

@@ -1,45 +0,0 @@
export const team = [
{
slug: 'andreynering',
avatar: 'https://www.github.com/andreynering.png',
name: 'Andrey Nering',
icon: '/img/flag-brazil.svg',
title: 'Creator & Maintainer',
sponsor: 'https://github.com/sponsors/andreynering',
links: [
{ icon: 'github', link: 'https://github.com/andreynering' },
{ icon: 'discord', link: 'https://discord.com/users/310141681926275082' },
{ icon: 'x', link: 'https://x.com/andreynering' },
{
icon: 'bluesky',
link: 'https://bsky.app/profile/andreynering.bsky.social'
},
{ icon: 'mastodon', link: 'https://mastodon.social/@andreynering' }
]
},
{
slug: 'pd93',
avatar: 'https://www.github.com/pd93.png',
name: 'Pete Davison',
icon: '/img/flag-wales.svg',
title: 'Maintainer',
sponsor: 'https://github.com/sponsors/pd93',
links: [
{ icon: 'github', link: 'https://github.com/pd93' },
{ icon: 'bluesky', link: 'https://bsky.app/profile/pd93.uk' }
]
},
{
slug: 'vmaerten',
avatar: 'https://www.github.com/vmaerten.png',
name: 'Valentin Maerten',
icon: '/img/flag-france.svg',
title: 'Maintainer',
sponsor: 'https://github.com/sponsors/vmaerten',
links: [
{ icon: 'github', link: 'https://github.com/vmaerten' },
{ icon: 'x', link: 'https://x.com/v_maerten' },
{ icon: 'bluesky', link: 'https://bsky.app/profile/vmaerten.bsky.social' }
]
}
];

View File

@@ -1,147 +0,0 @@
:root {
--ifm-color-primary: #43aba2;
--vp-home-hero-name-color: var(--ifm-color-primary);
--vp-c-brand-1: var(--ifm-color-primary);
--vp-c-brand-2: var(--ifm-color-primary);
--vp-c-brand-3: var(--ifm-color-primary);
--vp-icon-info: #3b82f6;
--vp-icon-tip: #10b981;
--vp-icon-warning: #f59e0b;
--vp-icon-danger: #ef4444;
--vp-icon-details: #6b7280;
}
.dark {
--vp-icon-info: #93c5fd;
--vp-icon-tip: #34d399;
--vp-icon-warning: #fbbf24;
--vp-icon-danger: #f87171;
--vp-icon-details: #9ca3af;
}
img[src*='shields.io'] {
display: inline;
vertical-align: text-bottom;
}
img[src*='custom-icon-badges.demolab.com'] {
display: inline;
height: 1em;
vertical-align: text-bottom;
}
.github-user-mention {
font-weight: 700 !important;
}
.vp-doc .blog-post:first-of-type {
margin-top: 2rem;
}
.blog-post {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.blog-post:nth-of-type(1) {
animation-delay: 0.1s;
}
.blog-post:nth-of-type(2) {
animation-delay: 0.2s;
}
.blog-post:nth-of-type(3) {
animation-delay: 0.3s;
}
.custom-block .custom-block-title::before {
content: '';
display: inline-block;
width: 20px;
height: 20px;
margin-right: 8px;
vertical-align: middle;
flex-shrink: 0;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
.custom-block.info .custom-block-title::before {
background-color: var(--vp-icon-info);
-webkit-mask-image: url('./icons/info.svg');
mask-image: url('./icons/info.svg');
}
.custom-block.tip .custom-block-title::before {
background-color: var(--vp-icon-tip);
-webkit-mask-image: url('./icons/tip.svg');
mask-image: url('./icons/tip.svg');
}
.custom-block.warning .custom-block-title::before {
background-color: var(--vp-icon-warning);
-webkit-mask-image: url('./icons/warning.svg');
mask-image: url('./icons/warning.svg');
}
.custom-block.danger .custom-block-title::before {
background-color: var(--vp-icon-danger);
-webkit-mask-image: url('./icons/danger.svg');
mask-image: url('./icons/danger.svg');
}
.custom-block.details[open] summary::before {
transform: rotate(90deg);
}
.custom-block .custom-block-title {
display: flex;
align-items: center;
}
@supports not (mask-image: none) {
.custom-block .custom-block-title::before,
.custom-block.details summary::before {
font-size: 18px;
width: auto;
height: auto;
background: none !important;
-webkit-mask: none !important;
mask: none !important;
}
.custom-block.info .custom-block-title::before {
content: '';
}
.custom-block.tip .custom-block-title::before {
content: '💡';
}
.custom-block.warning .custom-block-title::before {
content: '⚠️';
}
.custom-block.danger .custom-block-title::before {
content: '🔥';
}
}
.VPTeamPage > .VPTeamPageTitle {
padding-top: 0
}

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
<path d="M7.998 14.5c2.832 0 5-1.98 5-4.5 0-1.463-.68-2.19-1.879-3.383l-.036-.037c-1.013-1.008-2.3-2.29-2.834-4.434-.322.256-.63.579-.864.953-.432.696-.621 1.58-.046 2.73.473.947.67 2.284-.278 3.232-.61.61-1.545.84-2.403.633a2.788 2.788 0 0 1-1.436-.874A3.21 3.21 0 0 0 3 10c0 2.53 2.164 4.5 4.998 4.5zM9.533.753C9.496.34 9.16.009 8.77.146 7.035.75 4.34 3.187 5.997 6.5c.344.689.285 1.218.003 1.5-.419.419-1.54.487-2.04-.832-.173-.454-.659-.762-1.035-.454C2.036 7.44 1.5 8.702 1.5 10c0 3.512 2.998 6 6.498 6s6.5-2.5 6.5-6c0-2.137-1.128-3.26-2.312-4.438-1.19-1.184-2.436-2.425-2.653-4.81z"/>
</svg>

Before

Width:  |  Height:  |  Size: 681 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 350 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 375 375"><path fill="#29beb0" d="M 187.570312 190.933594 L 187.570312 375 L 30.070312 279.535156 L 30.070312 95.464844 Z"/><path fill="#69d2c8" d="M 187.570312 190.933594 L 187.570312 375 L 345.070312 279.535156 L 345.070312 95.464844 Z"/><path fill="#94dfd8" d="M 187.570312 190.933594 L 30.070312 95.464844 L 187.570312 0 L 345.070312 95.464844 Z"/></svg>

Before

Width:  |  Height:  |  Size: 435 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 796 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 405 B

View File

@@ -1,24 +0,0 @@
import DefaultTheme from 'vitepress/theme';
import type { Theme } from 'vitepress';
import './custom.css';
import HomePage from '../components/HomePage.vue';
import AuthorCard from '../components/AuthorCard.vue';
import BlogPost from '../components/BlogPost.vue';
import Version from '../components/Version.vue';
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client';
import { h } from 'vue';
import 'virtual:group-icons.css';
export default {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'home-features-after': () => h(HomePage)
});
},
enhanceApp({ app }) {
app.component('AuthorCard', AuthorCard);
app.component('BlogPost', BlogPost);
app.component('Version', Version);
enhanceAppWithTabs(app);
}
} satisfies Theme;

View File

@@ -1,35 +1,29 @@
version: '3' version: "3"
tasks: tasks:
install: yarn:install:
desc: Setup VitePress locally desc: Setup Docusaurus locally
cmds: cmds:
- pnpm install - yarn install
sources: sources:
- package.json - package.json
- pnpm-lock.yaml - yarn.lock
default: default:
desc: Start website desc: Start website
deps: [install] deps: [yarn:install]
aliases: [s, start] aliases: [s, start]
vars: vars:
HOST: '{{default "0.0.0.0" .HOST}}' HOST: '{{default "0.0.0.0" .HOST}}'
PORT: '{{default "3001" .PORT}}' PORT: '{{default "3001" .PORT}}'
cmds: cmds:
- pnpm dev --host={{.HOST}} --port={{.PORT}} - npx docusaurus start --no-open --host={{.HOST}} --port={{.PORT}}
lint:
desc: Lint website
deps: [install]
cmds:
- pnpm lint
build: build:
desc: Build website desc: Build website
deps: [install] deps: [yarn:install]
cmds: cmds:
- pnpm build - npx docusaurus build
preview: preview:
desc: Preview Website desc: Preview Website
@@ -39,19 +33,20 @@ tasks:
HOST: '{{default "localhost" .HOST}}' HOST: '{{default "localhost" .HOST}}'
PORT: '{{default "3001" .PORT}}' PORT: '{{default "3001" .PORT}}'
cmds: cmds:
- pnpm preview --host={{.HOST}} --port={{.PORT}} - npx docusaurus serve --no-open --host={{.HOST}} --port={{.PORT}}
clean: clean:
desc: Clean temp directories desc: Clean temp directories
cmds: cmds:
- rm -rf ./vitepress/dist - rm -rf ./build
deploy:next: deploy:
desc: Build and deploy next.taskfile.dev desc: Build and deploy Docusaurus
summary: Requires GIT_USER and GIT_PASS envs to be previous set
cmds: cmds:
- pnpm netlify deploy --prod --site=4e13dfcf-fc0d-4bec-ad60-b918a8dc3942 - npx docusaurus deploy
deploy:prod: upgrade:
desc: Build and deploy taskfile.dev desc: Upgrade Docusaurus
cmds: cmds:
- pnpm netlify deploy --prod --site=e625bc6a-1cd3-465d-ad30-7bbddaeb4f31 - yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest

3
website/babel.config.ts Normal file
View File

@@ -0,0 +1,3 @@
export default {
presets: ['@docusaurus/core/lib/babel/preset'],
};

View File

@@ -2,15 +2,13 @@
title: Introducing Experiments title: Introducing Experiments
description: description:
A look at where task is, where it's going and how we're going to get there. A look at where task is, where it's going and how we're going to get there.
author: pd93 slug: task-in-2023
date: 2024-05-09 authors: [pd93]
outline: deep tags: [experiments, breaking-changes, roadmap, v4]
image: https://i.imgur.com/mErPwqL.png
hide_table_of_contents: false
--- ---
# Introducing Experiments
<AuthorCard :author="$frontmatter.author" />
Lately, Task has been growing extremely quickly and I've found myself thinking a Lately, Task has been growing extremely quickly and I've found myself thinking a
lot about the future of the project and how we continue to evolve and grow. I'm lot about the future of the project and how we continue to evolve and grow. I'm
not much of a writer, but I think one of the things we could do better is to not much of a writer, but I think one of the things we could do better is to
@@ -18,17 +16,19 @@ communicate these kinds of thoughts to the community. So, with that in mind,
this is the first (hopefully of many) blog posts talking about Task and what this is the first (hopefully of many) blog posts talking about Task and what
we're up to. we're up to.
{/* truncate */}
## :calendar: So, what have we been up to? ## :calendar: So, what have we been up to?
Over the past 12 months or so, @andreynering (Author and maintainer of the Over the past 12 months or so, [@andreynering] (Author and maintainer of the
project) and I (@pd93) have been working in our spare time to maintain and project) and I ([@pd93]) have been working in our spare time to maintain and
improve v3 of Task and we've made some amazing progress. Here are just some of improve v3 of Task and we've made some amazing progress. Here are just some of
the things we've released in that time: the things we've released in that time:
- An official [extension for VS Code][vscode-task]. - An official [extension for VS Code][vscode-task].
- Internal Tasks (#818). - Internal Tasks ([#818](https://github.com/go-task/task/pull/818)).
- Task aliases (#879). - Task aliases ([#879](https://github.com/go-task/task/pull/879)).
- Looping over tasks (#1220). - Looping over tasks ([#1220](https://github.com/go-task/task/pull/1200)).
- A series of refactors to the core codebase to make it more maintainable and - A series of refactors to the core codebase to make it more maintainable and
extensible. extensible.
- Loads of bug fixes and improvements. - Loads of bug fixes and improvements.
@@ -38,13 +38,14 @@ the things we've released in that time:
- And much, much more! :sparkles: - And much, much more! :sparkles:
We're also working on adding some really exciting and highly requested features We're also working on adding some really exciting and highly requested features
to Task such as having the ability to run remote Taskfiles (#1317). to Task such as having the ability to run remote Taskfiles
([#1317](https://github.com/go-task/task/issues/1317)).
None of this would have been possible without the [150 or so (and growing) None of this would have been possible without the [150 or so (and growing)
contributors][contributors] to the project, numerous sponsors and a passionate contributors][contributors] to the project, numerous sponsors and a passionate
community of users. Together we have more than doubled the number of GitHub community of users. Together we have more than doubled the number of GitHub
stars to over 8400 :star: since the beginning of 2022 and this continues to stars to over 8400 :star: since the beginning of 2022 and this continues to
accelerate. We can't thank you all enough for your help and support! 🚀 accelerate. We can't thank you all enough for your help and support! :rocket:
[![Star History Chart](https://api.star-history.com/svg?repos=go-task/task&type=Date)](https://star-history.com/#go-task/task&Date) [![Star History Chart](https://api.star-history.com/svg?repos=go-task/task&type=Date)](https://star-history.com/#go-task/task&Date)
@@ -70,7 +71,7 @@ commitment to make. Smaller, more frequent major releases are also a significant
inconvenience for users as they have to constantly keep up-to-date with our inconvenience for users as they have to constantly keep up-to-date with our
breaking changes. Fortunately, there is a better way. breaking changes. Fortunately, there is a better way.
## What's going to change? :monocle_face: ## What's going to change? :monocle:
Going forwards, breaking changes will be allowed into _minor_ versions of Task Going forwards, breaking changes will be allowed into _minor_ versions of Task
as "experimental features". To access these features users will need opt-in by as "experimental features". To access these features users will need opt-in by
@@ -121,11 +122,14 @@ I plan to write more of these blog posts in the future on a variety of
Task-related topics, so make sure to check in occasionally and see what we're up Task-related topics, so make sure to check in occasionally and see what we're up
to! to!
{/* prettier-ignore-start */}
[vscode-task]: https://github.com/go-task/vscode-task [vscode-task]: https://github.com/go-task/vscode-task
[crowdin]: https://crowdin.com [crowdin]: https://crowdin.com
[contributors]: https://github.com/go-task/task/graphs/contributors [contributors]: https://github.com/go-task/task/graphs/contributors
[semver]: https://semver.org [semver]: https://semver.org
[breaking-change-proposal]: https://github.com/go-task/task/discussions/1191 [breaking-change-proposal]: https://github.com/go-task/task/discussions/1191
[@andreynering]: https://github.com/andreynering
[@pd93]: https://github.com/pd93
[experiments]: https://taskfile.dev/experiments [experiments]: https://taskfile.dev/experiments
[deprecations]: https://taskfile.dev/deprecations [deprecations]: https://taskfile.dev/deprecations
[deprecate-version-2-schema]: https://github.com/go-task/task/issues/1197 [deprecate-version-2-schema]: https://github.com/go-task/task/issues/1197
@@ -135,3 +139,4 @@ to!
[experiments-project]: https://github.com/orgs/go-task/projects/1 [experiments-project]: https://github.com/orgs/go-task/projects/1
[gentle-force-experiment]: https://github.com/go-task/task/issues/1200 [gentle-force-experiment]: https://github.com/go-task/task/issues/1200
[remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317 [remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317
{/* prettier-ignore-end */}

View File

@@ -1,13 +1,15 @@
--- ---
title: Any Variables title: Any Variables
author: pd93 description: Task variables are no longer limited to strings!
date: 2024-05-09 slug: any-variables
outline: deep authors: [pd93]
tags: [experiments, variables]
image: https://i.imgur.com/mErPwqL.png
hide_table_of_contents: false
--- ---
# Any Variables import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<AuthorCard :author="$frontmatter.author" />
Task has always had variables, but even though you were able to define them Task has always had variables, but even though you were able to define them
using different YAML types, they would always be converted to strings by Task. using different YAML types, they would always be converted to strings by Task.
@@ -16,6 +18,8 @@ simple problems. Starting from [v3.37.0][v3.37.0], this is no longer the case!
Task now supports most variable types, including **booleans**, **integers**, Task now supports most variable types, including **booleans**, **integers**,
**floats** and **arrays**! **floats** and **arrays**!
{/* truncate */}
## What's the big deal? ## What's the big deal?
These changes allow you to use variables in a much more natural way and opens up These changes allow you to use variables in a much more natural way and opens up
@@ -27,9 +31,10 @@ some of the examples below for some inspiration.
No more comparing strings to "true" or "false". Now you can use actual boolean No more comparing strings to "true" or "false". Now you can use actual boolean
values in your templates: values in your templates:
::: code-group <Tabs defaultValue="2">
<TabItem value="1" label="Before">
```yaml [Before] ```yaml
version: 3 version: 3
tasks: tasks:
@@ -40,7 +45,10 @@ tasks:
- '{{if eq .BOOL "true"}}echo foo{{end}}' - '{{if eq .BOOL "true"}}echo foo{{end}}'
``` ```
```yaml [After] </TabItem>
<TabItem value="2" label="After">
```yaml
version: 3 version: 3
tasks: tasks:
@@ -51,7 +59,8 @@ tasks:
- '{{if .BOOL}}echo foo{{end}}' # <-- No need to compare to "true" - '{{if .BOOL}}echo foo{{end}}' # <-- No need to compare to "true"
``` ```
::: </TabItem>
</Tabs>
### Arithmetic ### Arithmetic
@@ -101,9 +110,10 @@ to specify the delimiter. However, we have now added support for looping over
"collection-type" variables using the `for` keyword, so now you are able to loop "collection-type" variables using the `for` keyword, so now you are able to loop
over list variables directly: over list variables directly:
::: code-group <Tabs defaultValue="2">
<TabItem value="1" label="Before">
```yaml [Before] ```yaml
version: 3 version: 3
tasks: tasks:
@@ -117,7 +127,10 @@ tasks:
cmd: echo {{.ITEM}} cmd: echo {{.ITEM}}
``` ```
```yaml [After] </TabItem>
<TabItem value="2" label="After">
```yaml
version: 3 version: 3
tasks: tasks:
@@ -130,7 +143,8 @@ tasks:
cmd: echo {{.ITEM}} cmd: echo {{.ITEM}}
``` ```
::: </TabItem>
</Tabs>
## What about maps? ## What about maps?
@@ -140,9 +154,20 @@ at once, we have released support for all other variable types and we will
continue working on map support in the new "[Map Variables][map-variables]" continue working on map support in the new "[Map Variables][map-variables]"
experiment. experiment.
:::note
If you were previously using maps with the Any Variables experiment and wish to
continue using them, you will need to enable the new [Map Variables
experiment][map-variables] instead.
:::
We're looking for feedback on a couple of different proposals, so please give We're looking for feedback on a couple of different proposals, so please give
them a go and let us know what you think. :pray: them a go and let us know what you think. :pray:
{/* prettier-ignore-start */}
[v3.37.0]: https://github.com/go-task/task/releases/tag/v3.37.0 [v3.37.0]: https://github.com/go-task/task/releases/tag/v3.37.0
[slim-sprig-math]: https://go-task.github.io/slim-sprig/math.html [slim-sprig-math]: https://go-task.github.io/slim-sprig/math.html
[slim-sprig-list]: https://go-task.github.io/slim-sprig/lists.html [slim-sprig-list]: https://go-task.github.io/slim-sprig/lists.html
[map-variables]: /experiments/map-variables
{/* prettier-ignore-end */}

10
website/blog/authors.yml Normal file
View File

@@ -0,0 +1,10 @@
andreynering:
name: Andrey Nering
title: Maintainer of Task
url: https://github.com/andreynering
image_url: https://github.com/andreynering.png
pd93:
name: Pete Davison
title: Maintainer of Task
url: https://github.com/pd93
image_url: https://github.com/pd93.png

7
website/constants.ts Normal file
View File

@@ -0,0 +1,7 @@
export const GITHUB_URL = 'https://github.com/go-task/task';
export const TWITTER_URL = 'https://twitter.com/taskfiledev';
export const BLUESKY_URL = 'https://bsky.app/profile/taskfile.dev';
export const MASTODON_URL = 'https://fosstodon.org/@task';
export const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
export const STACK_OVERFLOW = 'https://stackoverflow.com/questions/tagged/taskfile';
export const ANSWER_OVERFLOW = 'https://www.answeroverflow.com/c/974121106208354339';

View File

@@ -1,63 +1,10 @@
--- ---
title: Changelog slug: /changelog/
outline: deep sidebar_position: 14
--- ---
# Changelog # Changelog
## v3.45.2 - 2025-09-15
- Task now includes built-in core utilities to greatly improve compatibility on
Windows. This means that your commands that uses `cp`, `mv`, `mkdir` or any
other common core utility will now work by default on Windows, without extra
setup. This is something we wanted to address for many many years, and it's
finally being shipped!
[Read our blog post this the topic](https://taskfile.dev/blog/windows-core-utils).
(#197, #2360 by @andreynering).
- :sparkles: Built and deployed a [brand new website](https://taskfile.dev)
using [VitePress](https://vitepress.dev) (#2359, #2369, #2371, #2375, #2378 by
@vmaerten, @andreynering, @pd93).
- Began releasing
[nightly builds](https://github.com/go-task/task/releases/tag/nightly). This
will allow people to test our changes before they are fully released and
without having to install Go to build them (#2358 by @vmaerten).
- Added support for global config files in `$XDG_CONFIG_HOME/task/taskrc.yml` or
`$HOME/.taskrc.yml`. Check out our new
[configuration guide](https://taskfile.dev/docs/reference/config) for more
details (#2247, #2380, #2390, #2391 by @vmaerten, @pd93).
- Added experiments to the taskrc schema to clarify the expected keys and values
(#2235 by @vmaerten).
- Added support for new properties in `.taskrc.yml`: insecure, verbose,
concurrency, remote offline, remote timeout, and remote expiry. :warning:
Note: setting offline via environment variable is no longer supported. (#2389
by @vmaerten)
- Added a `--nested` flag when outputting tasks using `--list --json`. This will
output tasks in a nested structure when tasks are namespaced (#2415 by @pd93).
- Enhanced support for tasks with wildcards: they are now logged correctly, and
wildcard parameters are fully considered during fingerprinting (#1808, #1795
by @vmaerten).
- Fixed panic when a variable was declared as an empty hash (`{}`) (#2416, #2417
by @trulede).
#### Package API
- Bumped the minimum version of Go to 1.24 (#2358 by @vmaerten).
#### Other news
We recently released our
[official GitHub Action](https://github.com/go-task/setup-task). This is based
on the fantastic work by the Arduino team who created and maintained the
community version. Now that this is officially adopted, fixes/updates should be
more timely. We have already merged a couple of longstanding PRs in our
[first release](https://github.com/go-task/setup-task/releases/tag/v1.0.0) (by
@pd93, @shrink, @trim21 and all the previous contributors to
[arduino/setup-task](https://github.com/arduino/setup-task/)).
## v3.45.0-v3.45.1 - 2025-09-15
Failed due to an issue with our release process.
## v3.44.1 - 2025-07-23 ## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be - Internal tasks will no longer be shown as suggestions since they cannot be

View File

@@ -1,9 +1,6 @@
--- ---
title: Community slug: /community/
description: sidebar_position: 10
Task community contributions, installation methods, and integrations
maintained by third parties
outline: deep
--- ---
# Community # Community
@@ -16,12 +13,13 @@ thankful for everyone that helps me to improve the overall experience.
Many of our integrations are contributed and maintained by the community. You Many of our integrations are contributed and maintained by the community. You
can view the full list of community integrations can view the full list of community integrations
[here](./integrations.md#community-integrations). [here](/integrations#community-integrations).
## Installation methods ## Installation methods
Some installation methods are maintained by third party: Some installation methods are maintained by third party:
- [GitHub Actions](https://github.com/arduino/setup-task) by @arduino
- [AUR](https://aur.archlinux.org/packages/go-task-bin) by @carlsmedstad - [AUR](https://aur.archlinux.org/packages/go-task-bin) by @carlsmedstad
- [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json) - [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json)
- [Fedora](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/) - [Fedora](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)

View File

@@ -1,9 +1,6 @@
--- ---
title: Contributing slug: /contributing/
description: sidebar_position: 12
Comprehensive guide for contributing to the Task project, including setup,
development, testing, and submitting PRs
outline: deep
--- ---
# Contributing # Contributing
@@ -11,7 +8,7 @@ outline: deep
Contributions to Task are very welcome, but we ask that you read this document Contributions to Task are very welcome, but we ask that you read this document
before submitting a PR. before submitting a PR.
::: info :::note
This document applies to the core [Task][task] repository _and_ [Task for Visual This document applies to the core [Task][task] repository _and_ [Task for Visual
Studio Code][vscode-task]. Studio Code][vscode-task].
@@ -30,10 +27,9 @@ Studio Code][vscode-task].
you invest your time into a PR. you invest your time into a PR.
- **Experiments** - If there is no way to make your change backward compatible - **Experiments** - If there is no way to make your change backward compatible
then there is a procedure to introduce breaking changes into minor versions. then there is a procedure to introduce breaking changes into minor versions.
We call these "[experiments](./experiments/index.md)". If you're intending to work on We call these "[experiments][experiments]". If you're intending to work on an
an experiment, then please read the experiment, then please read the [experiments workflow][experiments-workflow]
[experiments workflow](./experiments/index.md#workflow) document carefully and submit a document carefully and submit a proposal first.
proposal first.
## 1. Setup ## 1. Setup
@@ -42,7 +38,7 @@ Studio Code][vscode-task].
- **Node.js** - [Node.js][nodejs] is used to host Task's documentation server - **Node.js** - [Node.js][nodejs] is used to host Task's documentation server
and is required if you want to run this server locally. It is also required if and is required if you want to run this server locally. It is also required if
you want to contribute to the Visual Studio Code extension. you want to contribute to the Visual Studio Code extension.
- **Pnpm** - [Pnpm][pnpm] is the Node.js package manager used by Task. - **Yarn** - [Yarn][yarn] is the Node.js package manager used by Task.
## 2. Making changes ## 2. Making changes
@@ -53,11 +49,10 @@ Studio Code][vscode-task].
docs][golangci-lint-docs] for a guide on how to setup your editor to docs][golangci-lint-docs] for a guide on how to setup your editor to
auto-format your code. Any Markdown or TypeScript files should be formatted auto-format your code. Any Markdown or TypeScript files should be formatted
and linted by [Prettier][prettier]. This style is enforced by our CI to ensure and linted by [Prettier][prettier]. This style is enforced by our CI to ensure
that we have a consistent style across the project. You can use the that we have a consistent style across the project. You can use the `task
`task lint` command to lint the code locally and the `task lint:fix` command lint` command to lint the code locally and the `task lint:fix` command to try
to try to automatically fix any issues that are found. You can also use the to automatically fix any issues that are found. You can also use the `task
`task fmt` command to auto-format the files if your editor doesn't do it for fmt` command to auto-format the files if your editor doesn't do it for you.
you.
- **Documentation** - Ensure that you add/update any relevant documentation. See - **Documentation** - Ensure that you add/update any relevant documentation. See
the [updating documentation](#updating-documentation) section below. the [updating documentation](#updating-documentation) section below.
- **Tests** - Ensure that you add/update any relevant tests and that all tests - **Tests** - Ensure that you add/update any relevant tests and that all tests
@@ -79,23 +74,24 @@ install the extension.
### Updating documentation ### Updating documentation
Task uses [Vitepress][vitepress] to host a documentation server. The code for Task uses [Docusaurus][docusaurus] to host a documentation server. The code for
this is located in the core Task repository. This can be setup and run locally this is located in the core Task repository. This can be setup and run locally
by using `task website` (requires `nodejs` & `pnpm`). All content is written in by using `task website` (requires `nodejs` & `yarn`). All content is written in
Markdown and is located in the `website/src` directory. All Markdown documents [MDX][mdx] (an extension of Markdown) and is located in the `website/docs`
should have an 80 character line wrap limit (enforced by Prettier). directory. All Markdown documents should have an 80 character line wrap limit
(enforced by Prettier).
When making a change, consider whether a change to the [Usage Guide](/docs/guide) is When making a change, consider whether a change to the [Usage Guide](/usage) is
necessary. This document contains descriptions and examples of how to use Task necessary. This document contains descriptions and examples of how to use Task
features. If you're adding a new feature, try to find an appropriate place to features. If you're adding a new feature, try to find an appropriate place to
add a new section. If you're updating an existing feature, ensure that the add a new section. If you're updating an existing feature, ensure that the
documentation and any examples are up-to-date. Ensure that any examples follow documentation and any examples are up-to-date. Ensure that any examples follow
the [Taskfile Styleguide](./styleguide.md). the [Taskfile Styleguide](/styleguide).
If you added a new command or flag, ensure that you add it to the If you added a new command or flag, ensure that you add it to the [CLI
[CLI Reference](./reference/cli.md). New fields also need to be added to the Reference](/reference/cli). New fields also need to be added to the [Schema
[Schema Reference](./reference/schema.md) and [JSON Schema][json-schema]. The Reference](/reference/schema) and [JSON Schema][json-schema]. The descriptions
descriptions for fields in the docs and the schema should match. for fields in the docs and the schema should match.
### Writing tests ### Writing tests
@@ -147,7 +143,7 @@ contributions.
All kinds of contributions are welcome, whether its a typo fix or a shiny new All kinds of contributions are welcome, whether its a typo fix or a shiny new
feature. You can also contribute by upvoting/commenting on issues, helping to feature. You can also contribute by upvoting/commenting on issues, helping to
answer questions or contributing to other [community projects](./community.md). answer questions or contributing to other [community projects](/community).
> I'm stuck, where can I get help? > I'm stuck, where can I get help?
@@ -156,6 +152,9 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
--- ---
{/* prettier-ignore-start */}
[experiments]: /experiments
[experiments-workflow]: /experiments#workflow
[task]: https://github.com/go-task/task [task]: https://github.com/go-task/task
[vscode-task]: https://github.com/go-task/vscode-task [vscode-task]: https://github.com/go-task/vscode-task
[go]: https://go.dev [go]: https://go.dev
@@ -165,15 +164,14 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
[golangci-lint-docs]: https://golangci-lint.run/welcome/integrations/ [golangci-lint-docs]: https://golangci-lint.run/welcome/integrations/
[prettier]: https://prettier.io [prettier]: https://prettier.io
[nodejs]: https://nodejs.org/en/ [nodejs]: https://nodejs.org/en/
[pnpm]: https://pnpm.io/ [yarn]: https://yarnpkg.com/
[vitepress]: https://vitepress.dev [docusaurus]: https://docusaurus.io
[json-schema]: [json-schema]: https://github.com/go-task/task/blob/main/website/static/schema.json
https://github.com/go-task/task/blob/main/website/static/schema.json
[task-open-issues]: https://github.com/go-task/task/issues [task-open-issues]: https://github.com/go-task/task/issues
[vscode-task-open-issues]: https://github.com/go-task/vscode-task/issues [vscode-task-open-issues]: https://github.com/go-task/vscode-task/issues
[good-first-issue]: [good-first-issue]: https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
[discord-server]: https://discord.gg/6TY36E39UK [discord-server]: https://discord.gg/6TY36E39UK
[discussion]: https://github.com/go-task/task/discussions [discussion]: https://github.com/go-task/task/discussions
[conventional-commits]: https://www.conventionalcommits.org [conventional-commits]: https://www.conventionalcommits.org
[mdx]: https://mdxjs.com/ [mdx]: https://mdxjs.com/
{/* prettier-ignore-end */}

View File

@@ -1,12 +1,10 @@
--- ---
title: 'Completion Scripts' slug: /deprecations/completion-scripts/
description: Deprecation of direct completion scripts in Tasks Git directory
outline: deep
--- ---
# Completion Scripts # Completion Scripts
::: danger :::warning
This deprecation breaks the following functionality: This deprecation breaks the following functionality:
@@ -21,5 +19,7 @@ the future as the scripts may be moved or deleted entirely. Any configuration
should be updated to use the [new method for generating shell should be updated to use the [new method for generating shell
completions][completions] instead. completions][completions] instead.
[completions]: /docs/installation#setup-completions {/* prettier-ignore-start */}
[completions]: ../installation.mdx#setup-completions
[task]: https://github.com/go-task/task [task]: https://github.com/go-task/task
{/* prettier-ignore-end */}

View File

@@ -1,9 +1,6 @@
--- ---
title: Deprecations slug: /deprecations/
description: sidebar_position: 8
Guide to deprecated features in Task and how to migrate to the new
alternatives
outline: deep
--- ---
# Deprecations # Deprecations

View File

@@ -0,0 +1,23 @@
---
# This is a template for an experiments documentation
# Copy this page and fill in the details as necessary
title: '--- Template ---'
sidebar_position: -1 # Always push to the top
draft: true # Hide in production
---
# \{Name of Deprecated Feature\} (#\{Issue\})
:::warning
This deprecation breaks the following functionality:
- \{list any existing functionality that will be broken by this deprecation\}
- \{if there are no breaking changes, remove this admonition\}
:::
\{Short description of the feature/behavior and why it is being deprecated\}
\{Short explanation of any replacement features/behaviors and how users should
migrate to it\}

View File

@@ -1,14 +1,10 @@
--- ---
title: 'Template Functions' slug: /deprecations/template-functions/
description:
Deprecation of some templating functions in Task, with guidance on their
replacements.
outline: deep
--- ---
# Template Functions # Template Functions
::: danger :::warning
This deprecation breaks the following functionality: This deprecation breaks the following functionality:

View File

@@ -1,12 +1,10 @@
--- ---
title: 'Version 2 Schema (#1197)' slug: /deprecations/version-2-schema/
description: Deprecation of Taskfile schema version 2 and migration to version 3
outline: deep
--- ---
# Version 2 Schema (#1197) # Version 2 Schema (#1197)
::: danger :::warning
This deprecation breaks the following functionality: This deprecation breaks the following functionality:
@@ -28,6 +26,8 @@ main branch. To use a more recent version of Task, you will need to ensure that
your Taskfile uses the version 3 schema instead. A list of changes between your Taskfile uses the version 3 schema instead. A list of changes between
version 2 and version 3 are available in the [Task v3 Release Notes][v3.0.0]. version 2 and version 3 are available in the [Task v3 Release Notes][v3.0.0].
{/* prettier-ignore-start */}
[v3.0.0]: https://github.com/go-task/task/releases/tag/v3.0.0 [v3.0.0]: https://github.com/go-task/task/releases/tag/v3.0.0
[v3.33.0]: https://github.com/go-task/task/releases/tag/v3.33.0 [v3.33.0]: https://github.com/go-task/task/releases/tag/v3.33.0
[deprecation-notice]: https://github.com/go-task/task/issues/1197 [deprecation-notice]: https://github.com/go-task/task/issues/1197
{/* prettier-ignore-end */}

View File

@@ -1,13 +1,10 @@
--- ---
title: 'Env Precedence (#1038)' slug: '/experiments/env-precedence'
description:
Experiment to change the precedence of environment variables in Task
outline: deep
--- ---
# Env Precedence (#1038) # Env Precedence (#1038)
::: warning :::caution
All experimental features are subject to breaking changes and/or removal _at any All experimental features are subject to breaking changes and/or removal _at any
time_. We strongly recommend that you do not use these features in a production time_. We strongly recommend that you do not use these features in a production
@@ -15,7 +12,7 @@ environment. They are intended for testing and feedback only.
::: :::
::: danger :::warning
This experiment breaks the following functionality: This experiment breaks the following functionality:
@@ -23,12 +20,11 @@ This experiment breaks the following functionality:
::: :::
::: info :::info
To enable this experiment, set the environment variable: To enable this experiment, set the environment variable:
`TASK_X_ENV_PRECEDENCE=1`. Check out `TASK_X_ENV_PRECEDENCE=1`. Check out [our guide to enabling
[our guide to enabling experiments](./index.md#enabling-experiments) for more experiments][enabling-experiments] for more information.
information.
::: :::
@@ -48,12 +44,11 @@ tasks:
cmds: cmds:
- echo "$KEY" - echo "$KEY"
``` ```
Running `KEY=some task` before this experiment, the output would be `some`, but Running `KEY=some task` before this experiment, the output would be `some`, but
after this experiment, the output would be `other`. after this experiment, the output would be `other`.
If you still want to get the OS variable, you can use the template function env If you still want to get the OS variable, you can use the template function env
like follow : <span v-pre>`{{env "OS_VAR"}}`</span>. like follow : `{{env "OS_VAR"}}`.
```yml ```yml
version: '3' version: '3'
@@ -66,12 +61,14 @@ tasks:
- echo "$KEY" - echo "$KEY"
- echo {{env "KEY"}} - echo {{env "KEY"}}
``` ```
Running `KEY=some task`, the output would be `other` and `some`. Running `KEY=some task`, the output would be `other` and `some`.
Like other variables/envs, you can also fall back to a given value using the Like other variables/envs, you can also fall back to a given value using the
default template function: default template function:
```yml ```yml
MY_ENV: '{{.MY_ENV | default "fallback"}}' MY_ENV: '{{.MY_ENV | default "fallback"}}'
``` ```
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
{/* prettier-ignore-end */}

Some files were not shown because too many files have changed in this diff Show More