Compare commits

..

50 Commits

Author SHA1 Message Date
Andrey Nering
aa83651da2 v3.49.1 2026-03-08 17:01:25 -03:00
Andrey Nering
4ddad9f9f7 Revert "fix: Call ReplaceVars() to resolve Ref's for imported global vars." (#2723) 2026-03-08 20:00:12 +00:00
Jannis
080ee8869f docs: schema: add tasks.task.method (#2718) 2026-03-08 11:45:27 +01:00
Andrey Nering
af943b064b ci: fix netlify prod deploy after release 2026-03-07 20:02:53 -03:00
Andrey Nering
962eada344 docs: update releasing guide
We have now more package managers being released automatically by
GoReleaser. Only Snapcraft still require manual steps.
2026-03-07 19:42:03 -03:00
Andrey Nering
a0d9750edf docs(changelog): fix case: git -> Git 2026-03-07 19:26:22 -03:00
Andrey Nering
a1b8985df0 v3.49.0 2026-03-07 19:20:58 -03:00
dependabot[bot]
21daf6160a chore(deps): bump go.opentelemetry.io/otel/sdk from 1.39.0 to 1.40.0 (#2712)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 19:49:38 -03:00
renovate[bot]
c70d28f7b8 chore(deps): update actions/upload-artifact action to v7 (#2714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:48:51 -03:00
Andrey Nering
90e6ef88dc security: pin github actions by commit (#2719) 2026-03-06 19:20:25 -03:00
renovate[bot]
a788034148 chore(deps): update all non-major dependencies (#2713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 22:32:30 +01:00
Timothy Rule
90bcbe9ba5 fix: address "taskfile not found" in some environments like docker (#2709) 2026-02-27 09:47:59 -03:00
Andrey Nering
60a808ca23 docs(readme): update | to 2026-02-23 17:26:14 -03:00
renovate[bot]
edc80aed81 chore(deps): update goreleaser/goreleaser-action action to v7 (#2706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 15:35:02 -03:00
renovate[bot]
68bea7f273 chore(deps): update pnpm to v10.30.1 (#2705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 15:34:23 -03:00
Valentin Maerten
c62f9c7147 fix(build): exclude unsupported windows/arm target 2026-02-21 15:18:04 +01:00
Valentin Maerten
c4ecff753d chore: changelog for #2607 2026-02-18 19:00:15 +01:00
Valentin Maerten
2ed77716be feat(config): add environment variable support for all taskrc options (#2607) 2026-02-18 18:58:13 +01:00
Valentin Maerten
a2f8e144ca chore: changelog for #2632 2026-02-18 18:57:53 +01:00
Timothy Rule
2cdd7d3e43 fix: Call ReplaceVars() to resolve Ref's for imported global vars. (#2632) 2026-02-18 18:55:20 +01:00
Andrey Nering
56b316a124 ci: fix lint 2026-02-17 15:43:13 -03:00
renovate[bot]
39ce6a21ac chore(deps): update all non-major dependencies 2026-02-17 15:43:13 -03:00
Andrey Nering
fc5f6fa3aa fix: pin yaml package to v3 for now (#2693) 2026-02-17 15:29:51 -03:00
Valentin Maerten
44a2f2e5f5 docs: add Cloudsmith attribution for deb/rpm package hosting 2026-02-15 17:22:01 +01:00
Valentin Maerten
d356c649aa chore: changelog for #2682 2026-02-15 17:01:02 +01:00
Valentin Maerten
ca24d32f37 chore: changelog for #2669 2026-02-15 14:56:52 +01:00
Timothy Rule
f63a63fa6c fix: improve error message when searching for Taskfile. (#2682) 2026-02-15 14:56:35 +01:00
renovate[bot]
c0ff7105e7 chore(deps): update peter-evans/find-comment action to v4 (#2684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-15 14:49:49 +01:00
Valentin Maerten
8b063d6b92 fix(git): check cache before context timeout in getOrCloneRepo (#2669) 2026-02-15 14:46:55 +01:00
Valentin Maerten
dc8ac5e79f chore: changelog for #2686 2026-02-15 14:45:47 +01:00
Timothy Rule
df7810ab63 fix: copy watch when merging tasks during import (#2686) 2026-02-15 14:42:59 +01:00
Pete Davison
82783417ea docs: update integration doc with details of extension config namespace change (#2428)
* docs: update integration doc with details of extension config namespace change

* docs: add descriptions of sorting modes
2026-02-14 20:47:06 +00:00
renovate[bot]
d5bed6b716 chore(deps): update peter-evans/create-or-update-comment action to v5 (#2673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-02 09:42:48 +01:00
renovate[bot]
c8f722c0d5 chore(deps): update actions/upload-artifact action to v6 (#2672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-02 09:35:52 +01:00
Andrey Nering
b7743eda88 chore(goreleaser): update descriptions 2026-01-31 16:20:31 -03:00
Valentin Maerten
c0796e9701 chore: changelog for #2656 2026-01-31 18:53:52 +01:00
Trim21
cf54be3266 fix(node_git): always use unix path style (#2656) 2026-01-31 18:49:22 +01:00
Jens Erat
e129ae2fac docs: fix dir headline level (#2665)
The other commands in this section are on headline level 4, which probably is also the expected one for `dir`.
2026-01-28 18:21:50 +00:00
Andrey Nering
ed69256512 chore: update readme title and description to match website 2026-01-27 22:28:02 -03:00
Andrey Nering
40ad9719d4 chore(website): improve home page title, including on opengraph 2026-01-27 22:18:49 -03:00
Andrey Nering
48f75f0913 docs(cli): mention --list with --silent 2026-01-27 21:53:52 -03:00
Andrey Nering
f000ea2b22 chore(website): have a good opengraph image 2026-01-27 21:53:52 -03:00
Andrey Nering
e8be687a40 chore(website): add "edit this page on github" links 2026-01-27 21:53:52 -03:00
renovate[bot]
788605a3a9 chore(deps): update actions/setup-go action to v6 (#2662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 21:42:04 +01:00
Andrey Nering
697cf442a2 docs(blog): adjust post title 2026-01-26 09:51:43 -03:00
Andrey Nering
e957edf783 chore(website): add goodx sponsor 2026-01-26 09:39:06 -03:00
Andrey Nering
09e7247d05 v3.48.0 2026-01-26 09:26:23 -03:00
Andrey Nering
502f24a2ad docs(changelog): add entry for #2658 and #2660 2026-01-26 09:24:26 -03:00
Valentin Maerten
f09f31c6d5 fix: skip prompting for vars when task if condition fails
Move the prompt for required variables AFTER the if condition check.
This avoids asking the user for input when the task won't run anyway.

The order in RunTask() is now:
1. FastCompiledTask
2. Check required vars early (non-interactive mode only)
3. CompiledTask (resolve dynamic vars)
4. Check if condition → exit early if false
5. Prompt for missing vars (only if task will run)
6. Validate required vars
2026-01-26 09:21:09 -03:00
Valentin Maerten
5a78808caa fix: evaluate task-level if condition after resolving dynamic variables 2026-01-26 09:21:09 -03:00
101 changed files with 2453 additions and 1859 deletions

View File

@@ -8,7 +8,7 @@ jobs:
issue-awaiting-response:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |

View File

@@ -8,7 +8,7 @@ jobs:
issue-closed:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |

View File

@@ -9,7 +9,7 @@ jobs:
if: github.event.label.name == format('status{0} proposed', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |
@@ -23,7 +23,7 @@ jobs:
if: github.event.label.name == format('status{0} draft', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |
@@ -37,7 +37,7 @@ jobs:
if: github.event.label.name == format('status{0} candidate', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |
@@ -51,7 +51,7 @@ jobs:
if: github.event.label.name == format('status{0} stable', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |
@@ -65,7 +65,7 @@ jobs:
if: github.event.label.name == format('status{0} released', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |
@@ -85,7 +85,7 @@ jobs:
if: github.event.label.name == format('status{0} abandoned', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |
@@ -105,7 +105,7 @@ jobs:
if: github.event.label.name == format('status{0} superseded', ':')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |

View File

@@ -8,7 +8,7 @@ jobs:
issue-needs-triage:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{secrets.GH_PAT}}
script: |

View File

@@ -16,25 +16,25 @@ jobs:
go-version: [1.24.x, 1.25.x]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{matrix.go-version}}
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: v2.8.0
version: v2.11.1
lint-jsonschema:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.14
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: install check-jsonschema
run: python -m pip install 'check-jsonschema==0.27.3'

View File

@@ -13,49 +13,49 @@ jobs:
if: contains(github.event.pull_request.labels.*.name, 'needs-build')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: '1.25.x'
go-version: '1.26.x'
cache: true
- uses: goreleaser/goreleaser-action@v6
- uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
with:
version: '~> v2'
args: release --snapshot --clean --config .goreleaser-pr.yml
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: task_linux_amd64
path: dist/task_linux_amd64.tar.gz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: task_linux_arm64
path: dist/task_linux_arm64.tar.gz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: task_darwin_amd64
path: dist/task_darwin_amd64.tar.gz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: task_darwin_arm64
path: dist/task_darwin_arm64.tar.gz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: task_windows_amd64
path: dist/task_windows_amd64.zip
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: checksums
path: dist/task_checksums.txt
- uses: peter-evans/find-comment@v3
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
id: find-comment
with:
token: ${{ secrets.GH_PAT || github.token }}
issue-number: ${{ github.event.pull_request.number }}
body-includes: '📦 Build artifacts ready!'
- uses: peter-evans/create-or-update-comment@v4
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
token: ${{ secrets.GH_PAT || github.token }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}

View File

@@ -9,17 +9,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
go-version: 1.26.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
with:
distribution: goreleaser-pro
version: latest

View File

@@ -14,16 +14,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.25.x
go-version: 1.26.x
- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
@@ -32,16 +32,16 @@ jobs:
run: npm install -g npm@latest
- name: Install Task
uses: go-task/setup-task@v1
uses: go-task/setup-task@0ab1b2a65bc55236a3bc64cde78f80e20e8885c2 # v1
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
package_json_file: 'website/package.json'
run_install: 'true'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
with:
distribution: goreleaser-pro
version: latest
@@ -55,3 +55,5 @@ jobs:
shell: bash
run: |
task website:deploy:prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View File

@@ -18,13 +18,13 @@ jobs:
runs-on: ${{matrix.platform}}
steps:
- name: Set up Go ${{matrix.go-version}}
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{matrix.go-version}}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download Go modules
run: go mod download

View File

@@ -22,6 +22,8 @@ builds:
goarch: '386'
- goos: darwin
goarch: riscv64
- goos: windows
goarch: arm
- goos: windows
goarch: riscv64
env:
@@ -60,7 +62,7 @@ nfpms:
- vendor: Task
homepage: https://taskfile.dev
maintainer: The Task authors <task@taskfile.dev>
description: Simple task runner written in Go
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
section: golang
license: MIT
conflicts:
@@ -80,7 +82,7 @@ nfpms:
brews:
- name: go-task
description: Task runner / simpler Make alternative written in Go
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
license: MIT
homepage: https://taskfile.dev
directory: Formula
@@ -100,8 +102,8 @@ brews:
winget:
- name: Task
publisher: Task
short_description: A task runner / simpler Make alternative written in Go
description: Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make.
short_description: The modern task runner.
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
license: MIT
homepage: https://taskfile.dev/
publisher_url: https://taskfile.dev/
@@ -144,7 +146,7 @@ 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
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
homepage: https://taskfile.dev
license: MIT
author: "The Task authors"
@@ -155,7 +157,6 @@ npms:
- "build-tool"
- "task-runner"
cloudsmiths:
- organization: "task"
repository: "{{if not .IsNightly}}task{{end}}"

View File

@@ -1,7 +1,25 @@
# Changelog
## Unreleased
## v3.49.0 - 2026-03-07
- Fixed included Taskfiles with `watch: true` not triggering watch mode when
called from the root Taskfile (#2686, #1763 by @trulede).
- Fixed Remote Git Taskfiles failing on Windows due to backslashes in URL paths
(#2656 by @Trim21).
- Fixed remote Git Taskfiles timing out when resolving includes after accepting
the trust prompt (#2669, #2668 by @vmaerten).
- Fixed unclear error message when Taskfile search stops at a directory
ownership boundary (#2682, #1683 by @trulede).
- Fixed global variables from imported Taskfiles not resolving `ref:` values
correctly (#2632 by @trulede).
- Every `.taskrc.yml` option can now be overridden with a `TASK_`-prefixed
environment variable, making CI and container configuration easier (#2607,
#1066 by @vmaerten).
## v3.48.0 - 2026-01-26
- Fixed `if:` conditions when using to check dynamic variables. Also, skip
variable prompt if task would be skipped by `if:` (#2658, #2660 by @vmaerten).
- Fixed `ROOT_TASKFILE` variable pointing to directory instead of the actual
Taskfile path when no explicit `-t` flag is provided (#2635, #1706 by
@trulede).

View File

@@ -3,14 +3,14 @@
<img src="website/src/public/img/logo.svg" width="200px" height="200px" />
</a>
<h1>Task</h1>
<h1>Task: The Modern Task Runner</h1>
<p>
Task is a task runner / build tool that aims to be simpler and easier to use than, for example, <a href="https://www.gnu.org/software/make/">GNU Make<a>.
A fast, cross-platform build tool inspired by Make, designed for modern workflows.
</p>
<p>
<a href="https://taskfile.dev/docs/installation">Installation</a> | <a href="https://taskfile.dev/docs/getting-started">Getting Started</a> | <a href="https://taskfile.dev/docs/guide">Docs</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> | <a href="https://fosstodon.org/@task">Mastodon</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
<a href="https://taskfile.dev/docs/installation">Installation</a> &bullet; <a href="https://taskfile.dev/docs/getting-started">Getting Started</a> &bullet; <a href="https://taskfile.dev/docs/guide">Docs</a> &bullet; <a href="https://twitter.com/taskfiledev">Twitter</a> &bullet; <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> &bullet; <a href="https://fosstodon.org/@task">Mastodon</a> &bullet; <a href="https://discord.gg/6TY36E39UK">Discord</a>
</p>
<h1>Gold Sponsors</h1>
@@ -22,6 +22,11 @@
<img src="website/src/public/img/devowl.io.svg" height="100px" width="200px" title="devowl.io" />
</a>
</td>
<td align="center" valign="middle">
<a target="_blank" href="https://goodx.international/">
<img src="website/src/public/img/goodx.svg" height="80px" width="200px" title="GoodX" />
</a>
</td>
<td align="center" valign="middle">
<a target="_blank" href="https://magic.dev/">
<img src="website/src/public/img/magic.png" height="100px" width="200px" title="Magic" />

View File

@@ -174,8 +174,6 @@ func run() error {
// Merge CLI variables first (e.g. FOO=bar) so they take priority over Taskfile defaults
e.Taskfile.Vars.Merge(globals, nil)
// Store CLI vars for scoped mode where they need highest priority
e.Compiler.CLIVars = globals
// Then ReverseMerge special variables so they're available for templating
cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash)

View File

@@ -9,7 +9,6 @@ import (
"strings"
"sync"
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
@@ -26,8 +25,6 @@ type Compiler struct {
TaskfileEnv *ast.Vars
TaskfileVars *ast.Vars
CLIVars *ast.Vars // CLI vars passed via command line (e.g., task foo VAR=value)
Graph *ast.TaskfileGraph
Logger *logger.Logger
@@ -47,236 +44,8 @@ func (c *Compiler) FastGetVariables(t *ast.Task, call *Call) (*ast.Vars, error)
return c.getVariables(t, call, false)
}
// isScopedMode returns true if scoped variable resolution should be used.
// Scoped mode requires the experiment to be enabled, a task with location info, and a graph.
func (c *Compiler) isScopedMode(t *ast.Task) bool {
return experiments.ScopedTaskfiles.Enabled() &&
t != nil &&
t.Location != nil &&
c.Graph != nil
}
func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*ast.Vars, error) {
if c.isScopedMode(t) {
return c.getScopedVariables(t, call, evaluateShVars)
}
return c.getLegacyVariables(t, call, evaluateShVars)
}
// getScopedVariables resolves variables in scoped mode.
// In scoped mode:
// - OS env vars are in {{.env.XXX}} namespace, not at root
// - Variables from sibling includes are isolated
//
// Variable resolution order (lowest to highest priority):
// 1. Root Taskfile vars
// 2. Include Taskfile vars
// 3. Include passthrough vars (includes: name: vars:)
// 4. Task vars
// 5. Call vars
// 6. CLI vars
func (c *Compiler) getScopedVariables(t *ast.Task, call *Call, evaluateShVars bool) (*ast.Vars, error) {
result := ast.NewVars()
specialVars, err := c.getSpecialVars(t, call)
if err != nil {
return nil, err
}
for k, v := range specialVars {
result.Set(k, ast.Var{Value: v})
}
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
return func(k string, v ast.Var) error {
cache := &templater.Cache{Vars: result}
newVar := templater.ReplaceVar(v, cache)
if !evaluateShVars && newVar.Value == nil {
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
return nil
}
if !evaluateShVars {
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
return nil
}
if err := cache.Err(); err != nil {
return err
}
if newVar.Value != nil || newVar.Sh == nil {
result.Set(k, ast.Var{Value: newVar.Value})
return nil
}
static, err := c.HandleDynamicVar(newVar, dir, env.GetFromVars(result))
if err != nil {
return err
}
result.Set(k, ast.Var{Value: static})
return nil
}
}
rangeFunc := getRangeFunc(c.Dir)
var taskRangeFunc func(k string, v ast.Var) error
if t != nil {
cache := &templater.Cache{Vars: result}
dir := templater.Replace(t.Dir, cache)
if err := cache.Err(); err != nil {
return nil, err
}
dir = filepathext.SmartJoin(c.Dir, dir)
taskRangeFunc = getRangeFunc(dir)
}
rootVertex, err := c.Graph.Root()
if err != nil {
return nil, err
}
envMap := make(map[string]any)
for _, e := range os.Environ() {
k, v, _ := strings.Cut(e, "=")
envMap[k] = v
}
resolveEnvToMap := func(k string, v ast.Var, dir string) error {
cache := &templater.Cache{Vars: result}
newVar := templater.ReplaceVar(v, cache)
if err := cache.Err(); err != nil {
return err
}
if newVar.Value != nil || newVar.Sh == nil {
if newVar.Value != nil {
envMap[k] = newVar.Value
}
return nil
}
if evaluateShVars {
envSlice := os.Environ()
for ek, ev := range envMap {
if s, ok := ev.(string); ok {
envSlice = append(envSlice, fmt.Sprintf("%s=%s", ek, s))
}
}
static, err := c.HandleDynamicVar(newVar, dir, envSlice)
if err != nil {
return err
}
envMap[k] = static
}
return nil
}
for k, v := range rootVertex.Taskfile.Env.All() {
if err := resolveEnvToMap(k, v, c.Dir); err != nil {
return nil, err
}
}
for k, v := range rootVertex.Taskfile.Vars.All() {
if err := rangeFunc(k, v); err != nil {
return nil, err
}
}
if t.Location.Taskfile != rootVertex.URI {
predecessorMap, err := c.Graph.PredecessorMap()
if err != nil {
return nil, err
}
var parentChain []*ast.TaskfileVertex
currentURI := t.Location.Taskfile
for {
edges := predecessorMap[currentURI]
if len(edges) == 0 {
break
}
var parentURI string
for _, edge := range edges {
parentURI = edge.Source
break
}
if parentURI == rootVertex.URI {
break
}
parentVertex, err := c.Graph.Vertex(parentURI)
if err != nil {
return nil, err
}
parentChain = append([]*ast.TaskfileVertex{parentVertex}, parentChain...)
currentURI = parentURI
}
for _, parent := range parentChain {
parentDir := filepath.Dir(parent.URI)
for k, v := range parent.Taskfile.Env.All() {
if err := resolveEnvToMap(k, v, parentDir); err != nil {
return nil, err
}
}
// Vars use the parent's directory too
parentRangeFunc := getRangeFunc(parentDir)
for k, v := range parent.Taskfile.Vars.All() {
if err := parentRangeFunc(k, v); err != nil {
return nil, err
}
}
}
includeVertex, err := c.Graph.Vertex(t.Location.Taskfile)
if err != nil {
return nil, err
}
includeDir := filepath.Dir(includeVertex.URI)
for k, v := range includeVertex.Taskfile.Env.All() {
if err := resolveEnvToMap(k, v, includeDir); err != nil {
return nil, err
}
}
includeRangeFunc := getRangeFunc(includeDir)
for k, v := range includeVertex.Taskfile.Vars.All() {
if err := includeRangeFunc(k, v); err != nil {
return nil, err
}
}
}
if t.IncludeVars != nil {
for k, v := range t.IncludeVars.All() {
if err := rangeFunc(k, v); err != nil {
return nil, err
}
}
}
if call != nil {
for k, v := range t.Vars.All() {
if err := taskRangeFunc(k, v); err != nil {
return nil, err
}
}
for k, v := range call.Vars.All() {
if err := taskRangeFunc(k, v); err != nil {
return nil, err
}
}
}
for k, v := range c.CLIVars.All() {
if err := rangeFunc(k, v); err != nil {
return nil, err
}
}
result.Set("env", ast.Var{Value: envMap})
return result, nil
}
// getLegacyVariables resolves variables in legacy mode.
// In legacy mode, all variables (including OS env) are merged at root level.
func (c *Compiler) getLegacyVariables(t *ast.Task, call *Call, evaluateShVars bool) (*ast.Vars, error) {
result := env.GetEnviron()
specialVars, err := c.getSpecialVars(t, call)
if err != nil {
return nil, err
@@ -288,22 +57,30 @@ func (c *Compiler) getLegacyVariables(t *ast.Task, call *Call, evaluateShVars bo
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
return func(k string, v ast.Var) error {
cache := &templater.Cache{Vars: result}
// Replace values
newVar := templater.ReplaceVar(v, cache)
// If the variable should not be evaluated, but is nil, set it to an empty string
// This stops empty interface errors when using the templater to replace values later
// Preserve the Sh field so it can be displayed in summary
if !evaluateShVars && newVar.Value == nil {
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
return nil
}
// If the variable should not be evaluated and it is set, we can set it and return
if !evaluateShVars {
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
return nil
}
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
if err := cache.Err(); err != nil {
return err
}
// If the variable is already set, we can set it and return
if newVar.Value != nil || newVar.Sh == nil {
result.Set(k, ast.Var{Value: newVar.Value})
return nil
}
// If the variable is dynamic, we need to resolve it first
static, err := c.HandleDynamicVar(newVar, dir, env.GetFromVars(result))
if err != nil {
return err
@@ -316,6 +93,8 @@ func (c *Compiler) getLegacyVariables(t *ast.Task, call *Call, evaluateShVars bo
var taskRangeFunc func(k string, v ast.Var) error
if t != nil {
// NOTE(@andreynering): We're manually joining these paths here because
// this is the raw task, not the compiled one.
cache := &templater.Cache{Vars: result}
dir := templater.Replace(t.Dir, cache)
if err := cache.Err(); err != nil {
@@ -335,7 +114,6 @@ func (c *Compiler) getLegacyVariables(t *ast.Task, call *Call, evaluateShVars bo
return nil, err
}
}
if t != nil {
for k, v := range t.IncludeVars.All() {
if err := rangeFunc(k, v); err != nil {
@@ -371,6 +149,7 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string) (string,
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
// If the variable is not dynamic or it is empty, return an empty string
if v.Sh == nil || *v.Sh == "" {
return "", nil
}

View File

@@ -8,7 +8,7 @@ import (
"strings"
"github.com/fatih/color"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
)
type (
@@ -50,10 +50,10 @@ func (err *TaskfileDecodeError) Error() string {
if len(te.Errors) > 1 {
fmt.Fprintln(buf, color.RedString("errs:"))
for _, message := range te.Errors {
fmt.Fprintln(buf, color.RedString("- %s", message.Err.Error()))
fmt.Fprintln(buf, color.RedString("- %s", message))
}
} else {
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0].Err.Error()))
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0]))
}
} else {
// Otherwise print the error message normally

View File

@@ -195,9 +195,9 @@ type TaskNotAllowedVarsError struct {
func (err *TaskNotAllowedVarsError) Error() string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName))
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName)) //nolint:staticcheck
for _, s := range err.NotAllowedVars {
builder.WriteString(fmt.Sprintf(" - %s has an invalid value : '%s' (allowed values : %v)\n", s.Name, s.Value, s.Enum))
builder.WriteString(fmt.Sprintf(" - %s has an invalid value : '%s' (allowed values : %v)\n", s.Name, s.Value, s.Enum)) //nolint:staticcheck
}
return builder.String()

View File

@@ -11,14 +11,17 @@ import (
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
// searching the filesystem.
type TaskfileNotFoundError struct {
URI string
Walk bool
AskInit bool
URI string
Walk bool
AskInit bool
OwnerChange bool
}
func (err TaskfileNotFoundError) Error() string {
var walkText string
if err.Walk {
if err.OwnerChange {
walkText = " (or any of the parent directories until ownership changed)."
} else if err.Walk {
walkText = " (or any of the parent directories)."
}
if err.AskInit {

View File

@@ -63,7 +63,6 @@ type (
// Internal
Taskfile *ast.Taskfile
Graph *ast.TaskfileGraph
Logger *logger.Logger
Compiler *Compiler
Output output.Output

View File

@@ -1160,6 +1160,10 @@ func TestIf(t *testing.T) {
// For loop with if
{name: "if-in-for-loop", task: "if-in-for-loop", verbose: true},
// Task-level if with dynamic variable
{name: "task-if-dynamic-true", task: "task-if-dynamic-true"},
{name: "task-if-dynamic-false", task: "task-if-dynamic-false", verbose: true},
}
for _, test := range tests {
@@ -1180,114 +1184,3 @@ func TestIf(t *testing.T) {
NewExecutorTest(t, opts...)
}
}
//nolint:paralleltest // enableExperimentForTest modifies global state
func TestScopedTaskfiles(t *testing.T) {
// Legacy tests (without experiment) - vars should be merged globally
t.Run("legacy", func(t *testing.T) {
// Test with scoped taskfiles disabled (legacy) - vars should be merged globally
NewExecutorTest(t,
WithName("default"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
)
// In legacy mode, UNIQUE_B should be accessible (merged globally)
NewExecutorTest(t,
WithName("cross-include"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("a:try-access-b"),
)
})
// Scoped tests (with experiment enabled) - vars should be isolated
t.Run("scoped", func(t *testing.T) {
enableExperimentForTest(t, &experiments.ScopedTaskfiles, 1)
// Test with scoped taskfiles enabled - vars should be isolated
NewExecutorTest(t,
WithName("default"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
)
// Test inheritance: include can access root vars
NewExecutorTest(t,
WithName("inheritance-a"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("a:print"),
)
// Test isolation: each include sees its own vars
NewExecutorTest(t,
WithName("isolation-b"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("b:print"),
)
// In scoped mode, UNIQUE_B should be empty (isolated)
NewExecutorTest(t,
WithName("cross-include"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("a:try-access-b"),
)
// Test env namespace: {{.env.XXX}} should access env vars
NewExecutorTest(t,
WithName("env-namespace"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("print-env"),
)
// Test env separation: {{.ROOT_ENV}} at root should be empty (env not at root level)
NewExecutorTest(t,
WithName("env-separation"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("test-env-separation"),
)
// Test include env: include's env is accessible via {{.env.XXX}}
NewExecutorTest(t,
WithName("include-env"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("a:print-env"),
)
// Test call vars: vars passed when calling a task override task vars
NewExecutorTest(t,
WithName("call-vars"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("call-with-vars"),
)
// Test nested includes (3 levels: root → a → nested)
// Verifies that nested includes inherit vars from their parent chain
NewExecutorTest(t,
WithName("nested"),
WithExecutorOptions(
task.WithDir("testdata/scoped_taskfiles"),
task.WithSilent(true),
),
WithTask("a:nested:print"),
)
})
}

View File

@@ -19,7 +19,6 @@ var (
GentleForce Experiment
RemoteTaskfiles Experiment
EnvPrecedence Experiment
ScopedTaskfiles Experiment
)
// Inactive experiments. These are experiments that cannot be enabled, but are
@@ -44,7 +43,6 @@ func ParseWithConfig(dir string, config *ast.TaskRC) {
GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
EnvPrecedence = New("ENV_PRECEDENCE", config, 1)
ScopedTaskfiles = New("SCOPED_TASKFILES", config, 1)
AnyVariables = New("ANY_VARIABLES", config)
MapVariables = New("MAP_VARIABLES", config)
}

44
go.mod
View File

@@ -2,15 +2,15 @@ module github.com/go-task/task/v3
go 1.24.6
toolchain go1.25.6
toolchain go1.26.1
require (
charm.land/bubbles/v2 v2.0.0-rc.1
charm.land/bubbletea/v2 v2.0.0-rc.2
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7
charm.land/bubbles/v2 v2.0.0
charm.land/bubbletea/v2 v2.0.1
charm.land/lipgloss/v2 v2.0.0
github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/alecthomas/chroma/v2 v2.23.0
github.com/alecthomas/chroma/v2 v2.23.1
github.com/chainguard-dev/git-urls v1.0.2
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/dominikbraun/graph v0.23.0
@@ -23,15 +23,15 @@ require (
github.com/hashicorp/go-getter v1.8.4
github.com/joho/godotenv v1.5.1
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/puzpuzpuz/xsync/v4 v4.3.0
github.com/puzpuzpuz/xsync/v4 v4.4.0
github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.8.0
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/zeebo/xxh3 v1.0.2
go.yaml.in/yaml/v4 v4.0.0-rc.3
github.com/zeebo/xxh3 v1.1.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/sync v0.19.0
golang.org/x/term v0.39.0
golang.org/x/term v0.40.0
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997
mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b
)
@@ -70,15 +70,15 @@ require (
github.com/aws/smithy-go v1.24.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.3.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 // indirect
github.com/charmbracelet/x/ansi v0.11.1 // indirect
github.com/charmbracelet/colorprofile v0.4.2 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.5.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -95,12 +95,12 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
@@ -119,15 +119,15 @@ require (
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/api v0.256.0 // indirect

67
go.sum
View File

@@ -2,10 +2,16 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
charm.land/bubbles/v2 v2.0.0-rc.1 h1:EiIFVAc3Zi/yY86td+79mPhHR7AqZ1OxF+6ztpOCRaM=
charm.land/bubbles/v2 v2.0.0-rc.1/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
charm.land/bubbletea/v2 v2.0.0-rc.2 h1:TdTbUOFzbufDJmSz/3gomL6q+fR6HwfY+P13hXQzD7k=
charm.land/bubbletea/v2 v2.0.0-rc.2/go.mod h1:IXFmnCnMLTWw/KQ9rEatSYqbAPAYi8kA3Yqwa1SFnLk=
charm.land/bubbletea/v2 v2.0.1 h1:B8e9zzK7x9JJ+XvHGF4xnYu9Xa0E0y0MyggY6dbaCfQ=
charm.land/bubbletea/v2 v2.0.1/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 h1:059k1h5vvZ4ASinki9nmBguxu9Rq0UDDSa6q8LOUphk=
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU=
charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo=
charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
@@ -40,8 +46,8 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
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.23.0 h1:u/Orux1J0eLuZDeQ44froV8smumheieI0EofhbyKhhk=
github.com/alecthomas/chroma/v2 v2.23.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -94,10 +100,16 @@ github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiw
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 h1:7Rs87fbKJoIIxsQS8YKJYGYa0tlsDwwb0twQjV1KB+g=
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38/go.mod h1:6lfcr3MNP+kZR25sF1nQwJFuQnNYBlFy3PGX5rvslXc=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
github.com/charmbracelet/x/ansi v0.11.1 h1:iXAC8SyMQDJgtcz9Jnw+HU8WMEctHzoTAETIeA3JXMk=
github.com/charmbracelet/x/ansi v0.11.1/go.mod h1:M49wjzpIujwPceJ+t5w3qh2i87+HRtHohgb5iTyepL0=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
@@ -108,10 +120,14 @@ github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
@@ -183,8 +199,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/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
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=
@@ -202,6 +218,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
@@ -216,8 +234,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q=
github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -252,8 +270,8 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
@@ -262,20 +280,20 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
@@ -286,12 +304,11 @@ golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=

62
internal/env/env.go vendored
View File

@@ -3,7 +3,9 @@ package env
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/taskfile/ast"
@@ -61,3 +63,63 @@ func isTypeAllowed(v any) bool {
func GetTaskEnv(key string) string {
return os.Getenv(taskVarPrefix + key)
}
// GetTaskEnvBool returns the boolean value of a TASK_ prefixed env var.
// Returns the value and true if set and valid, or false and false if not set or invalid.
func GetTaskEnvBool(key string) (bool, bool) {
v := GetTaskEnv(key)
if v == "" {
return false, false
}
b, err := strconv.ParseBool(v)
return b, err == nil
}
// GetTaskEnvInt returns the integer value of a TASK_ prefixed env var.
// Returns the value and true if set and valid, or 0 and false if not set or invalid.
func GetTaskEnvInt(key string) (int, bool) {
v := GetTaskEnv(key)
if v == "" {
return 0, false
}
i, err := strconv.Atoi(v)
return i, err == nil
}
// GetTaskEnvDuration returns the duration value of a TASK_ prefixed env var.
// Returns the value and true if set and valid, or 0 and false if not set or invalid.
func GetTaskEnvDuration(key string) (time.Duration, bool) {
v := GetTaskEnv(key)
if v == "" {
return 0, false
}
d, err := time.ParseDuration(v)
return d, err == nil
}
// GetTaskEnvString returns the string value of a TASK_ prefixed env var.
// Returns the value and true if set (non-empty), or empty string and false if not set.
func GetTaskEnvString(key string) (string, bool) {
v := GetTaskEnv(key)
return v, v != ""
}
// GetTaskEnvStringSlice returns a comma-separated list from a TASK_ prefixed env var.
// Returns the slice and true if set (non-empty), or nil and false if not set.
func GetTaskEnvStringSlice(key string) ([]string, bool) {
v := GetTaskEnv(key)
if v == "" {
return nil, false
}
parts := strings.Split(v, ",")
result := make([]string, 0, len(parts))
for _, p := range parts {
if trimmed := strings.TrimSpace(p); trimmed != "" {
result = append(result, trimmed)
}
}
if len(result) == 0 {
return nil, false
}
return result, true
}

View File

@@ -130,15 +130,15 @@ func init() {
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", getConfig(config, "REMOTE_INSECURE", func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.")
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
pflag.BoolVar(&Interactive, "interactive", getConfig(config, func() *bool { return config.Interactive }, false), "Prompt for missing required variables.")
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, "VERBOSE", func() *bool { return config.Verbose }, false), "Enables verbose mode.")
pflag.BoolVarP(&Silent, "silent", "s", getConfig(config, "SILENT", func() *bool { return config.Silent }, false), "Disables echoing.")
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, "DISABLE_FUZZY", func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
pflag.BoolVarP(&AssumeYes, "yes", "y", getConfig(config, "ASSUME_YES", func() *bool { return nil }, false), "Assume \"yes\" as answer to all prompts.")
pflag.BoolVar(&Interactive, "interactive", getConfig(config, "INTERACTIVE", func() *bool { return config.Interactive }, false), "Prompt for missing required variables.")
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.")
pflag.BoolVarP(&Dry, "dry", "n", getConfig(config, "DRY", func() *bool { return nil }, false), "Compiles and prints tasks in the order that they would be run, without executing them.")
pflag.BoolVar(&Summary, "summary", false, "Show summary about a task.")
pflag.BoolVarP(&ExitCode, "exit-code", "x", false, "Pass-through the exit code of the task command.")
pflag.StringVarP(&Dir, "dir", "d", "", "Sets the directory in which Task will execute and look for a Taskfile.")
@@ -147,10 +147,10 @@ func init() {
pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.")
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
pflag.BoolVarP(&Color, "color", "c", getConfig(config, func() *bool { return config.Color }, 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.BoolVarP(&Color, "color", "c", getConfig(config, "COLOR", func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, "CONCURRENCY", func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, "FAILFAST", func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
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.")
@@ -165,21 +165,21 @@ 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.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
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", getConfig(config, "REMOTE_OFFLINE", func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, "REMOTE_TRUSTED_HOSTS", func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
pflag.DurationVar(&Timeout, "timeout", getConfig(config, "REMOTE_TIMEOUT", func() *time.Duration { return config.Remote.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.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
pflag.StringVar(&CACert, "cacert", getConfig(config, func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
pflag.StringVar(&Cert, "cert", getConfig(config, func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
pflag.StringVar(&CertKey, "cert-key", getConfig(config, func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, "REMOTE_CACHE_EXPIRY", func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, "REMOTE_CACHE_DIR", func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
pflag.StringVar(&CACert, "cacert", getConfig(config, "REMOTE_CACERT", func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
pflag.StringVar(&Cert, "cert", getConfig(config, "REMOTE_CERT", func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
pflag.StringVar(&CertKey, "cert-key", getConfig(config, "REMOTE_CERT_KEY", func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
}
pflag.Parse()
// Auto-detect color based on environment when not explicitly configured
// Priority: CLI flag > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
colorExplicitlySet := pflag.Lookup("color").Changed || (config != nil && config.Color != nil)
// Priority: CLI flag > TASK_COLOR env > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
colorExplicitlySet := pflag.Lookup("color").Changed || env.GetTaskEnv("COLOR") != "" || (config != nil && config.Color != nil)
if !colorExplicitlySet {
if os.Getenv("NO_COLOR") != "" {
Color = false
@@ -311,15 +311,45 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
)
}
// 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
// getConfig extracts a config value with priority: env var > taskrc config > fallback
func getConfig[T any](config *taskrcast.TaskRC, envKey string, fieldFunc func() *T, fallback T) T {
if envKey != "" {
if val, ok := getEnvAs[T](envKey); ok {
return val
}
}
field := fieldFunc()
if field != nil {
return *field
if config != nil {
if field := fieldFunc(); field != nil {
return *field
}
}
return fallback
}
// getEnvAs parses a TASK_ prefixed env var as type T
func getEnvAs[T any](envKey string) (T, bool) {
var zero T
switch any(zero).(type) {
case bool:
if val, ok := env.GetTaskEnvBool(envKey); ok {
return any(val).(T), true
}
case int:
if val, ok := env.GetTaskEnvInt(envKey); ok {
return any(val).(T), true
}
case time.Duration:
if val, ok := env.GetTaskEnvDuration(envKey); ok {
return any(val).(T), true
}
case string:
if val, ok := env.GetTaskEnvString(envKey); ok {
return any(val).(T), true
}
case []string:
if val, ok := env.GetTaskEnvStringSlice(envKey); ok {
return any(val).(T), true
}
}
return zero, false
}

View File

@@ -108,10 +108,10 @@ func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, er
}
}
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
if err != nil {
return nil, err
}
return append(entrypoints, paths...), nil
// The call to SearchNPathRecursively is ambiguous and may return
// os.ErrPermission if its search ends, however it may have still
// returned valid paths. Caller may choose to ignore that error.
return append(entrypoints, paths...), err
}
// SearchPath will check if a file at the given path exists or not. If it does,
@@ -153,13 +153,16 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
// 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)
if err != nil {
return "", err
if len(paths) > 0 {
// Regardless of the error, return the first possible filename.
return paths[0], nil
} else {
if err != nil {
return "", err
} else {
return "", os.ErrNotExist
}
}
if len(paths) == 0 {
return "", os.ErrNotExist
}
return paths[0], nil
}
// SearchNPathRecursively walks up the directory tree starting at the given
@@ -188,10 +191,13 @@ func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]s
return nil, 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) {
// If the user id of the directory changes indicate a permission error, otherwise
// the calling code will infer an error condition based on the accumulated
// contents of paths.
if path == parentPath {
return paths, nil
} else if parentOwner != owner {
return paths, os.ErrPermission
}
owner = parentOwner

View File

@@ -231,7 +231,7 @@ func formatMap(m map[string]any, indent int) string {
spaces := strings.Repeat(" ", indent)
for k, v := range m {
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v))
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v)) //nolint:staticcheck
}
return result.String()

View File

@@ -9,7 +9,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/google/uuid"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"mvdan.cc/sh/v3/shell"
"mvdan.cc/sh/v3/syntax"

View File

@@ -1 +1 @@
3.47.0
3.49.1

View File

@@ -13,11 +13,9 @@ import (
"github.com/sajari/fuzzy"
"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/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/version"
@@ -61,12 +59,10 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
taskfile.WithCert(e.Cert),
taskfile.WithCertKey(e.CertKey),
)
if os.IsNotExist(err) {
return nil, errors.TaskfileNotFoundError{
URI: fsext.DefaultDir(e.Entrypoint, e.Dir),
Walk: true,
AskInit: true,
}
var taskNotFoundError errors.TaskfileNotFoundError
if errors.As(err, &taskNotFoundError) {
taskNotFoundError.AskInit = true
return nil, taskNotFoundError
}
if err != nil {
return nil, err
@@ -105,8 +101,7 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
}
return err
}
e.Graph = graph
if e.Taskfile, err = graph.Merge(experiments.ScopedTaskfiles.Enabled()); err != nil {
if e.Taskfile, err = graph.Merge(); err != nil {
return err
}
return nil
@@ -228,7 +223,6 @@ func (e *Executor) setupCompiler() error {
UserWorkingDir: e.UserWorkingDir,
TaskfileEnv: e.Taskfile.Env,
TaskfileVars: e.Taskfile.Vars,
Graph: e.Graph,
Logger: e.Logger,
}
return nil

21
task.go
View File

@@ -148,6 +148,20 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
return nil
}
// Check required vars early (before template compilation) if we can't prompt.
// This gives a clear "missing required variables" error instead of a template error.
if !e.canPrompt() {
if err := e.areTaskRequiredVarsSet(t); err != nil {
return err
}
}
t, err = e.CompiledTask(call)
if err != nil {
return err
}
// Check if condition after CompiledTask so dynamic variables are resolved
if strings.TrimSpace(t.If) != "" {
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: t.If,
@@ -159,7 +173,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
}
}
// Prompt for missing required vars (just-in-time for sequential task calls)
// Prompt for missing required vars after if check (avoid prompting if task won't run)
prompted, err := e.promptTaskVars(t, call)
if err != nil {
return err
@@ -176,11 +190,6 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
return err
}
t, err = e.CompiledTask(call)
if err != nil {
return err
}
if err := e.areTaskRequiredVarsAllowedValuesSet(t); err != nil {
return err
}

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -45,21 +45,7 @@ func (tfg *TaskfileGraph) Visualize(filename string) error {
return draw.DOT(tfg.Graph, f)
}
// Root returns the root vertex of the graph (the entrypoint Taskfile).
func (tfg *TaskfileGraph) Root() (*TaskfileVertex, error) {
hashes, err := graph.TopologicalSort(tfg.Graph)
if err != nil {
return nil, err
}
if len(hashes) == 0 {
return nil, fmt.Errorf("task: graph has no vertices")
}
return tfg.Vertex(hashes[0])
}
// Merge merges all included Taskfiles into the root Taskfile.
// If skipVarsMerge is true, variables are not merged (used for scoped includes).
func (tfg *TaskfileGraph) Merge(skipVarsMerge bool) (*Taskfile, error) {
func (tfg *TaskfileGraph) Merge() (*Taskfile, error) {
hashes, err := graph.TopologicalSort(tfg.Graph)
if err != nil {
return nil, err
@@ -106,7 +92,6 @@ func (tfg *TaskfileGraph) Merge(skipVarsMerge bool) (*Taskfile, error) {
if err := vertex.Taskfile.Merge(
includedVertex.Taskfile,
include,
skipVarsMerge,
); err != nil {
return err
}

View File

@@ -5,7 +5,7 @@ import (
"sync"
"github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -4,7 +4,7 @@ import (
"iter"
"github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/goext"

View File

@@ -3,7 +3,7 @@ package ast
import (
"fmt"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/taskfile/ast"
)

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -5,7 +5,7 @@ import (
"regexp"
"strings"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
@@ -242,6 +242,7 @@ func (t *Task) DeepCopy() *Task {
Requires: t.Requires.DeepCopy(),
Namespace: t.Namespace,
FullName: t.FullName,
Watch: t.Watch,
Failfast: t.Failfast,
}
return c

View File

@@ -5,7 +5,7 @@ import (
"time"
"github.com/Masterminds/semver/v3"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)
@@ -36,9 +36,8 @@ type Taskfile struct {
Interval time.Duration
}
// Merge merges the second Taskfile into the first.
// If skipVarsMerge is true, variables are not merged (used for scoped includes).
func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include, skipVarsMerge bool) error {
// Merge merges the second Taskfile into the first
func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
if !t1.Version.Equal(t2.Version) {
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
}
@@ -68,11 +67,8 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include, skipVarsMerge bool) er
}
}
}
// Only merge vars if not using scoped includes, or if flattening
if !skipVarsMerge || include.Flatten {
t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include)
}
t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include)
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/taskfile/ast"
)

View File

@@ -8,7 +8,7 @@ import (
"sync"
"github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"

View File

@@ -1,7 +1,7 @@
package ast
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
)

View File

@@ -5,7 +5,7 @@ import (
"sync"
"github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -22,7 +22,13 @@ func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error)
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, DefaultTaskfiles)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: false}
if entrypoint == "" {
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: true}
} else {
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: false}
}
} else if errors.Is(err, os.ErrPermission) {
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: true, OwnerChange: true}
}
return nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"sync"
@@ -127,19 +128,19 @@ func (node *GitNode) getOrCloneRepo(ctx context.Context) (string, error) {
repoMutex.Lock()
defer repoMutex.Unlock()
// Check if context was cancelled while waiting for lock
if err := ctx.Err(); err != nil {
return "", fmt.Errorf("context cancelled while waiting for repository lock: %w", err)
}
cacheDir := filepath.Join(os.TempDir(), "task-git-repos", cacheKey)
// check if repo is already cached (under the lock)
// Check cache FIRST - if already cloned, no network needed, timeout irrelevant
gitDir := filepath.Join(cacheDir, ".git")
if _, err := os.Stat(gitDir); err == nil {
return cacheDir, nil
}
// Only check context if we need to clone (requires network)
if err := ctx.Err(); err != nil {
return "", fmt.Errorf("context cancelled while waiting for repository lock: %w", err)
}
getterURL := node.buildURL()
client := &getter.Client{
@@ -191,8 +192,8 @@ func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
return entrypoint, nil
}
dir, _ := filepath.Split(node.path)
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, filepath.Join(dir, entrypoint))
dir, _ := path.Split(node.path)
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, path.Join(dir, entrypoint))
if node.ref != "" {
return fmt.Sprintf("%s?ref=%s", resolvedEntrypoint, node.ref), nil
}

View File

@@ -9,7 +9,7 @@ import (
"time"
"github.com/dominikbraun/graph"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"golang.org/x/sync/errgroup"
"github.com/go-task/task/v3/errors"

View File

@@ -12,6 +12,7 @@ import (
type TaskRC struct {
Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"`
Silent *bool `yaml:"silent"`
Color *bool `yaml:"color"`
DisableFuzzy *bool `yaml:"disable-fuzzy"`
Concurrency *int `yaml:"concurrency"`
@@ -63,6 +64,7 @@ func (t *TaskRC) Merge(other *TaskRC) {
t.Remote.CertKey = cmp.Or(other.Remote.CertKey, t.Remote.CertKey)
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
t.Silent = cmp.Or(other.Silent, t.Silent)
t.Color = cmp.Or(other.Color, t.Color)
t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)

View File

@@ -3,7 +3,7 @@ package taskrc
import (
"os"
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v3"
"github.com/go-task/task/v3/taskrc/ast"
)

View File

@@ -6,6 +6,7 @@ import (
"slices"
"strings"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/taskrc/ast"
)
@@ -62,6 +63,9 @@ func GetConfig(dir string) (*ast.TaskRC, error) {
return config, err
}
entrypoints, err := fsext.SearchAll("", absDir, defaultTaskRCs)
if errors.Is(err, os.ErrPermission) {
err = nil
}
if err != nil {
return config, err
}

View File

@@ -158,3 +158,21 @@ tasks:
if: '{{ eq .ENV "dev" }}'
cmds:
- echo "should not appear"
# Task-level if with dynamic variable (condition met)
task-if-dynamic-true:
vars:
ENABLE_FEATURE:
sh: 'echo "true"'
if: '{{ eq .ENABLE_FEATURE "true" }}'
cmds:
- echo "dynamic feature enabled"
# Task-level if with dynamic variable (condition not met)
task-if-dynamic-false:
vars:
ENABLE_FEATURE:
sh: 'echo "false"'
if: '{{ eq .ENABLE_FEATURE "true" }}'
cmds:
- echo "should not appear"

View File

@@ -0,0 +1,2 @@
task: dynamic variable: "echo \"false\"" result: "false"
task: if condition not met - skipped: "task-if-dynamic-false"

View File

@@ -0,0 +1 @@
dynamic feature enabled

View File

@@ -1,57 +0,0 @@
version: "3"
env:
ROOT_ENV: env_from_root
SHARED_ENV: shared_from_root
vars:
ROOT_VAR: from_root
includes:
a: ./inc_a
b: ./inc_b
tasks:
default:
desc: Test scoped includes - vars should be isolated
cmds:
- task: a:print
- task: b:print
print-root-var:
desc: Print ROOT_VAR from root
cmds:
- echo "ROOT_VAR={{.ROOT_VAR}}"
print-env:
desc: Print env vars using {{.env.XXX}} syntax
cmds:
- echo "ROOT_ENV={{.env.ROOT_ENV}}"
- echo "SHARED_ENV={{.env.SHARED_ENV}}"
- echo "PATH_EXISTS={{if .env.PATH}}yes{{else}}no{{end}}"
test-env-separation:
desc: Test that env is NOT at root level in scoped mode
cmds:
# In scoped mode, {{.ROOT_ENV}} should be empty (env not at root)
# In legacy mode, {{.ROOT_ENV}} would have the value
- echo "ROOT_ENV_AT_ROOT={{.ROOT_ENV}}"
prout:
vars:
LOL: prout_from_root
cmds:
- echo "{{.LOL}}"
call-with-vars:
desc: Test calling a task with vars override
cmds:
- task: print-name
vars:
NAME: from_caller
print-name:
vars:
NAME: default_name
cmds:
- echo "NAME={{.NAME}}"

View File

@@ -1,38 +0,0 @@
version: "3"
env:
INC_A_ENV: env_from_a
SHARED_ENV: shared_from_a
vars:
VAR: value_from_a
UNIQUE_A: only_in_a
includes:
nested: ./nested
tasks:
print:
desc: Print vars from include A
cmds:
- echo "A:UNIQUE_A={{.UNIQUE_A}}"
- echo "A:ROOT_VAR={{.ROOT_VAR}}"
try-access-b:
desc: Try to access B's unique var (should fail in scoped mode)
cmds:
- echo "A:UNIQUE_B={{.UNIQUE_B}}"
print-env:
desc: Print env vars from include A
cmds:
- echo "A:INC_A_ENV={{.env.INC_A_ENV}}"
- echo "A:ROOT_ENV={{.env.ROOT_ENV}}"
- echo "A:SHARED_ENV={{.env.SHARED_ENV}}"
test-env-in-var:
desc: Test using env in a var template
vars:
COMPOSED: "env={{.env.ROOT_ENV}}"
cmds:
- echo "{{.COMPOSED}}"

View File

@@ -1,22 +0,0 @@
version: "3"
env:
NESTED_ENV: env_from_nested
vars:
NESTED_VAR: from_nested
tasks:
print:
desc: Print vars from nested include (3 levels deep)
cmds:
- echo "NESTED:ROOT_VAR={{.ROOT_VAR}}"
- echo "NESTED:UNIQUE_A={{.UNIQUE_A}}"
- echo "NESTED:NESTED_VAR={{.NESTED_VAR}}"
- echo "NESTED:NESTED_ENV={{.env.NESTED_ENV}}"
- echo "NESTED:ROOT_ENV={{.env.ROOT_ENV}}"
try-access-b:
desc: Try to access B's unique var (should fail - sibling isolation)
cmds:
- echo "NESTED:UNIQUE_B={{.UNIQUE_B}}"

View File

@@ -1,23 +0,0 @@
version: "3"
env:
INC_B_ENV: env_from_b
SHARED_ENV: shared_from_b
vars:
VAR: value_from_b
UNIQUE_B: only_in_b
tasks:
print:
desc: Print vars from include B
cmds:
- echo "B:UNIQUE_B={{.UNIQUE_B}}"
- echo "B:ROOT_VAR={{.ROOT_VAR}}"
print-env:
desc: Print env vars from include B
cmds:
- echo "B:INC_B_ENV={{.env.INC_B_ENV}}"
- echo "B:ROOT_ENV={{.env.ROOT_ENV}}"
- echo "B:SHARED_ENV={{.env.SHARED_ENV}}"

View File

@@ -1 +0,0 @@
A:UNIQUE_B=only_in_b

View File

@@ -1,4 +0,0 @@
A:UNIQUE_A=only_in_a
A:ROOT_VAR=from_root
B:UNIQUE_B=only_in_b
B:ROOT_VAR=from_root

View File

@@ -1 +0,0 @@
NAME=from_caller

View File

@@ -1,4 +0,0 @@
A:UNIQUE_A=only_in_a
A:ROOT_VAR=from_root
B:UNIQUE_B=only_in_b
B:ROOT_VAR=from_root

View File

@@ -1,3 +0,0 @@
ROOT_ENV=env_from_root
SHARED_ENV=shared_from_root
PATH_EXISTS=yes

View File

@@ -1 +0,0 @@
ROOT_ENV_AT_ROOT=

View File

@@ -1,3 +0,0 @@
A:INC_A_ENV=env_from_a
A:ROOT_ENV=env_from_root
A:SHARED_ENV=shared_from_a

View File

@@ -1,2 +0,0 @@
A:UNIQUE_A=only_in_a
A:ROOT_VAR=from_root

View File

@@ -1,2 +0,0 @@
B:UNIQUE_B=only_in_b
B:ROOT_VAR=from_root

View File

@@ -1,5 +0,0 @@
NESTED:ROOT_VAR=from_root
NESTED:UNIQUE_A=only_in_a
NESTED:NESTED_VAR=from_nested
NESTED:NESTED_ENV=env_from_nested
NESTED:ROOT_ENV=env_from_root

View File

@@ -90,12 +90,16 @@ export default defineConfig({
head.push(['link', { rel: 'canonical', href: canonicalUrl }])
// Dynamic Open Graph and Twitter meta tags
const pageTitle = pageData.frontmatter.title || pageData.title || taskName
const isHome = pageData.relativePath === 'index.md';
var pageTitle = pageData.frontmatter.title || pageData.title || taskName;
if (!isHome) {
pageTitle = `${pageTitle} | ${taskName}`;
}
const pageDescription = pageData.frontmatter.description || pageData.description || taskDescription
head.push(['meta', { property: 'og:title', content: `${pageTitle} | Task` }])
head.push(['meta', { property: 'og:title', content: pageTitle }])
head.push(['meta', { property: 'og:description', content: pageDescription }])
head.push(['meta', { property: 'og:url', content: canonicalUrl }])
head.push(['meta', { name: 'twitter:title', content: `${pageTitle} | Task` }])
head.push(['meta', { name: 'twitter:title', content: pageTitle }])
head.push(['meta', { name: 'twitter:description', content: pageDescription }])
// Noindex pour 404
@@ -302,10 +306,6 @@ export default defineConfig({
{
text: 'Remote Taskfiles (#1317)',
link: '/docs/experiments/remote-taskfiles'
},
{
text: 'Scoped Taskfiles',
link: '/docs/experiments/scoped-taskfiles'
}
]
},
@@ -374,6 +374,11 @@ export default defineConfig({
{ icon: 'mastodon', link: 'https://fosstodon.org/@task' }
],
editLink: {
text: 'Edit this page on GitHub',
pattern: 'https://github.com/go-task/task/edit/main/website/src/:path'
},
footer: {
message:
'Built with <a target="_blank" href="https://www.netlify.com">Netlify</a>'

View File

@@ -3,4 +3,4 @@ export const taskDescription =
'A fast, cross-platform build tool inspired by Make, designed for modern workflows.';
export const ogUrl = 'https://taskfile.dev/';
export const ogImage = 'https://taskfile.dev/img/logo.png';
export const ogImage = 'https://taskfile.dev/img/og_image.png';

View File

@@ -8,6 +8,11 @@ export const sponsors = [
url: 'https://devowl.io/',
img: '/img/devowl.io.svg'
},
{
name: 'GoodX',
url: 'https://goodx.international/',
img: '/img/goodx.svg'
},
{
name: 'Magic',
url: 'https://magic.dev/',

View File

@@ -19,9 +19,9 @@
"prettier": "^3.6.2",
"vitepress": "^1.6.3",
"vitepress-plugin-group-icons": "^1.6.1",
"vitepress-plugin-tabs": "^0.7.1",
"vitepress-plugin-tabs": "^0.8.0",
"vitepress-plugin-llms": "^1.9.1",
"vue": "^3.5.18"
},
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316"
"packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017"
}

2501
website/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ title: Any Variables
author: pd93
date: 2024-05-09
outline: deep
editLink: false
---
# Any Variables

View File

@@ -4,9 +4,10 @@ description: Introduction of the `if:` control and required variable prompts.
author: vmaerten
date: 2026-01-24
outline: deep
editLink: false
---
# New `if:` control and interactivity support
# New `if:` Control and Variable Prompt
<AuthorCard :author="$frontmatter.author" />

View File

@@ -1,6 +1,7 @@
---
title: Blog
description: Latest news and updates from the Task team
editLink: false
---
<BlogPost

View File

@@ -5,6 +5,7 @@ description:
author: pd93
date: 2024-05-09
outline: deep
editLink: false
---
# Introducing Experiments

View File

@@ -4,6 +4,7 @@ description: The journey of enhancing Windows support in Task.
author: andreynering
date: 2025-09-15
outline: deep
editLink: false
---
# Announcing Built-in Core Utilities for Windows

View File

@@ -1,12 +1,43 @@
---
title: Changelog
outline: deep
editLink: false
---
# Changelog
::: v-pre
## v3.49.0 - 2026-03-07
- Fixed included Taskfiles with `watch: true` not triggering watch mode when
called from the root Taskfile (#2686, #1763 by @trulede).
- Fixed Remote Git Taskfiles failing on Windows due to backslashes in URL paths
(#2656 by @Trim21).
- Fixed remote Git Taskfiles timing out when resolving includes after accepting
the trust prompt (#2669, #2668 by @vmaerten).
- Fixed unclear error message when Taskfile search stops at a directory
ownership boundary (#2682, #1683 by @trulede).
- Fixed global variables from imported Taskfiles not resolving `ref:` values
correctly (#2632 by @trulede).
- Every `.taskrc.yml` option can now be overridden with a `TASK_`-prefixed
environment variable, making CI and container configuration easier (#2607,
#1066 by @vmaerten).
## v3.48.0 - 2026-01-26
- Fixed `if:` conditions when using to check dynamic variables. Also, skip
variable prompt if task would be skipped by `if:` (#2658, #2660 by @vmaerten).
- Fixed `ROOT_TASKFILE` variable pointing to directory instead of the actual
Taskfile path when no explicit `-t` flag is provided (#2635, #1706 by
@trulede).
- Included Taskfiles with `silent: true` now properly propagate silence to their
tasks, while still allowing individual tasks to override with `silent: false`
(#2640, #1319 by @trulede).
- Added TLS certificate options for Remote Taskfiles: use `--cacert` for
self-signed certificates and `--cert`/`--cert-key` for mTLS authentication
(#2537, #2242 by @vmaerten).
## v3.47.0 - 2026-01-24
- Fixed remote git Taskfiles: cloning now works without explicit ref, and

View File

@@ -355,6 +355,8 @@ remote:
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Allow insecure connections when fetching remote Taskfiles
- **CLI equivalent**: `--insecure`
- **Environment variable**: `TASK_REMOTE_INSECURE`
```yaml
remote:
@@ -366,6 +368,8 @@ remote:
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Work in offline mode, preventing remote Taskfile fetching
- **CLI equivalent**: `--offline`
- **Environment variable**: `TASK_REMOTE_OFFLINE`
```yaml
remote:
@@ -378,6 +382,8 @@ remote:
- **Default**: 10s
- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$`
- **Description**: Timeout duration for remote operations (e.g., '30s', '5m')
- **CLI equivalent**: `--timeout`
- **Environment variable**: `TASK_REMOTE_TIMEOUT`
```yaml
remote:
@@ -391,6 +397,8 @@ remote:
- **Pattern**: `^[0-9]+(ns|us|µs|ms|s|m|h)$`
- **Description**: Cache expiry duration for remote Taskfiles (e.g., '1h',
'24h')
- **CLI equivalent**: `--expiry`
- **Environment variable**: `TASK_REMOTE_CACHE_EXPIRY`
```yaml
remote:
@@ -404,7 +412,7 @@ remote:
- **Description**: Directory where remote Taskfiles are cached. Can be an
absolute path (e.g., `/var/cache/task`) or relative to the Taskfile directory.
- **CLI equivalent**: `--remote-cache-dir`
- **Environment variable**: `TASK_REMOTE_DIR` (lowest priority)
- **Environment variable**: `TASK_REMOTE_CACHE_DIR`
```yaml
remote:
@@ -418,6 +426,7 @@ remote:
- **Description**: List of trusted hosts for remote Taskfiles. Hosts in this
list will not prompt for confirmation when downloading Taskfiles
- **CLI equivalent**: `--trusted-hosts`
- **Environment variable**: `TASK_REMOTE_TRUSTED_HOSTS` (comma-separated)
```yaml
remote:

View File

@@ -1,281 +0,0 @@
---
title: 'Scoped Taskfiles (#2035)'
description:
Experiment for variable isolation and env namespace in included Taskfiles
outline: deep
---
# Scoped Taskfiles (#2035)
::: warning
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
environment. They are intended for testing and feedback only.
:::
::: danger
This experiment breaks the following functionality:
- **Environment variables are no longer available at root level in templates**
- Before: <span v-pre>`{{.PATH}}`</span>, <span v-pre>`{{.MY_ENV}}`</span>
- After: <span v-pre>`{{.env.PATH}}`</span>,
<span v-pre>`{{.env.MY_ENV}}`</span>
- **Variables from sibling includes are no longer visible**
- Include A cannot access variables defined in Include B
- Each include only sees: root vars + its own vars + parent vars
:::
::: info
To enable this experiment, set the environment variable:
`TASK_X_SCOPED_TASKFILES=1`. Check out
[our guide to enabling experiments](./index.md#enabling-experiments) for more
information.
:::
This experiment introduces two major changes to how variables work in Task:
1. **Environment namespace**: Environment variables (both OS and Taskfile `env:`
sections) are moved to a dedicated <span v-pre>`{{.env.XXX}}`</span>
namespace, separating them from regular variables
2. **Variable scoping**: Variables defined in included Taskfiles are isolated -
sibling includes cannot see each other's variables
## Environment Namespace
With this experiment enabled, environment variables are no longer mixed with
regular variables at the template root level. Instead, they are accessible
through the <span v-pre>`{{.env.XXX}}`</span> namespace.
### Comparison Table
| Template | Legacy | SCOPED_TASKFILES |
| ----------------------------------------------- | ------ | ------------------------- |
| <span v-pre>`{{.MY_VAR}}`</span> (from `vars:`) | Works | Works |
| <span v-pre>`{{.MY_ENV}}`</span> (from `env:`) | Works | `<no value>` |
| <span v-pre>`{{.env.MY_ENV}}`</span> | - | Works |
| <span v-pre>`{{.PATH}}`</span> (OS) | Works | `<no value>` |
| <span v-pre>`{{.env.PATH}}`</span> (OS) | - | Works |
| <span v-pre>`{{.TASK}}`</span> (special) | Works | Works (stays at root) |
### Example
```yaml
version: '3'
env:
DB_HOST: localhost
vars:
DB_NAME: mydb
tasks:
show:
cmds:
# Access Taskfile env: section
- echo "Host: {{.env.DB_HOST}}"
# Access regular vars (unchanged)
- echo "Name: {{.DB_NAME}}"
# Access OS environment variables
- echo "Path: {{.env.PATH}}"
# Special variables stay at root level
- echo "Task: {{.TASK}}"
```
## Variable Scoping
Variables defined in included Taskfiles are now isolated from each other.
Sibling includes cannot access each other's variables, but child includes can
still inherit variables from their parent.
### Example
::: code-group
```yaml [Taskfile.yml]
version: '3'
vars:
ROOT_VAR: from_root
includes:
api: ./api
web: ./web
```
```yaml [api/Taskfile.yml]
version: '3'
vars:
API_VAR: from_api
tasks:
show:
cmds:
# Inherited from root - works
- echo "ROOT_VAR={{.ROOT_VAR}}"
# Own variable - works
- echo "API_VAR={{.API_VAR}}"
# From sibling include - NOT visible
- echo "WEB_VAR={{.WEB_VAR}}"
```
```yaml [web/Taskfile.yml]
version: '3'
vars:
WEB_VAR: from_web
tasks:
show:
cmds:
# Inherited from root - works
- echo "ROOT_VAR={{.ROOT_VAR}}"
# Own variable - works
- echo "WEB_VAR={{.WEB_VAR}}"
# From sibling include - NOT visible
- echo "API_VAR={{.API_VAR}}"
```
:::
## Variable Priority
With this experiment, variables follow a clear priority order (lowest to
highest):
| Priority | Source | Description |
| -------- | ------------------------ | ---------------------------------------- |
| 1 | Root Taskfile vars | `vars:` in the root Taskfile |
| 2 | Include Taskfile vars | `vars:` in the included Taskfile |
| 3 | Include passthrough vars | `includes: name: vars:` from parent |
| 4 | Task vars | `tasks: name: vars:` in the task |
| 5 | Call vars | `task: name` with `vars:` when calling |
| 6 | CLI vars | `task foo VAR=value` on command line |
### Example: Call vars override task vars
```yaml
version: '3'
tasks:
greet:
vars:
NAME: default
cmds:
- echo "Hello {{.NAME}}"
caller:
cmds:
- task: greet
vars:
NAME: from_caller
```
```bash
# Direct call uses task default
task greet
# Output: Hello default
# Call vars override task vars
task caller
# Output: Hello from_caller
# CLI vars override everything
task greet NAME=cli
# Output: Hello cli
```
## Migration Guide
To migrate your Taskfiles to use this experiment:
1. **Update environment variable references** in your templates:
- <span v-pre>`{{.PATH}}`</span> becomes
<span v-pre>`{{.env.PATH}}`</span>
- <span v-pre>`{{.HOME}}`</span> becomes
<span v-pre>`{{.env.HOME}}`</span>
- <span v-pre>`{{.MY_TASKFILE_ENV}}`</span> becomes
<span v-pre>`{{.env.MY_TASKFILE_ENV}}`</span>
2. **Variables in `vars:` sections remain unchanged**:
- <span v-pre>`{{.MY_VAR}}`</span> still works the same way
3. **Special variables stay at root level**:
- <span v-pre>`{{.TASK}}`</span>, <span v-pre>`{{.ROOT_DIR}}`</span>,
<span v-pre>`{{.TASKFILE}}`</span>, <span v-pre>`{{.TASKFILE_DIR}}`</span>,
etc.
4. **Review cross-include variable dependencies**:
- If your included Taskfiles rely on variables from sibling includes, you'll
need to either move those variables to the root Taskfile or pass them
explicitly via the `vars:` attribute in the `includes:` section.
5. **Use `flatten: true` for gradual migration**:
- If an include needs the legacy behavior (access to sibling variables), you
can use `flatten: true` on that include as an escape hatch.
## Using `flatten: true`
The `flatten: true` option on includes bypasses scoping for that specific
include. When an include has `flatten: true`:
- Its variables are merged globally (legacy behavior)
- It can access variables from sibling includes
- Sibling includes can access its variables
This is useful for gradual migration or when you have includes that genuinely
need to share variables.
### Example
```yaml
version: '3'
vars:
ROOT_VAR: from_root
includes:
# Scoped include - isolated from siblings
api:
taskfile: ./api
# Flattened include - uses legacy merge behavior
shared:
taskfile: ./shared
flatten: true
# Another scoped include
web:
taskfile: ./web
```
In this example:
- `api` and `web` are isolated from each other (cannot see each other's vars)
- `shared` uses legacy behavior: its vars are merged globally
- Both `api` and `web` can access variables from `shared`
- `shared` can access variables from `api` and `web`
::: tip
Use `flatten: true` sparingly. The goal of scoped taskfiles is to improve
isolation and predictability. Flattening should be a temporary measure during
migration or for utility includes that genuinely need global scope.
:::

View File

@@ -45,6 +45,17 @@ Then you can install Task with:
apt install task
```
:::info Package Repository Hosting
[![Hosted By: Cloudsmith](https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&style=for-the-badge)](https://cloudsmith.com)
Package repository hosting for deb/rpm is graciously provided by [Cloudsmith](https://cloudsmith.com).
Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that
enables your organization to create, store and share packages in any format, to any place, with total
confidence.
:::
### [Homebrew](https://brew.sh) ![macOS](https://img.shields.io/badge/MacOS-000000?logo=apple&logoColor=F0F0F0) ![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black) {#homebrew}
Task is available via our official Homebrew tap

View File

@@ -12,9 +12,9 @@ outline: deep
Task has an
[official extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=task.vscode-task).
The code for this project can be found
[here](https://github.com/go-task/vscode-task). To use this extension, you must
have Task v3.23.0+ installed on your system.
The code for this project can be found in
[our GitHub repository](https://github.com/go-task/vscode-task). To use this
extension, you must have Task v3.45.3+ installed on your system.
This extension provides the following features (and more):
@@ -30,6 +30,19 @@ To get autocompletion and validation for your Taskfile, see the
![Task for Visual Studio Code](https://github.com/go-task/vscode-task/blob/main/res/preview.png?raw=true)
### Configuration namespace change
In v1.0.0 of the extension, the configuration namespace was changed from `task`
to `taskfile` in order to fix
[an issue](https://github.com/go-task/vscode-task/issues/56).
![Configuration namespace change warning](../public/img/config-namespace-change.png)
If you receive a warning like the one above, you will need to update your
settings to use the new `taskfile` namespace instead:
![Configuration namespace diff](../public/img/config-namespace-diff.png)
## Schema
This was initially created by @KROSF in

View File

@@ -10,8 +10,8 @@ outline: deep
Task has multiple ways of being configured. These methods are parsed, in
sequence, in the following order with the highest priority last:
- [Environment variables](./environment.md)
- [Configuration files](./config.md)
- [Environment variables](./environment.md)
- _Command-line flags_
In this document, we will look at the last of the three options, command-line
@@ -72,6 +72,14 @@ task --init
task -i
```
::: tip
Combine `--list` or `--list-all` with `--silent` (`-ls` or `-as` for shortants)
to list only the task names in each line. Useful for scripting with `grep` or
similar.
:::
## Options
### General
@@ -96,6 +104,9 @@ task --version
Enable verbose mode for detailed output.
- **Config equivalent**: [`verbose`](./config.md#verbose)
- **Environment variable**: [`TASK_VERBOSE`](./environment.md#task-verbose)
```bash
task build --verbose
```
@@ -104,13 +115,20 @@ task build --verbose
Disable command echoing.
- **Config equivalent**: [`silent`](./config.md#silent)
- **Environment variable**: [`TASK_SILENT`](./environment.md#task-silent)
```bash
task deploy --silent
```
#### `--disable-fuzzy`
Disable fuzzy matching for task names. When enabled, Task will not suggest similar task names when you mistype a task name.
Disable fuzzy matching for task names. When enabled, Task will not suggest
similar task names when you mistype a task name.
- **Config equivalent**: [`disable-fuzzy`](./config.md#disable-fuzzy)
- **Environment variable**: [`TASK_DISABLE_FUZZY`](./environment.md#task-disable-fuzzy)
```bash
task buidl --disable-fuzzy
@@ -124,6 +142,9 @@ task buidl --disable-fuzzy
Stop executing dependencies as soon as one of them fails.
- **Config equivalent**: [`failfast`](./config.md#failfast)
- **Environment variable**: [`TASK_FAILFAST`](./environment.md#task-failfast)
```bash
task build --failfast
```
@@ -140,6 +161,8 @@ task build --force
Compile and print tasks without executing them.
- **Environment variable**: [`TASK_DRY`](./environment.md#task-dry)
```bash
task deploy --dry
```
@@ -156,6 +179,9 @@ task test lint --parallel
Limit the number of concurrent tasks. Zero means unlimited.
- **Config equivalent**: [`concurrency`](./config.md#concurrency)
- **Environment variable**: [`TASK_CONCURRENCY`](./environment.md#task-concurrency)
```bash
task test --concurrency 4
```
@@ -232,6 +258,9 @@ task test --output group --output-group-error-only
Control colored output. Enabled by default.
- **Config equivalent**: [`color`](./config.md#color)
- **Environment variable**: [`TASK_COLOR`](./environment.md#task-color)
```bash
task build --color=false
# or use environment variable
@@ -266,7 +295,12 @@ task --list --json
#### `--sort <mode>`
Change task listing order. Available modes: `default`, `alphanumeric`, `none`.
Change task listing order. Available modes:
- `default` - Sorts tasks alphabetically by name, but ensures that root tasks
(tasks without a namespace) are listed before namespaced tasks.
- `alphanumeric` - Sort tasks alphabetically by name.
- `none` - No sorting. Uses the order as defined in the Taskfile.
```bash
task --list --sort alphanumeric
@@ -297,6 +331,8 @@ task build --watch --interval 1s
Automatically answer "yes" to all prompts.
- **Environment variable**: [`TASK_ASSUME_YES`](./environment.md#task-assume-yes)
```bash
task deploy --yes
```
@@ -306,9 +342,11 @@ task deploy --yes
Enable interactive prompts for missing required variables. When a required
variable is not provided, Task will prompt for input instead of failing.
Task automatically detects non-TTY environments (like CI pipelines) and
skips prompts. This flag can also be set in `.taskrc.yml` to enable prompts
by default.
Task automatically detects non-TTY environments (like CI pipelines) and skips
prompts. This flag can also be set in `.taskrc.yml` to enable prompts by
default.
- **Environment variable**: [`TASK_INTERACTIVE`](./environment.md#task-interactive)
```bash
task deploy --interactive

View File

@@ -10,11 +10,11 @@ outline: deep
Task has multiple ways of being configured. These methods are parsed, in
sequence, in the following order with the highest priority last:
- [Environment variables](./environment.md)
- _Configuration files_
- [Environment variables](./environment.md)
- [Command-line flags](./cli.md)
In this document, we will look at the second of the three options, configuration
In this document, we will look at the first of the three options, configuration
files.
## File Precedence
@@ -86,17 +86,31 @@ experiments:
- **Default**: `false`
- **Description**: Enable verbose output for all tasks
- **CLI equivalent**: [`-v, --verbose`](./cli.md#-v---verbose)
- **Environment variable**: [`TASK_VERBOSE`](./environment.md#task-verbose)
```yaml
verbose: true
```
### `silent`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Disables echoing of commands
- **CLI equivalent**: [`-s, --silent`](./cli.md#-s---silent)
- **Environment variable**: [`TASK_SILENT`](./environment.md#task-silent)
```yaml
silent: true
```
### `color`
- **Type**: `boolean`
- **Default**: `true`
- **Description**: Enable colored output. Colors are automatically enabled in CI environments (`CI=true`).
- **CLI equivalent**: [`-c, --color`](./cli.md#-c---color)
- **Environment variable**: [`TASK_COLOR`](./environment.md#task-color)
```yaml
color: false
@@ -108,6 +122,7 @@ color: false
- **Default**: `false`
- **Description**: Disable fuzzy matching for task names. When enabled, Task will not suggest similar task names when you mistype a task name.
- **CLI equivalent**: [`--disable-fuzzy`](./cli.md#--disable-fuzzy)
- **Environment variable**: [`TASK_DISABLE_FUZZY`](./environment.md#task-disable-fuzzy)
```yaml
disable-fuzzy: true
@@ -119,6 +134,7 @@ disable-fuzzy: true
- **Minimum**: `1`
- **Description**: Number of concurrent tasks to run
- **CLI equivalent**: [`-C, --concurrency`](./cli.md#-c---concurrency-number)
- **Environment variable**: [`TASK_CONCURRENCY`](./environment.md#task-concurrency)
```yaml
concurrency: 4
@@ -129,7 +145,8 @@ concurrency: 4
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Stop executing dependencies as soon as one of them fail
- **CLI equivalent**: [`-F, --failfast`](./cli.md#f-failfast)
- **CLI equivalent**: [`-F, --failfast`](./cli.md#-f---failfast)
- **Environment variable**: [`TASK_FAILFAST`](./environment.md#task-failfast)
```yaml
failfast: true
@@ -156,6 +173,7 @@ Here's a complete example of a `.taskrc.yml` file with all available options:
```yaml
# Global settings
verbose: true
silent: false
color: true
disable-fuzzy: false
concurrency: 2

View File

@@ -9,16 +9,78 @@ outline: deep
Task has multiple ways of being configured. These methods are parsed, in
sequence, in the following order with the highest priority last:
- _Environment variables_
- [Configuration files](./config.md)
- _Environment variables_
- [Command-line flags](./cli.md)
In this document, we will look at the first of the three options, environment
In this document, we will look at the second of the three options, environment
variables. All Task-specific variables are prefixed with `TASK_` and override
their configuration file equivalents.
## Variables
All [configuration file options](./config.md) can also be set via environment
variables. The priority order is: CLI flags > environment variables > config files > defaults.
### `TASK_VERBOSE`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Enable verbose output for all tasks
- **Config equivalent**: [`verbose`](./config.md#verbose)
### `TASK_SILENT`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Disables echoing of commands
- **Config equivalent**: [`silent`](./config.md#silent)
### `TASK_COLOR`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `true`
- **Description**: Enable colored output
- **Config equivalent**: [`color`](./config.md#color)
### `TASK_DISABLE_FUZZY`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Disable fuzzy matching for task names
- **Config equivalent**: [`disable-fuzzy`](./config.md#disable-fuzzy)
### `TASK_CONCURRENCY`
- **Type**: `integer`
- **Description**: Limit number of tasks to run concurrently
- **Config equivalent**: [`concurrency`](./config.md#concurrency)
### `TASK_FAILFAST`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: When running tasks in parallel, stop all tasks if one fails
- **Config equivalent**: [`failfast`](./config.md#failfast)
### `TASK_DRY`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Compiles and prints tasks in the order that they would be run, without executing them
### `TASK_ASSUME_YES`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Assume "yes" as answer to all prompts
### `TASK_INTERACTIVE`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Prompt for missing required variables
### `TASK_TEMP_DIR`
Defines the location of Task's temporary directory which is used for storing
@@ -34,18 +96,6 @@ Valid values are `true` (`1`) or `false` (`0`). By default, this is `true` on
Windows and `false` on other operating systems. We might consider making this
enabled by default on all platforms in the future.
### `TASK_REMOTE_DIR`
Defines the location of Task's remote temporary directory which is used for
caching remote files. Can be relative like `tmp/task` or absolute like
`/tmp/.task` or `~/.task`. Relative paths are relative to the root Taskfile, not
the working directory. Defaults to: `./.task`.
### `TASK_OFFLINE`
Set the `--offline` flag through the environment variable. Only for remote
experiment. CLI flag `--offline` takes precedence over the env variable.
### `FORCE_COLOR`
Force color output usage.

View File

@@ -543,6 +543,21 @@ tasks:
- go build ./...
```
#### `method`
- **Type**: `string`
- **Default**: `checksum`
- **Options**: `checksum`, `timestamp`, `none`
- **Description**: Method for checking if the task is up-to-date. Refer to the `method` root property for details.
```yaml
tasks:
build:
sources:
- go.mod
method: timestamp
```
#### `sources`
- **Type**: `[]string` or `[]Glob`
@@ -637,7 +652,7 @@ tasks:
- go build -ldflags="-s -w" ./...
```
### `dir`
#### `dir`
- **Type**: `string`
- **Description**: The directory in which this task should run

View File

@@ -16,59 +16,32 @@ the Taskfile.
artifacts automatically when a new Git tag is pushed to `main` branch (raw
executables and DEB and RPM packages).
Since v3.15.0, raw executables can also be reproduced and verified locally by
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
## Package managers
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.
GoReleaser will automatically publish the release to most package managers:
## npm
* Cloudsmith (DEB and RPM repositories)
* Homebrew
* npm
* winget
To release to npm update the version in the [`package.json`][packagejson] file
and then run `task npm:publish` to push it.
A single package manager still require manual steps:
## Snapcraft
* Snapcraft:
* Update the `version:` field on [snapcraft.yaml][snapcraftyaml]
<!-- * Trigger a new build on [Snapcraft -> Builds][snapcraftbuilds] -->
* Once finished, move the new build to "stable" on [Snapcraft -> Releases][snapcraftreleases]
The [snap package][snappackage] requires to manual steps to release a new
version:
These package managers are updated automatically by the community:
- Updating the current version on [snapcraft.yaml][snapcraftyaml].
- Moving both `amd64`, `armhf` and `arm64` new artifacts to the stable channel
on the [Snapcraft dashboard][snapcraftdashboard].
## winget
winget also requires manual steps to be completed. By running
`task goreleaser:test` locally, manifest files will be generated on
`dist/winget/manifests/t/Task/Task/v{version}`.
[Upload the manifest directory into this fork](https://github.com/go-task/winget-pkgs/tree/master/manifests/t/Task/Task)
and open a pull request into
[this repository](https://github.com/microsoft/winget-pkgs).
## 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
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 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/master/pkgs/by-name/go/go-task/package.nix).
If you think its Task version is outdated, open an issue to let us know.
* [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json)
* [Nix](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/go/go-task/package.nix)
[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
[snapcraftdashboard]: https://snapcraft.io/task/releases
[snapcraftyaml]: https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml#L2
[snapcraftbuilds]: https://snapcraft.io/task/builds
[snapcraftreleases]: https://snapcraft.io/task/releases

View File

@@ -2,6 +2,7 @@
title: Donate
layout: doc
outline: false
editLink: false
---
# :raised_hands: Support Task

View File

@@ -1,4 +1,5 @@
---
title: "Task: The Modern Task Runner"
layout: home
hero:
name: Task

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 809.71 363.16">
<defs>
<style>
.cls-1 {
fill: #4d4d4e;
}
.cls-2 {
fill-rule: evenodd;
}
.cls-2, .cls-3 {
fill: #0580c3;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<g id="International_logo" data-name="International logo">
<g id="International_logo-2" data-name="International logo">
<g>
<path class="cls-3" d="M79.14,271.86c-14.64,0-27.95-2.94-39.93-8.81-11.99-5.87-21.52-14.76-28.59-26.66-7.08-11.9-10.62-26.78-10.62-44.64,0-13.19,2.09-24.89,6.27-35.1,4.18-10.22,9.89-18.86,17.13-25.94,7.24-7.08,15.56-12.42,24.97-16.04,9.41-3.62,19.34-5.43,29.8-5.43,11.58,0,21.63,2.05,30.16,6.15,8.52,4.1,15.52,8.97,20.99,14.6l-21.72,26.54c-3.86-3.38-7.92-6.15-12.18-8.33-4.26-2.17-9.53-3.25-15.81-3.25-10.62,0-19.46,3.98-26.54,11.94-7.08,7.96-10.62,19.11-10.62,33.42s3.33,25.94,10.01,33.9c6.67,7.96,17.01,11.94,31,11.94,2.25,0,4.46-.24,6.64-.72,2.17-.48,3.98-1.21,5.43-2.17v-21.71h-23.16v-33.78h59.84v74.8c-5.47,5.31-12.91,9.85-22.32,13.63-9.41,3.78-19.66,5.67-30.76,5.67Z"/>
<path class="cls-3" d="M209.2,271.86c-10.3,0-19.99-2.5-29.08-7.48-9.09-4.98-16.49-12.19-22.2-21.6-5.71-9.41-8.56-20.79-8.56-34.14s2.85-24.73,8.56-34.14c5.71-9.41,13.11-16.61,22.2-21.6,9.09-4.98,18.78-7.48,29.08-7.48s19.99,2.5,29.07,7.48c9.09,4.99,16.49,12.19,22.2,21.6,5.71,9.41,8.57,20.79,8.57,34.14s-2.86,24.73-8.57,34.14c-5.71,9.41-13.11,16.61-22.2,21.6-9.09,4.98-18.78,7.48-29.07,7.48ZM209.2,238.56c6.27,0,10.73-2.69,13.39-8.08,2.65-5.39,3.98-12.67,3.98-21.83s-1.33-16.45-3.98-21.84c-2.65-5.39-7.12-8.08-13.39-8.08s-10.74,2.7-13.39,8.08c-2.65,5.39-3.98,12.67-3.98,21.84s1.33,16.45,3.98,21.83c2.65,5.39,7.12,8.08,13.39,8.08Z"/>
<path class="cls-3" d="M340.7,271.86c-10.3,0-19.99-2.5-29.08-7.48-9.09-4.98-16.49-12.19-22.2-21.6-5.71-9.41-8.56-20.79-8.56-34.14s2.85-24.73,8.56-34.14c5.71-9.41,13.11-16.61,22.2-21.6,9.09-4.98,18.78-7.48,29.08-7.48s19.99,2.5,29.07,7.48c9.09,4.99,16.49,12.19,22.2,21.6,5.71,9.41,8.57,20.79,8.57,34.14s-2.86,24.73-8.57,34.14c-5.71,9.41-13.11,16.61-22.2,21.6-9.09,4.98-18.78,7.48-29.07,7.48ZM340.7,238.56c6.27,0,10.73-2.69,13.39-8.08,2.65-5.39,3.98-12.67,3.98-21.83s-1.33-16.45-3.98-21.84c-2.65-5.39-7.12-8.08-13.39-8.08s-10.74,2.7-13.39,8.08c-2.65,5.39-3.98,12.67-3.98,21.84s1.33,16.45,3.98,21.83c2.65,5.39,7.12,8.08,13.39,8.08Z"/>
<path class="cls-3" d="M462.55,271.86c-14.96,0-26.91-5.67-35.83-17.01-8.92-11.34-13.39-26.74-13.39-46.2,0-13.19,2.37-24.49,7.12-33.9,4.74-9.41,10.9-16.65,18.46-21.71,7.56-5.07,15.44-7.6,23.65-7.6,6.59,0,12.02,1.09,16.29,3.26,4.26,2.17,8.16,5.11,11.7,8.81l-1.45-17.37v-39.09h41.5v167.93h-33.78l-2.9-11.1h-.96c-4.02,4.02-8.77,7.36-14.24,10.02-5.47,2.65-10.86,3.98-16.16,3.98ZM473.65,238.08c3.22,0,6.07-.61,8.56-1.81,2.49-1.21,4.78-3.5,6.88-6.87v-44.4c-2.41-2.25-5.07-3.78-7.96-4.58-2.89-.8-5.71-1.21-8.44-1.21-4.34,0-8.24,2.17-11.7,6.51-3.46,4.35-5.19,11.83-5.19,22.44s1.61,18.46,4.83,23.04c3.22,4.58,7.56,6.88,13.03,6.88Z"/>
<path class="cls-2" d="M584.19,32.22c-14.21,12.99-18.69,16.8-27.15,28.46.21,65.05,23.73,121.48,60.5,167.9-4.91,9.5-39.25,73.17-45.59,45.05-.7-5.94-3.63-21.69.55-42.73,3.85-19.37,7.26-25.27,5.59-26-1.8-.79-7.28,14.73-9.16,34.18-2.28,23.59-4.26,32.83-5.32,39.44-6.27,43.92-24.53,68.97-29.23,82.98-.37,1.11.33,1.51.8,1.61,1.09.23,2.36-.33,3.46-1.82,15.5-21.08,57.6-80.44,89.72-125.2,28.35,22.13,57.88,36.09,87.12,47.47,25.61,10.21,53.58,17.27,72.3,14.88,1.07-.14,1.98-2.67,1.24-2.92-79.16-26.76-140.62-88.41-140.62-88.24,51.5-72.07,94.01-128.26,110.82-139.89,14.2-11.59,27.97-22.19,30.12-30.91,4.8-15.45,5.94-34.81,3.29-36.2-3.48-1.83-12.74,5.78-20.27,14.63-38.35,59.87-88.42,122.73-133.25,183.69-.01.02-12.4-9.58-28.38-45.1-11.5-25.54-19.84-69.6-23.65-119.86-.14-1.88-2.03-2.6-2.88-1.42"/>
</g>
<g>
<path class="cls-1" d="M98.55,353.46c-3.64,0-7.02-.7-10.13-2.1-3.11-1.4-5.81-3.26-8.07-5.58l3.96-4.59c1.84,1.95,4.02,3.52,6.53,4.71,2.5,1.19,5.11,1.78,7.8,1.78,3.43,0,6.09-.78,7.99-2.33,1.9-1.56,2.85-3.6,2.85-6.13,0-1.79-.38-3.22-1.15-4.27-.76-1.05-1.79-1.95-3.09-2.69-1.29-.74-2.76-1.48-4.39-2.21l-7.44-3.24c-1.64-.69-3.26-1.58-4.87-2.69-1.61-1.11-2.95-2.53-4.04-4.27-1.08-1.74-1.62-3.88-1.62-6.41s.7-5,2.1-7.08c1.4-2.08,3.33-3.72,5.82-4.91,2.48-1.19,5.28-1.78,8.39-1.78s5.99.59,8.63,1.78c2.64,1.19,4.88,2.73,6.73,4.63l-3.56,4.27c-1.58-1.53-3.33-2.73-5.26-3.6-1.92-.87-4.1-1.31-6.53-1.31-2.9,0-5.24.69-7,2.06s-2.65,3.22-2.65,5.54c0,1.64.44,2.99,1.31,4.08.87,1.08,1.97,1.97,3.28,2.65,1.32.69,2.66,1.32,4.03,1.9l7.36,3.17c2.01.85,3.81,1.86,5.42,3.05,1.61,1.19,2.89,2.64,3.84,4.35.95,1.71,1.43,3.87,1.43,6.45,0,2.74-.71,5.24-2.14,7.48-1.42,2.24-3.45,4.02-6.09,5.34-2.64,1.32-5.77,1.98-9.42,1.98Z"/>
<path class="cls-1" d="M157.43,353.46c-4.33,0-8.16-1.11-11.51-3.32-3.35-2.21-5.96-5.35-7.83-9.42-1.87-4.06-2.81-8.86-2.81-14.4s.94-10.3,2.81-14.28c1.87-3.98,4.48-7.04,7.83-9.18,3.35-2.14,7.19-3.21,11.51-3.21s8.23,1.07,11.55,3.21c3.32,2.14,5.94,5.2,7.84,9.18,1.9,3.98,2.85,8.74,2.85,14.28s-.95,10.34-2.85,14.4c-1.9,4.06-4.51,7.2-7.84,9.42-3.32,2.22-7.17,3.32-11.55,3.32ZM157.43,347.68c3.11,0,5.82-.88,8.11-2.65,2.3-1.77,4.08-4.25,5.34-7.44,1.26-3.19,1.9-6.95,1.9-11.28,0-6.44-1.4-11.53-4.2-15.27-2.8-3.74-6.52-5.62-11.16-5.62s-8.36,1.87-11.16,5.62c-2.8,3.75-4.2,8.84-4.2,15.27,0,4.33.63,8.09,1.9,11.28,1.26,3.19,3.06,5.67,5.38,7.44,2.32,1.77,5.01,2.65,8.07,2.65Z"/>
<path class="cls-1" d="M202.69,352.51v-51.91h29.91v5.54h-23.34v17.57h19.79v5.54h-19.79v23.27h-6.57Z"/>
<path class="cls-1" d="M264.41,352.51v-46.37h-15.67v-5.54h37.98v5.54h-15.67v46.37h-6.65Z"/>
<path class="cls-1" d="M313.64,352.51l-11-51.91h6.8l5.46,28.25c.48,2.8.98,5.59,1.51,8.39.53,2.8,1.03,5.59,1.5,8.39h.32c.58-2.8,1.19-5.61,1.82-8.43.63-2.82,1.24-5.61,1.82-8.35l7.2-28.25h6.02l7.2,28.25c.63,2.74,1.26,5.53,1.9,8.35.63,2.82,1.27,5.63,1.9,8.43h.32c.48-2.8.95-5.61,1.43-8.43.47-2.82.98-5.61,1.5-8.35l5.46-28.25h6.33l-10.76,51.91h-7.91l-7.83-31.26c-.48-2-.91-3.97-1.31-5.9-.4-1.92-.8-3.89-1.23-5.9h-.32c-.37,2.01-.79,3.97-1.26,5.9-.48,1.93-.92,3.89-1.34,5.9l-7.68,31.26h-7.83Z"/>
<path class="cls-1" d="M375.12,352.51l17.57-51.91h7.44l17.57,51.91h-7.04l-4.91-15.83h-18.91l-4.98,15.83h-6.73ZM390.95,323.47l-2.45,7.91h15.59l-2.45-7.91c-.95-2.9-1.85-5.79-2.69-8.67-.85-2.88-1.69-5.82-2.54-8.82h-.32c-.79,3.01-1.61,5.95-2.45,8.82-.85,2.88-1.74,5.76-2.69,8.67Z"/>
<path class="cls-1" d="M436.93,352.51v-51.91h16.22c3.53,0,6.67.46,9.42,1.39,2.74.92,4.89,2.45,6.45,4.59,1.56,2.14,2.33,5,2.33,8.58,0,4.01-1.05,7.24-3.16,9.7-2.11,2.45-4.96,4.13-8.55,5.02l13.22,22.63h-7.44l-12.51-21.92h-9.42v21.92h-6.57ZM443.49,325.21h8.71c4.06,0,7.17-.83,9.34-2.49,2.16-1.66,3.25-4.18,3.25-7.56s-1.08-5.83-3.25-7.2c-2.16-1.37-5.28-2.06-9.34-2.06h-8.71v19.31Z"/>
<path class="cls-1" d="M493.82,352.51v-51.91h29.91v5.54h-23.34v16.3h19.71v5.62h-19.71v18.83h24.14v5.62h-30.7Z"/>
</g>
</g>
<g>
<path class="cls-3" d="M795.78,300.14v-8.62h-2.91v-1.03h7.06v1.03h-2.91v8.62h-1.24Z"/>
<path class="cls-3" d="M801.66,300.14v-9.66h1.47l1.85,5.15c.12.33.23.67.35,1.01.11.34.23.67.35,1.01h.06c.12-.33.23-.67.33-1.01.1-.34.21-.67.33-1.01l1.83-5.15h1.49v9.66h-1.15v-5.31c0-.43.02-.91.06-1.44.04-.52.07-1,.1-1.42h-.06l-.77,2.19-1.83,5h-.81l-1.83-5-.77-2.19h-.06c.03.42.06.9.1,1.42.03.53.05,1,.05,1.44v5.31h-1.1Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

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