Compare commits

..

1 Commits

Author SHA1 Message Date
Pete Davison
3c30a8066d feat: bump minor version when repo is dirty or untagged 2025-08-06 19:45:37 +00:00
182 changed files with 19499 additions and 15321 deletions

View File

@@ -8,6 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true
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_size = 2

View File

@@ -13,14 +13,14 @@ jobs:
name: Lint
strategy:
matrix:
go-version: [1.24.x, 1.25.x]
go-version: [1.23.x, 1.24.x]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: ${{matrix.go-version}}
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
@@ -32,12 +32,43 @@ jobs:
steps:
- uses: actions/setup-python@v5
with:
python-version: 3.13
python-version: 3.12
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: install check-jsonschema
run: python -m pip install 'check-jsonschema==0.27.3'
- 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:
workflow_dispatch:
@@ -9,22 +9,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.25.x
go-version: 1.24.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
args: release --clean --nightly -f .goreleaser-nightly.yml
args: release --clean --nightly
env:
GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}

View File

@@ -10,39 +10,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.25.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:
package_json_file: 'website/package.json'
run_install: 'true'
go-version: 1.24.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
args: release --clean --draft
args: release --clean
env:
GITHUB_TOKEN: ${{secrets.GH_PAT}}
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
strategy:
matrix:
go-version: [1.24.x, 1.25.x]
go-version: [1.23.x, 1.24.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:
@@ -24,7 +24,7 @@ jobs:
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Download Go modules
run: go mod download

View File

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

View File

@@ -30,8 +30,7 @@ builds:
flags:
- -trimpath
ldflags:
- "-s -w"
- "{{if .IsNightly}}-X github.com/go-task/task/v3/internal/version.version={{.Version}}{{end}}"
- -s -w # Don't set main.version.
gomod:
proxy: true
@@ -46,10 +45,19 @@ archives:
- goos: windows
formats: [zip]
release:
draft: true
git:
ignore_tags:
- "{{if not .IsNightly}}nightly{{end}}"
nightly:
publish_release: true
keep_single_release: true
version_template: "{{incminor .Version}}-nightly"
snapshot:
version_template: '{{.Version}}'
@@ -68,7 +76,6 @@ nfpms:
formats:
- deb
- rpm
- apk
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
contents:
- src: completion/bash/task.bash
@@ -136,37 +143,3 @@ winget:
owner: microsoft
name: winget-pkgs
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
## v3.45.3 - 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.2 - 2025-09-15
Failed due to an issue with our release process.
## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be

View File

@@ -1,6 +1,6 @@
<div align="center">
<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>
<h1>Task</h1>
@@ -19,7 +19,7 @@
<tr>
<td align="center" valign="middle">
<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>
</td>
</tr>

View File

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

View File

@@ -3,23 +3,33 @@ package main
import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"time"
"github.com/Masterminds/semver/v3"
"github.com/otiai10/copy"
"github.com/spf13/pflag"
"github.com/go-task/task/v3/errors"
)
const (
changelogSource = "CHANGELOG.md"
changelogTarget = "website/src/docs/changelog.md"
versionFile = "internal/version/version.txt"
changelogSource = "CHANGELOG.md"
changelogTarget = "website/docs/changelog.mdx"
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
var (
@@ -43,7 +53,7 @@ func release() error {
return errors.New("error: expected version number")
}
version, err := getVersion(versionFile)
version, err := getVersion()
if err != nil {
return err
}
@@ -61,18 +71,36 @@ func release() error {
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 nil
}
func getVersion(filename string) (*semver.Version, error) {
b, err := os.ReadFile(filename)
func getVersion() (*semver.Version, error) {
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
b, err := cmd.Output()
if err != nil {
return nil, err
}
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 {
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

@@ -128,7 +128,6 @@ func run() error {
flags.ListAll,
flags.ListJson,
flags.NoStatus,
flags.Nested,
)
if listOptions.ShouldListTasks() {
if flags.Silent {

View File

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

View File

@@ -3,6 +3,7 @@ package task_test
import (
"bytes"
"cmp"
"context"
"fmt"
"os"
"path/filepath"
@@ -188,7 +189,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
}
// Run the task and check for errors
ctx := t.Context()
ctx := context.Background()
if err := e.Run(ctx, call); tt.wantRunError {
require.Error(t, err)
tt.writeFixtureErrRun(t, g, err)

View File

@@ -9,7 +9,6 @@ import (
"github.com/joho/godotenv"
"github.com/go-task/task/v3/taskrc"
"github.com/go-task/task/v3/taskrc/ast"
)
const envPrefix = "TASK_X_"
@@ -32,15 +31,16 @@ var (
var xList []Experiment
func Parse(dir string) {
config, _ := taskrc.GetConfig(dir)
ParseWithConfig(dir, config)
}
func ParseWithConfig(dir string, config *ast.TaskRC) {
// Read any .env files
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
GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)

21
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/go-task/task/v3
go 1.24.0
go 1.23.0
require (
github.com/Ladicle/tabwriter v1.0.0
@@ -19,16 +19,16 @@ require (
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/otiai10/copy v1.14.1
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.7.1
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.10.0
github.com/zeebo/xxh3 v1.0.2
golang.org/x/sync v0.17.0
golang.org/x/term v0.35.0
golang.org/x/sync v0.16.0
golang.org/x/term v0.33.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa
mvdan.cc/sh/v3 v3.12.0
)
@@ -39,28 +39,23 @@ require (
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // 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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/pgzip v1.2.6 // indirect
github.com/mattn/go-colorable v0.1.13 // 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/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // 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
golang.org/x/crypto v0.37.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
)

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/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
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=
@@ -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/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
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/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
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/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/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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
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/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
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/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.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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
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-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
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.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
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-20201119102817-f84b799fce68/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.5.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.35.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/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
@@ -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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=

65
help.go
View File

@@ -24,17 +24,15 @@ type ListOptions struct {
ListAllTasks bool
FormatTaskListAsJSON bool
NoStatus bool
Nested bool
}
// 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{
ListOnlyTasksWithDescriptions: list,
ListAllTasks: listAll,
FormatTaskListAsJSON: listAsJson,
NoStatus: noStatus,
Nested: nested,
}
}
@@ -65,7 +63,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
return false, err
}
if o.FormatTaskListAsJSON {
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested)
output, err := e.ToEditorOutput(tasks, o.NoStatus)
if err != nil {
return false, err
}
@@ -137,17 +135,33 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
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
editorTasks := make([]editors.Task, len(tasks))
// Look over each task in parallel and turn it into an editor task
for i := range tasks {
aliases := []string{}
if len(tasks[i].Aliases) > 0 {
aliases = tasks[i].Aliases
}
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 {
editorTasks[i] = editorTask
return nil
}
@@ -166,35 +180,10 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool)
return err
}
editorTask.UpToDate = &upToDate
editorTasks[i] = editorTask
o.Tasks[i].UpToDate = upToDate
return nil
})
}
if err := g.Wait(); err != nil {
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()
return o, g.Wait()
}

View File

@@ -1,15 +1,10 @@
package editors
import (
"github.com/go-task/task/v3/taskfile/ast"
)
type (
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc)
Namespace struct {
Tasks []Task `json:"tasks"`
Namespaces map[string]*Namespace `json:"namespaces,omitempty"`
Location string `json:"location,omitempty"`
// Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
Taskfile struct {
Tasks []Task `json:"tasks"`
Location string `json:"location"`
}
// Task describes a single task
Task struct {
@@ -18,7 +13,7 @@ type (
Desc string `json:"desc"`
Summary string `json:"summary"`
Aliases []string `json:"aliases"`
UpToDate *bool `json:"up_to_date,omitempty"`
UpToDate bool `json:"up_to_date"`
Location *Location `json:"location"`
}
// Location describes a task's location in a taskfile
@@ -28,59 +23,3 @@ type (
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"
"path/filepath"
"strings"
"time"
"mvdan.cc/sh/moreinterp/coreutils"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
@@ -59,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
r, err := interp.New(
interp.Params(params...),
interp.Env(expand.ListEnviron(environ...)),
interp.ExecHandlers(execHandlers()...),
interp.ExecHandlers(execHandler),
interp.OpenHandler(openHandler),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
dirOption(opts.Dir),
@@ -143,11 +143,8 @@ func ExpandFields(s string) ([]string, error) {
return expand.Fields(cfg, words...)
}
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) {
if useGoCoreUtils {
handlers = append(handlers, coreutils.ExecHandler)
}
return
func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
return interp.DefaultExecHandler(15 * time.Second)
}
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {

View File

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

View File

@@ -5,6 +5,7 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"time"
"github.com/spf13/pflag"
@@ -12,10 +13,9 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/sort"
"github.com/go-task/task/v3/taskfile/ast"
"github.com/go-task/task/v3/taskrc"
taskrcast "github.com/go-task/task/v3/taskrc/ast"
)
const usage = `Usage: task [flags...] [task...]
@@ -51,7 +51,6 @@ var (
TaskSort string
Status bool
NoStatus bool
Nested bool
Insecure bool
Force bool
ForceAll bool
@@ -96,9 +95,7 @@ func init() {
// Parse the experiments
dir = cmp.Or(dir, filepath.Dir(entrypoint))
config, _ := taskrc.GetConfig(dir)
experiments.ParseWithConfig(dir, config)
experiments.Parse(dir)
// Parse the rest of the flags
log.SetFlags(0)
@@ -107,7 +104,10 @@ func init() {
log.Print(usage)
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.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
@@ -118,10 +118,9 @@ func init() {
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(&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", getConfig(config, func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVar(&Insecure, "insecure", false, "Forces Task to download Taskfiles over insecure connections.")
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(&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.")
@@ -135,7 +134,7 @@ func init() {
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.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.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.")
@@ -151,11 +150,12 @@ func init() {
// Remote Taskfiles experiment will adds the "download" and "offline" flags
if experiments.RemoteTaskfiles.Enabled() {
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.DurationVar(&Timeout, "timeout", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
pflag.BoolVar(&Offline, "offline", offline, "Forces Task to only use local or cached Taskfiles.")
pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
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()
}
@@ -196,10 +196,6 @@ func Validate() error {
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
}
@@ -255,16 +251,3 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithVersionCheck(true),
)
}
// getConfig extracts a config value directly from a pointer field with a fallback default
func getConfig[T any](config *taskrcast.TaskRC, fieldFunc func() *T, fallback T) T {
if config == nil {
return fallback
}
field := fieldFunc()
if field != nil {
return *field
}
return fallback
}

View File

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

View File

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

View File

@@ -4,6 +4,8 @@ import (
_ "embed"
"runtime/debug"
"strings"
"github.com/Masterminds/semver/v3"
)
var (
@@ -46,6 +48,10 @@ func getCommit(info *debug.BuildInfo) string {
// However, it can also be overridden at build time using:
// -ldflags="-X 'github.com/go-task/task/v3/internal/version.version=vX.X.X'".
func GetVersion() string {
// If its a development version, we bump the minor version.
if commit != "" || dirty {
return semver.MustParse(version).IncMinor().String()
}
return version
}
@@ -61,7 +67,7 @@ func GetVersionWithBuildInfo() string {
buildMetadata = append(buildMetadata, "dirty")
}
if len(buildMetadata) > 0 {
return version + "+" + strings.Join(buildMetadata, ".")
return GetVersion() + "+" + strings.Join(buildMetadata, ".")
}
return version
return GetVersion()
}

View File

@@ -1 +1 @@
3.45.3
3.44.1

View File

@@ -0,0 +1,58 @@
package version
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVersionTxt(t *testing.T) {
// Check that the version.txt is a valid semver version.
require.NotEmpty(t, GetVersion(), "version.txt is not semver compliant")
}
func TestGetVersion(t *testing.T) {
tests := []struct {
version string
commit string
dirty bool
want string
}{
{"1.2.3", "", false, "1.2.3"},
{"1.2.3", "", true, "1.3.0"},
{"1.2.3", "abcdefg", false, "1.3.0"},
{"1.2.3", "abcdefg", true, "1.3.0"},
}
for _, tt := range tests {
version = tt.version
commit = tt.commit
dirty = tt.dirty
t.Run(tt.want, func(t *testing.T) {
require.Equal(t, tt.want, GetVersion())
})
}
}
func TestGetVersionWithBuildInfo(t *testing.T) {
tests := []struct {
version string
commit string
dirty bool
want string
}{
{"1.2.3", "", false, "1.2.3"},
{"1.2.3", "", true, "1.3.0+dirty"},
{"1.2.3", "abcdefg", false, "1.3.0+abcdefg"},
{"1.2.3", "abcdefg", true, "1.3.0+abcdefg.dirty"},
}
for _, tt := range tests {
version = tt.version
commit = tt.commit
dirty = tt.dirty
t.Run(tt.want, func(t *testing.T) {
require.Equal(t, tt.want, GetVersionWithBuildInfo())
})
}
}

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

View File

@@ -2,6 +2,7 @@ package task_test
import (
"bytes"
"context"
"fmt"
"io"
"io/fs"
@@ -355,7 +356,7 @@ func (fct fileContentTest) Run(t *testing.T) {
)
require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(t.Context(), &task.Call{Task: fct.Target}), "e.Run(target)")
require.NoError(t, e.Run(context.Background(), &task.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
path := filepathext.SmartJoin(e.Dir, name)
@@ -406,7 +407,7 @@ func TestGenerates(t *testing.T) {
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
// Run task for the first time.
require.NoError(t, e.Run(t.Context(), &task.Call{Task: theTask}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: theTask}))
if _, err := os.Stat(srcFile); err != nil {
t.Errorf("File should exist: %v", err)
@@ -421,7 +422,7 @@ func TestGenerates(t *testing.T) {
buff.Reset()
// Re-run task to ensure it's now found to be up-to-date.
require.NoError(t, e.Run(t.Context(), &task.Call{Task: theTask}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: theTask}))
if buff.String() != upToDate {
t.Errorf("Wrong output message: %s", buff.String())
}
@@ -437,7 +438,6 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
task string
}{
{[]string{"generated.txt", ".task/checksum/build"}, "build"},
{[]string{"generated-wildcard.txt", ".task/checksum/build-wildcard"}, "build-wildcard"},
{[]string{"generated.txt", ".task/checksum/build-with-status"}, "build-with-status"},
}
@@ -463,7 +463,7 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: test.task}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
for _, f := range test.files {
_, err := os.Stat(filepathext.SmartJoin(dir, f))
require.NoError(t, err)
@@ -476,7 +476,7 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
time := s.ModTime()
buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: test.task}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
s, err = os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task))
@@ -507,12 +507,12 @@ func TestStatusVariables(t *testing.T) {
task.WithVerbose(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-checksum"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-checksum"}))
assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-ts"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-ts"}))
inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
require.NoError(t, err)
@@ -543,12 +543,12 @@ func TestCmdsVariables(t *testing.T) {
task.WithVerbose(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-checksum"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-checksum"}))
assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-ts"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-ts"}))
inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
require.NoError(t, err)
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
@@ -569,7 +569,7 @@ func TestCyclicDep(t *testing.T) {
task.WithStderr(io.Discard),
)
require.NoError(t, e.Setup())
assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(t.Context(), &task.Call{Task: "task-1"}))
assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), &task.Call{Task: "task-1"}))
}
func TestTaskVersion(t *testing.T) {
@@ -619,10 +619,10 @@ func TestTaskIgnoreErrors(t *testing.T) {
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task-should-pass"}))
require.Error(t, e.Run(t.Context(), &task.Call{Task: "task-should-fail"}))
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "cmd-should-pass"}))
require.Error(t, e.Run(t.Context(), &task.Call{Task: "cmd-should-fail"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-should-pass"}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: "task-should-fail"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "cmd-should-pass"}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: "cmd-should-fail"}))
}
func TestExpand(t *testing.T) {
@@ -642,7 +642,7 @@ func TestExpand(t *testing.T) {
task.WithStderr(&buff),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "pwd"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "pwd"}))
assert.Equal(t, home, strings.TrimSpace(buff.String()))
}
@@ -663,7 +663,7 @@ func TestDry(t *testing.T) {
task.WithDry(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build"}))
assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
@@ -692,13 +692,13 @@ func TestDryChecksum(t *testing.T) {
task.WithDry(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
_, err := os.Stat(checksumFile)
require.Error(t, err, "checksum file should not exist")
e.Dry = false
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
_, err = os.Stat(checksumFile)
require.NoError(t, err, "checksum file should exist")
}
@@ -840,7 +840,7 @@ func TestIncludesRemote(t *testing.T) {
path := filepath.Join(dir, outputFile)
require.NoError(t, os.RemoveAll(path))
require.NoError(t, e.executor.Run(t.Context(), taskCall))
require.NoError(t, e.executor.Run(context.Background(), taskCall))
actualContent, err := os.ReadFile(path)
require.NoError(t, err)
@@ -1120,11 +1120,11 @@ func TestIncludesRelativePath(t *testing.T) {
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "common:pwd"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "common:pwd"}))
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "included:common:pwd"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "included:common:pwd"}))
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
}
@@ -1156,7 +1156,7 @@ func TestIncludesInternal(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task})
err := e.Run(context.Background(), &task.Call{Task: test.task})
if test.expectedErr {
require.Error(t, err)
} else {
@@ -1203,7 +1203,7 @@ func TestIncludesFlatten(t *testing.T) {
assert.EqualError(t, err, test.expectedOutput)
} else {
require.NoError(t, err)
_ = e.Run(t.Context(), &task.Call{Task: test.task})
_ = e.Run(context.Background(), &task.Call{Task: test.task})
assert.Equal(t, test.expectedOutput, buff.String())
}
})
@@ -1235,7 +1235,7 @@ func TestIncludesInterpolation(t *testing.T) { // nolint:paralleltest // cannot
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task})
err := e.Run(context.Background(), &task.Call{Task: test.task})
if test.expectedErr {
require.Error(t, err)
} else {
@@ -1258,20 +1258,20 @@ func TestIncludesWithExclude(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "included:bar"})
err := e.Run(context.Background(), &task.Call{Task: "included:bar"})
require.NoError(t, err)
assert.Equal(t, "bar\n", buff.String())
buff.Reset()
err = e.Run(t.Context(), &task.Call{Task: "included:foo"})
err = e.Run(context.Background(), &task.Call{Task: "included:foo"})
require.Error(t, err)
buff.Reset()
err = e.Run(t.Context(), &task.Call{Task: "bar"})
err = e.Run(context.Background(), &task.Call{Task: "bar"})
require.Error(t, err)
buff.Reset()
err = e.Run(t.Context(), &task.Call{Task: "foo"})
err = e.Run(context.Background(), &task.Call{Task: "foo"})
require.NoError(t, err)
assert.Equal(t, "foo\n", buff.String())
}
@@ -1301,7 +1301,7 @@ func TestIncludedTaskfileVarMerging(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task})
err := e.Run(context.Background(), &task.Call{Task: test.task})
require.NoError(t, err)
assert.Contains(t, buff.String(), test.expectedOutput)
})
@@ -1336,7 +1336,7 @@ func TestInternalTask(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task})
err := e.Run(context.Background(), &task.Call{Task: test.task})
if test.expectedErr {
require.Error(t, err)
} else {
@@ -1421,7 +1421,7 @@ func TestSummary(t *testing.T) {
task.WithSilent(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task-with-summary"}, &task.Call{Task: "other-task-with-summary"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-with-summary"}, &task.Call{Task: "other-task-with-summary"}))
data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
require.NoError(t, err)
@@ -1447,7 +1447,7 @@ func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "whereami"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "whereami"}))
// got should be the "dir" part of "testdata/dir"
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
@@ -1467,7 +1467,7 @@ func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "whereami"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "whereami"}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1493,7 +1493,7 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
t.Errorf("Directory should not exist: %v", err)
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: target}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1522,7 +1522,7 @@ func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
t.Errorf("Directory should not exist: %v", err)
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: target}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1593,7 +1593,7 @@ func TestShortTaskNotation(t *testing.T) {
task.WithSilent(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
}
@@ -1791,7 +1791,7 @@ func TestExitImmediately(t *testing.T) {
)
require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
}
@@ -1811,22 +1811,6 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
})
}
func TestRunOnlyRunsJobsHashOnceWithWildcard(t *testing.T) {
t.Parallel()
tt := fileContentTest{
Dir: "testdata/run",
Target: "deploy",
Files: map[string]string{
"wildcard.txt": "Deploy infra\nDeploy js\nDeploy go\n",
},
}
t.Run("", func(t *testing.T) {
t.Parallel()
tt.Run(t)
})
}
func TestRunOnceSharedDeps(t *testing.T) {
t.Parallel()
@@ -1840,7 +1824,7 @@ func TestRunOnceSharedDeps(t *testing.T) {
task.WithForceAll(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build"}))
rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`)
matches := rx.FindAllStringSubmatch(buff.String(), -1)
@@ -1872,10 +1856,10 @@ task-1 ran successfully
task: [task-1] echo 'task-1 ran successfully'
task-1 ran successfully
`)
require.Error(t, e.Run(t.Context(), &task.Call{Task: "task-2"}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: "task-2"}))
assert.Contains(t, buff.String(), expectedOutputOrder)
buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "parent"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "parent"}))
assert.Contains(t, buff.String(), "child task deferred value-from-parent")
}
@@ -1891,7 +1875,7 @@ func TestExitCodeZero(t *testing.T) {
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "exit-zero"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "exit-zero"}))
assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=", strings.TrimSpace(buff.String()))
}
@@ -1907,7 +1891,7 @@ func TestExitCodeOne(t *testing.T) {
)
require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "exit-one"}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: "exit-one"}))
assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String()))
}
@@ -1936,7 +1920,7 @@ func TestIgnoreNilElements(t *testing.T) {
task.WithSilent(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\n", buff.String())
})
}
@@ -1964,7 +1948,7 @@ task: [bye] echo 'Bye!'
Bye!
::endgroup::
`)
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "bye"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "bye"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
@@ -1981,7 +1965,7 @@ func TestOutputGroupErrorOnlySwallowsOutputOnSuccess(t *testing.T) {
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "passing"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "passing"}))
t.Log(buff.String())
assert.Empty(t, buff.String())
}
@@ -1998,7 +1982,7 @@ func TestOutputGroupErrorOnlyShowsOutputOnFailure(t *testing.T) {
)
require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "failing"}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: "failing"}))
t.Log(buff.String())
assert.Contains(t, "failing-output", strings.TrimSpace(buff.String()))
assert.NotContains(t, "passing", strings.TrimSpace(buff.String()))
@@ -2030,7 +2014,7 @@ VAR_1 is included-default-var1
task: [included3:task1] echo "VAR_2 is included-default-var2"
VAR_2 is included-default-var2
`)
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task1"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task1"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
@@ -2068,7 +2052,7 @@ Hello foo
task: [bar:lib:greet] echo 'Hello bar'
Hello bar
`)
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
t.Log(buff.String())
assert.Equal(t, expectedOutputOrder, strings.TrimSpace(buff.String()))
}
@@ -2106,7 +2090,7 @@ func TestErrorCode(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task})
err := e.Run(context.Background(), &task.Call{Task: test.task})
require.Error(t, err)
taskRunErr, ok := err.(*errors.TaskRunError)
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
@@ -2158,7 +2142,7 @@ func TestEvaluateSymlinksInPaths(t *testing.T) { // nolint:paralleltest // canno
for _, test := range tests { // nolint:paralleltest // cannot run in parallel
t.Run(test.name, func(t *testing.T) {
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task})
err := e.Run(context.Background(), &task.Call{Task: test.task})
require.NoError(t, err)
assert.Equal(t, test.expected, strings.TrimSpace(buff.String()))
buff.Reset()
@@ -2201,7 +2185,7 @@ func TestTaskfileWalk(t *testing.T) {
task.WithStderr(&buff),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, test.expected, buff.String())
})
}
@@ -2219,7 +2203,7 @@ func TestUserWorkingDirectory(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
}
@@ -2241,7 +2225,7 @@ func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
require.NoError(t, err)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "included:echo"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "included:echo"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
}
@@ -2255,7 +2239,7 @@ func TestPlatforms(t *testing.T) {
task.WithStderr(&buff),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-" + runtime.GOOS}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-" + runtime.GOOS}))
assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String())
}
@@ -2270,7 +2254,7 @@ func TestPOSIXShellOptsGlobalLevel(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "pipefail"})
err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String())
}
@@ -2286,7 +2270,7 @@ func TestPOSIXShellOptsTaskLevel(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "pipefail"})
err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String())
}
@@ -2302,7 +2286,7 @@ func TestPOSIXShellOptsCommandLevel(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "pipefail"})
err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String())
}
@@ -2318,7 +2302,7 @@ func TestBashShellOptsGlobalLevel(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "globstar"})
err := e.Run(context.Background(), &task.Call{Task: "globstar"})
require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String())
}
@@ -2334,7 +2318,7 @@ func TestBashShellOptsTaskLevel(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "globstar"})
err := e.Run(context.Background(), &task.Call{Task: "globstar"})
require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String())
}
@@ -2350,7 +2334,7 @@ func TestBashShellOptsCommandLevel(t *testing.T) {
)
require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "globstar"})
err := e.Run(context.Background(), &task.Call{Task: "globstar"})
require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String())
}
@@ -2370,7 +2354,7 @@ func TestSplitArgs(t *testing.T) {
vars := ast.NewVars()
vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"})
err := e.Run(t.Context(), &task.Call{Task: "default", Vars: vars})
err := e.Run(context.Background(), &task.Call{Task: "default", Vars: vars})
require.NoError(t, err)
assert.Equal(t, "3\n", buff.String())
}
@@ -2411,14 +2395,14 @@ func TestSilence(t *testing.T) {
// Then test the two basic cases where the task is silent or not.
// A silenced task.
err = e.Run(t.Context(), &task.Call{Task: "silent"})
err = e.Run(context.Background(), &task.Call{Task: "silent"})
require.NoError(t, err)
require.Empty(t, buff.String(), "siWhile running lent: Expected not see output, because the task is silent")
buff.Reset()
// A chatty (not silent) task.
err = e.Run(t.Context(), &task.Call{Task: "chatty"})
err = e.Run(context.Background(), &task.Call{Task: "chatty"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "chWhile running atty: Expected to see output, because the task is not silent")
@@ -2426,42 +2410,42 @@ func TestSilence(t *testing.T) {
// Then test invoking the two task from other tasks.
// A silenced task that calls a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-silent-calls-chatty-non-silenced: Expected to see output. The task is silenced, but the called task is not. Silence does not propagate to called tasks.")
buff.Reset()
// A silent task that does a silent call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-silent-calls-chatty-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-silent-calls-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-silent-calls-chatty-silenced: Expected not to see output. The task calls chatty task, but the call is silenced.")
buff.Reset()
// A chatty task that does a call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-non-silenced: Expected to see output. Both caller and callee are chatty and not silenced.")
buff.Reset()
// A chatty task that does a silenced call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-chatty-calls-chatty-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-chatty-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-silenced: Expected to see output. Call to a chatty task is silenced, but the parent task is not.")
buff.Reset()
// A chatty task with no cmd's of its own that does a silenced call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-no-cmds-calls-chatty-silenced: Expected not to see output. While the task itself is not silenced, it does not have any cmds and only does an invocation of a silenced task.")
buff.Reset()
// A chatty task that does a silenced invocation of a task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-chatty-calls-silenced-cmd"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-silenced-cmd"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-chatty-calls-silenced-cmd: Expected not to see output. While the task itself is not silenced, its call to the chatty task is silent.")
@@ -2469,21 +2453,21 @@ func TestSilence(t *testing.T) {
// Then test calls via dependencies.
// A silent task that depends on a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-non-silenced: Expected to see output. The task is silent and depends on a chatty task. Dependencies does not inherit silence.")
buff.Reset()
// A silent task that depends on a silenced chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-silenced: Expected not to see output. The task is silent and has a silenced dependency on a chatty task.")
buff.Reset()
// A chatty task that, depends on a silenced chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
err = e.Run(context.Background(), &task.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-is-chatty-depends-on-chatty-silenced: Expected not to see output. The task is chatty but does not have commands and has a silenced dependency on a chatty task.")
@@ -2535,7 +2519,7 @@ func TestForce(t *testing.T) {
task.WithForceAll(tt.forceAll),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task-with-dep"}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-with-dep"}))
})
}
}
@@ -2595,10 +2579,10 @@ func TestWildcard(t *testing.T) {
)
require.NoError(t, e.Setup())
if test.wantErr {
require.Error(t, e.Run(t.Context(), &task.Call{Task: test.call}))
require.Error(t, e.Run(context.Background(), &task.Call{Task: test.call}))
return
}
require.NoError(t, e.Run(t.Context(), &task.Call{Task: test.call}))
require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.call}))
assert.Equal(t, test.expectedOutput, buff.String())
})
}

View File

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

View File

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

View File

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

View File

@@ -1,48 +1,8 @@
package ast
import (
"cmp"
"maps"
"time"
"github.com/Masterminds/semver/v3"
)
import "github.com/Masterminds/semver/v3"
type TaskRC struct {
Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"`
Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"`
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
import (
"github.com/go-task/task/v3/internal/fsext"
)
import "github.com/go-task/task/v3/internal/fsext"
type Node struct {
entrypoint string
dir string
}
func NewNode(
entrypoint string,
dir string,
possibleFileNames []string,
) (*Node, error) {
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 {
return nil, err
}
return &Node{
entrypoint: resolvedEntrypoint,
entrypoint: entrypoint,
dir: dir,
}, nil
}

View File

@@ -1,89 +1,6 @@
package taskrc
import (
"os"
"path/filepath"
"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
var defaultTaskRCs = []string{
".taskrc.yml",
".taskrc.yaml",
}

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)
}

View File

@@ -12,14 +12,6 @@ tasks:
generates:
- ./generated.txt
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:
cmds:

View File

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

View File

@@ -22,14 +22,3 @@ tasks:
run: once
cmds:
- 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

@@ -44,14 +44,9 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
if err != nil {
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}
new := ast.Task{
Task: origTask.Task,
Label: templater.Replace(origTask.Label, cache),
@@ -81,7 +76,6 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
Requires: origTask.Requires,
Watch: origTask.Watch,
Namespace: origTask.Namespace,
FullName: fullName,
}
new.Dir, err = execext.ExpandLiteral(new.Dir)
if err != nil {

18
website/.gitignore vendored
View File

@@ -1,9 +1,21 @@
# Dependencies
/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*
yarn-debug.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:
install:
desc: Setup VitePress locally
yarn:install:
desc: Setup Docusaurus locally
cmds:
- pnpm install
- yarn install
sources:
- package.json
- pnpm-lock.yaml
- yarn.lock
default:
desc: Start website
deps: [install]
deps: [yarn:install]
aliases: [s, start]
vars:
HOST: '{{default "0.0.0.0" .HOST}}'
PORT: '{{default "3001" .PORT}}'
cmds:
- pnpm dev --host={{.HOST}} --port={{.PORT}}
lint:
desc: Lint website
deps: [install]
cmds:
- pnpm lint
- npx docusaurus start --no-open --host={{.HOST}} --port={{.PORT}}
build:
desc: Build website
deps: [install]
deps: [yarn:install]
cmds:
- pnpm build
- npx docusaurus build
preview:
desc: Preview Website
@@ -39,19 +33,20 @@ tasks:
HOST: '{{default "localhost" .HOST}}'
PORT: '{{default "3001" .PORT}}'
cmds:
- pnpm preview --host={{.HOST}} --port={{.PORT}}
- npx docusaurus serve --no-open --host={{.HOST}} --port={{.PORT}}
clean:
desc: Clean temp directories
cmds:
- rm -rf ./vitepress/dist
- rm -rf ./build
deploy:next:
desc: Build and deploy next.taskfile.dev
deploy:
desc: Build and deploy Docusaurus
summary: Requires GIT_USER and GIT_PASS envs to be previous set
cmds:
- pnpm netlify deploy --prod --site=4e13dfcf-fc0d-4bec-ad60-b918a8dc3942
- npx docusaurus deploy
deploy:prod:
desc: Build and deploy taskfile.dev
upgrade:
desc: Upgrade Docusaurus
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
description:
A look at where task is, where it's going and how we're going to get there.
author: pd93
date: 2024-05-09
outline: deep
slug: task-in-2023
authors: [pd93]
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
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
@@ -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
we're up to.
{/* truncate */}
## :calendar: So, what have we been up to?
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
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
improve v3 of Task and we've made some amazing progress. Here are just some of
the things we've released in that time:
- An official [extension for VS Code][vscode-task].
- Internal Tasks (#818).
- Task aliases (#879).
- Looping over tasks (#1220).
- Internal Tasks ([#818](https://github.com/go-task/task/pull/818)).
- Task aliases ([#879](https://github.com/go-task/task/pull/879)).
- 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
extensible.
- Loads of bug fixes and improvements.
@@ -38,13 +38,14 @@ the things we've released in that time:
- And much, much more! :sparkles:
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)
contributors][contributors] to the project, numerous sponsors and a passionate
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
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)
@@ -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
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
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
to!
{/* prettier-ignore-start */}
[vscode-task]: https://github.com/go-task/vscode-task
[crowdin]: https://crowdin.com
[contributors]: https://github.com/go-task/task/graphs/contributors
[semver]: https://semver.org
[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
[deprecations]: https://taskfile.dev/deprecations
[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
[gentle-force-experiment]: https://github.com/go-task/task/issues/1200
[remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317
{/* prettier-ignore-end */}

View File

@@ -1,13 +1,15 @@
---
title: Any Variables
author: pd93
date: 2024-05-09
outline: deep
description: Task variables are no longer limited to strings!
slug: any-variables
authors: [pd93]
tags: [experiments, variables]
image: https://i.imgur.com/mErPwqL.png
hide_table_of_contents: false
---
# Any Variables
<AuthorCard :author="$frontmatter.author" />
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
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.
@@ -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**,
**floats** and **arrays**!
{/* truncate */}
## What's the big deal?
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
values in your templates:
::: code-group
<Tabs defaultValue="2">
<TabItem value="1" label="Before">
```yaml [Before]
```yaml
version: 3
tasks:
@@ -40,7 +45,10 @@ tasks:
- '{{if eq .BOOL "true"}}echo foo{{end}}'
```
```yaml [After]
</TabItem>
<TabItem value="2" label="After">
```yaml
version: 3
tasks:
@@ -51,7 +59,8 @@ tasks:
- '{{if .BOOL}}echo foo{{end}}' # <-- No need to compare to "true"
```
:::
</TabItem>
</Tabs>
### 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
over list variables directly:
::: code-group
<Tabs defaultValue="2">
<TabItem value="1" label="Before">
```yaml [Before]
```yaml
version: 3
tasks:
@@ -117,7 +127,10 @@ tasks:
cmd: echo {{.ITEM}}
```
```yaml [After]
</TabItem>
<TabItem value="2" label="After">
```yaml
version: 3
tasks:
@@ -130,7 +143,8 @@ tasks:
cmd: echo {{.ITEM}}
```
:::
</TabItem>
</Tabs>
## 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]"
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
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
[slim-sprig-math]: https://go-task.github.io/slim-sprig/math.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
outline: deep
slug: /changelog/
sidebar_position: 14
---
# Changelog
## v3.45.3 - 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.2 - 2025-09-15
Failed due to an issue with our release process.
## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be

View File

@@ -1,9 +1,6 @@
---
title: Community
description:
Task community contributions, installation methods, and integrations
maintained by third parties
outline: deep
slug: /community/
sidebar_position: 10
---
# 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
can view the full list of community integrations
[here](./integrations.md#community-integrations).
[here](/integrations#community-integrations).
## Installation methods
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
- [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json)
- [Fedora](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)

View File

@@ -1,9 +1,6 @@
---
title: Contributing
description:
Comprehensive guide for contributing to the Task project, including setup,
development, testing, and submitting PRs
outline: deep
slug: /contributing/
sidebar_position: 12
---
# Contributing
@@ -11,7 +8,7 @@ outline: deep
Contributions to Task are very welcome, but we ask that you read this document
before submitting a PR.
::: info
:::note
This document applies to the core [Task][task] repository _and_ [Task for Visual
Studio Code][vscode-task].
@@ -30,10 +27,9 @@ Studio Code][vscode-task].
you invest your time into a PR.
- **Experiments** - If there is no way to make your change backward compatible
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
an experiment, then please read the
[experiments workflow](./experiments/index.md#workflow) document carefully and submit a
proposal first.
We call these "[experiments][experiments]". If you're intending to work on an
experiment, then please read the [experiments workflow][experiments-workflow]
document carefully and submit a proposal first.
## 1. Setup
@@ -42,7 +38,7 @@ Studio Code][vscode-task].
- **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
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
@@ -53,11 +49,10 @@ Studio Code][vscode-task].
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
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
`task lint` command to lint the code locally and the `task lint:fix` command
to try to automatically fix any issues that are found. You can also use the
`task fmt` command to auto-format the files if your editor doesn't do it for
you.
that we have a consistent style across the project. You can use the `task
lint` command to lint the code locally and the `task lint:fix` command to try
to automatically fix any issues that are found. You can also use the `task
fmt` command to auto-format the files if your editor doesn't do it for you.
- **Documentation** - Ensure that you add/update any relevant documentation. See
the [updating documentation](#updating-documentation) section below.
- **Tests** - Ensure that you add/update any relevant tests and that all tests
@@ -79,23 +74,24 @@ install the extension.
### 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
by using `task website` (requires `nodejs` & `pnpm`). All content is written in
Markdown and is located in the `website/src` directory. All Markdown documents
should have an 80 character line wrap limit (enforced by Prettier).
by using `task website` (requires `nodejs` & `yarn`). All content is written in
[MDX][mdx] (an extension of Markdown) and is located in the `website/docs`
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
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
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
[CLI Reference](./reference/cli.md). New fields also need to be added to the
[Schema Reference](./reference/schema.md) and [JSON Schema][json-schema]. The
descriptions for fields in the docs and the schema should match.
If you added a new command or flag, ensure that you add it to the [CLI
Reference](/reference/cli). New fields also need to be added to the [Schema
Reference](/reference/schema) and [JSON Schema][json-schema]. The descriptions
for fields in the docs and the schema should match.
### Writing tests
@@ -147,7 +143,7 @@ contributions.
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
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?
@@ -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
[vscode-task]: https://github.com/go-task/vscode-task
[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/
[prettier]: https://prettier.io
[nodejs]: https://nodejs.org/en/
[pnpm]: https://pnpm.io/
[vitepress]: https://vitepress.dev
[json-schema]:
https://github.com/go-task/task/blob/main/website/static/schema.json
[yarn]: https://yarnpkg.com/
[docusaurus]: https://docusaurus.io
[json-schema]: https://github.com/go-task/task/blob/main/website/static/schema.json
[task-open-issues]: https://github.com/go-task/task/issues
[vscode-task-open-issues]: https://github.com/go-task/vscode-task/issues
[good-first-issue]:
https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
[good-first-issue]: https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
[discord-server]: https://discord.gg/6TY36E39UK
[discussion]: https://github.com/go-task/task/discussions
[conventional-commits]: https://www.conventionalcommits.org
[mdx]: https://mdxjs.com/
{/* prettier-ignore-end */}

View File

@@ -1,12 +1,10 @@
---
title: 'Completion Scripts'
description: Deprecation of direct completion scripts in Tasks Git directory
outline: deep
slug: /deprecations/completion-scripts/
---
# Completion Scripts
::: danger
:::warning
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
completions][completions] instead.
[completions]: /docs/installation#setup-completions
{/* prettier-ignore-start */}
[completions]: ../installation.mdx#setup-completions
[task]: https://github.com/go-task/task
{/* prettier-ignore-end */}

View File

@@ -1,9 +1,6 @@
---
title: Deprecations
description:
Guide to deprecated features in Task and how to migrate to the new
alternatives
outline: deep
slug: /deprecations/
sidebar_position: 8
---
# 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'
description:
Deprecation of some templating functions in Task, with guidance on their
replacements.
outline: deep
slug: /deprecations/template-functions/
---
# Template Functions
::: danger
:::warning
This deprecation breaks the following functionality:

View File

@@ -1,12 +1,10 @@
---
title: 'Version 2 Schema (#1197)'
description: Deprecation of Taskfile schema version 2 and migration to version 3
outline: deep
slug: /deprecations/version-2-schema/
---
# Version 2 Schema (#1197)
::: danger
:::warning
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
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.33.0]: https://github.com/go-task/task/releases/tag/v3.33.0
[deprecation-notice]: https://github.com/go-task/task/issues/1197
{/* prettier-ignore-end */}

View File

@@ -1,13 +1,10 @@
---
title: 'Env Precedence (#1038)'
description:
Experiment to change the precedence of environment variables in Task
outline: deep
slug: '/experiments/env-precedence'
---
# Env Precedence (#1038)
::: warning
:::caution
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
@@ -15,7 +12,7 @@ environment. They are intended for testing and feedback only.
:::
::: danger
:::warning
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:
`TASK_X_ENV_PRECEDENCE=1`. Check out
[our guide to enabling experiments](./index.md#enabling-experiments) for more
information.
`TASK_X_ENV_PRECEDENCE=1`. Check out [our guide to enabling
experiments][enabling-experiments] for more information.
:::
@@ -48,12 +44,11 @@ tasks:
cmds:
- echo "$KEY"
```
Running `KEY=some task` before this experiment, the output would be `some`, but
after this experiment, the output would be `other`.
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
version: '3'
@@ -66,12 +61,14 @@ tasks:
- echo "$KEY"
- echo {{env "KEY"}}
```
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
default template function:
```yml
MY_ENV: '{{.MY_ENV | default "fallback"}}'
```
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
{/* prettier-ignore-end */}

View File

@@ -1,12 +1,14 @@
---
title: Experiments
description: Guide to Tasks experimental features and how to use them
outline: deep
slug: /experiments/
sidebar_position: 7
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Experiments
::: warning
:::caution
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
@@ -44,36 +46,37 @@ Which method you use depends on how you intend to use the experiment:
`.bashrc`, `.zshrc` etc.). This will permanently enable experimental features
for your personal environment.
```shell
# ~/.bashrc
```shell title="~/.bashrc"
export TASK_X_FEATURE=1
```
3. Creating a `.env` or a `.taskrc.yml` file in the same directory as your root
Taskfile.\
The `.env` file should contain the relevant environment variable(s), while
the `.taskrc.yml` file should use a YAML format where each experiment is
defined as a key with a corresponding value.
3. Creating a `.env` or a `.taskrc.yml` file in the same directory as
your root Taskfile.\
The `.env` file should contain the relevant environment
variable(s), while the `.taskrc.yml` file should use a YAML format
where each experiment is defined as a key with a corresponding value.
This allows you to enable an experimental feature at a project level. If you
commit this file to source control, then other users of your project will
also have these experiments enabled.
If both files are present, the values in the `.taskrc.yml` file will take
precedence.
If both files are present, the values in the `.taskrc.yml` file
will take precedence.
::: code-group
<Tabs values={[ {label: '.taskrc.yml', value: 'yaml'}, {label: '.env', value: 'env'}]}>
<TabItem value="yaml">
```yaml title=".taskrc.yml"
experiments:
FEATURE: 1
```
</TabItem>
```yaml [.taskrc.yml]
experiments:
FEATURE: 1
```
```shell [.env]
TASK_X_FEATURE=1
```
:::
<TabItem value="env">
```shell title=".env"
TASK_X_FEATURE=1
```
</TabItem>
</Tabs>
## Workflow
@@ -109,7 +112,7 @@ the status will be updated via the `status: draft` label. This indicates that an
implementation is now available for use in a release and the experiment is open
for feedback.
::: info
:::note
During the draft period, major changes to the implementation may be made based
on the feedback received from users. There are _no stability guarantees_ and
@@ -136,13 +139,13 @@ version.
### 5. Released
When making a new major release of Task, all experiments marked as
`status: stable` will move to `status: released` and their behaviors will become
the new default in Task. Experiments in an earlier stage (i.e. not stable)
cannot be released and so will continue to be experiments in the new version.
When making a new major release of Task, all experiments marked as `status:
stable` will move to `status: released` and their behaviors will become the new
default in Task. Experiments in an earlier stage (i.e. not stable) cannot be
released and so will continue to be experiments in the new version.
### Abandoned / Superseded
If an experiment is unsuccessful at any point then it will be given the
`status: abandoned` or `status: superseded` labels depending on which is more
suitable. These experiments will be removed from Task.
If an experiment is unsuccessful at any point then it will be given the `status:
abandoned` or `status: superseded` labels depending on which is more suitable.
These experiments will be removed from Task.

View File

@@ -1,12 +1,10 @@
---
title: 'Gentle Force (#1200)'
description: Experiment to modify the behavior of the --force flag in Task
outline: deep
slug: /experiments/gentle-force/
---
# Gentle Force (#1200)
::: warning
:::caution
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
@@ -14,7 +12,7 @@ environment. They are intended for testing and feedback only.
:::
::: danger
:::warning
This experiment breaks the following functionality:
@@ -22,12 +20,11 @@ This experiment breaks the following functionality:
:::
::: info
:::info
To enable this experiment, set the environment variable:
`TASK_X_GENTLE_FORCE=1`. Check out
[our guide to enabling experiments](./index.md#enabling-experiments) for more
information.
`TASK_X_GENTLE_FORCE=1`. Check out [our guide to enabling experiments
][enabling-experiments] for more information.
:::
@@ -46,3 +43,7 @@ If you want to migrate, but continue to force all dependant tasks to run, you
should replace all uses of the `--force` flag with `--force-all`. Alternatively,
if you want to adopt the new behavior, you can continue to use the `--force`
flag as you do now!
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
{/* prettier-ignore-end */}

View File

@@ -1,12 +1,13 @@
---
title: 'Remote Taskfiles (#1317)'
description: Experimentation for using Taskfiles stored in remote locations
outline: deep
slug: /experiments/remote-taskfiles/
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Remote Taskfiles (#1317)
::: warning
:::caution
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
@@ -14,19 +15,16 @@ environment. They are intended for testing and feedback only.
:::
::: info
:::info
To enable this experiment, set the environment variable:
`TASK_X_REMOTE_TASKFILES=1`. Check out
[our guide to enabling experiments](./index.md#enabling-experiments) for more
information.
`TASK_X_REMOTE_TASKFILES=1`. Check out [our guide to enabling experiments
][enabling-experiments] for more information.
:::
::: danger
:::danger
Never run remote Taskfiles from sources that you do not trust.
:::
This experiment allows you to use Taskfiles which are stored in remote
@@ -36,35 +34,19 @@ when including Taskfiles.
Task uses "nodes" to reference remote Taskfiles. There are a few different types
of node which you can use:
::: code-group
```text [HTTP/HTTPS]
https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
```
```text [Git over HTTP]
https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
```
```text [Git over SSH]
git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
```
:::
## Node Types
### HTTP/HTTPS
<Tabs groupId="method" queryString>
<TabItem value="http" label="HTTP/HTTPS">
`https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml`
This is the most basic type of remote node and works by downloading the file
from the specified URL. The file must be a valid Taskfile and can be of any
name. If a file is not found at the specified URL, Task will append each of the
supported file names in turn until it finds a valid file. If it still does not
find a valid Taskfile, an error is returned.
[supported file names][supported-file-names] in turn until it finds a valid
file. If it still does not find a valid Taskfile, an error is returned.
### Git over HTTP
</TabItem>
<TabItem value="git-http" label="Git over HTTP">
`https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main`
@@ -73,12 +55,13 @@ HTTP/HTTPS. The first part of the URL is the base URL of the Git repository.
This is the same URL that you would use to clone the repo over HTTP.
- You can optionally add the path to the Taskfile in the repository by appending
`//<path>` to the URL.
`//<path>` to the URL.
- You can also optionally specify a branch or tag to use by appending
`?ref=<ref>` to the end of the URL. If you omit a reference, the default
branch will be used.
`?ref=<ref>` to the end of the URL. If you omit a reference, the default branch
will be used.
### Git over SSH
</TabItem>
<TabItem value="git-ssh" label="Git over SSH">
`git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main`
@@ -90,13 +73,16 @@ To use Git over SSH, you need to make sure that your SSH agent has your private
SSH keys added so that they can be used during authentication.
- You can optionally add the path to the Taskfile in the repository by appending
`//<path>` to the URL.
`//<path>` to the URL.
- You can also optionally specify a branch or tag to use by appending
`?ref=<ref>` to the end of the URL. If you omit a reference, the default
branch will be used.
`?ref=<ref>` to the end of the URL. If you omit a reference, the default branch
will be used.
Task has an example remote Taskfile in our repository that you can use for
testing and that we will use throughout this document:
</TabItem>
</Tabs>
Task has an [example remote Taskfile][example-remote-taskfile] in our repository
that you can use for testing and that we will use throughout this document:
```yaml
version: '3'
@@ -113,32 +99,34 @@ tasks:
## Specifying a remote entrypoint
By default, Task will look for one of the supported file names on your local
filesystem. If you want to use a remote file instead, you can pass its URI into
the `--taskfile`/`-t` flag just like you would to specify a different local
file. For example:
By default, Task will look for one of the [supported file
names][supported-file-names] on your local filesystem. If you want to use a
remote file instead, you can pass its URI into the `--taskfile`/`-t` flag just
like you would to specify a different local file. For example:
::: code-group
```shell [HTTP/HTTPS]
<Tabs groupId="method" queryString>
<TabItem value="http" label="HTTP/HTTPS">
```shell
$ task --taskfile https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
task: [hello] echo "Hello Task!"
Hello Task!
```
```shell [Git over HTTP]
</TabItem>
<TabItem value="git-http" label="Git over HTTP">
```shell
$ task --taskfile https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
task: [hello] echo "Hello Task!"
Hello Task!
```
```shell [Git over SSH]
</TabItem>
<TabItem value="git-ssh" label="Git over SSH">
```shell
$ task --taskfile git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
task: [hello] echo "Hello Task!"
Hello Task!
```
:::
</TabItem>
</Tabs>
## Including remote Taskfiles
@@ -146,30 +134,32 @@ Including a remote file works exactly the same way that including a local file
does. You just need to replace the local path with a remote URI. Any tasks in
the remote Taskfile will be available to run from your main Taskfile.
::: code-group
```yaml [HTTP/HTTPS]
<Tabs groupId="method" queryString>
<TabItem value="http" label="HTTP/HTTPS">
```yaml
version: '3'
includes:
my-remote-namespace: https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
```
```yaml [Git over HTTP]
</TabItem>
<TabItem value="git-http" label="Git over HTTP">
```yaml
version: '3'
includes:
my-remote-namespace: https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
```
```yaml [Git over SSH]
</TabItem>
<TabItem value="git-ssh" label="Git over SSH">
```yaml
version: '3'
includes:
my-remote-namespace: git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
```
:::
</TabItem>
</Tabs>
```shell
$ task my-remote-namespace:hello
@@ -257,8 +247,9 @@ Task currently supports both `http` and `https` URLs. However, the `http`
requests will not execute by default unless you run the task with the
`--insecure` flag. This is to protect you from accidentally running a remote
Taskfile that is downloaded via an unencrypted connection. Sources that are not
protected by TLS are vulnerable to man-in-the-middle attacks and should be
avoided unless you know what you are doing.
protected by TLS are vulnerable to [man-in-the-middle
attacks][man-in-the-middle-attacks] and should be avoided unless you know what
you are doing.
## Caching & Running Offline
@@ -282,72 +273,18 @@ the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will
set the timeout to 5 seconds.
By default, the cache is stored in the Task temp directory, represented by the
`TASK_TEMP_DIR` environment variable. You can override the location of the cache
by setting the `TASK_REMOTE_DIR` environment variable. This way, you can share
the cache between different projects.
`TASK_TEMP_DIR` [environment variable](../reference/environment.mdx) You can
override the location of the cache by setting the `TASK_REMOTE_DIR` environment
variable. This way, you can share the cache between different projects.
You can force Task to ignore the cache and download the latest version by using
the `--download` flag.
You can force Task to ignore the cache and download the latest version
by using the `--download` flag.
You can use the `--clear-cache` flag to clear all cached remote files.
## Configuration
This experiment adds a new `remote` section to the [configuration file](../reference/config.md).
- **Type**: `object`
- **Description**: Remote configuration settings for handling remote Taskfiles
```yaml
remote:
insecure: false
offline: false
timeout: "30s"
cache-expiry: "24h"
```
#### `insecure`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Allow insecure connections when fetching remote Taskfiles
```yaml
remote:
insecure: true
```
#### `offline`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Work in offline mode, preventing remote Taskfile fetching
```yaml
remote:
offline: true
```
#### `timeout`
- **Type**: `string`
- **Default**: Not specified
- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$`
- **Description**: Timeout duration for remote operations (e.g., '30s', '5m')
```yaml
remote:
timeout: "1m"
```
#### `cache-expiry`
- **Type**: `string`
- **Default**: Not specified
- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$`
- **Description**: Cache expiry duration for remote Taskfiles (e.g., '1h', '24h')
```yaml
remote:
cache-expiry: "6h"
```
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
[supported-file-names]: https://taskfile.dev/usage/#supported-file-names
[example-remote-taskfile]: https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
{/* prettier-ignore-end */}

View File

@@ -1,10 +1,14 @@
---
# 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 Experiment\} (#\{Issue\})
::: warning
:::caution
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
@@ -12,7 +16,7 @@ environment. They are intended for testing and feedback only.
:::
::: warning
:::warning
This experiment breaks the following functionality:
@@ -33,4 +37,6 @@ information.
\{Short explanation of how users should migrate to the new behavior\}
[enabling-experiments]: /docs/experiments/#enabling-experiments
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
{/* prettier-ignore-end */}

View File

@@ -1,9 +1,6 @@
---
title: FAQ
description:
Frequently asked questions about Task, including ETAs, shell limitations, and
Windows compatibility
outline: deep
slug: /faq/
sidebar_position: 15
---
# FAQ
@@ -27,7 +24,7 @@ be patient and avoid asking for ETAs.
The best way to speed things up is to contribute to the project yourself. We
always appreciate new contributors. If you are interested in contributing, check
out the [contributing guide](./contributing.md).
out the [contributing guide](./contributing.mdx).
## Why won't my task update my shell environment?
@@ -97,39 +94,27 @@ a=foo
echo $a
```
## Are shell core utilities available on Windows?
## 'x' builtin command doesn't work on Windows
The most common ones, yes. And we might add more in the future.
This is possible because Task compiles a small set of core utilities in Go and
enables them by default on Windows for greater compatibility.
The default shell on Windows (`cmd` and `powershell`) do not have commands like
`rm` and `cp` available as builtins. This means that these commands won't work.
If you want to make your Taskfile fully cross-platform, you'll need to work
around this limitation using one of the following methods:
It's possible to control whether these builtin core utilities are used or not
with the [`TASK_CORE_UTILS`](/docs/reference/environment#task-core-utils)
environment variable:
- Use the `{{OS}}` function to run an OS-specific script.
- Use something like `{{if eq OS "windows"}}powershell {{end}}<my_cmd>` to
detect windows and run the command in Powershell directly.
- Use a shell on Windows that supports these commands as builtins, such as [Git
Bash][git-bash] or [WSL][wsl].
```bash
# Enable, even on non-Windows platforms
env TASK_CORE_UTILS=1 task ...
We want to make improvements to this part of Task and the issues below track
this work. Constructive comments and contributions are very welcome!
# Disable, even on Windows
env TASK_CORE_UTILS=0 task ...
```
- #197
- [mvdan/sh#93](https://github.com/mvdan/sh/issues/93)
- [mvdan/sh#97](https://github.com/mvdan/sh/issues/97)
This is the list of core utils that are currently available:
* `base64`
* `cat`
* `chmod`
* `cp`
* `find`
* `gzip`
* `ls`
* `mkdir`
* `mktemp`
* `mv`
* `rm`
* `shasum`
* `tar`
* `touch`
* `xargs`
* (more might be added in the future)
{/* prettier-ignore-start */}
[git-bash]: https://gitforwindows.org/
[wsl]: https://learn.microsoft.com/en-us/windows/wsl/install
{/* prettier-ignore-end */}

View File

@@ -1,14 +1,14 @@
---
title: Getting Started
description: Guide for getting started with Task
outline: deep
slug: /getting-started/
sidebar_position: 3
---
# Getting Started
The following guide will help introduce you to the basics of Task. We'll cover
how to create a Taskfile, how to write a basic task and how to call it. If you
haven't installed Task yet, head over to our [installation guide](installation).
haven't installed Task yet, head over to our [installation
guide][installation].
## Creating your first Taskfile
@@ -35,7 +35,7 @@ task --init Custom.yml
This will create a Taskfile that looks something like this:
```yaml [Taskfile.yml]
```yaml
version: '3'
vars:
@@ -48,11 +48,11 @@ tasks:
silent: true
```
As you can see, all Taskfiles are written in [YAML format](https://yaml.org/).
The `version` attribute specifies the minimum version of Task that can be used
to run this file. The `vars` attribute is used to define variables that can be
used in tasks. In this case, we are creating a string variable called `GREETING`
with a value of `Hello, World!`.
As you can see, all Taskfiles are written in [YAML format][yaml]. The `version`
attribute specifies the minimum version of Task that can be used to run this
file. The `vars` attribute is used to define variables that can be used in
tasks. In this case, we are creating a string variable called `GREETING` with a
value of `Hello, World!`.
Finally, the `tasks` attribute is used to define the tasks that can be run. In
this case, we have a task called `default` that echoes the value of the
@@ -70,10 +70,10 @@ task default
```
Note that we don't have to specify the name of the Taskfile. Task will
automatically look for a file called `Taskfile.yml` (or any of Task's
[supported file names](/docs/guide#supported-file-names)) in the current
directory. Additionally, tasks with the name `default` are special. They can
also be run without specifying the task name.
automatically look for a file called `Taskfile.yml` (or any of Task's [supported
file names][supported-file-names]) in the current directory. Additionally, tasks
with the name `default` are special. They can also be run without specifying the
task name.
If you created a Taskfile in a different directory, you can run it by passing
the absolute or relative path to the directory as an argument using the `--dir`
@@ -96,10 +96,10 @@ Let's create a task to build a program in Go. Start by adding a new task called
`build` below the existing `default` task. We can then add a `cmds` attribute
with a single command to build the program.
Task uses [mvdan/sh](https://github.com/mvdan/sh), a native Go sh interpreter.
So you can write sh/bash-like commands - even in environments where `sh` or
`bash` are usually not available (like Windows). Just remember any executables
called must be available as a built-in or in the system's `PATH`.
Task uses [mvdan/sh][mvdan/sh], a native Go sh interpreter. So you can write
sh/bash-like commands - even in environments where `sh` or `bash` are usually
not available (like Windows). Just remember any executables called must be
available as a built-in or in the system's `PATH`.
When you're done, it should look something like this:
@@ -128,6 +128,16 @@ task build
That's about it for the basics, but there's _so much_ more that you can do with
Task. Check out the rest of the documentation to learn more about all the
features Task has to offer! We recommend taking a look at the
[usage guide](/docs/guide) next. Alternatively, you can check out our reference
docs for the [Taskfile schema](reference/schema) and [CLI](reference/cli).
features Task has to offer! We recommend taking a look at the [usage
guide][usage] next. Alternatively, you can check out our reference docs for the
[Taskfile schema][schema] and [CLI][cli].
{/* prettier-ignore-start */}
[yaml]: https://yaml.org/
[installation]: /installation/
[supported-file-names]: /usage/#supported-file-names
[mvdan/sh]: https://github.com/mvdan/sh
[usage]: /usage/
[schema]: /reference/schema/
[cli]: /reference/cli/
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,354 @@
---
slug: /installation/
sidebar_position: 2
toc_max_heading_level: 4
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Installation
Task offers many installation methods. Check out the available methods below.
:::info
Some of the methods below are marked as ![Community][community]. This means they
are not maintained by the Task team and may not be up-to-date.
:::
## Package Managers
### [Homebrew][homebrew] ![][macos] ![][linux] \{#homebrew}
Task is available via our official Homebrew tap [[source](https://github.com/go-task/homebrew-tap/blob/main/Formula/go-task.rb)]:
```shell
brew install go-task/tap/go-task
```
Alternatively it can be installed from the official Homebrew
repository [[package](https://formulae.brew.sh/formula/go-task)]
[[source](https://github.com/Homebrew/homebrew-core/blob/master/Formula/g/go-task.rb)] by running:
```shell
brew install go-task
```
### [Macports][macports] ![][macos] ![][community] \{#macports}
Task repository is tracked by Macports [[package](https://ports.macports.org/port/go-task/details/)] [[source](https://github.com/macports/macports-ports/blob/master/devel/go-task/Portfile)]:
```shell
port install go-task
```
### [Snap][snapcraft] ![][macos] ![][linux] \{#snap}
Task is available on [Snapcraft][snapcraft] [[source](https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml)], but keep in mind that your Linux
distribution should allow classic confinement for Snaps to Task work correctly:
```shell
sudo snap install task --classic
```
### [npm][npm] ![][macos] ![][linux] ![][windows] \{#npm}
Npm can be used as cross-platform way to install Task globally or as a
dependency of your project
[[package](https://www.npmjs.com/package/@go-task/cli)] [[source](https://github.com/go-task/task/blob/main/package.json)]:
```shell
npm install -g @go-task/cli
```
### [pip][pip] ![][macos] ![][linux] ![][windows] ![][community] \{#pip}
Like npm, pip can be used as a cross-platform way to install Task
[[package](https://pypi.org/project/go-task-bin)] [[source](https://github.com/Bing-su/pip-binary-factory/tree/main/task)]:
```shell
pip install go-task-bin
```
### [WinGet][winget] ![][windows] \{#winget}
Task is available via the [community repository](https://github.com/microsoft/winget-pkgs) [[source](https://github.com/microsoft/winget-pkgs/tree/master/manifests/t/Task/Task)]:
```shell
winget install Task.Task
```
### [Chocolatey][choco] ![][windows] ![][community] \{#chocolatey}
[[package](https://community.chocolatey.org/packages/go-task)] [[source](https://github.com/Starz0r/ChocolateyPackagingScripts/blob/master/src/go-task_gh_build.py)]
```shell
choco install go-task
```
### [Scoop][scoop] ![][windows] ![][community] \{#scoop}
[[source](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json)]
```shell
scoop install task
```
### Arch ([pacman][pacman]) ![][arch] ![][community] \{#arch}
[[package](https://archlinux.org/packages/extra/x86_64/go-task/)] [[source](https://gitlab.archlinux.org/archlinux/packaging/packages/go-task)]
```shell
pacman -S go-task
```
### Fedora ([dnf][dnf]) ![][fedora] ![][community] \{#fedora}
[[package](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)] [[source](https://src.fedoraproject.org/rpms/golang-github-task)]
```shell
dnf install go-task
```
### FreeBSD ([Ports][freebsdports]) ![][freebsd] ![][community] \{#freebsd}
[[package](https://cgit.freebsd.org/ports/tree/devel/task)] [[source](https://cgit.freebsd.org/ports/tree/devel/task/Makefile)]
```shell
pkg install task
```
### NixOS ([nix][nix]) ![][nixos] ![][linux] ![][community] \{#nix}
[[source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/go/go-task/package.nix)]
```shell
nix-env -iA nixpkgs.go-task
```
### [pacstall][pacstall] ![][debian] ![][ubuntu] ![][community] \{#pacstall}
[[package](https://pacstall.dev/packages/go-task-deb)] [[source](https://github.com/pacstall/pacstall-programs/blob/master/packages/go-task-deb/go-task-deb.pacscript)]
```shell
pacstall -I go-task-deb
```
### [pkgx][pkgx] ![][macos] ![][linux] ![][community] \{#pkgx}
[[package](https://pkgx.dev/pkgs/taskfile.dev)] [[source](https://github.com/pkgxdev/pantry/blob/main/projects/taskfile.dev/package.yml)]
```shell
pkgx task
```
or, if you have pkgx integration enabled:
```shell
task
```
## Get The Binary
### Binary
You can download the binary from the [releases page on GitHub][releases] and add
to your `$PATH`.
DEB and RPM packages are also available.
The `task_checksums.txt` file contains the SHA-256 checksum for each file.
### Install Script
We also have an [install script][installscript] which is very useful in
scenarios like CI. Many thanks to [GoDownloader][godownloader] for enabling the
easy generation of this script.
By default, it installs on the `./bin` directory relative to the working
directory:
```shell
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
```
It is possible to override the installation directory with the `-b` parameter.
On Linux, common choices are `~/.local/bin` and `~/bin` to install for the
current user or `/usr/local/bin` to install for all users:
```shell
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
```
:::caution
On macOS and Windows, `~/.local/bin` and `~/bin` are not added to `$PATH` by
default.
:::
By default, it installs the latest version available.
You can also specify a tag (available in [releases](https://github.com/go-task/task/releases))
to install a specific version:
```shell
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d v3.36.0
```
Parameters are order specific, to set both installation directory and version:
```shell
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin v3.42.1
```
### GitHub Actions
If you want to install Task in GitHub Actions you can try using
[this action](https://github.com/arduino/setup-task) by the Arduino team:
```yaml
- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
```
This installation method is community owned.
## Build From Source
### Go Modules
Ensure that you have a supported version of [Go][go] properly installed and
setup. You can find the minimum required version of Go in the
[go.mod](https://github.com/go-task/task/blob/main/go.mod#L3) file.
You can then install the latest release globally by running:
```shell
go install github.com/go-task/task/v3/cmd/task@latest
```
Or you can install into another directory:
```shell
env GOBIN=/bin go install github.com/go-task/task/v3/cmd/task@latest
```
:::tip
For CI environments we recommend using the [install script](#install-script)
instead, which is faster and more stable, since it'll just download the latest
released binary.
:::
## Setup completions
Some installation methods will automatically install completions too, but if
this isn't working for you or your chosen method doesn't include them, you can
run `task --completion <shell>` to output a completion script for any supported
shell. There are a couple of ways these completions can be added to your shell
config:
### Option 1. Load the completions in your shell's startup config (Recommended)
This method loads the completion script from the currently installed version of
task every time you create a new shell. This ensures that your completions are
always up-to-date.
<Tabs values={[ {label: 'bash', value: '1'}, {label: 'zsh', value: '2'},
{label: 'fish', value: '3'},
{label: 'powershell', value: '4'}
]}>
<TabItem value="1">
```shell title="~/.bashrc"
eval "$(task --completion bash)"
```
</TabItem>
<TabItem value="2">
```shell title="~/.zshrc"
eval "$(task --completion zsh)"
```
</TabItem>
<TabItem value="3">
```shell title="~/.config/fish/config.fish"
task --completion fish | source
```
</TabItem>
<TabItem value="4">
```powershell title="$PROFILE\Microsoft.PowerShell_profile.ps1"
Invoke-Expression (&task --completion powershell | Out-String)
```
</TabItem></Tabs>
### Option 2. Copy the script to your shell's completions directory
This method requires you to manually update the completions whenever Task is
updated. However, it is useful if you want to modify the completions yourself.
<Tabs
values={[
{label: 'bash', value: '1'},
{label: 'zsh', value: '2'},
{label: 'fish', value: '3'}
]}>
<TabItem value="1">
```shell
task --completion bash > /etc/bash_completion.d/task
```
</TabItem>
<TabItem value="2">
```shell
task --completion zsh > /usr/local/share/zsh/site-functions/_task
```
</TabItem>
<TabItem value="3">
```shell
task --completion fish > ~/.config/fish/completions/task.fish
```
</TabItem></Tabs>
{/* prettier-ignore-start */}
[homebrew]: https://brew.sh
[macports]: https://macports.org
[snapcraft]: https://snapcraft.io/task
[winget]: https://github.com/microsoft/winget-cli
[choco]: https://chocolatey.org
[scoop]: https://scoop.sh
[pacman]: https://wiki.archlinux.org/title/Pacman
[dnf]: https://docs.fedoraproject.org/en-US/quick-docs/dnf
[nix]: https://nixos.org
[npm]: https://www.npmjs.com
[pip]: https://pip.pypa.io
[mise]: https://mise.jdx.dev
[aqua]: https://aquaproj.github.io
[pacstall]: https://github.com/pacstall/pacstall
[pkgx]: https://pkgx.sh
[freebsdports]: https://ports.freebsd.org/cgi/ports.cgi
[go]: https://golang.org
[godownloader]: https://github.com/goreleaser/godownloader
[releases]: https://github.com/go-task/task/releases
[installscript]: https://github.com/go-task/task/blob/main/install-task.sh
[community]: https://img.shields.io/badge/Community%20Owned-orange
[windows]: https://custom-icon-badges.demolab.com/badge/Windows-0078D6?logo=windows11&logoColor=white
[macos]: https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0
[linux]: https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black
[arch]: https://img.shields.io/badge/Arch%20Linux-1793D1?logo=arch-linux&logoColor=fff
[fedora]: https://img.shields.io/badge/Fedora-51A2DA?logo=fedora&logoColor=fff
[nixos]: https://img.shields.io/badge/NixOS-5277C3?logo=nixos&logoColor=fff
[debian]: https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=fff
[ubuntu]: https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=fff
[freebsd]: https://img.shields.io/badge/FreeBSD-990000?logo=freebsd&logoColor=fff
{/* prettier-ignore-end */}

View File

@@ -1,9 +1,6 @@
---
title: Integrations
description:
Official and community integrations for Task, including VS Code, JSON schemas,
and other tools
outline: deep
slug: /integrations/
sidebar_position: 9
---
# Integrations

68
website/docs/intro.mdx Normal file
View File

@@ -0,0 +1,68 @@
---
slug: /
sidebar_position: 1
title: Home
hide_title: true
---
<div align="center">
<img id="logo" src="/img/logo.svg" height="250px" width="250px" />
</div>
<br />
Task is a task runner / build tool that aims to be simpler and easier to use
than, for example, [GNU Make][make].
Since it's written in [Go][go], Task is just a single binary and has no other
dependencies, which means you don't need to mess with any complicated install
setups just to use a build tool.
## Features
- [Easy installation](/installation): just download a single binary, add to
`$PATH` and you're done! Or you can also install using [Homebrew][homebrew],
[Snapcraft][snapcraft], or [Scoop][scoop] if you want.
- Available on CIs: by adding
[this simple command](/installation#install-script) to install on your CI
script and you're ready to use Task as part of your CI pipeline;
- Truly cross-platform: while most build tools only work well on Linux or macOS,
Task also supports Windows thanks to [this shell interpreter for Go][sh].
- Great for code generation: you can easily
[prevent a task from running](/usage#prevent-unnecessary-work) if a given set
of files haven't changed since last run (based either on its timestamp or
content).
## Documentation
- If you're new to Task, we recommend taking a look at our [getting started
guide][getting-started] for an quick introduction.
- You can also browse our [usage documentation][usage] for more details on how
all the features work.
- Or use our quick reference documentation for the [Taskfile schema][schema] or
[CLI][cli].
## Gold Sponsors
<table class="gold-sponsors">
<tr>
<td align="center" valign="middle">
<a target="_blank" href="https://devowl.io">
<img src="/img/devowl.io.svg" height="100px" title="devowl.io" />
</a>
</td>
</tr>
</table>
{/* prettier-ignore-start */}
[make]: https://www.gnu.org/software/make/
[go]: https://go.dev/
[yaml]: http://yaml.org/
[homebrew]: https://brew.sh/
[snapcraft]: https://snapcraft.io/
[scoop]: https://scoop.sh/
[sh]: https://github.com/mvdan/sh
[getting-started]: /getting-started/
[usage]: /usage/
[schema]: /reference/schema/
[cli]: /reference/cli/
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,2 @@
position: 5
label: Reference

View File

@@ -0,0 +1,121 @@
---
slug: /reference/cli
sidebar_position: 1
---
# CLI Reference
Task CLI commands have the following syntax:
```shell
task [--flags] [tasks...] [-- CLI_ARGS...]
```
:::tip
If `--` is given, all remaining arguments will be assigned to a special
`CLI_ARGS` variable
:::
## Flags
| Short | Flag | Type | Default | Description |
| ----- | --------------------------- | -------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c` | `--color` | `bool` | `true` | Colored output. Enabled by default. Set flag to `false` or use `NO_COLOR=1` to disable. |
| `-C` | `--concurrency` | `int` | `0` | Limit number tasks to run concurrently. Zero means unlimited. |
| `-d` | `--dir` | `string` | Working directory | Sets the directory in which Task will execute and look for a Taskfile. |
| `-n` | `--dry` | `bool` | `false` | Compiles and prints tasks in the order that they would be run, without executing them. |
| `-x` | `--exit-code` | `bool` | `false` | Pass-through the exit code of the task command. |
| `-f` | `--force` | `bool` | `false` | Forces execution even when the task is up-to-date. |
| `-g` | `--global` | `bool` | `false` | Runs global Taskfile, from `$HOME/Taskfile.{yml,yaml}`. |
| `-h` | `--help` | `bool` | `false` | Shows Task usage. |
| `-i` | `--init` | `bool` | `false` | Creates a new Taskfile.yml in the current folder. |
| `-I` | `--interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
| `-l` | `--list` | `bool` | `false` | Lists tasks with description of current Taskfile. |
| `-a` | `--list-all` | `bool` | `false` | Lists tasks with or without a description. |
| | `--sort` | `string` | `default` | Changes the order of the tasks when listed.<br />`default` - Alphanumeric with root tasks first<br />`alphanumeric` - Alphanumeric<br />`none` - No sorting (As they appear in the Taskfile) |
| | `--json` | `bool` | `false` | See [JSON Output](#json-output) |
| `-o` | `--output` | `string` | Default set in the Taskfile or `interleaved` | Sets output style: [`interleaved`/`group`/`prefixed`]. |
| | `--output-group-begin` | `string` | | Message template to print before a task's grouped output. |
| | `--output-group-end` | `string` | | Message template to print after a task's grouped output. |
| | `--output-group-error-only` | `bool` | `false` | Swallow command output on zero exit code. |
| `-p` | `--parallel` | `bool` | `false` | Executes tasks provided on command line in parallel. |
| `-s` | `--silent` | `bool` | `false` | Disables echoing. |
| `-y` | `--yes` | `bool` | `false` | Assume "yes" as answer to all prompts. |
| | `--status` | `bool` | `false` | Exits with non-zero exit code if any of the given tasks is not up-to-date. |
| | `--summary` | `bool` | `false` | Show summary about a task. |
| `-t` | `--taskfile` | `string` | | Taskfile path to run.<br />Check the list of default filenames [here](../usage/#supported-file-names). |
| `-v` | `--verbose` | `bool` | `false` | Enables verbose mode. |
| | `--version` | `bool` | `false` | Show Task version. |
| `-w` | `--watch` | `bool` | `false` | Enables watch of the given task.
## Exit Codes
Task will sometimes exit with specific exit codes. These codes are split into
four groups with the following ranges:
- Success (0)
- General errors (1-99)
- Taskfile errors (100-199)
- Task errors (200-255)
A full list of the exit codes and their descriptions can be found below:
| Code | Description |
|------|---------------------------------------------------------------------|
| 0 | Success |
| 1 | An unknown error occurred |
| 100 | No Taskfile was found |
| 101 | A Taskfile already exists when trying to initialize one |
| 102 | The Taskfile is invalid or cannot be parsed |
| 103 | A remote Taskfile could not be downloaded |
| 104 | A remote Taskfile was not trusted by the user |
| 105 | A remote Taskfile was could not be fetched securely |
| 106 | No cache was found for a remote Taskfile in offline mode |
| 107 | No schema version was defined in the Taskfile |
| 200 | The specified task could not be found |
| 201 | An error occurred while executing a command inside of a task |
| 202 | The user tried to invoke a task that is internal |
| 203 | There a multiple tasks with the same name or alias |
| 204 | A task was called too many times |
| 205 | A task was cancelled by the user |
| 206 | A task was not executed due to missing required variables |
| 207 | A task was not executed due to a variable having an incorrect value |
These codes can also be found in the repository in
[`errors/errors.go`](https://github.com/go-task/task/blob/main/errors/errors.go).
:::info
When Task is run with the `-x`/`--exit-code` flag, the exit code of any failed
commands will be passed through to the user instead.
:::
## JSON Output
When using the `--json` flag in combination with either the `--list` or
`--list-all` flags, the output will be a JSON object with the following
structure:
```json
{
"tasks": [
{
"name": "",
"task": "",
"desc": "",
"summary": "",
"up_to_date": false,
"location": {
"line": 54,
"column": 3,
"taskfile": "/path/to/Taskfile.yml"
}
}
// ...
],
"location": "/path/to/Taskfile.yml"
}
```

View File

@@ -0,0 +1,49 @@
---
slug: /reference/environment
sidebar_position: 5
---
# Environment Reference
Task allows you to configure some behavior using environment variables. This
page lists all the environment variables that Task supports.
| ENV | Default | Description |
|-------------------|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| `TASK_TEMP_DIR` | `.task` | Location of the temp dir. Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
| `TASK_REMOTE_DIR` | `TASK_TEMP_DIR` | Location of the remote temp dir (used for caching). Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
| `TASK_OFFLINE` | `false` | Set the `--offline` flag through the environment variable. Only for remote experiment. CLI flag `--offline` takes precedence over the env variable |
| `FORCE_COLOR` | | Force color output usage. |
## Custom Colors
| ENV | Default | Description |
|-----------------------------|---------|-------------------------|
| `TASK_COLOR_RESET` | `0` | Color used for white. |
| `TASK_COLOR_RED` | `31` | Color used for red. |
| `TASK_COLOR_GREEN` | `32` | Color used for green. |
| `TASK_COLOR_YELLOW` | `33` | Color used for yellow. |
| `TASK_COLOR_BLUE` | `34` | Color used for blue. |
| `TASK_COLOR_MAGENTA` | `35` | Color used for magenta. |
| `TASK_COLOR_CYAN` | `36` | Color used for cyan. |
| `TASK_COLOR_BRIGHT_RED` | `91` | Color used for red. |
| `TASK_COLOR_BRIGHT_GREEN` | `92` | Color used for green. |
| `TASK_COLOR_BRIGHT_YELLOW` | `93` | Color used for yellow. |
| `TASK_COLOR_BRIGHT_BLUE` | `94` | Color used for blue. |
| `TASK_COLOR_BRIGHT_MAGENTA` | `95` | Color used for magenta. |
| `TASK_COLOR_BRIGHT_CYAN` | `96` | Color used for cyan. |
All color variables are [ANSI color codes][ansi]. You can specify multiple codes
separated by a semicolon. For example: `31;1` will make the text bold and red.
Task also supports 8-bit color (256 colors). You can specify these colors by
using the sequence `38;2;R:G:B` for foreground colors and `48;2;R:G:B` for
background colors where `R`, `G` and `B` should be replaced with values between
0 and 255.
For convenience, we allow foreground colors to be specified using shorthand,
comma-separated syntax: `R,G,B`. For example, `255,0,0` is equivalent to
`38;2;255:0:0`.
{/* prettier-ignore-start */}
[ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
{/* prettier-ignore-end */}

View File

@@ -1,11 +1,11 @@
---
title: Package API Reference
description: A reference for Task's Golang package API
slug: /reference/package
sidebar_position: 2
---
# Package API Reference
# Package API
::: warning
:::warning
**_Task's package API is still experimental and subject to breaking changes._**
@@ -39,11 +39,9 @@ fetching and executing tasks from a Taskfile. At this time, the vast majority of
the this package's functionality is exposed via the [`task.Executor`] which
allows the user to fetch and execute tasks from a Taskfile.
::: info
:::note
This is the package which is most likely to be the subject of breaking changes
as we refine the API.
:::
### [`github.com/go-task/task/v3/taskfile`]
@@ -70,10 +68,10 @@ represent the Taskfile syntax in Go. This package provides a way to parse
Taskfile YAML into an AST and store them in memory.
- [`ast.TaskfileGraph`] - Represents a set of Taskfiles and their dependencies
between one another.
between one another.
- [`ast.Taskfile`] - Represents a single Taskfile or a set of merged Taskfiles.
The `Taskfile` type contains all of the subtypes for the Taskfile syntax, such
as `tasks`, `includes`, `vars`, etc. These are not listed here for brevity.
The `Taskfile` type contains all of the subtypes for the Taskfile syntax, such
as `tasks`, `includes`, `vars`, etc. These are not listed here for brevity.
### [`github.com/go-task/task/v3/errors`]
@@ -138,44 +136,32 @@ tf, err := tfg.Merge()
This compiles the DAG into a single [`ast.Taskfile`] containing all the
namespaces and tasks from all the Taskfiles we read.
::: info
:::note
We plan to remove AST merging in the future as it is unnecessarily complex and
causes lots of issues with scoping.
:::
{/* prettier-ignore-start */}
[`github.com/go-task/task/v3`]: https://pkg.go.dev/github.com/go-task/task/v3
[`github.com/go-task/task/v3/taskfile`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile
[`github.com/go-task/task/v3/taskfile/ast`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast
[`github.com/go-task/task/v3/errors`]:
https://pkg.go.dev/github.com/go-task/task/v3/errors
[`ast.TaskfileGraph`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast#TaskfileGraph
[`ast.Taskfile`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast#Taskfile
[`github.com/go-task/task/v3/taskfile`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile
[`github.com/go-task/task/v3/taskfile/ast`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast
[`github.com/go-task/task/v3/errors`]: https://pkg.go.dev/github.com/go-task/task/v3/errors
[`ast.TaskfileGraph`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast#TaskfileGraph
[`ast.Taskfile`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast#Taskfile
[`taskfile.Node`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Node
[`taskfile.FileNode`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#FileNode
[`taskfile.HTTPNode`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#HTTPNode
[`taskfile.GitNode`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#GitNode
[`taskfile.StdinNode`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#StdinNode
[`taskfile.NewFileNode`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#NewFileNode
[`taskfile.Reader`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader
[`taskfile.NewReader`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#NewReader
[`taskfile.Snippet`]:
https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Snippet
[`taskfile.FileNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#FileNode
[`taskfile.HTTPNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#HTTPNode
[`taskfile.GitNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#GitNode
[`taskfile.StdinNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#StdinNode
[`taskfile.NewFileNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#NewFileNode
[`taskfile.Reader`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader
[`taskfile.NewReader`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#NewReader
[`taskfile.Snippet`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Snippet
[`task.Executor`]: https://pkg.go.dev/github.com/go-task/task/v3#Executor
[`task.Formatter`]: https://pkg.go.dev/github.com/go-task/task/v3#Formatter
[`errors.TaskError`]:
https://pkg.go.dev/github.com/go-task/task/v3/errors#TaskError
[`errors.TaskError`]: https://pkg.go.dev/github.com/go-task/task/v3/errors#TaskError
[`error`]: https://pkg.go.dev/builtin#error
[ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,242 @@
---
slug: /reference/schema
sidebar_position: 3
toc_min_heading_level: 2
toc_max_heading_level: 5
---
# Schema Reference
| Attribute | Type | Default | Description |
|------------|------------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `version` | `string` | | Version of the Taskfile. The current version is `3`. |
| `output` | `string` | `interleaved` | Output mode. Available options: `interleaved`, `group` and `prefixed`. |
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overridden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
| `includes` | [`map[string]Include`](#include) | | Additional Taskfiles to be included. |
| `vars` | [`map[string]Variable`](#variable) | | A set of global variables. |
| `env` | [`map[string]Variable`](#variable) | | A set of global environment variables. |
| `tasks` | [`map[string]Task`](#task) | | A set of task definitions. |
| `silent` | `bool` | `false` | Default 'silent' options for this Taskfile. If `false`, can be overridden with `true` in a task by task basis. |
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
| `run` | `string` | `always` | Default 'run' option for this Taskfile. Available options: `always`, `once` and `when_changed`. |
| `interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
## Include
| Attribute | Type | Default | Description |
|------------|-----------------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
| `flatten` | `bool` | `false` | If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown. |
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
| `checksum` | `string` | | The checksum of the file you expect to include. If the checksum does not match, the file will not be included. |
:::info
Informing only a string like below is equivalent to setting that value to the
`taskfile` attribute.
```yaml
includes:
foo: ./path
```
:::
## Variable
| Attribute | Type | Default | Description |
| --------- | -------- | ------- | ------------------------------------------------------------------------ |
| _itself_ | `string` | | A static value that will be set to the variable. |
| `sh` | `string` | | A shell command. The output (`STDOUT`) will be assigned to the variable. |
:::info
Static and dynamic variables have different syntaxes, like below:
```yaml
vars:
STATIC: static
DYNAMIC:
sh: echo "dynamic"
```
:::
:::info
In a variables map, variables defined later may reference variables defined
earlier (declaration order is respected):
```yaml
vars:
FIRST_VAR: "hello"
SECOND_VAR: "{{.FIRST_VAR}} world"
```
:::
## Task
| Attribute | Type | Default | Description |
| --------------- | ---------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cmds` | [`[]Command`](#command) | | A list of shell commands to be executed. |
| `deps` | [`[]Dependency`](#dependency) | | A list of dependencies of this task. Tasks defined here will run in parallel before this task. |
| `label` | `string` | | Overrides the name of the task in the output when a task is run. Supports variables. |
| `desc` | `string` | | A short description of the task. This is displayed when calling `task --list`. |
| `prompt` | `[]string` | | One or more prompts that will be presented before a task is run. Declining will cancel running the current and any subsequent tasks. |
| `summary` | `string` | | A longer description of the task. This is displayed when calling `task --summary [task]`. |
| `aliases` | `[]string` | | A list of alternative names by which the task can be called. |
| `sources` | `[]string` | | A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
| `generates` | `[]string` | | A list of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs. |
| `status` | `[]string` | | A list of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`. |
| `preconditions` | [`[]Precondition`](#precondition) | | A list of commands to check if this task should run. If a condition is not met, the task will error. |
| `requires` | [`Requires`](#requires) | | A list of required variables which should be set if this task is to run, if any variables listed are unset the task will error and not run. |
| `dir` | `string` | | The directory in which this task should run. Defaults to the current working directory. |
| `vars` | [`map[string]Variable`](#variable) | | A set of variables that can be used in the task. |
| `env` | [`map[string]Variable`](#variable) | | A set of environment variables that will be made available to shell commands. |
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
| `silent` | `bool` | `false` | Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. When combined with the `--list` flag, task descriptions will be hidden. |
| `interactive` | `bool` | `false` | Tells task that the command is interactive. |
| `internal` | `bool` | `false` | Stops a task from being callable on the command line. It will also be omitted from the output when used with `--list`. |
| `method` | `string` | `checksum` | Defines which method is used to check the task is up-to-date. `timestamp` will compare the timestamp of the sources and generates files. `checksum` will check the checksum (You probably want to ignore the .task folder in your .gitignore file). `none` skips any validation and always run the task. |
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go). Task will be skipped otherwise. |
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
:::info
These alternative syntaxes are available. They will set the given values to
`cmds` and everything else will be set to their default values:
```yaml
tasks:
foo: echo "foo"
foobar:
- echo "foo"
- echo "bar"
baz:
cmd: echo "baz"
```
:::
### Command
| Attribute | Type | Default | Description |
| -------------- | ---------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cmd` | `string` | | The shell command to be executed. |
| `task` | `string` | | Set this to trigger execution of another task instead of running a command. This cannot be set together with `cmd`. |
| `for` | [`For`](#for) | | Runs the command once for each given value. |
| `silent` | `bool` | `false` | Skips some output for this command. Note that STDOUT and STDERR of the commands will still be redirected. |
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
| `defer` | [`Defer`](#defer) | | Alternative to `cmd`, but schedules the command or a task to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go). Command will be skipped otherwise. |
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
:::info
If given as a a string, the value will be assigned to `cmd`:
```yaml
tasks:
foo:
cmds:
- echo "foo"
- echo "bar"
```
:::
### Dependency
| Attribute | Type | Default | Description |
| --------- | ---------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `task` | `string` | | The task to be execute as a dependency. |
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to this task. |
| `silent` | `bool` | `false` | Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. |
:::tip
If you don't want to set additional variables, it's enough to declare the
dependency as a list of strings (they will be assigned to `task`):
```yaml
tasks:
foo:
deps: [foo, bar]
```
:::
### Defer
The `defer` parameter defines a shell command to run, or a task to trigger, at the end of the current task instead of immediately.
If defined as a string this is a shell command, otherwise it is a map defining a task to call:
| Attribute | Type | Default | Description |
| --------- | ---------------------------------- | ------- | ----------------------------------------------------------------- |
| `task` | `string` | | The deferred task to trigger. |
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the deferred task. |
| `silent` | `bool` | `false` | Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. |
### For
The `for` parameter can be defined as a string, a list of strings or a map. If
it is defined as a string, you can give it any of the following values:
- `sources` - Will run the command for each source file defined on the task.
(Glob patterns will be resolved, so `*.go` will run for every Go file that
matches).
- `generates` - Will run the command for each file defined in the task's generates
list. (Glob patterns will be resolved, so `*.txt` will run for every text file
that matches).
If it is defined as a list of strings, the command will be run for each value.
Finally, the `for` parameter can be defined as a map when you want to use a
variable to define the values to loop over:
| Attribute | Type | Default | Description |
| --------- | -------- | ---------------- | -------------------------------------------- |
| `var` | `string` | | The name of the variable to use as an input. |
| `split` | `string` | (any whitespace) | What string the variable should be split on. |
| `as` | `string` | `ITEM` | The name of the iterator variable. |
### Precondition
| Attribute | Type | Default | Description |
| --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `sh` | `string` | | Command to be executed. If a non-zero exit code is returned, the task errors without executing its commands. |
| `msg` | `string` | | Optional message to print if the precondition isn't met. |
:::tip
If you don't want to set a different message, you can declare a precondition
like this and the value will be assigned to `sh`:
```yaml
tasks:
foo:
precondition: test -f Taskfile.yml
```
:::
### Requires
| Attribute | Type | Default | Description |
| --------- | ---------- | ------- | -------------------------------------------------------------------------------------------------- |
| `vars` | `[]string` | | List of variable or environment variable names that must be set if this task is to execute and run |

View File

@@ -0,0 +1,426 @@
---
slug: /reference/templating/
sidebar_position: 4
toc_min_heading_level: 2
toc_max_heading_level: 5
---
# Templating Reference
Task's templating engine uses Go's [text/template][text/template] package to
interpolate values. For detailed information about the usage of Go's templating
engine, we recommend reading [the official documentation][text/template].
However, we will provide a basic overview of the main features here.
## Basic Usage
Most string values in Task (though, not all) can be templated. The templating
engine uses double curly braces `{{` and `}}` to denote a template. Anything
inside the curly braces will be executed as a Go template and the result will be
inserted into the resulting string. For example:
```yaml
version: '3'
tasks:
hello:
vars:
MESSAGE: 'Hello, World!'
cmds:
- 'echo {{.MESSAGE}}'
```
In this example, we have a task called `hello` with a single variable, `MESSAGE`
defined. When the command is run, the templating engine will evaluate the
variable and replace `{{.MESSAGE}}` with the variable's contents. This task will
output `Hello, World!` to the terminal. Note that when referring to a variable,
you must use dot notation.
You are also able to do more complex things in templates, such as conditional
logic:
```yaml
version: '3'
tasks:
maybe-happy:
vars:
SMILE: ':\)'
FROWN: ':\('
HAPPY: true
cmds:
- 'echo {{if .HAPPY}}{{.SMILE}}{{else}}{{.FROWN}}{{end}}'
```
```txt
:)
```
...calling functions and piping values:
```yaml
version: '3'
tasks:
uniq:
vars:
NUMBERS: '0,1,1,1,2,2,3'
cmds:
- 'echo {{splitList "," .NUMBERS | uniq | join ", " }}!'
```
```txt
0, 1, 2, 3
```
...looping over values with control flow operators:
```yaml
version: '3'
tasks:
loop:
vars:
NUMBERS: [0, 1, 1, 1, 2, 2, 3]
cmds:
# Ranges over NUMBERS and prints the index and value of each number until it finds a number greater than 1
- "{{range $index, $num := .NUMBERS}}{{if gt $num 1 }}{{break}}{{end}}echo {{$index}}: {{$num}}\n{{end}}"
```
```txt
0: 0
1: 1
2: 1
3: 1
```
## Special Variables
Task defines some special variables that are always available to the templating
engine. If you define a variable with the same name as a special variable, the
special variable will be overridden.
| Var | Description |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI as a string. |
| `CLI_ARGS_LIST` | Contain all extra arguments passed after `--` when calling Task through the CLI as a shell parsed list. |
| `CLI_FORCE` | A boolean containing whether the `--force` or `--force-all` flags were set. |
| `CLI_SILENT` | A boolean containing whether the `--silent` flag was set. |
| `CLI_VERBOSE` | A boolean containing whether the `--verbose` flag was set. |
| `CLI_OFFLINE` | A boolean containing whether the `--offline` flag was set. |
| `TASK` | The name of the current task. |
| `ALIAS` | The alias used for the current task, otherwise matches `TASK`. |
| `TASK_EXE` | The Task executable name or path. |
| `ROOT_TASKFILE` | The absolute path of the root Taskfile. |
| `ROOT_DIR` | The absolute path of the root Taskfile directory. |
| `TASKFILE` | The absolute path of the included Taskfile. |
| `TASKFILE_DIR` | The absolute path of the included Taskfile directory. |
| `TASK_DIR` | The absolute path of the directory where the task is executed. |
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from. |
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
| `TIMESTAMP` | The date object of the greatest timestamp of the files listed in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
| `TASK_VERSION` | The current version of task. |
| `ITEM` | The value of the current iteration when using the `for` property. Can be changed to a different variable name using `as:`. |
| `EXIT_CODE` | Available exclusively inside the `defer:` command. Contains the failed command exit code. Only set when non-zero. |
## Functions
Functions are provided at a few different levels in Task. Below, we list all the
functions available for use in Task.
:::note
Functions marked with an asterisk (\*) also have `must` variants that will panic
rather than erroring.
:::
### Built-in Functions
The first set of functions are [provided by Golang
itself][go-template-functions]:
| Function | Description |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `and` | Returns the boolean AND of its arguments by returning the first empty argument or the last argument. That is, `and x y` behaves as `if x then y else x`. Evaluation proceeds through the arguments left to right and returns when the result is determined. |
| `call` | Returns the result of calling the first argument, which must be a function, with the remaining arguments as parameters. Thus `call .X.Y 1 2` is, in Go notation, `dot.X.Y(1, 2)` where `Y` is a func-valued field, map entry, or the like. The first argument must be the result of an evaluation that yields a value of function type (as distinct from a predefined function such as print). The function must return either one or two result values, the second of which is of type error. If the arguments don't match the function or the returned error value is non-nil, execution stops. |
| `html` | Returns the escaped HTML equivalent of the textual representation of its arguments. This function is unavailable in [html/template][html/template], with a few exceptions. |
| `index` | Returns the result of indexing its first argument by the following arguments. Thus `index x 1 2 3` is, in Go syntax, `x[1][2][3]`. Each indexed item must be a map, slice, or array. |
| `slice` | slice returns the result of slicing its first argument by the remaining arguments. Thus `slice x 1 2` is, in Go syntax, `x[1:2]`, while `slice x` is `x[:]`, `slice x 1` is `x[1:]`, and `slice x 1 2 3` is `x[1:2:3]`. The first argument must be a string, slice, or array. |
| `js` | Returns the escaped JavaScript equivalent of the textual representation of its arguments. |
| `len` | Returns the integer length of its argument. |
| `not` | Returns the boolean negation of its single argument. |
| `or` | Returns the boolean OR of its arguments by returning the first non-empty argument or the last argument, that is, `or x y` behaves as `if x then x else y`. Evaluation proceeds through the arguments left to right and returns when the result is determined. |
| `print` | An alias for `fmt.Sprint`. |
| `printf` | An alias for `fmt.Sprintf`. |
| `println` | An alias for `fmt.Sprintln`. |
| `urlquery` | Returns the escaped value of the textual representation of its arguments in a form suitable for embedding in a URL query. This function is unavailable in [html/template][html/template], with a few exceptions. |
### Slim-Sprig Functions
In addition to the built-in functions, Task also provides a set of functions
imported via the [slim-sprig][slim-sprig] package. We only provide a very basic
description here for completeness. For detailed usage, please refer to the
[slim-sprig documentation][slim-sprig]:
#### [String Functions][string-functions]
| Function | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `trim` | Removes space from either side of a string. |
| `trimAll` | Removes given characters from the front or back of a string. |
| `trimSuffix` | Trims just the suffix from a string. |
| `trimPrefix` | Trims just the prefix from a string. |
| `upper` | Converts the entire string to uppercase. |
| `lower` | Converts the entire string to lowercase. |
| `title` | Converts to title case. |
| `repeat` | Repeats a string multiple times. |
| `substr` | Gets a substring from a string. |
| `trunc` | Truncates a string. |
| `contains` | Tests to see if one string is contained inside of another. |
| `hasPrefix` | Tests whether a string has a given prefix. |
| `hasSuffix` | Tests whether a string has a given suffix. |
| `quote` | Wraps a string in double quotes. |
| `squote` | Wraps a string in single quotes. |
| `cat` | Concatenates multiple strings together into one, separating them with spaces. |
| `indent` | Indents every line in a given string to the specified indent width. |
| `nindent` | Identical to `indent`, but prepends a new line to the beginning of the string. |
| `replace` | Replaces a string. |
| `plural` | Pluralizes a string. |
| `regexMatch`\* | Returns true if the input string contains any match of the regular expression. |
| `regexFindAll`\* | Returns a slice of all matches of the regular expression in the input string. |
| `regexFind`\* | Returns the first (left most) match of the regular expression in the input string. |
| `regexReplaceAll`\* | Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement. |
| `regexReplaceAllLiteral`\* | Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement without expanding `$`. |
| `regexSplit`\* | Slices the input string into substrings separated by the expression and returns a slice of the substrings between those expression matches. |
| `regexQuoteMeta`\* | Returns a string that escapes all regular expression metacharacters inside the argument text. |
#### [String Slice Functions][string-list-functions]
| Function | Description |
| ----------- | ----------------------------------------------------------------------- |
| `join` | Joins a list of strings into a single string, with the given separator. |
| `splitList` | Splits a string into a list of strings. |
| `split` | Splits a string into a map of strings where each key is an index. |
| `splitn` | Identical to `split`, but stops splitting after `n` values. |
| `sortAlpha` | Sorts a list of strings into alphabetical (lexicographical) order. |
#### [Integer Functions][math-functions]
| Function | Description |
| --------- | ------------------------------------------------------------------------------------------------------- |
| `add` | Sum a set of numbers. |
| `add1` | Increments a number by 1. |
| `sub` | Subtracts one number from another. |
| `div` | Performs integer division. |
| `mod` | Modulo. |
| `mul` | Multiplies a set of numbers. |
| `max` | Returns the largest of a set of integers. |
| `min` | Returns the smallest of a set of integers. |
| `floor` | Returns the greatest float value less than or equal to input value |
| `ceil` | Returns the greatest float value greater than or equal to input value |
| `round` | Returns a float value with the remainder rounded to the given number to digits after the decimal point. |
| `randInt` | Returns a random integer value from min (inclusive) to max (exclusive). |
#### [Integer Slice Functions][integer-list-functions]
| Function | Description |
| ----------- | --------------------------------------------------------------------------- |
| `until` | Builds a range of integers. |
| `untilStep` | Builds a range of integers, but allows you to define a start, stop and step |
| `seq` | Works like the bash `seq` command. |
#### [Date Functions][date-functions]
| Function | Description |
| ---------------- | ------------------------------------------------------------------------------ |
| `now` | Gets the current date/time. |
| `ago` | Returns the duration since the given date/time. |
| `date` | Formats a date. |
| `dateInZone` | Identical to `date`, but with the given timezone. |
| `duration` | Formats the number of seconds into a string. |
| `durationRound` | Identical to `duration`, but rounds the duration to the most significant unit. |
| `unixEpoch` | Returns the seconds since the unix epoch for the given date/time. |
| `dateModify`\* | Modifies a date using the given input string. |
| `htmlDate` | Formats a date for inserting into an HTML date picker input field. |
| `htmlDateInZone` | Identical to `htmlDate`, but with the given timezone. |
| `toDate`\* | Converts a string to a date/time. |
#### [Default Functions][default-functions]
| Function | Description |
| ---------- | ------------------------------------------------------------------------ |
| `default` | Uses a default value if the given value is considered "zero" or "empty". |
| `empty` | Returns true if a value is considered "zero" or "empty". |
| `coalesce` | Takes a list of values and returns the first non-empty one. |
| `all` | Returns true if all values are non-empty. |
| `any` | Returns true if any value is non-empty. |
| `ternary` | The ternary function takes two values, and a test value. If the test value is true, the first value will be returned. If the test value is empty, the second value will be returned. |
#### [Encoding Functions][encoding-functions]
| Function | Description |
| ---------------- | ------------------------------------------------------------------ |
| `fromJson`\* | Decodes a JSON string into an object. |
| `toJson`\* | Encodes an object as a JSON string. |
| `toPrettyJson`\* | Encodes an object as a JSON string with new lines and indentation. |
| `toRawJson`\* | Encodes an object as a JSON string with HTML characters unescaped. |
| `b64enc` | Encodes a string into base 64. |
| `b64dec` | Decodes a string from base 64. |
| `b32enc` | Encodes a string into base 32. |
| `b32dec` | Decodes a string from base 32. |
:::note
YAML encoding functions are [provided directly by Task](#task-functions).
:::
#### [List Functions][list-functions]
| Function | Description |
| ----------- | ---------------------------------------------------------------- |
| `list` | Creates a list from a set of values. |
| `first`\* | Gets the first value in a list. |
| `rest`\* | Gets all values except the first value in the list. |
| `last`\* | Gets the last value in the list. |
| `initial`\* | Get all values except the last value in the list. |
| `append`\* | Adds a new value to the end of the list. |
| `prepend`\* | Adds a new value to the start of the list. |
| `concat` | Joins two or more lists together. |
| `reverse`\* | Reverses the order of a list. |
| `uniq`\* | Generate a list with all of the duplicates removed. |
| `without`\* | Filters matching items out of a list. |
| `has`\* | Tests to see if a list has a particular element. |
| `compact`\* | Removes entries with empty values. |
| `slice`\* | Returns a partial copy of a list given start and end parameters. |
| `chunk` | Splits a list into chunks of given size. |
#### [Dictionary Functions][dictionary-functions]
| Function | Description |
| ------------------ | ------------------------------------------------------------------------------------------ |
| `dict` | Creates a dictionary from a set of key/value pairs. |
| `get` | Gets the value from the dictionary with the given key. |
| `set` | Adds a new key/value pair to a dictionary. |
| `unset` | Deletes a key from a dictionary. |
| `hasKey` | Returns true if a dictionary contains the given key. |
| `pluck` | Gets a list of all of the matching values in a set of maps given a key. |
| `dig` | Returns the value in a nested map given a path of keys. |
| `merge`\* | Merges two or more dictionaries into one. |
| `mergeOverwrite`\* | Identical to `merge`, but giving precedence from right to left. |
| `keys` | Returns a list of all of the keys in a dictionary. |
| `pick` | Creates a new dictionary containing only the given keys of an existing map. |
| `omit` | Creates a new dictionary containing all the keys of an existing map except the ones given. |
| `values` | Returns a list of all the values in a dictionary. |
#### [Type Conversion Functions][type-conversion-functions]
| Function | Description |
| ----------- | ------------------------------------------------------ |
| `atoi` | Converts a string to an integer. |
| `float64` | Converts to a float64. |
| `int` | Converts to an int at the system's width. |
| `int64` | Converts to an int64. |
| `toDecimal` | Converts a unix octal to a int64. |
| `toString` | Converts to a string. |
| `toStrings` | Converts a list, slice, or array to a list of strings. |
| `toStrings` | Produces a slice of strings from any list. |
| `toDecimal` | Given a unix octal permission, produce a decimal. |
#### [Path and Filepath Functions][path-functions]
| Function | Description |
| --------- | ----------------------------------------- |
| `base` | Returns the last element of a path. |
| `dir` | Returns the directory of a path. |
| `clean` | Cleans up a path. |
| `ext` | Returns the file extension of a path. |
| `isAbs` | Checks if a path is absolute. |
| `osBase` | Returns the last element of a filepath. |
| `osDir` | Returns the directory of a filepath. |
| `osClean` | Cleans up a filepath. |
| `osExt` | Returns the file extension of a filepath. |
| `osIsAbs` | Checks if a filepath is absolute. |
:::note
More filepath encoding functions are [provided directly by Task](#task-functions).
:::
#### [Flow Control Functions][flow-control-functions]
| Function | Description |
| -------- | ----------------------------------------------------------------------------- |
| `fail` | Unconditionally returns an empty string and an error with the specified text. |
#### [OS Functions][os-functions]
| Function | Description |
| ----------- | --------------------------------------------- |
| `env` | Reads an environment variable. |
| `expandenv` | Substitutes environment variables in a string |
#### [Reflection Functions][reflection-functions]
| Function | Description |
| ------------ | ------------------------------------------------------ |
| `kindOf` | Returns the kind of a value. |
| `kindIs` | Verifies that a value is a particular kind. |
| `typeOf` | Returns the underlying type of a value. |
| `typeIs` | Verifies that a value is of a particular type. |
| `typeIsLike` | Identical to `typeIs`, but also dereferences pointers. |
| `deepEqual` | Returns true if two values are "deeply equal". |
#### [Cryptographic and Security Functions][crypto-functions]
| Function | Description |
| ------------ | -------------------------------------- |
| `sha1sum` | Computes a string's SHA1 digest. |
| `sha256sum` | Computes a string's SHA256 digest. |
| `adler32sum` | Computes a string's Adler-32 checksum. |
### Task Functions
Lastly, Task itself provides a few functions:
| Function | Description |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OS` | Returns the operating system. Possible values are `windows`, `linux`, `darwin` (macOS) and `freebsd`. |
| `ARCH` | Returns the architecture Task was compiled to: `386`, `amd64`, `arm` or `s390x`. |
| `numCPU` | Returns the number of logical CPU's usable by the current process. |
| `splitLines` | Splits Unix (`\n`) and Windows (`\r\n`) styled newlines. |
| `catLines` | Replaces Unix (`\n`) and Windows (`\r\n`) styled newlines with a space. |
| `toSlash` | Does nothing on Unix, but on Windows converts a string from `\` path format to `/`. |
| `fromSlash` | Opposite of `toSlash`. Does nothing on Unix, but on Windows converts a string from `/` path format to `\`. |
| `exeExt` | Returns the right executable extension for the current OS (`".exe"` for Windows, `""` for others). |
| `shellQuote` | (aliased to `q`): Quotes a string to make it safe for use in shell scripts. Task uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote) for this. The Bash dialect is assumed. |
| `splitArgs` | Splits a string as if it were a command's arguments. Task uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/shell#Fields). |
| `joinPath` | Joins any number of arguments into a path. The same as Go's [filepath.Join](https://pkg.go.dev/path/filepath#Join). |
| `relPath` | Converts an absolute path (second argument) into a relative path, based on a base path (first argument). The same as Go's [filepath.Rel](https://pkg.go.dev/path/filepath#Rel). |
| `merge` | Creates a new map that is a copy of the first map with the keys of each subsequent map merged into it. If there is a duplicate key, the value of the last map with that key is used. |
| `spew` | Returns the Go representation of a specific variable. Useful for debugging. Uses the [davecgh/go-spew](https://github.com/davecgh/go-spew) package. |
| `fromYaml`\* | Decodes a YAML string into an object. |
| `toYaml`\* | Encodes an object as a YAML string. |
| `uuid` | Generates a new pseudo-random UUIDv4 string. |
| `randIntN` | Generates a new pseudo-random, non-negative, 32bit integer in the half-open interval `[0,n)`. Generated numbers are not suitable for security-sensitive work. |
{/* prettier-ignore-start */}
[text/template]: https://pkg.go.dev/text/template
[html/template]: https://pkg.go.dev/html/template
[go-template-functions]: https://pkg.go.dev/text/template#hdr-Functions
[slim-sprig]: https://go-task.github.io/slim-sprig/
[os-functions]: https://go-task.github.io/slim-sprig/os.html
[string-functions]: https://go-task.github.io/slim-sprig/strings.html
[string-list-functions]: https://go-task.github.io/slim-sprig/string_slice.html
[math-functions]: https://go-task.github.io/slim-sprig/math.html
[integer-list-functions]: https://go-task.github.io/slim-sprig/integer_slice.html
[date-functions]: https://go-task.github.io/slim-sprig/date.html
[default-functions]: https://go-task.github.io/slim-sprig/defaults.html
[encoding-functions]: https://go-task.github.io/slim-sprig/encoding.html
[list-functions]: https://go-task.github.io/slim-sprig/lists.html
[dictionary-functions]: https://go-task.github.io/slim-sprig/dicts.html
[type-conversion-functions]: https://go-task.github.io/slim-sprig/conversion.html
[path-functions]: https://go-task.github.io/slim-sprig/paths.html
[flow-control-functions]: https://go-task.github.io/slim-sprig/flow_control.html
[os-functions]: https://go-task.github.io/slim-sprig/os.html
[reflection-functions]: https://go-task.github.io/slim-sprig/reflection.html
[crypto-functions]: https://go-task.github.io/slim-sprig/crypto.html
{/* prettier-ignore-end */}

View File

@@ -1,9 +1,6 @@
---
title: Releasing
description:
Task release process including GoReleaser, Homebrew, npm, Snapcraft, winget,
and other package managers
outline: deep
slug: /releasing/
sidebar_position: 13
---
# Releasing
@@ -20,18 +17,18 @@ Since v3.15.0, raw executables can also be reproduced and verified locally by
checking out a specific tag and calling `goreleaser build`, using the Go version
defined in the above GitHub Actions.
## Homebrew
# Homebrew
Goreleaser will automatically push a new commit to the
[Formula/go-task.rb][gotaskrb] file in the [Homebrew tap][homebrewtap]
repository to release the new version.
## npm
# npm
To release to npm update the version in the [`package.json`][packagejson] file
and then run `task npm:publish` to push it.
## Snapcraft
# Snapcraft
The [snap package][snappackage] requires to manual steps to release a new
version:
@@ -40,7 +37,7 @@ version:
- Moving both `amd64`, `armhf` and `arm64` new artifacts to the stable channel
on the [Snapcraft dashboard][snapcraftdashboard].
## winget
# winget
winget also requires manual steps to be completed. By running
`task goreleaser:test` locally, manifest files will be generated on
@@ -49,7 +46,7 @@ winget also requires manual steps to be completed. By running
and open a pull request into
[this repository](https://github.com/microsoft/winget-pkgs).
## Scoop
# Scoop
Scoop is a command-line package manager for the Windows operating system. Scoop
package manifests are maintained by the community. Scoop owners usually take
@@ -57,18 +54,19 @@ care of updating versions there by editing
[this file](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json).
If you think its Task version is outdated, open an issue to let us know.
## Nix
# Nix
Nix is a community owned installation method. Nix package maintainers usually
take care of updating versions there by editing
[this file](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/by-name/go/go-task/package.nix).
If you think its Task version is outdated, open an issue to let us know.
{/* prettier-ignore-start */}
[goreleaser]: https://goreleaser.com/
[homebrewtap]: https://github.com/go-task/homebrew-tap
[gotaskrb]: https://github.com/go-task/homebrew-tap/blob/main/Formula/go-task.rb
[packagejson]: https://github.com/go-task/task/blob/main/package.json#L3
[snappackage]: https://github.com/go-task/snap
[snapcraftyaml]:
https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml#L2
[snapcraftyaml]: https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml#L2
[snapcraftdashboard]: https://snapcraft.io/task/releases
{/* prettier-ignore-end */}

View File

@@ -1,9 +1,6 @@
---
title: Style Guide
description:
Official style guide for Taskfile.yml files with best practices and
recommended conventions
outline: deep
slug: /styleguide/
sidebar_position: 11
---
# Style Guide

View File

@@ -1,9 +1,6 @@
---
title: Taskfile Versions
description:
How to use the Taskfile schema version to ensure users are using the correct
versions of Task
outline: deep
slug: /taskfile-versions/
sidebar_position: 6
---
# Taskfile Versions

View File

@@ -1,8 +1,12 @@
---
outline: deep
slug: /usage/
sidebar_position: 4
---
# Guide
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Usage
## Running Taskfiles
@@ -35,12 +39,11 @@ the file tree until it finds one (similar to how `git` works). When running Task
from a subdirectory like this, it will behave as if you ran it from the
directory containing the Taskfile.
You can use this functionality along with the special
<span v-pre>`{{.USER_WORKING_DIR}}`</span> variable to create some very useful
reusable tasks. For example, if you have a monorepo with directories for each
microservice, you can `cd` into a microservice directory and run a task command
to bring it up without having to create multiple tasks or Taskfiles with
identical content. For example:
You can use this functionality along with the special `{{.USER_WORKING_DIR}}`
variable to create some very useful reusable tasks. For example, if you have a
monorepo with directories for each microservice, you can `cd` into a
microservice directory and run a task command to bring it up without having to
create multiple tasks or Taskfiles with identical content. For example:
```yaml
version: '3'
@@ -66,14 +69,14 @@ Taskfile that matches `$HOME/{T,t}askfile.{yml,yaml}` .
This is useful to have automation that you can run from anywhere in your system!
::: info
:::info
When running your global Taskfile with `-g`, tasks will run on `$HOME` by
default, and not on your working directory!
As mentioned in the previous section, the
<span v-pre>`{{.USER_WORKING_DIR}}`</span> special variable can be very handy
here to run stuff on the directory you're calling `task -g` from.
As mentioned in the previous section, the `{{.USER_WORKING_DIR}}` special
variable can be very handy here to run stuff on the directory you're calling
`task -g` from.
```yaml
version: '3'
@@ -136,7 +139,7 @@ tasks:
- echo $GREETING
```
::: info
:::info
`env` supports expansion and retrieving output from a shell command just like
variables, as you can see in the [Variables](#variables) section.
@@ -148,19 +151,15 @@ variables, as you can see in the [Variables](#variables) section.
You can also ask Task to include `.env` like files by using the `dotenv:`
setting:
::: code-group
```shell [.env]
```shell title=".env"
KEYNAME=VALUE
```
```shell [testing/.env]
```shell title="testing/.env"
ENDPOINT=testing.com
```
:::
```yaml
```yaml title="Taskfile.yml"
version: '3'
env:
@@ -207,7 +206,7 @@ tasks:
- echo "Using $KEYNAME and endpoint $ENDPOINT"
```
::: info
:::info
Please note that you are not currently able to use the `dotenv` key inside
included Taskfiles.
@@ -261,7 +260,7 @@ includes:
dir: ./docs
```
::: info
:::info
The included Taskfiles must be using the same schema version as the main
Taskfile uses.
@@ -306,13 +305,14 @@ includes:
### Flatten includes
You can flatten the included Taskfile tasks into the main Taskfile by using the
`flatten` option. It means that the included Taskfile tasks will be available
without the namespace.
You can flatten the included Taskfile tasks into the main Taskfile by using the `flatten` option.
It means that the included Taskfile tasks will be available without the namespace.
::: code-group
```yaml [Taskfile.yml]
<Tabs defaultValue="1">
<TabItem value="1" label="Taskfile.yml">
```yaml
version: '3'
includes:
@@ -327,7 +327,10 @@ tasks:
- task: foo
```
```yaml [Included.yml]
</TabItem>
<TabItem value="2" label="Included.yml">
```yaml
version: '3'
tasks:
@@ -336,7 +339,8 @@ tasks:
- echo "Foo"
```
:::
</TabItem>
</Tabs>
If you run `task -a` it will print :
@@ -348,8 +352,7 @@ task: Available tasks for this project:
You can run `task foo` directly without the namespace.
You can also reference the task in other tasks without the namespace. So if you
run `task greet` it will run `greet` and `foo` tasks and the output will be :
You can also reference the task in other tasks without the namespace. So if you run `task greet` it will run `greet` and `foo` tasks and the output will be :
```text
Greet
@@ -358,9 +361,10 @@ Foo
If multiple tasks have the same name, an error will be thrown:
::: code-group
<Tabs defaultValue="1">
<TabItem value="1" label="Taskfile.yml">
```yaml [Taskfile.yml]
```yaml
version: '3'
includes:
lib:
@@ -374,7 +378,10 @@ tasks:
- task: foo
```
```yaml [Included.yml]
</TabItem>
<TabItem value="2" label="Included.yml">
```yaml
version: '3'
tasks:
@@ -383,28 +390,27 @@ tasks:
- echo "Foo"
```
:::
</TabItem>
</Tabs>
If you run `task -a` it will print:
```text
task: Found multiple tasks (greet) included by "lib"
```
If the included Taskfile has a task with the same name as a task in the main
Taskfile, you may want to exclude it from the flattened tasks.
If the included Taskfile has a task with the same name as a task in the main Taskfile,
you may want to exclude it from the flattened tasks.
You can do this by using the
[`excludes` option](#exclude-tasks-from-being-included).
You can do this by using the [`excludes` option](#exclude-tasks-from-being-included).
### Exclude tasks from being included
You can exclude tasks from being included by using the `excludes` option. This
option takes the list of tasks to be excluded from this include.
You can exclude tasks from being included by using the `excludes` option. This option takes the list of tasks to be excluded from this include.
::: code-group
<Tabs defaultValue="1">
<TabItem value="1" label="Taskfile.yml">
```yaml [Taskfile.yml]
```yaml
version: '3'
includes:
included:
@@ -412,7 +418,10 @@ version: '3'
excludes: [foo]
```
```yaml [Included.yml]
</TabItem>
<TabItem value="2" label="Included.yml">
```yaml
version: '3'
tasks:
@@ -420,10 +429,9 @@ tasks:
bar: echo "Bar"
```
:::
</TabItem></Tabs>
`task included:foo` will throw an error because the `foo` task is excluded but
`task included:bar` will work and display `Bar`.
`task included:foo` will throw an error because the `foo` task is excluded but `task included:bar` will work and display `Bar`.
It's compatible with the `flatten` option.
@@ -462,13 +470,13 @@ includes:
aliases: [gen]
```
::: info
:::info
Vars declared in the included Taskfile have preference over the variables in the
including Taskfile! If you want a variable in an included Taskfile to be
overridable, use the
[default function](https://go-task.github.io/slim-sprig/defaults.html):
<span v-pre>`MY_VAR: '{{.MY_VAR | default "my-default-value"}}'`</span>.
`MY_VAR: '{{.MY_VAR | default "my-default-value"}}'`.
:::
@@ -561,7 +569,7 @@ tasks:
If there is more than one dependency, they always run in parallel for better
performance.
::: tip
:::tip
You can also make the tasks given by the command line run in parallel by using
the `--parallel` flag (alias `-p`). Example: `task --parallel js css`.
@@ -710,7 +718,7 @@ tasks:
The above syntax is also supported in `deps`.
::: tip
:::tip
NOTE: If you want to call a task declared in the root Taskfile from within an
[included Taskfile](#including-other-taskfiles), add a leading `:` like this:
@@ -777,6 +785,7 @@ instead of its checksum (content), just set the `method` property to
At the task level for a specific task:
```yaml
version: '3'
@@ -796,7 +805,7 @@ At the root level of the Taskfile to apply it globally to all tasks:
```yaml
version: '3'
method: timestamp # Will be the default for all tasks
method: timestamp # Will be the default for all tasks
tasks:
build:
@@ -808,12 +817,13 @@ tasks:
- app{{exeExt}}
```
In situations where you need more flexibility the `status` keyword can be used.
You can even combine the two. See the documentation for
[status](#using-programmatic-checks-to-indicate-a-task-is-up-to-date) for an
example.
::: info
:::info
By default, task stores checksums on a local `.task` directory in the project's
directory. Most of the time, you'll want to have this directory on `.gitignore`
@@ -833,7 +843,7 @@ export TASK_TEMP_DIR='~/.task'
:::
::: info
:::info
Each task has only one checksum stored for its `sources`. If you want to
distinguish a task by any of its input variables, you can add those variables as
@@ -846,13 +856,13 @@ change even if the source has not.
:::
::: tip
:::tip
The method `none` skips any validation and always runs the task.
:::
::: info
:::info
For the `checksum` (default) or `timestamp` method to work, it is only necessary
to inform the source files. When the `timestamp` method is used, the last time
@@ -886,14 +896,12 @@ tasks that generate remote artifacts (Docker images, deploys, CD releases) the
checksum source and timestamps require either access to the artifact or for an
out-of-band refresh of the `.checksum` fingerprint file.
Two special variables <span v-pre>`{{.CHECKSUM}}`</span> and
<span v-pre>`{{.TIMESTAMP}}`</span> are available for interpolation within
`cmds` and `status` commands, depending on the method assigned to fingerprint
the sources. Only `source` globs are fingerprinted.
Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available for
interpolation within `cmds` and `status` commands, depending on the method assigned to
fingerprint the sources. Only `source` globs are fingerprinted.
Note that the <span v-pre>`{{.TIMESTAMP}}`</span> variable is a "live" Go
`time.Time` struct, and can be formatted using any of the methods that
`time.Time` responds to.
Note that the `{{.TIMESTAMP}}` variable is a "live" Go `time.Time` struct, and
can be formatted using any of the methods that `time.Time` responds to.
See [the Go Time documentation](https://golang.org/pkg/time/) for more
information.
@@ -901,8 +909,8 @@ information.
You can use `--force` or `-f` if you want to force a task to run even when
up-to-date.
Also, `task --status [tasks]...` will exit with a non-zero
[exit code](/docs/reference/cli#exit-codes) if any of the tasks are not up-to-date.
Also, `task --status [tasks]...` will exit with a non-zero [exit
code](/reference/cli#exit-codes) if any of the tasks are not up-to-date.
`status` can be combined with the
[fingerprinting](#by-fingerprinting-locally-generated-files-and-their-sources)
@@ -1042,7 +1050,7 @@ requires:
vars: [] # Array of strings
```
::: info
:::note
Variables set to empty zero length strings, will pass the `requires` check.
@@ -1065,16 +1073,11 @@ tasks:
### Ensuring required variables have allowed values
If you want to ensure that a variable is set to one of a predefined set of valid
values before executing a task, you can use requires. This is particularly
useful when there are strict requirements for what values a variable can take,
and you want to provide clear feedback to the user when an invalid value is
detected.
If you want to ensure that a variable is set to one of a predefined set of valid values before executing a task, you can use requires.
This is particularly useful when there are strict requirements for what values a variable can take, and you want to provide clear feedback to the user when an invalid value is detected.
To use `requires`, you specify an array of allowed values in the vars
sub-section under requires. Task will check if the variable is set to one of the
allowed values. If the variable does not match any of these values, the task
will raise an error and stop execution.
To use `requires`, you specify an array of allowed values in the vars sub-section under requires. Task will check if the variable is set to one of the allowed values.
If the variable does not match any of these values, the task will raise an error and stop execution.
This check applies both to user-defined variables and environment variables.
@@ -1096,7 +1099,7 @@ tasks:
If `ENV` is not one of 'dev', 'beta' or 'prod' an error will be raised.
::: info
:::note
This is supported only for string variables.
@@ -1114,7 +1117,7 @@ variable types are supported:
- `array`
- `map`
::: info
:::note
Defining a map requires that you use a special `map` subkey (see example below).
@@ -1132,16 +1135,16 @@ tasks:
FLOAT: 3.14
ARRAY: [1, 2, 3]
MAP:
map: { A: 1, B: 2, C: 3 }
map: {A: 1, B: 2, C: 3}
cmds:
- 'echo {{.STRING}}' # Hello, World!
- 'echo {{.BOOL}}' # true
- 'echo {{.INT}}' # 42
- 'echo {{.FLOAT}}' # 3.14
- 'echo {{.ARRAY}}' # [1 2 3]
- 'echo {{.STRING}}' # Hello, World!
- 'echo {{.BOOL}}' # true
- 'echo {{.INT}}' # 42
- 'echo {{.FLOAT}}' # 3.14
- 'echo {{.ARRAY}}' # [1 2 3]
- 'echo {{index .ARRAY 0}}' # 1
- 'echo {{.MAP}}' # map[A:1 B:2 C:3]
- 'echo {{.MAP.A}}' # 1
- 'echo {{.MAP}}' # map[A:1 B:2 C:3]
- 'echo {{.MAP.A}}' # 1
```
Variables can be set in many places in a Taskfile. When executing
@@ -1164,7 +1167,7 @@ Example of sending parameters with environment variables:
$ TASK_VARIABLE=a-value task do-something
```
::: tip
:::tip
A special variable `.TASK` is always available containing the task name.
@@ -1210,9 +1213,8 @@ Example of a `default` value to be overridden from CLI:
```yaml
version: '3'
tasks:
greet_user:
desc: 'Greet the user with a name.'
desc: "Greet the user with a name."
vars:
USER_NAME: '{{.USER_NAME| default "DefaultUser"}}'
cmds:
@@ -1250,14 +1252,15 @@ This works for all types of variables.
### Referencing other variables
Templating is great for referencing string values if you want to pass a value
from one task to another. However, the templating engine is only able to output
strings. If you want to pass something other than a string to another task then
you will need to use a reference (`ref`) instead.
Templating is great for referencing string values if you want to pass
a value from one task to another. However, the templating engine is only able to
output strings. If you want to pass something other than a string to another
task then you will need to use a reference (`ref`) instead.
::: code-group
<Tabs defaultValue="2">
<TabItem value="1" label="Templating Engine">
```yaml [Templating Engine]
```yaml
version: 3
tasks:
@@ -1273,7 +1276,10 @@ tasks:
- 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A'
```
```yaml [Reference]
</TabItem>
<TabItem value="2" label="Reference">
```yaml
version: 3
tasks:
@@ -1290,10 +1296,11 @@ tasks:
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
```
:::
</TabItem>
</Tabs>
This also works the same way when calling `deps` and when defining a variable
and can be used in any combination:
This also works the same way when calling `deps` and when defining
a variable and can be used in any combination:
```yaml
version: 3
@@ -1350,7 +1357,7 @@ tasks:
vars:
JSON: '{"a": 1, "b": 2, "c": 3}'
FOO:
ref: 'fromJson .JSON'
ref: "fromJson .JSON"
cmds:
- echo {{.FOO}}
```
@@ -1395,10 +1402,9 @@ tasks:
cmds:
- for:
matrix:
OS: ['windows', 'linux', 'darwin']
ARCH: ['amd64', 'arm64']
cmd:
echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
```
This will output:
@@ -1415,11 +1421,11 @@ darwin/arm64
You can also use references to other variables as long as they are also lists:
```yaml
version: '3'
version: "3"
vars:
OS_VAR: ['windows', 'linux', 'darwin']
ARCH_VAR: ['amd64', 'arm64']
OS_VAR: ["windows", "linux", "darwin"]
ARCH_VAR: ["amd64", "arm64"]
tasks:
default:
@@ -1430,8 +1436,7 @@ tasks:
ref: .OS_VAR
ARCH:
ref: .ARCH_VAR
cmd:
echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
```
### Looping over your task's sources or generated files
@@ -1439,9 +1444,10 @@ tasks:
You are also able to loop over the sources of your task or the files it
generates:
::: code-group
<Tabs defaultValue="1" groupId="sources-generates">
<TabItem value="1" label="Sources">
```yaml [Sources]
```yaml
version: '3'
tasks:
@@ -1454,7 +1460,10 @@ tasks:
cmd: cat {{ .ITEM }}
```
```yaml [Generates]
</TabItem>
<TabItem value="2" label="Generates">
```yaml
version: '3'
tasks:
@@ -1467,7 +1476,8 @@ tasks:
cmd: cat {{ .ITEM }}
```
:::
</TabItem>
</Tabs>
This will also work if you use globbing syntax in `sources` or `generates`. For
example, if you specify a source for `*.txt`, the loop will iterate over all
@@ -1475,13 +1485,14 @@ files that match that glob.
Paths will always be returned as paths relative to the task directory. If you
need to convert this to an absolute path, you can use the built-in `joinPath`
function. There are some
[special variables](/docs/reference/templating#special-variables) that you may find
useful for this.
function. There are some [special
variables](/reference/templating/#special-variables) that you may find useful
for this.
::: code-group
<Tabs defaultValue="1" groupId="sources-generates">
<TabItem value="1" label="Sources">
```yaml [Sources]
```yaml
version: '3'
tasks:
@@ -1497,7 +1508,10 @@ tasks:
cmd: cat {{joinPath .MY_DIR .ITEM}}
```
```yaml [Generates]
</TabItem>
<TabItem value="2" label="Generates">
```yaml
version: '3'
tasks:
@@ -1513,7 +1527,8 @@ tasks:
cmd: cat {{joinPath .MY_DIR .ITEM}}
```
:::
</TabItem>
</Tabs>
### Looping over variables
@@ -1563,9 +1578,9 @@ tasks:
cmd: echo {{.ITEM}}
```
When looping over a map we also make an additional <span v-pre>`{{.KEY}}`</span>
variable available that holds the string value of the map key. Remember that
maps are unordered, so the order in which the items are looped over is random.
When looping over a map we also make an additional `{{.KEY}}` variable available
that holds the string value of the map key. Remember that maps are unordered, so
the order in which the items are looped over is random.
All of this also works with dynamic variables!
@@ -1716,14 +1731,14 @@ version: '3'
tasks:
start:*:*:
vars:
SERVICE: '{{index .MATCH 0}}'
REPLICAS: '{{index .MATCH 1}}'
SERVICE: "{{index .MATCH 0}}"
REPLICAS: "{{index .MATCH 1}}"
cmds:
- echo "Starting {{.SERVICE}} with {{.REPLICAS}} replicas"
start:*:
vars:
SERVICE: '{{index .MATCH 0}}'
SERVICE: "{{index .MATCH 0}}"
cmds:
- echo "Starting {{.SERVICE}}"
```
@@ -1789,7 +1804,7 @@ tasks:
cleanup: rm -rf tmpdir/
```
::: info
:::info
Due to the nature of how the
[Go's own `defer` work](https://go.dev/tour/flowcontrol/13), the deferred
@@ -1798,7 +1813,7 @@ commands are executed in the reverse order if you schedule multiple of them.
:::
A special variable `.EXIT_CODE` is exposed when a command exited with a non-zero
[exit code](/docs/reference/cli#exit-codes). You can check its presence to know if
[exit code](/reference/cli#exit-codes). You can check its presence to know if
the task completed successfully or not:
```yaml
@@ -1807,8 +1822,7 @@ version: '3'
tasks:
default:
cmds:
- defer:
echo '{{if .EXIT_CODE}}Failed with {{.EXIT_CODE}}!{{else}}Success!{{end}}'
- defer: echo '{{if .EXIT_CODE}}Failed with {{.EXIT_CODE}}!{{else}}Success!{{end}}'
- exit 1
```
@@ -1930,13 +1944,12 @@ version: '3'
tasks:
default:
cmds:
- task: print
vars:
MESSAGE: hello
- task: print
vars:
MESSAGE: world
- task: print
vars:
MESSAGE: hello
- task: print
vars:
MESSAGE: world
print:
label: 'print-{{.MESSAGE}}'
@@ -1993,14 +2006,14 @@ tasks:
dangerous:
prompt:
- This is a dangerous command... Do you want to continue?
- Are you sure?
- This is a dangerous command... Do you want to continue?
- Are you sure?
cmds:
- echo 'dangerous command'
```
Warning prompts are called before executing a task. If a prompt is denied Task
will exit with [exit code](/docs/reference/cli#exit-codes) 205. If approved, Task
will exit with [exit code](/reference/cli#exit-codes) 205. If approved, Task
will continue as normal.
```shell
@@ -2016,7 +2029,7 @@ To skip warning prompts automatically, you can use the `--yes` (alias `-y`)
option when calling the task. By including this option, all warnings, will be
automatically confirmed, and no prompts will be shown.
::: warning
:::caution
Tasks with prompts always fail by default on non-terminal environments, like a
CI, where an `stdin` won't be available for the user to answer. In those cases,
@@ -2259,7 +2272,7 @@ $ task default
[print-baz] baz
```
::: tip
:::tip
The `output` option can also be specified by the `--output` or `-o` flags.
@@ -2323,7 +2336,7 @@ tasks:
default: echo **/*.go
```
::: info
:::info
Keep in mind that not all options are available in the
[shell interpreter library](https://github.com/mvdan/sh) that Task uses.
@@ -2338,9 +2351,9 @@ which files to watch.
The default watch interval is 100 milliseconds, but it's possible to change it
by either setting `interval: '500ms'` in the root of the Taskfile or by passing
it as an argument like `--interval=500ms`. This interval is the time Task will
wait for duplicated events. It will only run the task again once, even if
multiple changes happen within the interval.
it as an argument like `--interval=500ms`.
This interval is the time Task will wait for duplicated events. It will only run
the task again once, even if multiple changes happen within the interval.
Also, it's possible to set `watch: true` in a given task and it'll automatically
run in watch mode:
@@ -2360,7 +2373,7 @@ tasks:
- go build # ...
```
::: info
:::info
Note that when setting `watch: true` to a task, it'll only run in watch mode
when running from the CLI via `task my-watch-task`, but won't run in watch mode
@@ -2368,12 +2381,13 @@ if called by another task, either directly or as a dependency.
:::
::: warning
:::caution
The watcher can misbehave in certain scenarios, in particular for long-running
servers. There is a known bug where child processes of the running might not be
killed appropriately. It's advised to avoid running commands as `go run` and
prefer `go build [...] && ./binary` instead.
servers.
There is a known bug where child processes of the running might not be killed
appropriately. It's adviced to avoid running commands as `go run` and prefer
`go build [...] && ./binary` instead.
If you are having issues, you might want to try tools specifically designed for
live-reloading, like [Air](https://github.com/air-verse/air/). Also, be sure to
@@ -2382,5 +2396,7 @@ to us.
:::
{/* prettier-ignore-start */}
[gotemplate]: https://golang.org/pkg/text/template/
[templating-reference]: /docs/reference/templating
[templating-reference]: ./reference/templating.mdx
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,249 @@
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
import { EnumChangefreq } from 'sitemap';
import remarkGithub from 'remark-github';
import remarkGfm from 'remark-gfm';
import { DISCORD_URL } from './constants';
import { GITHUB_URL } from './constants';
import { BLUESKY_URL } from './constants';
import { MASTODON_URL } from './constants';
import { TWITTER_URL } from './constants';
import { STACK_OVERFLOW } from './constants';
import { ANSWER_OVERFLOW } from './constants';
import lightCodeTheme from './src/themes/prismLight';
import darkCodeTheme from './src/themes/prismDark';
const config: Config = {
title: 'Task',
tagline: 'A task runner / simpler Make alternative written in Go ',
url: 'https://taskfile.dev',
baseUrl: '/',
onBrokenLinks: 'warn',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
organizationName: 'go-task',
projectName: 'task',
deploymentBranch: 'gh-pages',
i18n: {
defaultLocale: 'en',
locales: ['en'],
localeConfigs: {
en: {
label: 'English',
direction: 'ltr',
htmlLang: 'en-US'
}
}
},
presets: [
[
'classic',
{
docs: {
routeBasePath: '/',
sidebarPath: './sidebars.ts',
remarkPlugins: [remarkGithub, remarkGfm],
includeCurrentVersion: true,
versions: {
current: {
label: `Next 🚧`,
path: 'next',
badge: false
},
latest: {
label: 'Latest',
badge: false
}
}
},
blog: {
blogSidebarTitle: 'All posts',
blogSidebarCount: 'ALL'
},
theme: {
customCss: [
'./src/css/custom.css',
'./src/css/carbon.css',
]
},
gtag: {
trackingID: 'G-4RT25NXQ7N',
anonymizeIP: true
},
sitemap: {
changefreq: EnumChangefreq.WEEKLY,
priority: 0.5,
ignorePatterns: ['/tags/**']
}
} satisfies Preset.Options,
]
],
scripts: [
{
src: '/js/carbon.js',
async: true
}
],
themeConfig:{
metadata: [
{
name: 'og:image',
content: 'https://taskfile.dev/img/og-image.png'
}
],
navbar: {
title: 'Task',
logo: {
alt: 'Task Logo',
src: 'img/logo.svg'
},
items: [
{
type: 'doc',
docId: 'intro',
position: 'left',
label: 'Docs'
},
{
to: 'blog',
label: 'Blog',
position: 'left'
},
{
to: '/donate',
position: 'left',
label: 'Donate'
},
{
type: 'docsVersionDropdown',
position: 'right',
dropdownActiveClassDisabled: true,
},
{
href: GITHUB_URL,
title: 'GitHub',
position: 'right',
className: "header-icon-link icon-github",
},
{
href: DISCORD_URL,
title: 'Discord',
position: 'right',
className: "header-icon-link icon-discord",
},
{
href: TWITTER_URL,
title: 'X (Twitter)',
position: 'right',
className: "header-icon-link icon-twitter",
},
{
href: BLUESKY_URL,
title: 'Bluesky',
position: 'right',
className: "header-icon-link icon-bluesky",
},
{
href: MASTODON_URL,
title: 'Mastodon',
rel: 'me',
position: 'right',
className: "header-icon-link icon-mastodon",
}
]
},
footer: {
style: 'dark',
links: [
{
title: 'Pages',
items: [
{
label: 'Installation',
to: '/installation/'
},
{
label: 'Usage',
to: '/usage/'
},
{
label: 'Donate',
to: '/donate/'
}
]
},
{
title: 'Community',
items: [
{
label: 'GitHub',
href: GITHUB_URL
},
{
label: 'Discord',
href: DISCORD_URL
},
{
label: 'Twitter',
href: TWITTER_URL
},
{
label: 'Bluesky',
href: BLUESKY_URL,
rel: 'me'
},
{
label: 'Mastodon',
href: MASTODON_URL,
rel: 'me'
},
{
label: 'Stack Overflow',
href: STACK_OVERFLOW
},
{
label: 'Answer Overflow',
href: ANSWER_OVERFLOW
},
{
label: 'OpenCollective',
href: 'https://opencollective.com/task'
}
]
},
{
items: [
{
html: '<a target="_blank" href="https://www.netlify.com"><img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" /></a>'
}
]
}
]
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
additionalLanguages: [
"bash", // aka. shell
"json",
"powershell"
]
},
// NOTE(@andreynering): Don't worry, these keys are meant to be public =)
algolia: {
appId: '7IZIJ13AI7',
apiKey: '34b64ae4fc8d9da43d9a13d9710aaddc',
indexName: 'taskfile'
}
} satisfies Preset.ThemeConfig,
};
export default config;

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